"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import type { FormConfig, FormDataPayload, SubmitPayload } from "@/types/form";
import { evaluate } from "@/lib/conditional";
import { StageSection } from "./StageSection";
import { loadDraft, saveDraft, clearDraft } from "@/lib/draft";

interface EngagementFormProps {
  config: FormConfig;
  cid: string;
  cs: string;
}

type LoadState =
  | { kind: "loading" }
  | { kind: "error"; message: string }
  | { kind: "ready"; data: FormDataPayload };

type SubmitStatus =
  | { kind: "idle" }
  | { kind: "submitting" }
  | { kind: "success" }
  | { kind: "error"; message: string };

const STAGE_RANK: Record<string, number> = {
  Inquiry: 0,
  Organizing: 1,
  Feasibility: 2,
  "Business feasibility": 3,
  "Store Implementation": 4,
  "Stabilize newly opened co-op": 5,
};

export function EngagementForm({ config, cid, cs }: EngagementFormProps) {
  const [load, setLoad] = useState<LoadState>({ kind: "loading" });
  const [submitState, setSubmitState] = useState<SubmitStatus>({ kind: "idle" });
  const [draftSavedAt, setDraftSavedAt] = useState<string | null>(null);
  const [draftRestored, setDraftRestored] = useState(false);

  const {
    register,
    handleSubmit,
    reset,
    watch,
    formState: { errors, isDirty },
  } = useForm({ mode: "onBlur" });

  const formValues = watch();

  // ── Initial load ───────────────────────────────────────────────────────
  useEffect(() => {
    let cancelled = false;
    async function go() {
      try {
        const url = new URL("/api/data", window.location.origin);
        url.searchParams.set("cid", cid);
        url.searchParams.set("cs", cs);
        const res = await fetch(url.toString(), { cache: "no-store" });
        if (!res.ok) {
          const text = await res.text();
          let message = `Could not load form (HTTP ${res.status}).`;
          try {
            const j = JSON.parse(text) as { error?: string };
            if (j.error) message = j.error;
          } catch { /* keep default */ }
          if (!cancelled) setLoad({ kind: "error", message });
          return;
        }
        const data: FormDataPayload = await res.json();
        if (cancelled) return;

        // Build defaults: server prefill + current stage. Then check if a
        // local draft exists newer than the server data; if so, layer it on top.
        const defaults: Record<string, unknown> = {
          [config.stageField]: data.currentStage,
          ...data.prefill,
        };
        const draft = loadDraft(cid);
        if (draft && Object.keys(draft.values).length > 0) {
          Object.assign(defaults, draft.values);
          // Re-set the stage from server (draft can never override the org's actual stage).
          defaults[config.stageField] = data.currentStage;
          setDraftRestored(true);
          setDraftSavedAt(draft.savedAt);
        }
        reset(defaults);
        setLoad({ kind: "ready", data });
      } catch (e) {
        if (!cancelled) {
          setLoad({
            kind: "error",
            message: e instanceof Error ? e.message : "Unexpected error loading form.",
          });
        }
      }
    }
    void go();
    return () => {
      cancelled = true;
    };
  }, [cid, cs, reset, config.stageField]);

  // ── Auto-save draft on idle ────────────────────────────────────────────
  // Debounce — save 1.5s after the user stops editing.
  const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  useEffect(() => {
    if (load.kind !== "ready") return;
    if (!isDirty) return;
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(() => {
      saveDraft(cid, formValues);
      setDraftSavedAt(new Date().toISOString());
    }, 1500);
    return () => {
      if (saveTimer.current) clearTimeout(saveTimer.current);
    };
  }, [formValues, isDirty, load.kind, cid]);

  // ── Section ordering ───────────────────────────────────────────────────
  const sectionsToRender = useMemo(
    () =>
      config.sections.map((s) => ({
        section: s,
        sectionVisible: evaluate(s.visibleWhen, formValues),
      })),
    [config.sections, formValues],
  );

  const currentStageValue = formValues[config.stageField] as string | undefined;
  const currentRank = currentStageValue ? STAGE_RANK[currentStageValue] ?? 0 : 0;

  // ── Render ─────────────────────────────────────────────────────────────
  if (load.kind === "loading") return <LoadingState />;
  if (load.kind === "error") return <ErrorState message={load.message} />;

  const onSubmit = async (values: Record<string, unknown>) => {
    setSubmitState({ kind: "submitting" });
    try {
      // Strip values for hidden fields — never write data the user couldn't see.
      const visibleFieldNames = new Set<string>();
      for (const s of config.sections) {
        if (!evaluate(s.visibleWhen, values)) continue;
        for (const f of s.fields) {
          if (evaluate(f.visibleWhen, values)) visibleFieldNames.add(f.name);
        }
        for (const m of s.matrixGroups ?? []) {
          for (const row of m.rows) {
            for (const fname of row.fields) visibleFieldNames.add(fname);
          }
        }
      }
      const filtered: Record<string, unknown> = {};
      for (const [k, v] of Object.entries(values)) {
        if (visibleFieldNames.has(k)) filtered[k] = v;
      }
      const payload: SubmitPayload = { cid, cs, values: filtered };
      const res = await fetch("/api/submit", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (!res.ok) {
        const text = await res.text();
        let message = `Submit failed (HTTP ${res.status}).`;
        try {
          const j = JSON.parse(text) as { error?: string };
          if (j.error) message = j.error;
        } catch { /* keep default */ }
        setSubmitState({ kind: "error", message });
        return;
      }
      // Successful submit — clear draft and show confirmation.
      clearDraft(cid);
      setDraftSavedAt(null);
      setSubmitState({ kind: "success" });
    } catch (e) {
      setSubmitState({
        kind: "error",
        message: e instanceof Error ? e.message : "Unexpected submit error.",
      });
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-5" noValidate>
      <SubmissionContextHeader
        orgName={load.data.orgName}
        currentStage={load.data.currentStage}
        currentRank={currentRank}
      />

      {draftRestored && draftSavedAt && (
        <DraftRestoredNotice savedAt={draftSavedAt} onDiscard={() => {
          clearDraft(cid);
          setDraftRestored(false);
          setDraftSavedAt(null);
          // Force re-fetch by bumping a key — simplest is full page reload.
          window.location.reload();
        }} />
      )}

      <ol className="space-y-5">
        {sectionsToRender.map(({ section, sectionVisible }) => {
          if (!sectionVisible) return null;
          return (
            <li key={section.id} className="list-none">
              <StageSection
                section={section}
                register={register}
                errors={errors}
                formValues={formValues}
                isCurrent={section.rank === currentRank}
                defaultOpen={section.rank === currentRank || section.rank === 0}
                options={load.data.options ?? {}}
              />
            </li>
          );
        })}
      </ol>

      <SubmitBar
        state={submitState}
        isDirty={isDirty}
        draftSavedAt={draftSavedAt}
      />
    </form>
  );
}

function SubmissionContextHeader({
  orgName,
  currentStage,
  currentRank,
}: {
  orgName: string;
  currentStage: string;
  currentRank: number;
}) {
  return (
    <header className="rounded-lg border border-rule bg-paper-2/40 px-6 py-5 sm:px-7 sm:py-6">
      <p className="text-[11px] uppercase tracking-[0.18em] text-ink-mute">Check-in for</p>
      <h1 className="mt-1 font-display text-3xl font-medium leading-tight tracking-tight text-ink sm:text-[34px]">
        {orgName}
      </h1>
      <div className="mt-3 flex items-center gap-3">
        <StageProgress currentRank={currentRank} />
        <span className="text-sm text-ink-soft">
          <span className="text-ink-mute">Stage</span>{" "}
          <span className="font-medium text-leaf-800">{currentStage || "—"}</span>
        </span>
      </div>
    </header>
  );
}

/**
 * Six small dots representing the six stages, with the current rank filled
 * and earlier ranks marked as visited. Quietly conveys progression without
 * pretending to be a "100% complete" progress bar.
 */
function StageProgress({ currentRank }: { currentRank: number }) {
  return (
    <div className="flex items-center gap-1.5" role="img" aria-label={`Stage ${currentRank} of 5`}>
      {[0, 1, 2, 3, 4, 5].map((r) => (
        <span
          key={r}
          className={
            "h-1.5 rounded-full transition-all " +
            (r < currentRank
              ? "w-2.5 bg-leaf-300"
              : r === currentRank
                ? "w-6 bg-leaf-700"
                : "w-2.5 bg-rule-soft")
          }
        />
      ))}
    </div>
  );
}

function DraftRestoredNotice({
  savedAt,
  onDiscard,
}: {
  savedAt: string;
  onDiscard: () => void;
}) {
  const ago = formatRelative(savedAt);
  return (
    <div
      role="status"
      className="flex flex-col gap-2 rounded-lg border border-clay-200 bg-clay-100/40 px-5 py-3 text-sm text-clay-700 sm:flex-row sm:items-center sm:justify-between"
    >
      <p>
        <span className="font-medium">Draft restored</span> from {ago}. Your in-progress edits were
        saved locally and have been re-applied.
      </p>
      <button
        type="button"
        onClick={onDiscard}
        className="self-start rounded border border-clay-400/40 bg-paper px-3 py-1 text-xs font-medium text-clay-700 transition hover:bg-clay-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-clay-500/40 sm:self-auto"
      >
        Discard draft
      </button>
    </div>
  );
}

function SubmitBar({
  state,
  isDirty,
  draftSavedAt,
}: {
  state: SubmitStatus;
  isDirty: boolean;
  draftSavedAt: string | null;
}) {
  return (
    <div className="sticky bottom-3 z-20 flex flex-col gap-3 rounded-lg border border-rule bg-paper/95 px-5 py-4 shadow-[0_-4px_24px_-12px_rgba(60,80,40,0.2)] backdrop-blur sm:flex-row sm:items-center sm:justify-between sm:px-6">
      <div className="flex flex-col gap-0.5">
        <SubmitFeedback state={state} />
        {state.kind === "idle" && (
          <p className="text-xs text-ink-mute">
            {isDirty
              ? draftSavedAt
                ? `Draft saved ${formatRelative(draftSavedAt)} (locally on this device)`
                : "Editing — your draft will save automatically"
              : "Submitting saves a new check-in record on your co-op."}
          </p>
        )}
      </div>
      <button
        type="submit"
        disabled={state.kind === "submitting"}
        className="inline-flex items-center justify-center gap-2 rounded-md bg-leaf-700 px-6 py-2.5 font-medium text-paper transition hover:bg-leaf-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-leaf-500/40 disabled:cursor-not-allowed disabled:bg-ink-mute"
      >
        {state.kind === "submitting" ? (
          <>
            <Spinner /> Saving check-in…
          </>
        ) : (
          <>Submit check-in</>
        )}
      </button>
    </div>
  );
}

function SubmitFeedback({ state }: { state: SubmitStatus }) {
  if (state.kind === "submitting")
    return (
      <p className="text-sm text-ink-soft" role="status">
        Saving your check-in…
      </p>
    );
  if (state.kind === "success")
    return (
      <p className="text-sm font-medium text-leaf-800" role="status">
        ✓ Check-in saved. Thank you.
      </p>
    );
  if (state.kind === "error")
    return (
      <p className="text-sm font-medium text-clay-700" role="alert">
        ✕ {state.message}
      </p>
    );
  return null;
}

function Spinner() {
  return (
    <svg
      aria-hidden
      viewBox="0 0 16 16"
      className="h-4 w-4 animate-spin"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
    >
      <path d="M14 8a6 6 0 11-6-6" strokeLinecap="round" />
    </svg>
  );
}

function LoadingState() {
  return (
    <div className="rounded-lg border border-rule bg-paper px-6 py-12 text-center">
      <div
        aria-hidden
        className="mx-auto mb-4 h-7 w-7 animate-spin rounded-full border-[1.5px] border-rule border-t-leaf-700"
      />
      <p className="font-display text-base text-ink-soft italic">Loading your check-in…</p>
    </div>
  );
}

function ErrorState({ message }: { message: string }) {
  return (
    <div
      role="alert"
      className="rounded-lg border-2 border-clay-200 bg-clay-100/30 px-6 py-7"
    >
      <h2 className="font-display text-xl font-medium text-clay-700">
        We couldn&apos;t open your check-in.
      </h2>
      <p className="mt-3 text-sm leading-relaxed text-ink-soft">{message}</p>
      <p className="mt-4 text-sm text-ink-soft">
        If this keeps happening, please contact your engagement coordinator and ask for a fresh
        link.
      </p>
    </div>
  );
}

function formatRelative(iso: string): string {
  const then = new Date(iso).getTime();
  if (Number.isNaN(then)) return iso;
  const seconds = Math.round((Date.now() - then) / 1000);
  if (seconds < 60) return "just now";
  const minutes = Math.round(seconds / 60);
  if (minutes < 60) return `${minutes} min ago`;
  const hours = Math.round(minutes / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.round(hours / 24);
  if (days < 30) return `${days} days ago`;
  if (days < 365) return `${Math.round(days / 30)} months ago`;
  return `${Math.round(days / 365)} years ago`;
}
