"use client";
import Link from "next/link";
import { FormEvent, useEffect, useMemo, useState } from "react";
import { useParams } from "next/navigation";
import { PublicFooter, PublicHeader } from "../../components/PublicChrome";
import Toast from "../../components/Toast";
import { PaymentSelector } from "../../components/PaymentSelector";
import { Turnstile } from "../../components/Turnstile";
import { userMessage } from "../../lib/messages";
import AppointmentPicker from "./AppointmentPicker";
import "./appointment-picker.css";

const apiBase = process.env.NEXT_PUBLIC_API_BASE_URL || "/api/v1";
function validPhone(value: string) {
  const raw = value.trim().replace(/[\s().-]/g, "").replace(/^\+/, "");
  const national = raw.startsWith("56") && raw.length === 11 ? raw.slice(2) : raw;
  return /^[2-9]\d{8}$/.test(national) && new Set(national).size > 1;
}
type Row = Record<string, unknown>;
type Customer = { name: string; email: string; rut: string; phone: string };
type PayResponse = { pago_id?:number;redirect_url?:string;redirect_method?:string;token?:string;detail?:string;request_id?:string;error?:{message?:string} };
type FieldOption = { value: string; label: string };
type AppointmentOffer = { requiere_reserva: boolean; notaria_id: number; servicio_id: number | null; servicio: string | null; precio: number };
type AppointmentSlot = { id: number; inicio: string; fin: string; funcionario: string | null };
function santiagoToday() {
  return new Intl.DateTimeFormat("en-CA", { timeZone: "America/Santiago", year: "numeric", month: "2-digit", day: "2-digit" }).format(new Date());
}
function appointmentLabel(value: string) {
  return new Intl.DateTimeFormat("es-CL", { timeZone: "America/Santiago", weekday: "long", day: "numeric", month: "long", hour: "2-digit", minute: "2-digit", hour12: false }).format(new Date(value));
}
const SYSTEM_CUSTOMER_FIELDS = new Set([
  "cliente_nombre_completo",
  "cliente_email",
  "cliente_rut",
  "cliente_telefono",
]);

function fieldGridClass(field: Row) {
  const kind = String(field.tipo_dato || "TEXTO").toUpperCase();
  const maximum = Number(field.largo_maximo || 0);
  if (["TEXTO_LARGO", "ARCHIVO"].includes(kind) || maximum > 160)
    return "is-wide";
  if (["FECHA", "FECHA_HORA", "ENTERO", "DECIMAL", "BOOLEANO"].includes(kind) || (maximum > 0 && maximum <= 20))
    return "is-compact";
  return "is-standard";
}

function fieldUnits(field: Row) {
  const size = fieldGridClass(field);
  return size === "is-wide" ? 12 : size === "is-compact" ? 4 : 6;
}

function paginateFields(fields: Row[], capacity = 30) {
  const pages: Row[][] = [];
  let page: Row[] = [], used = 0;
  for (const field of fields) {
    const units = fieldUnits(field);
    if (page.length && used + units > capacity) {
      pages.push(page); page = []; used = 0;
    }
    page.push(field); used += units;
  }
  if (page.length) pages.push(page);
  return pages;
}

function fieldOptions(field: Row): FieldOption[] {
  try {
    const parsed = JSON.parse(String(field.opciones_json || "[]"));
    if (!Array.isArray(parsed)) return [];
    return parsed.map((item) => typeof item === "object" && item !== null
      ? { value: String((item as Row).value ?? (item as Row).codigo ?? (item as Row).label ?? ""), label: String((item as Row).label ?? (item as Row).nombre ?? (item as Row).value ?? "") }
      : { value: String(item), label: String(item) }).filter((item) => item.value);
  } catch { return []; }
}
const formatRut = (value: string) => {
  const c = value.replace(/[^0-9kK]/g, "").toUpperCase();
  return c.length < 2
    ? c
    : `${c.slice(0, -1).replace(/\B(?=(\d{3})+(?!\d))/g, ".")}-${c.slice(-1)}`;
};
const validRut = (value: string) => {
  const c = value.replace(/[^0-9kK]/g, "").toUpperCase();
  if (c.length < 2 || !/^[0-9]+[0-9K]$/.test(c)) return false;
  let s = 0,
    f = 2;
  for (let i = c.length - 2; i >= 0; i--) {
    s += Number(c[i]) * f;
    f = f === 7 ? 2 : f + 1;
  }
  const r = 11 - (s % 11),
    e = r === 11 ? "0" : r === 10 ? "K" : String(r);
  return e === c.at(-1);
};
const isRutKind = (value: string) => ["RUT", "RUT_CHILENO", "RUT CHILENO"].includes(value.trim().toUpperCase());
const beneficiaryRut = (field: Row, value: string) => {
  const label = `${field.codigo || ""} ${field.etiqueta || ""}`.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
  return label.includes("rut") && label.includes("razon") && label.includes("social") && /^[0-9.kK-]+$/.test(value);
};

