// ═══════════════════════════════════════════════════════════════════
//  Form validation primitives
// ═══════════════════════════════════════════════════════════════════
//
//  Slint can't model "validators returning errors" natively (no
//  closures-as-values, no Result type), so this file provides the
//  visual scaffolding. The host (Rust) owns:
//    1. The validation rules.
//    2. Re-running validation on edit / blur / submit.
//    3. Pushing the resulting `error` string back into the matching
//       FormField via a property binding.
//    4. Aggregating per-field errors into a single `is-valid` flag.
//
//  Components:
//
//    NeonFormField    — wraps a single input with label + error +
//                       required marker + help text.
//    NeonFormGroup    — visually groups fields under a section
//                       title with optional description.
//    NeonFormSummary  — compact error count + list, shown above
//                       a Submit button when validation fails.
//    NeonFormSubmit   — Submit button + busy spinner that gates
//                       on `is-valid` from the host.
//
//  Suggested host pattern (Rust):
//
//    struct FormState {
//        name_error: SharedString,
//        email_error: SharedString,
//        ...
//        is_valid: bool,    // && all errors are empty
//    }
//
//  Each field's `error` property is bound to the matching
//  FormState slot; the submit button reads `is_valid`. The host
//  recomputes on `validate(state)` whenever a field changes.
//
//  Why this split (Slint UI + Rust validation logic):
//   • Validators need access to other fields ("password ==
//     confirm") and external state ("email already taken via
//     API"). Slint's expression language can't reach there.
//   • Rust can leverage proper validator crates (e.g. `validator`,
//     `garde`) and async checks.
//   • Slint stays focused on rendering, not business logic.
//
// ═══════════════════════════════════════════════════════════════════

import { Theme } from "../theme.slint";

// ── NeonFormField ──────────────────────────────────────────────────
// Wrap any input control inside this — caller passes the input as
// `@children`. We render: label (top), the input slot, error +
// help text underneath. The input gets a red border on error via
// the wrapper Rectangle's border-color binding (no per-input
// styling needed — works with any input that accepts a parent
// border).
//
// The host sets `error: "..."` to flag invalid; setting it to ""
// clears the error styling.
//
export component NeonFormField inherits Rectangle {
    in property <string> label: "";
    in property <string> help: "";
    in property <string> error: "";
    in property <bool> required: false;
    /// True after the user has interacted with the field at least
    /// once. Errors typically suppress until then so a fresh form
    /// doesn't open red. The host flips this on focus-out / first
    /// edit.
    in property <bool> touched: false;

    property <bool> show-error: root.touched && root.error != "";

    VerticalLayout {
        spacing: Theme.sp-1;
        padding: 0px;

        if (root.label != ""): HorizontalLayout {
            spacing: 4px;
            Text {
                text: root.label;
                font-size: Theme.font-sm;
                font-weight: 600;
                color: root.show-error ? Theme.neon-red : Theme.text-muted;
            }
            if (root.required): Text {
                text: "*";
                font-size: Theme.font-sm;
                color: Theme.neon-red;
            }
            Rectangle { horizontal-stretch: 1; }
        }

        // Input slot — the @children sit here.
        Rectangle {
            border-radius: Theme.radius-md;
            border-width: root.show-error ? 1px : 0px;
            border-color: root.show-error ? Theme.neon-red : transparent;
            VerticalLayout {
                padding: 0px;
                @children
            }
        }

        if (root.show-error): Text {
            text: root.error;
            font-size: Theme.font-xs;
            color: Theme.neon-red;
            wrap: word-wrap;
        }
        if (!root.show-error && root.help != ""): Text {
            text: root.help;
            font-size: Theme.font-xs;
            color: Theme.text-dim;
        }
    }
}

// ── NeonFormGroup ──────────────────────────────────────────────────
// Section divider + title + optional description, with @children
// for the contained fields. Use to break up large forms into
// "Personal info / Address / Preferences" sections.
//
export component NeonFormGroup inherits Rectangle {
    in property <string> title: "";
    in property <string> description: "";

    background: transparent;

    VerticalLayout {
        spacing: Theme.sp-3;
        padding: 0px;

        if (root.title != "" || root.description != ""): VerticalLayout {
            spacing: 2px;
            if (root.title != ""): Text {
                text: root.title;
                font-size: Theme.font-base;
                font-weight: 700;
                color: Theme.text-primary;
            }
            if (root.description != ""): Text {
                text: root.description;
                font-size: Theme.font-sm;
                color: Theme.text-muted;
                wrap: word-wrap;
            }
        }

        VerticalLayout {
            spacing: Theme.sp-3;
            @children
        }
    }
}

