import { X } from 'lucide-preact';
import { useEffect, useRef, useState } from 'preact/hooks';
import { formatPhone } from '../contact-rules.js';
import { ApiError, createLead } from './api.js';
import { COPY, errorMessage } from './copy.js';
import { QuestionError, QuestionHeader } from './questions/shared.jsx';
import {
  cleanVisitor,
  emptyVisitor,
  loadVisitor,
  saveVisitor,
  VISITOR_FIELDS,
  VISITOR_RULES,
  visitorErrors,
} from './visitor.js';

/**
 * Etiqueta, tipo y autocompletado de cada campo (los del form de /estimate). El
 * teléfono se formatea mientras se escribe, como allá.
 *
 * @type {Record<string, { label: string, type: string, autocomplete: string, inputmode?: string, placeholder?: string, format?: (value: string) => string }>}
 */
const FIELDS = {
  first_name: { label: COPY.lead.firstName, type: 'text', autocomplete: 'given-name' },
  last_name: { label: COPY.lead.lastName, type: 'text', autocomplete: 'family-name' },
  email: { label: COPY.lead.email, type: 'email', autocomplete: 'email' },
  phone: {
    label: COPY.lead.phone,
    type: 'tel',
    autocomplete: 'tel',
    placeholder: 'XXX-XXX-XXXX',
    format: formatPhone,
  },
  zip_code: {
    label: COPY.lead.zipCode,
    type: 'text',
    autocomplete: 'postal-code',
    inputmode: 'numeric',
  },
};

/**
 * Los datos que la tool pide antes de generar (decisión de la owner: generar
 * gasta OpenAI). El `modal` de daisyUI sobre un `<dialog>` nativo: queda encima en
 * la capa superior, Esc lo cierra y el foco queda atrapado en él; entra y sale con
 * la transición de daisyUI (sin animación con movimiento reducido).
 *
 * Cerrar vuelve al paso de generar sin perder nada. Al abrirse sale prellenado
 * con lo que el visitante dejó antes en este navegador (`visitor.js`). Con los
 * datos guardados en el servidor (POST /api/ai/lead), `onSaved` sigue con la
 * generación.
 *
 * Mismas piezas que /estimate: la card del form, `.field-label`, `.field-input`,
 * el error rojo bajo el campo y `.btn .btn-primary` (con el `loading` de daisyUI
 * mientras guarda).
 *
 * @param {{ open: boolean, analysisId?: string | null, onClose: () => void, onSaved: () => void }} props
 */