export default function PublicProcedure() {
  const { slug } = useParams<{ slug: string }>();
  const [step, setStep] = useState(1),
    [dataPage, setDataPage] = useState(0),
    [matter, setMatter] = useState<Row | null>(null),
    [notaries, setNotaries] = useState<Row[]>([]),
    [selected, setSelected] = useState<Row | null>(null),
    [paymentProvider, setPaymentProvider] = useState("");
  const [appointment, setAppointment] = useState<AppointmentOffer | null>(null);
  const [appointmentReady, setAppointmentReady] = useState(false);
  const [appointmentDate, setAppointmentDate] = useState(santiagoToday);
  const [appointmentSlots, setAppointmentSlots] = useState<AppointmentSlot[]>([]);
  const [appointmentSlot, setAppointmentSlot] = useState<AppointmentSlot | null>(null);
  const [appointmentBusy, setAppointmentBusy] = useState(false);
  const [customer, setCustomer] = useState<Customer>({
      name: "",
      email: "",
      rut: "",
      phone: "",
    }),
    [answers, setAnswers] = useState<Record<string, string>>({});
  const [files, setFiles] = useState<Record<string, File[]>>({});
  const [loading, setLoading] = useState(true),
    [busy, setBusy] = useState(false),
    [notaryLocked, setNotaryLocked] = useState(false),
    [error, setError] = useState(""),
    [allOpen, setAllOpen] = useState(false),
    [search, setSearch] = useState(""),
    [commune, setCommune] = useState("");
  const [legalText, setLegalText] = useState("Acepto los términos y condiciones, la política de privacidad y el tratamiento de datos necesario para preparar este trámite.");
  const [terms, setTerms] = useState(false),
    [captchaToken, setCaptchaToken] = useState(""),
    [canCreate, setCanCreate] = useState(true),
    [createAccount, setCreateAccount] = useState(false),
    [password, setPassword] = useState(""),
    [confirmPassword, setConfirmPassword] = useState("");
  async function loadNotaries(lat?: number, lng?: number) {
    const q = new URLSearchParams({ servicio: slug });
    if (lat != null && lng != null) {
      q.set("lat", String(lat));
      q.set("lng", String(lng));
    }
    const r = await fetch(`${apiBase}/entidades?${q}`, { cache: "no-store" });
    const b = await r.json();
    if (!r.ok) throw new Error(b.detail || "No fue posible cargar entidades.");
    setNotaries(b.items || []);
    return b.items || [];
  }
  useEffect(() => {
    const locationParams = new URLSearchParams(window.location.search);
    const embeddedEntity = locationParams.get("entidad") || locationParams.get("notaria");
    const q = new URLSearchParams({ servicio: slug });
    if (embeddedEntity) q.set("entidad_uuid",embeddedEntity);
    const serviceQuery = embeddedEntity ? `?entidad_uuid=${encodeURIComponent(embeddedEntity)}` : "";
    Promise.all([
      fetch(`${apiBase}/servicios/${encodeURIComponent(slug)}${serviceQuery}`, {
        cache: "no-store",
      }).then((r) => (r.ok ? r.json() : Promise.reject())),
      fetch(`${apiBase}/entidades?${q}`, { cache: "no-store" }).then((r) =>
        r.ok ? r.json() : Promise.reject(),
      ),
      fetch(`${apiBase}/legal`, { cache: "no-store" }).then((r) => r.ok ? r.json() : {}),
    ])
      .then(([m, n, legal]) => {
        if (embeddedEntity && n.items?.length !== 1) {
          throw new Error("La entidad de este enlace no ofrece el servicio seleccionado.");
        }
        setMatter(m);
        setNotaries(n.items || []);
        const locked = Boolean(embeddedEntity && n.items?.length === 1);
        setNotaryLocked(locked);
        if (locked) {
          setSelected(n.items[0]);
          fetch(`${apiBase}/reservas/tramite/ofertas/${Number(n.items[0].directorio_id)}`, { cache: "no-store" })
            .then(async response => { if (!response.ok) throw Error("No fue posible consultar la reserva de esta entidad."); return response.json(); })
            .then((offer: AppointmentOffer) => {
              if (Number(offer.precio) !== Number(n.items[0].precio)) throw Error("El valor cambió. Recarga el trámite.");
              setAppointment(offer); setAppointmentReady(true);
            })
            .catch(reason => setError(userMessage(reason, "No fue posible cargar la entidad.")));
        }
        const legalSettings = legal as Row;
        if (legalSettings.texto_consentimiento) setLegalText(String(legalSettings.texto_consentimiento));
      })
      .catch((reason) => setError(userMessage(reason, "No fue posible cargar este trámite.")))
      .finally(() => setLoading(false));
  }, [slug]);
  useEffect(() => {
    if (loading) return;
    const motion = "auto";
    requestAnimationFrame(() => document.querySelector(".procedure-heading")
      ?.scrollIntoView({behavior:motion,block:"start"}));
  }, [step,loading]);
  useEffect(() => {
    if (!slug) return;
    let frame = 0;
    try {
      const raw = localStorage.getItem(`documenta:draft:${slug}`);
      if (!raw) return;
      const saved = JSON.parse(raw);
      if (Date.now() - Number(saved.savedAt || 0) > 10 * 60 * 1000) {
        localStorage.removeItem(`documenta:draft:${slug}`);
        return;
      }
      frame = requestAnimationFrame(() => {
        // Al volver hay que elegir de nuevo la entidad y consultar el valor vigente.
        setStep(1);
        setDataPage(Math.max(0, Number(saved.dataPage || 0)));
        if (saved.customer) setCustomer(saved.customer);
        if (saved.answers) setAnswers(saved.answers);
        setTerms(Boolean(saved.terms));
        setCreateAccount(Boolean(saved.createAccount));
        if (saved.selectedId) sessionStorage.setItem(`documenta:selected:${slug}`, String(saved.selectedId));
      });
    } catch { /* Un borrador inválido se ignora de forma segura. */ }
    return () => { if (frame) cancelAnimationFrame(frame); };
  }, [slug]);
  useEffect(() => {
    if (!slug || loading) return;
    const selectedId = selected?.directorio_id || sessionStorage.getItem(`documenta:selected:${slug}`);
    localStorage.setItem(`documenta:draft:${slug}`, JSON.stringify({
      savedAt: Date.now(), step, dataPage, customer, answers, terms, createAccount,
      selectedId: selected?.directorio_id || selectedId || null,
    }));
  }, [slug, loading, step, dataPage, customer, answers, terms, createAccount, selected, notaries]);
  const filtered = useMemo(
    () =>
      notaries.filter(
        (n) =>
          (!commune || String(n.comuna) === commune) &&
          (!search ||
            `${n.nombre_fantasia} ${n.nombre_notario || ""} ${n.direccion} ${n.comuna}`
              .toLowerCase()
              .includes(search.toLowerCase())),
      ),
    [notaries, commune, search],
  );
  const communes = useMemo(
    () => Array.from(new Set(notaries.map((n) => String(n.comuna)))).sort(),
    [notaries],
  );
  const paymentProviders = useMemo(
    () => Array.isArray(selected?.medios_pago) ? selected.medios_pago.map(String) : [],
    [selected],
  );
  useEffect(() => {
    if (!paymentProviders.includes(paymentProvider)) {
      const frame = requestAnimationFrame(() => setPaymentProvider(paymentProviders[0] || ""));
      return () => cancelAnimationFrame(frame);
    }
  }, [paymentProviders, paymentProvider]);

  const fields = ((matter?.campos || []) as Row[]).filter(
    (item) => !SYSTEM_CUSTOMER_FIELDS.has(String(item.codigo)),
  );
  const documentFieldPages = paginateFields(fields);
  const dataPageCount = 1 + documentFieldPages.length;
  const currentDocumentFields = dataPage > 0 ? documentFieldPages[dataPage - 1] || [] : [];

  useEffect(() => {
    if (step !== 3 || !selected || !appointment?.requiere_reserva) return;
    const controller = new AbortController();
    setAppointmentBusy(true); setAppointmentSlots([]); setAppointmentSlot(null);
    fetch(`${apiBase}/reservas/tramite/ofertas/${Number(selected.directorio_id)}/horarios?fecha=${encodeURIComponent(appointmentDate)}`,
      { signal: controller.signal, cache: "no-store" })
      .then(async response => { if (!response.ok) throw Error("No se pudo consultar la disponibilidad."); return response.json(); })
      .then((slots: AppointmentSlot[]) => setAppointmentSlots(slots))
      .catch(reason => { if (!controller.signal.aborted) setError(userMessage(reason, "No se pudo consultar la disponibilidad.")); })
      .finally(() => { if (!controller.signal.aborted) setAppointmentBusy(false); });
    return () => controller.abort();
  }, [step, selected, appointment, appointmentDate]);

  async function chooseEntity(item: Row) {
    setBusy(true); setError(""); setAppointmentReady(false); setAppointmentSlot(null); setAppointment(null);
    try {
      const [response, appointmentResponse] = await Promise.all([
        fetch(`${apiBase}/servicios/${encodeURIComponent(slug)}?entidad_uuid=${encodeURIComponent(String(item.entidad_uuid))}`, { cache: "no-store" }),
        fetch(`${apiBase}/reservas/tramite/ofertas/${Number(item.directorio_id)}`, { cache: "no-store" }),
      ]);
      const configured = await response.json();
      if (!response.ok) throw new Error(configured.detail || "No fue posible cargar la configuración de la entidad.");
      if (!appointmentResponse.ok) throw Error("No fue posible comprobar el precio y la reserva de esta entidad.");
      const booking = await appointmentResponse.json() as AppointmentOffer;
      if (Number(booking.precio) !== Number(item.precio) || Number(booking.notaria_id) !== Number(item.notaria_id))
        throw Error("El precio o la entidad cambió. Recarga la página antes de continuar.");
      const previousFields = (matter?.campos || []) as Row[];
      const previousByCode = new Map(previousFields.map(field => [String(field.codigo), answers[String(field.id)] || ""]));
      const nextFields = (configured.campos || []) as Row[];
      const nextAnswers: Record<string,string> = {};
      nextFields.forEach(field => { const value = previousByCode.get(String(field.codigo)); if (value) nextAnswers[String(field.id)] = value; });
      const fieldShape = (list: Row[]) => list.map(field => `${field.codigo}:${field.requerido}:${field.tipo_dato}`).join("|");
      const requirementShape = (source: Row | null) => ((source?.requisitos || []) as Row[]).map(req => `${req.codigo}:${req.requerido}`).join("|");
      const fieldsChanged = fieldShape(previousFields) !== fieldShape(nextFields);
      const requirementsChanged = requirementShape(matter) !== requirementShape(configured);
      setAnswers(nextAnswers); setFiles({}); setMatter(configured); setSelected(item); setAllOpen(false);
      setAppointment(booking); setAppointmentReady(true);
      sessionStorage.setItem(`documenta:selected:${slug}`, String(item.directorio_id));
      if (requirementsChanged || fieldsChanged) {
        setDataPage(0);
        setError("Revisa los requisitos y campos de esta entidad antes de continuar.");
      }
    } catch (reason) {
      setError(userMessage(reason,"No fue posible seleccionar la entidad."));
    } finally { setBusy(false); }
  }

  async function dataStep(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setError("");
    const f = new FormData(e.currentTarget);
    let nextCustomer = customer;
    const nextAnswers = { ...answers };
    if (dataPage === 0) {
      const draft = {
        name: String(f.get("name") || "").trim(),
        email: String(f.get("email") || "").trim(),
        rut: formatRut(String(f.get("rut") || "")),
        phone: String(f.get("phone") || "").trim(),
      };
      if (draft.name.split(/\s+/).length < 2 || draft.name.split(/\s+/).some(part => part.length < 2))
        return setError("Ingresa al menos un nombre y un apellido.");
      if (!validRut(draft.rut)) return setError("Ingresa un RUT chileno válido.");
      if (draft.phone && !validPhone(draft.phone)) return setError("Ingresa un teléfono chileno válido de 9 dígitos.");
      nextCustomer = draft;
      setCustomer(draft);
      if (documentFieldPages.length) {
        setDataPage(1);
        return;
      }
    } else {
      for (const field of currentDocumentFields) {
        const kind = String(field.tipo_dato || "TEXTO").toUpperCase();
        if (kind === "ARCHIVO") continue;
        let value = String(f.get(`field-${field.id}`) || "").trim();
        if (Boolean(field.requerido) && !value)
          return setError(`Completa el campo: ${field.etiqueta}.`);
        if (value && isRutKind(kind)) {
          value = formatRut(value);
          if (!validRut(value)) return setError(`${field.etiqueta}: ingresa un RUT chileno válido.`);
        }
        if (value && beneficiaryRut(field, value) && !validRut(value))
          return setError(`${field.etiqueta}: el RUT es inválido. Si es una empresa, escribe su razón social.`);
        if (value && kind === "TELEFONO" && !validPhone(value))
          return setError(`${field.etiqueta}: ingresa un teléfono chileno válido.`);
        nextAnswers[String(field.id)] = value;
      }
      setAnswers(nextAnswers);
      if (dataPage < documentFieldPages.length) {
        setDataPage((value) => value + 1);
        return;
      }
    }
    try {
      const r = await fetch(`${apiBase}/auth/account-availability`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email: nextCustomer.email }),
      });
      const b = await r.json();
      setCanCreate(Boolean(b.can_create));
      setCreateAccount(false);
    } catch {
      setCanCreate(false);
    }
    setStep(appointment?.requiere_reserva ? 3 : 4);
  }
  function nearby() {
    if (!navigator.geolocation)
      return setError("Tu navegador no permite geolocalización.");
    setBusy(true);
    navigator.geolocation.getCurrentPosition(
      async (p) => {
        try {
          const items = await loadNotaries(
            p.coords.latitude,
            p.coords.longitude,
          );
          if (items[0]) await chooseEntity(items[0]);
          setError(
            items.length
              ? "Entidades ordenadas por cercanía."
              : "No encontramos entidades disponibles.",
          );
        } catch {
          setError("No fue posible ordenar por ubicación.");
        } finally {
          setBusy(false);
        }
      },
      () => {
        setBusy(false);
        setError(
          "No fue posible acceder a tu ubicación. Revisa el permiso del navegador.",
        );
      },
      { enableHighAccuracy: false, timeout: 10000 },
    );
  }
  async function finish() {
    if (!selected || !terms || !paymentProvider)
      return setError("Selecciona un medio de pago disponible.");
    if (!appointmentReady || !appointment || (appointment.requiere_reserva && !appointmentSlot))
      return setError("Selecciona una hora disponible antes de pagar.");
    if (createAccount && (password.length < 12 || password !== confirmPassword))
      return setError("La contraseña debe tener 12 caracteres y coincidir.");
    if (!captchaToken) return setError("Completa la verificación de seguridad.");
    const expected: (Row & { key: string })[] = [...((matter?.requisitos || []) as Row[]).filter(item => item.tipo === "ARCHIVO").map(item => ({...item,key:`requisito-${item.id}`})),
      ...((matter?.campos || []) as Row[]).filter(item => item.tipo_dato === "ARCHIVO").map(item => ({...item,key:`campo-${item.id}`}))];
    const missing = expected.find(item => Boolean(item.requerido) && !files[item.key]?.length);
    if (missing) return setError(`Adjunta el archivo obligatorio: ${String(missing.nombre || missing.etiqueta)}.`);
    const exceeded = expected.find(item => (files[item.key]?.length || 0) > Number(item.cantidad_maxima || 1));
    if (exceeded) return setError(`Hay demasiados archivos en: ${String(exceeded.nombre || exceeded.etiqueta)}.`);
    setBusy(true);
    setError("");
    try {
      const parts = customer.name.trim().split(/\s+/);
      const surnameCount = parts.length >= 3 ? 2 : 1;
      const r = await fetch(`${apiBase}/solicitudes`, {
        method: "POST",
        credentials: "include",
        headers: {
          "Content-Type": "application/json",
          "Idempotency-Key": crypto.randomUUID(),
        },
        body: JSON.stringify({
          oferta_id: Number(selected.directorio_id),
          captcha_token: captchaToken,
          consentimientos: [],
          respuestas: Object.entries(answers)
            .filter(([, v]) => v)
            .map(([campo_id, valor]) => ({
              campo_id: Number(campo_id),
              valor,
            })),
          cliente: {
            nombres: parts.slice(0, -surnameCount).join(" "),
            apellidos: parts.slice(-surnameCount).join(" "),
            email: customer.email,
            rut: customer.rut,
            telefono: customer.phone || null,
          },
        }),
      });
      const sale = await r.json();
      if (!r.ok)
        throw new Error(sale.detail || "No fue posible crear el trámite.");
      if (Number(sale.total) !== Number(selected.precio))
        throw Error("El valor cambió. No se iniciará el pago; vuelve a seleccionar la notaría.");
      localStorage.setItem(`documenta:${sale.folio}`, sale.access_token);
      if (appointment.requiere_reserva && appointmentSlot) {
        const held = await fetch(`${apiBase}/reservas/tramite/retener`, {
          method: "POST", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ folio: sale.folio, access_token: sale.access_token, bloque_id: appointmentSlot.id }),
        });
        if (!held.ok) {
          const detail = await held.json().catch(() => ({}));
          throw Error(String(detail.detail || "La hora ya no está disponible. Elige otra antes de pagar."));
        }
      }
      for (const item of expected) {
        const [type,id] = item.key.split("-");
        for (const file of files[item.key] || []) {
          const form = new FormData();
          form.append("file",file);
          form.append(type === "campo" ? "campo_id" : "requisito_id",id);
          const uploaded = await fetch(`${apiBase}/solicitudes/${encodeURIComponent(sale.folio)}/archivos`,{
            method:"POST",headers:{"X-Access-Token":sale.access_token},body:form,
          });
          if (!uploaded.ok) {
            const detail = await uploaded.json();
            throw new Error(String(detail.detail || `No fue posible subir ${file.name}.`));
          }
        }
      }
      let accountCreated = false;
      if (createAccount && canCreate) {
        const ar = await fetch(`${apiBase}/auth/register-customer`, {
          method: "POST",
          credentials: "include",
          headers: {
            "Content-Type": "application/json",
            "Idempotency-Key": crypto.randomUUID(),
          },
          body: JSON.stringify({
            folio: sale.folio,
            access_token: sale.access_token,
            password,
          }),
        });
        const ab = await ar.json();
        if (!ar.ok)
          throw new Error(ab.detail || "No fue posible crear la cuenta.");
        sessionStorage.setItem("documenta:csrf", ab.csrf_token);
        accountCreated = true;
      }
      const pr = await fetch(`${apiBase}/pagos`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Idempotency-Key": crypto.randomUUID(),
        },
        body: JSON.stringify({
          folio: sale.folio,
          access_token: sale.access_token,
          proveedor: paymentProvider,
        }),
      });
      const rawPay = await pr.text();
      let pay: PayResponse = {};
      try { pay = rawPay ? JSON.parse(rawPay) : {}; } catch { pay = {}; }
      if (!pr.ok)
        throw new Error(String(pay.detail || pay.error?.message || (pay.request_id ? `No fue posible iniciar el pago. ID: ${pay.request_id}` : "No fue posible iniciar el pago.")));
      sessionStorage.setItem(
        "documenta:pending-payment",
        JSON.stringify({
          pago_id: Number(pay.pago_id),
          folio: sale.folio,
          access_token: sale.access_token,
          proveedor: paymentProvider,
          account_created: accountCreated,
          account_exists: !canCreate,
          summary: {
            documento: matter?.nombre,
            cliente: customer,
            entidad: selected,
            notaria: selected,
            total: sale.total,
            reserva: appointmentSlot ? appointmentLabel(appointmentSlot.inicio) : null,
          },
        }),
      );
      sessionStorage.removeItem("documenta:completed-payment");
      localStorage.removeItem(`documenta:draft:${slug}`);
      if (pay.redirect_method === "POST") {
        const form = document.createElement("form");
        form.method = "POST";
        form.action = String(pay.redirect_url);
        const token = document.createElement("input");
        token.type = "hidden";
        token.name = "token_ws";
        token.value = String(pay.token);
        form.appendChild(token);
        document.body.appendChild(form);
        form.submit();
      } else {
        window.location.href = String(pay.redirect_url);
      }
    } catch (e) {
      setError(userMessage(e, "No fue posible iniciar el pago."));
      setBusy(false);
    }
  }
  if (loading)
    return (
      <main className="procedure-page">
        <p>Cargando trámite…</p>
      </main>
    );
  const allFields = (matter?.campos || []) as Row[],
    requirements = (matter?.requisitos || []) as Row[],
    matterCommunes = (matter?.comunas || []) as Row[];
  return (
    <main className="procedure-page">
      <PublicHeader />
      <section className="procedure-shell">
        <div className="procedure-heading">
          <span className="section-kicker">PASO {step} DE 4</span>
          <h1>{String(matter?.nombre || "Trámite")}</h1>
          <div className="procedure-progress">
            {[1, 2, 3, 4].map((n) => (
              <span key={n} className={step >= n ? "active" : ""}>
                {step > n ? "✓" : n}
              </span>
            ))}
          </div>
        </div>
        {step === 1 && (
          <section className="procedure-card intro">
            <span className="section-kicker">ELIGE TU ENTIDAD Y CONOCE EL VALOR</span>
            <h2>¿Dónde quieres realizar este trámite?</h2>
            <p>{String(matter?.descripcion || matter?.descripcion_corta || "")}</p>
            <div className="notary-title">
              <p>Elige una notaría para ver el valor del trámite antes de ingresar tus datos.</p>
              {!notaryLocked && <div className="notary-tools">
                <button type="button" className="button button-secondary" disabled={busy} onClick={nearby}>⌖ Buscar por mi ubicación</button>
                <button type="button" className="button button-secondary" onClick={() => setAllOpen(true)}>Ver todas las entidades</button>
              </div>}
            </div>
            <div className="notary-selection-grid">
              {(notaryLocked ? notaries : notaries.slice(0, 6)).map((n) => (
                <Notary key={String(n.directorio_id)} item={n} selected={selected}
                  choose={(item) => { if (!notaryLocked) void chooseEntity(item); }} />
              ))}
            </div>
            {!notaries.length && <p>No hay notarías disponibles para este trámite.</p>}
            {selected && <p className="procedure-price-confirmation" role="status">
              {String(selected.nombre_fantasia)} · Valor del trámite: <strong>${Number(selected.precio).toLocaleString("es-CL")}</strong>
              {appointment?.requiere_reserva && " · Incluye elección de hora antes del pago."}
            </p>}
            <h3>Qué necesitas</h3>
            <div className="requirements-list">
              {requirements.length ? requirements.map((r) => (
                <article key={String(r.id)}><b>✓</b><span><strong>{String(r.nombre)}{Boolean(r.requerido) ? " *" : ""}</strong>
                  <small>{String(r.descripcion || "")}</small></span></article>
              )) : <p>No hay requisitos adicionales configurados.</p>}
            </div>
            <button className="button button-primary" disabled={!selected || !appointmentReady || busy}
              onClick={() => { setDataPage(0); setStep(2); }}>
              {busy ? "Comprobando notaría…" : "Continuar con mis datos →"}
            </button>
          </section>
        )}
        {step === 2 && (
          <form className="procedure-card procedure-form" onSubmit={dataStep}>
            <div className="procedure-form-heading">
              <div>
                <span className="section-kicker">FORMULARIO {dataPage + 1} DE {dataPageCount}</span>
                <h2>{dataPage === 0 ? "Tus datos" : "Datos del documento"}</h2>
                <p>{dataPage === 0 ? "Usaremos estos datos para identificar la solicitud y enviarte su seguimiento." : `Completa esta sección del documento. ${fields.length} campo${fields.length === 1 ? "" : "s"} configurado${fields.length === 1 ? "" : "s"} en total.`}</p>
              </div>
              <div className="procedure-substeps" aria-label={`Formulario ${dataPage + 1} de ${dataPageCount}`}>
                {Array.from({ length: dataPageCount }, (_, index) => <span key={index} className={index <= dataPage ? "active" : ""} aria-current={index === dataPage ? "step" : undefined}>{index + 1}</span>)}
              </div>
            </div>
            {dataPage === 0 ? <div className="procedure-field-grid">
              <label className="procedure-field is-standard">
                {String(allFields.find((item) => item.codigo === "cliente_nombre_completo")?.etiqueta || "Nombre completo")} *
                <input name="name" value={customer.name} onChange={(event) => setCustomer((value) => ({ ...value, name: event.target.value }))} autoComplete="name" required />
              </label>
              <label className="procedure-field is-standard">
                {String(allFields.find((item) => item.codigo === "cliente_email")?.etiqueta || "Correo electrónico")} *
                <input
                  name="email"
                  type="email"
                  value={customer.email}
                  onChange={(event) => setCustomer((value) => ({ ...value, email: event.target.value }))}
                  autoComplete="email"
                  required
                />
              </label>
              <label className="procedure-field is-compact">
                {String(allFields.find((item) => item.codigo === "cliente_rut")?.etiqueta || "RUT")} *
                <input
                  name="rut"
                  value={customer.rut}
                  onChange={(event) => setCustomer((value) => ({ ...value, rut: formatRut(event.target.value) }))}
                  required
                />
              </label>
              <label className="procedure-field is-compact">
                {String(allFields.find((item) => item.codigo === "cliente_telefono")?.etiqueta || "Teléfono")}{Boolean(allFields.find((item) => item.codigo === "cliente_telefono")?.requerido) ? " *" : ""}
                <input name="phone" type="tel" value={customer.phone} onChange={(event) => setCustomer((value) => ({ ...value, phone: event.target.value }))} autoComplete="tel" required={Boolean(allFields.find((item) => item.codigo === "cliente_telefono")?.requerido)} />
              </label>
            </div> : <div className="procedure-field-grid">
            {currentDocumentFields.map((f) => {
              const kind = String(f.tipo_dato || "TEXTO").toUpperCase();
              const options = fieldOptions(f);
              const fieldValue = answers[String(f.id)] || "";
              const displayedValue = isRutKind(kind) ? formatRut(fieldValue) : fieldValue;
              return <label key={String(f.id)} className={`procedure-field ${fieldGridClass(f)}`}>
                {String(f.etiqueta)}
                {Boolean(f.requerido) ? " *" : ""}
                {kind === "ARCHIVO" ? (
                  <>
                    <input type="file" accept="application/pdf,image/jpeg,image/png"
                      onChange={(event) => setFiles(current => ({...current,[`campo-${f.id}`]:Array.from(event.target.files || [])}))} />
                    {Boolean(files[`campo-${f.id}`]?.length) && <small>Seleccionado: {files[`campo-${f.id}`].map(file => file.name).join(", ")}</small>}
                  </>
                ) : kind === "COMUNA" ? (
                  <CommuneCombobox name={`field-${f.id}`} items={matterCommunes} value={fieldValue}
                    onChange={(value) => setAnswers((current) => ({ ...current, [String(f.id)]: value }))} required={Boolean(f.requerido)} />
                ) : kind === "TEXTO_LARGO" ? (
                  <textarea name={`field-${f.id}`} value={fieldValue} onChange={(event) => setAnswers((current) => ({ ...current, [String(f.id)]: event.target.value }))} required={Boolean(f.requerido)} placeholder={String(f.placeholder || "")} minLength={f.largo_minimo ? Number(f.largo_minimo) : undefined} maxLength={f.largo_maximo ? Number(f.largo_maximo) : undefined} />
                ) : kind === "LISTA" ? (
                  <select name={`field-${f.id}`} value={fieldValue} onChange={(event) => setAnswers((current) => ({ ...current, [String(f.id)]: event.target.value }))} required={Boolean(f.requerido)}><option value="">Selecciona una opción</option>{options.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}</select>
                ) : kind === "BOOLEANO" ? (
                  <select name={`field-${f.id}`} value={fieldValue} onChange={(event) => setAnswers((current) => ({ ...current, [String(f.id)]: event.target.value }))} required={Boolean(f.requerido)}><option value="">Selecciona</option><option value="1">Sí</option><option value="0">No</option></select>
                ) : (
                  <input
                    name={`field-${f.id}`}
                    value={displayedValue}
                    onChange={(event) => setAnswers((current) => ({ ...current, [String(f.id)]: isRutKind(kind) ? formatRut(event.target.value) : event.target.value }))}
                    type={
                      kind === "FECHA"
                        ? "date"
                        : kind === "FECHA_HORA"
                          ? "datetime-local"
                        : kind === "EMAIL"
                          ? "email"
                          : kind === "TELEFONO"
                            ? "tel"
                          : kind === "ENTERO" || kind === "DECIMAL"
                            ? "number"
                            : "text"
                    }
                    step={kind === "DECIMAL" ? "any" : kind === "ENTERO" ? "1" : undefined}
                    min={f.valor_minimo !== null && f.valor_minimo !== undefined ? Number(f.valor_minimo) : undefined}
                    max={f.valor_maximo !== null && f.valor_maximo !== undefined ? Number(f.valor_maximo) : undefined}
                    minLength={f.largo_minimo ? Number(f.largo_minimo) : undefined}
                    maxLength={f.largo_maximo ? Number(f.largo_maximo) : undefined}
                    required={Boolean(f.requerido)}
                    placeholder={isRutKind(kind) ? "12.345.678-5" : String(f.placeholder || "")}
                    inputMode={isRutKind(kind) ? "text" : undefined}
                  />
                )}
                {Boolean(f.ayuda) && <small>{String(f.ayuda)}</small>}
              </label>;
            })}
            </div>}
            <div className="procedure-actions">
              <button
                type="button"
                className="button button-secondary"
                onClick={() => dataPage > 0 ? setDataPage((value) => value - 1) : setStep(1)}
              >
                Volver
              </button>
              <button className="button button-primary">
                {dataPage < dataPageCount - 1 ? "Continuar →" : appointment?.requiere_reserva ? "Elegir hora →" : "Revisar y pagar →"}
              </button>
            </div>
          </form>
        )}
        {step === 3 && (
          <AppointmentPicker
            entity={String(selected?.nombre_fantasia || "La notaría elegida")}
            service={String(appointment?.servicio || matter?.nombre || "Este trámite")}
            date={appointmentDate}
            today={santiagoToday()}
            busy={appointmentBusy}
            slots={appointmentSlots}
            selectedSlot={appointmentSlot}
            onDateChange={setAppointmentDate}
            onSlotChange={setAppointmentSlot}
            onBack={() => { setDataPage(documentFieldPages.length); setStep(2); }}
            onContinue={() => setStep(4)}
          />
        )}
        {step === 4 && (
          <section className="procedure-card">
            <h2>Revisa antes de pagar</h2>
            {requirements.some(item => item.tipo === "ARCHIVO") && (
              <div className="procedure-review-files">
                <div className="procedure-review-files-heading">
                  <h3>Documentos para este trámite</h3>
                  <p>Adjunta los archivos solicitados antes de continuar al pago.</p>
                </div>
                <div className="procedure-review-files-grid">
                  {requirements.filter(item => item.tipo === "ARCHIVO").map(item => (
                    <label key={String(item.id)} className="procedure-review-file">
                      <span className="procedure-review-file-title">
                        {String(item.nombre)}
                        {Boolean(item.requerido) && <span className="procedure-review-required">Obligatorio</span>}
                      </span>
                      {Boolean(item.descripcion) && <span className="procedure-review-file-description">{String(item.descripcion)}</span>}
                      <input type="file" accept={String(item.mime_permitidos || "application/pdf,image/jpeg,image/png")}
                        multiple={Number(item.cantidad_maxima || 1) > 1}
                        onChange={(event) => setFiles(current => ({...current,[`requisito-${item.id}`]:Array.from(event.target.files || [])}))} />
                      <span className="procedure-review-file-hint">
                        {files[`requisito-${item.id}`]?.length
                          ? `Seleccionado: ${files[`requisito-${item.id}`].map(file => file.name).join(", ")}`
                          : `Máximo ${Number(item.cantidad_maxima || 1)} archivo(s)`}
                      </span>
                    </label>
                  ))}
                </div>
              </div>
            )}
            <div className="summary-grid public-summary">
              <Summary
                title="Documento"
                rows={[
                  ["Trámite", matter?.nombre],
                  [
                    "Campos completados",
                    Object.values(answers).filter(Boolean).length,
                  ],
                ]}
              />
              <Summary
                title="Persona informada"
                rows={[
                  ["Nombre", customer.name],
                  ["RUT", customer.rut],
                  ["Correo", customer.email],
                  ["Teléfono", customer.phone || "—"],
                ]}
              />
              <Summary
                title="Entidad"
                rows={[
                  ["Nombre", selected?.nombre_fantasia],
                  ["Dirección", `${selected?.direccion}, ${selected?.comuna}`],
                  [
                    "Plazo",
                    selected?.plazo_estimado_horas
                      ? `${selected.plazo_estimado_horas} horas`
                      : "Por confirmar",
                  ],
                ]}
              />
              {appointment?.requiere_reserva && <Summary title="Hora solicitada" rows={[
                ["Fecha y hora", appointmentSlot ? appointmentLabel(appointmentSlot.inicio) : "Por seleccionar"],
                ["Confirmación", "Después de un pago aprobado"],
              ]} />}
              <Summary
                title="Pago"
                rows={[
                  [
                    "Total",
                    `$${Number(selected?.precio || 0).toLocaleString("es-CL")}`,
                  ],
                  ["Medio", paymentProvider || "Por seleccionar"],
                ]}
              />
            </div>
            <PaymentSelector providers={paymentProviders} selected={paymentProvider}
              onSelect={setPaymentProvider} />
            <label className="wizard-check">
              <input
                type="checkbox"
                checked={terms}
                onChange={(e) => setTerms(e.target.checked)}
              />
              <span>{legalText} <Link href="/terminos" target="_blank">Ver términos</Link> · <Link href="/privacidad" target="_blank">Ver privacidad</Link></span>
            </label>
            <Turnstile onToken={setCaptchaToken} />
            {canCreate ? (
              <>
                <label className="wizard-check">
                  <input
                    type="checkbox"
                    checked={createAccount}
                    onChange={(e) => setCreateAccount(e.target.checked)}
                  />
                  <span>
                    Deseo crearme una cuenta para consultar mis trámites.
                  </span>
                </label>
                {createAccount && (
                  <div className="form-pair account-password">
                    <label>
                      Contraseña
                      <input
                        type="password"
                        minLength={12}
                        value={password}
                        onChange={(e) => setPassword(e.target.value)}
                      />
                    </label>
                    <label>
                      Repetir contraseña
                      <input
                        type="password"
                        minLength={12}
                        value={confirmPassword}
                        onChange={(e) => setConfirmPassword(e.target.value)}
                      />
                    </label>
                  </div>
                )}
              </>
            ) : (
              <p className="account-existing">
                Ya existe una cuenta para este correo. El trámite se asociará de
                forma segura cuando inicies sesión.
              </p>
            )}
            <div className="procedure-actions">
              <button
                className="button button-secondary"
                onClick={() => { if (!appointment?.requiere_reserva) setDataPage(documentFieldPages.length); setStep(appointment?.requiere_reserva ? 3 : 2); }}
              >
                Volver
              </button>
              <button
                className="button button-primary"
                disabled={!terms || !paymentProvider || !captchaToken || busy}
                onClick={finish}
              >
                {busy ? "Preparando pago…" : "Finalizar y pagar →"}
              </button>
            </div>
          </section>
        )}
        {error && <Toast message={error} onClose={() => setError("")} type={error.includes("ordenadas por cercanía") ? "success" : "error"} />}
      </section>
      <PublicFooter />
      {allOpen && (
        <div className="admin-modal-backdrop">
          <section className="admin-modal admin-modal-large">
            <header>
              <h2>Todas las entidades</h2>
              <button className="modal-close" onClick={() => setAllOpen(false)}>
                ×
              </button>
            </header>
            <div className="admin-modal-content">
              <div className="table-toolbar">
                <input
                  type="search"
                  placeholder="Buscar por nombre o dirección…"
                  value={search}
                  onChange={(e) => setSearch(e.target.value)}
                />
                <select
                  value={commune}
                  onChange={(e) => setCommune(e.target.value)}
                >
                  <option value="">Todas las comunas</option>
                  {communes.map((c) => (
                    <option key={c}>{c}</option>
                  ))}
                </select>
                <span>{filtered.length} entidades</span>
              </div>
              <div className="notary-selection-grid modal-grid">
                {filtered.map((n) => (
                  <Notary
                    key={String(n.directorio_id)}
                    item={n}
                    selected={selected}
                    choose={(item) => void chooseEntity(item)}
                  />
                ))}
              </div>
            </div>
          </section>
        </div>
      )}
    </main>
  );
}
function Notary({
  item,
  selected,
  choose,
}: {
  item: Row;
  selected: Row | null;
  choose: (r: Row) => void;
}) {
  const active = String(selected?.directorio_id) === String(item.directorio_id),
    maps = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${item.direccion}, ${item.comuna}, Chile`)}`;
  return (
    <article
      className={`public-notary-card ${active ? "selected" : ""}`}
      onClick={() => choose(item)}
    >
      <div className="notary-card-head">
        <span className="notary-building">▦</span>
        <a
          href={maps}
          target="_blank"
          rel="noreferrer"
          title="Abrir en Google Maps"
          aria-label="Abrir en Google Maps"
          onClick={(e) => e.stopPropagation()}
        >
          ⌖
        </a>
      </div>
      <h3>{String(item.nombre_fantasia)}</h3>
      {Boolean(item.nombre_notario) && <p>{String(item.nombre_notario)}</p>}
      <address>
        {String(item.direccion)}
        <br />
        {String(item.comuna)} · {String(item.region)}
      </address>
      <div className="notary-card-meta">
        <strong>${Number(item.precio).toLocaleString("es-CL")}</strong>
        <span>
          {item.distancia_km
            ? `${item.distancia_km} km`
            : item.plazo_estimado_horas
              ? `${item.plazo_estimado_horas} h`
              : "Disponible"}
        </span>
      </div>
      {active && <b className="selected-mark">✓</b>}
    </article>
  );
}
function Summary({
  title,
  rows,
}: {
  title: string;
  rows: Array<[string, unknown]>;
}) {
  return (
    <article className="summary-block">
      <h3>{title}</h3>
      <dl>
        {rows.map(([k, v]) => (
          <div key={k}>
            <dt>{k}</dt>
            <dd>{String(v ?? "—")}</dd>
          </div>
        ))}
      </dl>
    </article>
  );
}