// ── FormErrorRow ───────────────────────────────────────────────────
// One row in the FormSummary's error list.
export struct FormErrorRow {
    /// The user-facing field label (e.g. "Email"). Empty for
    /// form-level errors (e.g. "Username already taken").
    field: string,
    /// The error message to show.
    message: string,
}

// ── NeonFormSummary ────────────────────────────────────────────────
// Compact red banner + bulleted list of errors. Shown right
// above the submit button when `errors.length > 0`.
//
// Host populates `errors` by walking each FormField, picking the
// non-empty `.error` strings. When the list is empty the
// component renders nothing.
//
export component NeonFormSummary inherits Rectangle {
    in property <[FormErrorRow]> errors;
    in property <string> intro: "Revisá los siguientes campos antes de continuar:";

    visible: root.errors.length > 0;
    background: Theme.bg-danger-hover;
    border-radius: Theme.radius-md;
    border-width: 1px;
    border-color: Theme.neon-red;

    VerticalLayout {
        padding: Theme.sp-3;
        spacing: Theme.sp-2;

        HorizontalLayout {
            spacing: Theme.sp-2;
            Text {
                text: "✕";
                font-size: Theme.font-base;
                font-weight: 700;
                color: Theme.neon-red;
                vertical-alignment: center;
            }
            Text {
                text: root.intro;
                font-size: Theme.font-sm;
                font-weight: 600;
                color: Theme.text-primary;
                horizontal-stretch: 1;
                wrap: word-wrap;
            }
        }

        for err in root.errors: HorizontalLayout {
            spacing: Theme.sp-2;
            padding-left: Theme.sp-3;
            Text {
                text: "•";
                font-size: Theme.font-sm;
                color: Theme.neon-red;
                min-width: 8px;
            }
            Text {
                text: err.field != ""
                    ? (err.field + ": " + err.message)
                    : err.message;
                font-size: Theme.font-sm;
                color: Theme.text-primary;
                horizontal-stretch: 1;
                wrap: word-wrap;
            }
        }
    }
}

// ── NeonFormSubmit ─────────────────────────────────────────────────
// Submit button with a busy spinner overlay + gating on
// `is-valid`. Disabled when invalid OR busy. The host owns both
// flags — flipping `busy = true` while waiting on a network call,
// then back to false on response.
//
export component NeonFormSubmit inherits Rectangle {
    in property <string> label: "Guardar";
    in property <string> busy-label: "Guardando…";
    in property <bool> is-valid: true;
    in property <bool> busy: false;
    /// `"primary"` (cyan, default) | `"danger"` (red) | `"success"` (green).
    /// Picks the button accent.
    in property <string> variant: "primary";

    callback submitted;

    property <color> accent: root.variant == "danger" ? Theme.neon-red
                            : root.variant == "success" ? Theme.neon-green
                            : Theme.neon-cyan;
    property <bool> can-submit: root.is-valid && !root.busy;

    min-height: 36px;
    min-width: 120px;
    background: !root.can-submit ? Theme.bg-surface
                : touch.has-hover ? Theme.bg-elevated : root.accent;
    border-radius: Theme.radius-md;
    border-width: 1px;
    border-color: !root.can-submit ? Theme.border : root.accent;
    opacity: root.can-submit ? 1.0 : 0.6;
    animate background, opacity { duration: Theme.transition-fast; }

    HorizontalLayout {
        padding-left: Theme.sp-4;
        padding-right: Theme.sp-4;
        spacing: Theme.sp-2;
        alignment: center;

        if (root.busy): Rectangle {
            width: 14px;
            height: 14px;
            border-radius: 7px;
            border-width: 2px;
            border-color: Theme.text-inverse;
            opacity: 0.4;
            // Approximation of a spinner — pulse opacity since
            // Slint doesn't have rotation animations on borders
            // out of the box.
            animate opacity { duration: 600ms; }
        }

        Text {
            text: root.busy ? root.busy-label : root.label;
            font-size: Theme.font-base;
            font-weight: 700;
            color: !root.can-submit ? Theme.text-dim
                : touch.has-hover ? root.accent : Theme.text-inverse;
            vertical-alignment: center;
        }
    }

    touch := TouchArea {
        enabled: root.can-submit;
        clicked => { root.submitted(); }
        mouse-cursor: root.can-submit ? MouseCursor.pointer : MouseCursor.not-allowed;
        accessible-role: button;
        accessible-label: root.label;
    }
}