export default function LeadModal({ open, analysisId = null, onClose, onSaved }) {
  const dialogRef = useRef(/** @type {HTMLDialogElement | null} */ (null));
  const headingRef = useRef(/** @type {HTMLElement | null} */ (null));
  const formRef = useRef(/** @type {HTMLFormElement | null} */ (null));
  const [values, setValues] = useState(emptyVisitor);
  const [errors, setErrors] = useState(/** @type {Partial<Record<string, string>>} */ ({}));
  const [formError, setFormError] = useState(/** @type {string | null} */ (null));
  const [sending, setSending] = useState(false);

  useEffect(() => {
    const dialog = dialogRef.current;
    if (!dialog) return;
    if (open && !dialog.open) {
      // Lo que quedó escrito al cerrarlo se conserva; vacío, sale lo guardado.
      setValues((current) =>
        VISITOR_FIELDS.some((field) => current[field].trim() !== '')
          ? current
          : (loadVisitor() ?? emptyVisitor()),
      );
      setErrors({});
      setFormError(null);
      dialog.showModal();
      headingRef.current?.focus({ preventScroll: true });
    } else if (!open && dialog.open) {
      dialog.close();
    }
  }, [open]);

  /** @param {Partial<Record<string, string>>} found */
  const focusFirstError = (found) => {
    const first = VISITOR_FIELDS.find((field) => found[field]);
    if (first) formRef.current?.querySelector(`[name="${first}"]`)?.focus();
  };

  /** @param {SubmitEvent} event */
  const onSubmit = async (event) => {
    event.preventDefault();
    setFormError(null);
    const found = visitorErrors(values);
    setErrors(found);
    if (Object.keys(found).length > 0) {
      focusFirstError(found);
      return;
    }

    setSending(true);
    const visitor = cleanVisitor(values);
    try {
      await createLead(visitor, { analysisId });
      saveVisitor(visitor);
      onSaved();
    } catch (error) {
      const fields = error instanceof ApiError ? error.details?.fields : null;
      if (fields && typeof fields === 'object') {
        // 422: un motivo por campo; el texto es el mismo que muestra el cliente.
        /** @type {Partial<Record<string, string>>} */
        const fromServer = {};
        for (const field of VISITOR_FIELDS) {
          if (fields[field]) fromServer[field] = VISITOR_RULES[field].message;
        }
        setErrors(fromServer);
        focusFirstError(fromServer);
      } else {
        setFormError(error instanceof ApiError ? errorMessage(error) : COPY.generic);
      }
    } finally {
      setSending(false);
    }
  };

  /** @param {string} field @param {string} value */
  const onInput = (field, value) => {
    setValues((current) => ({ ...current, [field]: value }));
    if (errors[field]) setErrors((current) => ({ ...current, [field]: undefined }));
  };

  return (
    // El `modal` de daisyUI: entra y sale con su transición (opacidad del fondo y
    // escala de la caja); `.ai-lead-modal` la apaga con movimiento reducido. Fondo y
    // caja con los tokens del sitio (la card del form de /estimate).
    <dialog
      ref={dialogRef}
      aria-labelledby="ai-designer-lead-title"
      onCancel={(event) => {
        event.preventDefault();
        if (!sending) onClose();
      }}
      class="ai-lead-modal modal text-fg open:bg-deep-3/80"
    >
      <div class="relative modal-box max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-xl rounded-2xl border border-line/60 bg-deep-2 px-6 py-10 shadow-xl md:p-10">
        <button
          type="button"
          onClick={onClose}
          disabled={sending}
          aria-label={COPY.lead.close}
          class="absolute top-4 right-4 flex size-10 cursor-pointer items-center justify-center rounded-full border border-line text-fg transition hover:border-brand-light hover:text-brand-light disabled:pointer-events-none disabled:opacity-50"
        >
          <X class="size-5" aria-hidden="true" />
        </button>

        <div class="pr-10">
          <QuestionHeader
            id="ai-designer-lead-title"
            headingRef={headingRef}
            title={COPY.lead.title}
            hint={COPY.lead.hint}
          />
        </div>

        <form ref={formRef} novalidate onSubmit={onSubmit} class="mt-8 space-y-4">
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            {VISITOR_FIELDS.slice(0, 2).map((field) => (
              <Field
                key={field}
                name={field}
                value={values[field]}
                error={errors[field]}
                onInput={onInput}
              />
            ))}
          </div>
          <Field name="email" value={values.email} error={errors.email} onInput={onInput} />
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            {VISITOR_FIELDS.slice(3).map((field) => (
              <Field
                key={field}
                name={field}
                value={values[field]}
                error={errors[field]}
                onInput={onInput}
              />
            ))}
          </div>

          <p class="text-sm leading-relaxed text-muted">{COPY.lead.notice}</p>
          <QuestionError message={formError} />

          <div class="pt-4">
            <button type="submit" class="btn btn-primary w-full sm:w-auto" disabled={sending}>
              {sending && <span class="loading loading-sm loading-spinner" aria-hidden="true" />}
              {sending ? COPY.lead.sending : COPY.lead.submit}
            </button>
          </div>
        </form>
      </div>

      {/* Tocar fuera de la caja cierra, como la X (y vuelve al paso de generar). */}
      <div class="modal-backdrop">
        <button type="button" onClick={onClose} disabled={sending} tabIndex={-1}>
          {COPY.lead.close}
        </button>
      </div>
    </dialog>
  );
}

/**
 * Un campo como los de /estimate: etiqueta, input y el error rojo debajo.
 *
 * @param {{ name: string, value: string, error?: string, onInput: (field: string, value: string) => void }} props
 */
function Field({ name, value, error, onInput }) {
  const field = FIELDS[name];
  const id = `ai-designer-lead-${name}`;
  return (
    <div>
      <label for={id} class="field-label">
        {field.label}
      </label>
      <input
        id={id}
        name={name}
        type={field.type}
        autocomplete={field.autocomplete}
        inputmode={field.inputmode}
        placeholder={field.placeholder}
        value={value}
        aria-invalid={error ? true : undefined}
        aria-describedby={error ? `${id}-error` : undefined}
        onInput={(event) => {
          const input = /** @type {HTMLInputElement} */ (event.currentTarget);
          if (field.format) input.value = field.format(input.value);
          onInput(name, input.value);
        }}
        class={`field-input mt-1.5 ${error ? 'border-red-400' : ''}`}
      />
      {error && (
        <p id={`${id}-error`} class="mt-1 text-sm text-red-400">
          {error}
        </p>
      )}
    </div>
  );
}
