"use client";

import * as React from "react";
import { InfoIcon, OctagonAlertIcon, TriangleAlertIcon } from "lucide-react";

import { AlertDialog, AlertDialogContent, AlertDialogDescription, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";

/**
 * Confirmation levels (spec: components/04-overlays-and-feedback §2).
 * - neutral: irreversible but not destructive ("Send purchase order"), primary button.
 * - A: destructive but recoverable or low value. Cancel has focus.
 * - B: destructive and recorded: a reason is required.
 * - C: the most serious: a reason (optional per action) and typing a word or record number.
 */
export type ConfirmLevel = "neutral" | "A" | "B" | "C";

export type ConfirmDialogProps = {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  level: ConfirmLevel;
  /** A question naming the action and the object: "Reverse sale RCP-005197?" */
  title: string;
  /** Short lines with the figures that matter. The last line usually says whether it can be undone. */
  consequences: React.ReactNode[];
  /** Names the result: "Reverse sale", "Write off 12 units". */
  actionLabel: string;
  /** Shown while submitting: "Reversing…". */
  busyLabel?: string;
  /** Level B and C. Managed reasons as a select; omit for a free-text reason. */
  reasons?: string[];
  /** Level C only: if false, no reason is asked (e.g. approving a payroll run). Default true. */
  reasonRequired?: boolean;
  /** Who sees the reason, e.g. "Saved with the reversal and shown to approvers." */
  reasonHelp?: string;
  /** Level C: the text to type, e.g. "DELETE" or "OCT-2026". */
  confirmText?: string;
  /** Level C approvals that are not destructive use the primary colour. */
  tone?: "danger" | "primary";
  /** Shown above the reason: this creates a request rather than acting now. */
  approvalNote?: string;
  /** Resolve to close; throw (with a message) to keep the dialog open with an error and the inputs kept. */
  onConfirm: (input: { reason?: string; details?: string }) => Promise<void> | void;
};

const norm = (s: string) => s.trim().toUpperCase();

export function ConfirmDialog({
  open,
  onOpenChange,
  level,
  title,
  consequences,
  actionLabel,
  busyLabel,
  reasons,
  reasonRequired = true,
  reasonHelp,
  confirmText,
  tone = "danger",
  approvalNote,
  onConfirm,
}: ConfirmDialogProps) {
  const askReason = level === "B" || (level === "C" && reasonRequired);
  const askTyped = level === "C" && !!confirmText;
  const danger = level !== "neutral" && tone === "danger";

  const [reason, setReason] = React.useState("");
  const [details, setDetails] = React.useState("");
  const [typed, setTyped] = React.useState("");
  const [tried, setTried] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [failure, setFailure] = React.useState("");
  const cancelRef = React.useRef<HTMLButtonElement>(null);
  const reasonRef = React.useRef<HTMLButtonElement & HTMLTextAreaElement>(null);
  const alertRef = React.useRef<HTMLParagraphElement>(null);
  const id = React.useId();

  // With a list, "Other" (or a free-text reason) needs the details text.
  const needsDetails = !reasons || reason === "Other";
  const reasonError = !askReason ? "" : reasons && !reason ? "Choose a reason." : needsDetails && !details.trim() ? "Enter a reason." : "";
  const typedError = askTyped && norm(typed) !== norm(confirmText!) ? `Type ${confirmText} exactly to confirm.` : "";

  // Spec: focus starts on the reason (B, C) or Cancel, never the danger button. Base UI's initialFocus waits
  // for an animation frame; this timer is the fallback for when frames are paused (background tab, slow device).
  React.useEffect(() => {
    if (!open) return;
    const t = setTimeout(() => {
      const target = (askReason ? reasonRef : cancelRef).current;
      if (target && !target.contains(document.activeElement)) target.focus();
    }, 60);
    return () => clearTimeout(t);
  }, [open, askReason]);

  // Spec: on failure, focus moves to the error so it is announced. The submit button was disabled while busy,
  // so the focus trap first pulls focus back to its initial target; move it to the alert after that settles.
  React.useEffect(() => {
    if (!failure) return;
    const t = setTimeout(() => alertRef.current?.focus(), 120);
    return () => clearTimeout(t);
  }, [failure]);

  const reset = () => {
    setReason("");
    setDetails("");
    setTyped("");
    setTried(false);
    setBusy(false);
    setFailure("");
  };

  const close = (o: boolean) => {
    if (busy) return; // Esc is ignored while submitting
    onOpenChange(o);
    if (!o) reset();
  };

  const submit = async () => {
    setTried(true);
    if (reasonError || typedError) return;
    setBusy(true);
    setFailure("");
    try {
      await onConfirm({ reason: askReason ? (reasons ? reason : details.trim()) : undefined, details: reasons && details.trim() ? details.trim() : undefined });
      setBusy(false);
      onOpenChange(false);
      reset();
    } catch (e) {
      setBusy(false);
      setFailure(e instanceof Error && e.message ? e.message : "That did not go through. Nothing was changed; try again.");
    }
  };

  const Icon = level === "neutral" ? InfoIcon : level === "C" && danger ? OctagonAlertIcon : TriangleAlertIcon;
  const iconTone = level === "neutral" ? "bg-accent text-accent-foreground" : level === "C" && danger ? "bg-status-error-bg text-status-error-text" : "bg-status-warning-bg text-status-warning-text";

  return (
    <AlertDialog open={open} onOpenChange={close}>
      <AlertDialogContent
        size="sm"
        initialFocus={askReason ? reasonRef : cancelRef}
        // Same data-[size=sm] variant as the primitive's max-w-xs, so cn replaces it instead of both applying.
        className="flex max-h-[min(85dvh,calc(100dvh-2rem))] flex-col gap-0 overflow-hidden p-0 data-[size=sm]:max-w-[calc(100%-2rem)] data-[size=sm]:sm:max-w-md"
        aria-describedby={`${id}-consequences`}
      >
        <div className="flex gap-3 px-5 pt-5 pb-4">
          <span className={cn("flex size-9 shrink-0 items-center justify-center rounded-full", iconTone)} aria-hidden>
            <Icon className="size-4.5" />
          </span>
          <div className="grid min-w-0 gap-1.5">
            <AlertDialogTitle className="text-left text-lg leading-6 font-semibold">{title}</AlertDialogTitle>
            <AlertDialogDescription id={`${id}-consequences`} render={<ul />} className="space-y-1 text-left text-sm text-muted-foreground">
              {consequences.map((c, i) => (
                <li key={i}>{c}</li>
              ))}
            </AlertDialogDescription>
          </div>
        </div>

        {failure || approvalNote || askReason || askTyped ? (
          <div className="min-h-0 flex-1 space-y-3 overflow-y-auto px-5 pb-5">
            {failure ? (
              <p ref={alertRef} tabIndex={-1} role="alert" className="rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text outline-none">
                {failure}
              </p>
            ) : null}
            {approvalNote ? <p className="rounded-lg border border-status-info-border bg-status-info-bg px-3 py-2 text-sm text-status-info-text">{approvalNote}</p> : null}

            {askReason ? (
              <Field data-invalid={tried && !!reasonError}>
                <FieldLabel htmlFor={reasons ? undefined : `${id}-reason`}>Reason</FieldLabel>
                {reasons ? (
                  <Select value={reason || null} onValueChange={(v) => setReason(String(v ?? ""))} disabled={busy}>
                    <SelectTrigger ref={reasonRef} className="w-full" aria-invalid={tried && !!reasonError && !reason}>
                      <SelectValue placeholder="Choose a reason" />
                    </SelectTrigger>
                    <SelectContent>
                      {reasons.map((r) => (
                        <SelectItem key={r} value={r}>
                          {r}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                ) : null}
                {needsDetails || (reasons && reason) ? (
                  <Textarea
                    ref={reasons ? undefined : reasonRef}
                    id={`${id}-reason`}
                    aria-label={reasons ? "Details" : undefined}
                    rows={2}
                    placeholder={reasons ? (reason === "Other" ? "Describe the reason" : "Details (optional)") : "Why is this needed?"}
                    value={details}
                    disabled={busy}
                    aria-invalid={tried && needsDetails && !details.trim()}
                    onChange={(e) => setDetails(e.target.value)}
                    // Enter must not submit: the action button has to be pressed on purpose.
                    onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && e.stopPropagation()}
                  />
                ) : null}
                {tried && reasonError ? <FieldError>{reasonError}</FieldError> : reasonHelp ? <FieldDescription>{reasonHelp}</FieldDescription> : null}
              </Field>
            ) : null}

            {askTyped ? (
              <Field data-invalid={tried && !!typedError}>
                <FieldLabel htmlFor={`${id}-typed`}>
                  Type <span className="rounded bg-muted px-1 font-mono font-semibold text-foreground">{confirmText}</span> to confirm
                </FieldLabel>
                <Input
                  id={`${id}-typed`}
                  value={typed}
                  disabled={busy}
                  autoComplete="off"
                  spellCheck={false}
                  className="font-mono"
                  aria-invalid={tried && !!typedError}
                  onChange={(e) => setTyped(e.target.value)}
                  onKeyDown={(e) => e.key === "Enter" && e.preventDefault()}
                />
                {tried && typedError ? <FieldError>{typedError}</FieldError> : null}
              </Field>
            ) : null}
          </div>
        ) : null}

        <div className="flex shrink-0 justify-end gap-2 rounded-b-xl border-t border-border bg-muted/50 px-5 py-3">
          <Button ref={cancelRef} variant="outline" disabled={busy} onClick={() => close(false)}>
            Cancel
          </Button>
          <Button
            variant={danger ? "destructive" : "default"}
            className={danger ? "bg-action-danger-default text-fg-on-danger hover:bg-action-danger-hover" : undefined}
            disabled={busy}
            onClick={submit}
          >
            {busy ? <Spinner /> : null}
            {busy ? (busyLabel ?? "Working…") : actionLabel}
          </Button>
        </div>
      </AlertDialogContent>
    </AlertDialog>
  );
}
