"use client";

import * as React from "react";
import { ArrowLeftIcon, ArrowRightIcon, CircleCheckIcon, InfoIcon, OctagonAlertIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";

import { ConfirmDialog, type ConfirmDialogProps } from "./confirm-dialog";
import { Stepper } from "./stepper";
import { TONE_BOX } from "./tones";
import { useUnsavedChangesGuard } from "./unsaved-changes";

export type WizardApi = { goTo: (index: number) => void };

export type WizardStep = {
  label: string;
  sub?: string;
  /** Step heading; defaults to the label. */
  heading?: string;
  content: React.ReactNode | ((api: WizardApi) => React.ReactNode);
  /** Messages that block Next (shown in a summary that gets focus). Empty or undefined = valid. */
  validate?: () => string[];
  /** Grid steps use the full width; form steps are capped at 720 px. */
  wide?: boolean;
};

export type WizardResult = { title: string; reference: string; lines: string[]; actions?: React.ReactNode };

/**
 * Wizard page (spec 03-structure-and-navigation §10 and §11.6): ordered steps committed once at the end.
 * - Next validates; errors are listed at the top of the step and focused. No skipping ahead; done steps can
 *   be revisited from the stepper or from Edit links on the review step.
 * - Each step change moves focus to the step heading and is announced ("Step 2 of 3, Receive lines").
 * - The last step is the review. The commit button names the outcome, can't be pressed twice, and can ask
 *   for confirmation first. The result shows the reference and what happens next.
 * - Leaving with entered data asks first.
 */
export function Wizard({
  title,
  approvalNote,
  steps,
  dirty,
  onDiscard,
  commitLabel,
  busyLabel = "Saving…",
  confirm,
  onCommit,
  onCancel,
  onRestart,
}: {
  title: string;
  /** Said before step 1 when the result needs approval. */
  approvalNote?: string;
  steps: WizardStep[];
  /** Data has been entered: leaving asks first. */
  dirty: boolean;
  onDiscard?: () => void;
  /** Names the outcome: "Receive goods", "Close shift and print Z-report". */
  commitLabel: string;
  busyLabel?: string;
  /** Irreversible commits confirm first (level and wording from the spec). */
  confirm?: Omit<ConfirmDialogProps, "open" | "onOpenChange" | "onConfirm" | "actionLabel" | "busyLabel">;
  onCommit: (input: { reason?: string; details?: string }) => Promise<WizardResult>;
  onCancel?: () => void;
  onRestart?: () => void;
}) {
  const [current, setCurrent] = React.useState(0);
  const [done, setDone] = React.useState<Set<number>>(new Set());
  const [reached, setReached] = React.useState(0);
  const [errors, setErrors] = React.useState<string[]>([]);
  const [errorSteps, setErrorSteps] = React.useState<Set<number>>(new Set());
  const [confirming, setConfirming] = React.useState(false);
  const [committing, setCommitting] = React.useState(false);
  const [result, setResult] = React.useState<WizardResult | null>(null);
  const headingRef = React.useRef<HTMLHeadingElement>(null);
  const summaryRef = React.useRef<HTMLDivElement>(null);
  const firstRender = React.useRef(true);
  const last = steps.length - 1;
  const step = steps[current];

  const { guard, dialog } = useUnsavedChangesGuard({ dirty: dirty && !result && !committing, changes: ["the details entered so far"], discard: onDiscard });

  React.useEffect(() => {
    if (firstRender.current) {
      firstRender.current = false;
      return;
    }
    headingRef.current?.focus();
  }, [current]);

  const goTo = (i: number) => {
    if (i === current || i < 0 || i > reached) return; // never skip ahead of what has been reached
    setErrors([]);
    setCurrent(i);
  };

  const validateCurrent = () => {
    const errs = step.validate?.() ?? [];
    setErrors(errs);
    setErrorSteps((s) => {
      const n = new Set(s);
      if (errs.length) n.add(current);
      else n.delete(current);
      return n;
    });
    if (errs.length) setTimeout(() => summaryRef.current?.focus(), 0);
    return errs.length === 0;
  };

  const next = () => {
    if (!validateCurrent()) return;
    setDone((d) => new Set(d).add(current));
    setReached((r) => Math.max(r, Math.min(last, current + 1)));
    setCurrent((c) => Math.min(last, c + 1));
  };

  const runCommit = async (input: { reason?: string; details?: string } = {}) => {
    setCommitting(true);
    try {
      setResult(await onCommit(input));
    } finally {
      setCommitting(false);
    }
  };

  const commit = () => {
    if (committing) return;
    // Every earlier step must still be valid (an edit may have broken one).
    const bad = steps.findIndex((s, i) => i < last && (s.validate?.() ?? []).length > 0);
    if (bad >= 0) {
      setErrorSteps((e) => new Set(e).add(bad));
      setCurrent(bad);
      setErrors(steps[bad].validate?.() ?? []);
      setTimeout(() => summaryRef.current?.focus(), 0);
      return;
    }
    if (confirm) setConfirming(true);
    else void runCommit();
  };

  if (result) {
    return (
      <section aria-labelledby="wizard-result" className="space-y-4">
        <div className={cn("space-y-2 rounded-lg border p-4", TONE_BOX.success)} role="status">
          <h2 id="wizard-result" className="flex items-center gap-2 text-lg font-semibold">
            <CircleCheckIcon className="size-5" aria-hidden /> {result.title}
          </h2>
          <p className="text-sm">
            Reference <span className="font-mono font-semibold">{result.reference}</span>
          </p>
          <ul className="list-disc space-y-0.5 pl-5 text-sm">
            {result.lines.map((l) => (
              <li key={l}>{l}</li>
            ))}
          </ul>
        </div>
        <div className="flex flex-wrap gap-2">
          {result.actions}
          {onRestart ? (
            <Button variant="outline" onClick={onRestart}>
              Start another
            </Button>
          ) : null}
        </div>
      </section>
    );
  }

  return (
    <div className="flex min-h-0 flex-col">
      <div className="space-y-4 pb-4">
        <header className="space-y-1">
          <p className="text-xs text-muted-foreground tabular-nums">
            Step {current + 1} of {steps.length}
          </p>
          <h2 className="text-xl leading-7 font-semibold">{title}</h2>
          {approvalNote && current === 0 ? (
            <p className={cn("mt-2 flex gap-2 rounded-lg border px-3 py-2 text-sm", TONE_BOX.info)}>
              <InfoIcon className="mt-0.5 size-4 shrink-0" aria-hidden /> {approvalNote}
            </p>
          ) : null}
        </header>

        <Stepper steps={steps} current={current} done={done} errors={errorSteps} reached={reached} onStepClick={goTo} />

        <p className="sr-only" aria-live="polite">
          Step {current + 1} of {steps.length}, {step.label}
        </p>

        <section className={cn(!step.wide && "max-w-[720px]")} aria-labelledby="wizard-step-heading">
          <h3 id="wizard-step-heading" ref={headingRef} tabIndex={-1} className="mb-3 text-base font-semibold outline-none">
            {step.heading ?? step.label}
          </h3>
          {errors.length ? (
            <div ref={summaryRef} tabIndex={-1} role="alert" className={cn("mb-3 space-y-1 rounded-lg border px-3 py-2 text-sm outline-none", TONE_BOX.error)}>
              <p className="flex items-center gap-2 font-semibold">
                <OctagonAlertIcon className="size-4" aria-hidden /> Fix {errors.length === 1 ? "this" : `these ${errors.length}`} before continuing
              </p>
              <ul className="list-disc pl-5">
                {errors.map((e) => (
                  <li key={e}>{e}</li>
                ))}
              </ul>
            </div>
          ) : null}
          {typeof step.content === "function" ? step.content({ goTo }) : step.content}
        </section>
      </div>

      {/* Sticky footer: Back · Cancel · Next / commit */}
      <div role="region" aria-label="Wizard actions" className="sticky bottom-0 z-10 -mx-4 flex items-center gap-2 border-t border-border bg-card/95 px-4 py-3 shadow-[0_-4px_12px_-6px_rgb(0_17_16/0.18)] backdrop-blur-sm">
        <Button variant="outline" onClick={() => goTo(current - 1)} disabled={current === 0 || committing}>
          <ArrowLeftIcon data-icon="inline-start" />
          Back
        </Button>
        <Button variant="ghost" onClick={() => guard(() => onCancel?.())} disabled={committing}>
          Cancel
        </Button>
        <div className="ml-auto">
          {current < last ? (
            <Button onClick={next}>
              Next: {steps[current + 1].label}
              <ArrowRightIcon data-icon="inline-end" />
            </Button>
          ) : (
            <Button onClick={commit} disabled={committing}>
              {committing ? <Spinner /> : null}
              {committing ? busyLabel : commitLabel}
            </Button>
          )}
        </div>
      </div>

      {confirm ? (
        <ConfirmDialog {...confirm} open={confirming} onOpenChange={setConfirming} actionLabel={commitLabel} busyLabel={busyLabel} onConfirm={(input) => runCommit(input)} />
      ) : null}
      {dialog}
    </div>
  );
}