function CommuneCombobox({ name, items, value, onChange, required }: {
  name: string; items: Row[]; value: string; onChange: (value: string) => void; required: boolean;
}) {
  const initial = items.find((item) => String(item.codigo) === String(value));
  const [query, setQuery] = useState(initial ? `${initial.nombre} · ${initial.region}` : "");
  const [open, setOpen] = useState(false);
  const normalizeSearch = (text: unknown) => String(text || "")
    .normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim();
  const search = normalizeSearch(query);
  const matches = query.trim().length >= 3
    ? items.filter((item) => normalizeSearch(item.nombre).includes(search)).slice(0, 12)
    : [];
  return <div className="commune-combobox">
    <input type="hidden" name={name} value={value} />
    <input type="search" value={query} required={required} autoComplete="off"
      placeholder="Escribe al menos 3 letras para buscar"
      onFocus={() => setOpen(true)} onChange={(event) => { setQuery(event.target.value); onChange(""); setOpen(true); }} />
    {open && query.trim().length > 0 && <div className="commune-options" role="listbox">
      {query.trim().length < 3 ? <span>Escribe 3 letras para ver coincidencias.</span> : matches.length ? matches.map((item) =>
        <button type="button" key={String(item.codigo)} onClick={() => {
          onChange(String(item.codigo)); setQuery(`${item.nombre} · ${item.region}`); setOpen(false);
        }}>{String(item.nombre)} <small>{String(item.region)}</small></button>) : <span>No hay comunas coincidentes.</span>}
    </div>}
  </div>;
}
