package de.gecheckt.pdf.umbenenner.adapter.in.gui.editor;

import java.time.Instant;
import java.util.List;
import java.util.Objects;

/**
 * Represents the result of validating the current editor state in the GUI configuration editor.
 * <p>
 * Each validation run produces one immutable result containing all findings split into two
 * complementary views:
 * <ul>
 *   <li>{@code messages} – a consolidated list of {@link GuiMessageEntry} objects that feed
 *       the central message area.</li>
 *   <li>{@code fieldFindings} – field-specific {@link GuiFieldFinding} objects that are
 *       rendered directly below the affected input fields.</li>
 * </ul>
 * <p>
 * A single root cause may appear in both lists: once as a central message (with full context)
 * and once as a compact field finding (with a short, field-specific description).
 * <p>
 * The {@code evaluatedAt} timestamp records when the validation ran; the GUI may use it to
 * determine whether a displayed result is still current.
 * <p>
 * This record contains no JavaFX references and can be created and inspected on any thread.
 *
 * @param messages       consolidated list of message entries for the central message area;
 *                       never {@code null}
 * @param fieldFindings  list of field-level findings; never {@code null}
 * @param evaluatedAt    instant at which the validation was performed; never {@code null}
 */
public record GuiEditorValidationResult(
        List<GuiMessageEntry> messages,
        List<GuiFieldFinding> fieldFindings,
        Instant evaluatedAt) {

    /**
     * Creates a new validation result.
     *
     * @param messages      central message entries; must not be {@code null}
     * @param fieldFindings field-level findings; must not be {@code null}
     * @param evaluatedAt   validation timestamp; must not be {@code null}
     * @throws NullPointerException if any parameter is {@code null}
     */
    public GuiEditorValidationResult {
        Objects.requireNonNull(messages, "messages must not be null");
        Objects.requireNonNull(fieldFindings, "fieldFindings must not be null");
        Objects.requireNonNull(evaluatedAt, "evaluatedAt must not be null");
        messages = List.copyOf(messages);
        fieldFindings = List.copyOf(fieldFindings);
    }

    /**
     * Returns an empty validation result representing the state before any validation has run.
     * <p>
     * Callers must not interpret an empty result as "no errors found"; they should wait for
     * a non-empty result from the first actual validation run.
     *
     * @return an empty result with the current instant as timestamp; never {@code null}
     */
    public static GuiEditorValidationResult empty() {
        return new GuiEditorValidationResult(List.of(), List.of(), Instant.now());
    }

    /**
     * Returns {@code true} when at least one message or field finding has severity
     * {@link GuiMessageSeverity#ERROR}.
     * <p>
     * A result with errors indicates that the current editor state is not operational and
     * should not be relied upon to start a processing run without correction.
     *
     * @return {@code true} when at least one error is present
     */
    public boolean hasErrors() {
        boolean messageError = messages.stream()
                .anyMatch(m -> m.severity() == GuiMessageSeverity.ERROR);
        boolean fieldError = fieldFindings.stream()
                .anyMatch(f -> f.severity() == GuiMessageSeverity.ERROR);
        return messageError || fieldError;
    }

    /**
     * Returns {@code true} when the findings list contains at least one finding for the
     * requested field key, regardless of severity.
     *
     * @param fieldKey the property key to look up; must not be {@code null}
     * @return {@code true} when at least one finding refers to the requested field
     */
    public boolean hasFieldFindingFor(String fieldKey) {
        Objects.requireNonNull(fieldKey, "fieldKey must not be null");
        return fieldFindings.stream().anyMatch(f -> f.fieldKey().equals(fieldKey));
    }
}
