"use client";

import * as React from "react";
import Link from "next/link";
import { ChevronDownIcon, CircleXIcon, CloudOffIcon, FileQuestionIcon, LockIcon, LogInIcon, RotateCcwIcon, SearchXIcon } from "lucide-react";

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

import { TONE_BOX } from "./tones";

/* ------------------------------------- Page error ------------------------------------ */

export type PageErrorKind = "not-found" | "no-access" | "record-not-found" | "failed" | "signed-out";

const PAGE: Record<PageErrorKind, { icon: typeof LockIcon; title: string; body: string }> = {
  "not-found": { icon: SearchXIcon, title: "We can't find that page", body: "It may have moved. Go back to the dashboard." },
  "no-access": { icon: LockIcon, title: "You don't have access to this page", body: "Ask an administrator if you need it." },
  "record-not-found": { icon: FileQuestionIcon, title: "This record no longer exists", body: "It may have been deleted or moved to the deleted list." },
  failed: { icon: CircleXIcon, title: "We couldn't load this page", body: "Check your connection, then try again." },
  "signed-out": { icon: LogInIcon, title: "You were signed out", body: "For security, you're signed out after a period of inactivity. Sign in to continue." },
};

/**
 * Page error (spec 04 §9.3). Renders inside the normal app frame (top bar and navigation stay), centred,
 * with the empty-state anatomy: icon, title, one line of what to do, one action. No codes in the title.
 * `title`/`body` override the defaults for a specific record ("This sale no longer exists").
 */
export function PageError({
  kind,
  title,
  body,
  action,
  reference,
  autoFocus = true,
  className,
}: {
  kind: PageErrorKind;
  title?: string;
  body?: string;
  /** Defaults: "Go to dashboard" for not-found / no-access. */
  action?: { label: string; href?: string; onClick?: () => void; busy?: boolean };
  /** Move focus to the title so the change is announced (on by default; the page content was replaced). */
  autoFocus?: boolean;
  /** Shown small for support, never in the title. */
  reference?: string;
  className?: string;
}) {
  const d = PAGE[kind];
  const Icon = d.icon;
  const titleRef = React.useRef<HTMLHeadingElement>(null);
  React.useEffect(() => {
    if (autoFocus) titleRef.current?.focus();
  }, [kind, autoFocus]);
  const a = action ?? (kind === "not-found" || kind === "no-access" ? { label: "Go to dashboard", href: "/dashboard" } : undefined);
  return (
    <section className={cn("flex min-h-[360px] flex-col items-center justify-center gap-3 px-6 py-12 text-center", className)} aria-labelledby="page-error-title">
      <span className={cn("flex size-12 items-center justify-center rounded-full", kind === "failed" ? "bg-status-error-bg text-status-error-text" : "bg-muted text-muted-foreground")} aria-hidden>
        <Icon className="size-6" />
      </span>
      <h1 id="page-error-title" ref={titleRef} tabIndex={-1} className="font-display text-page-title text-foreground outline-none">
        {title ?? d.title}
      </h1>
      <p className="max-w-md text-base text-muted-foreground">{body ?? d.body}</p>
      {a ? (
        a.href ? (
          <Button className="mt-2" render={<Link href={a.href} />} nativeButton={false}>
            {a.label}
          </Button>
        ) : (
          <Button className="mt-2" onClick={a.onClick} disabled={a.busy}>
            {a.busy ? <Spinner /> : kind === "failed" ? <RotateCcwIcon data-icon="inline-start" /> : null}
            {a.busy ? "Trying again…" : a.label}
          </Button>
        )
      ) : null}
      {reference ? <p className="text-xs text-muted-foreground">Reference for support: {reference}</p> : null}
    </section>
  );
}

/* ------------------------------------ Panel error ------------------------------------ */

/**
 * Panel error (spec 04 §9.2): one panel, table or chart couldn't load. Stays inside the panel's frame (the
 * panel stays white; red is for the message only), other panels keep working, Try again reloads only this
 * panel. After repeated failures it adds who to contact. `reference` goes in a Details disclosure.
 */
export function PanelError({ what, onRetry, retrying = false, attempts = 1, reference, framed = false }: { what: string; onRetry: () => void; retrying?: boolean; attempts?: number; reference?: string; /** R12 (card 037): drawn in a list's border, where a page's list would be. */ framed?: boolean }) {
  const [details, setDetails] = React.useState(false);
  return (
    <div role="alert" className={cn("flex flex-col items-center gap-2 px-4 py-8 text-center", framed && "rounded-lg border border-[var(--color-border-panel)] bg-card")}>
      <CircleXIcon className="size-5 text-status-error-text" aria-hidden />
      <p className="text-sm font-semibold text-foreground">We couldn&apos;t load {what}</p>
      <p className="text-sm text-muted-foreground">
        Check your connection, then try again.
        {attempts >= 3 ? " If this keeps happening, contact your administrator." : ""}
      </p>
      <Button size="sm" variant="outline" onClick={onRetry} disabled={retrying}>
        {retrying ? <Spinner /> : <RotateCcwIcon data-icon="inline-start" />}
        {retrying ? "Trying again…" : "Try again"}
      </Button>
      {reference ? (
        <div className="text-xs text-muted-foreground">
          <button type="button" className="inline-flex cursor-pointer items-center gap-1 underline-offset-2 hover:underline" aria-expanded={details} onClick={() => setDetails((v) => !v)}>
            Details <ChevronDownIcon className={cn("size-3 transition-transform", details && "rotate-180")} aria-hidden />
          </button>
          {details ? <p className="mt-1 font-mono">Reference {reference}</p> : null}
        </div>
      ) : null}
    </div>
  );
}

/* --------------------------------------- Offline -------------------------------------- */

/**
 * Offline banner (spec 04 §9.4): sticky under the top bar while the API can't be reached (not only the
 * browser's offline flag). Data on screen stays, labelled with its "as at" time; nothing typed is lost.
 * Going offline is announced assertively. Medora doesn't promise offline sales beyond eTIMS queueing.
 */
export function OfflineBanner({ onRetry, checking = false, asAt }: { onRetry: () => void; checking?: boolean; asAt?: string }) {
  return (
    <div role="alert" className={cn("flex flex-wrap items-center gap-x-3 gap-y-1 border-b px-4 py-2 text-sm", TONE_BOX.warning)}>
      <CloudOffIcon className="size-4 shrink-0" aria-hidden />
      <p className="min-w-0 flex-1">
        <span className="font-semibold">You&apos;re offline.</span> Changes can&apos;t be saved until the connection is back. We&apos;ll keep trying.
        {asAt ? <span className="ml-1">Figures on screen are as at {asAt}.</span> : null}
      </p>
      <Button size="sm" variant="outline" className="bg-background" onClick={onRetry} disabled={checking}>
        {checking ? <Spinner /> : null}
        {checking ? "Checking…" : "Try now"}
      </Button>
    </div>
  );
}

/** Small "Offline" status (icon + word) for the top bar, on every page while disconnected. */
export function OfflineStatus() {
  return (
    <span className={cn("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium", TONE_BOX.warning)}>
      <CloudOffIcon className="size-3.5" aria-hidden /> Offline
    </span>
  );
}

/** A server action that can't run right now says so on the button, before the user fills anything in. */
export function NeedsConnection({ children }: { children: React.ReactNode }) {
  return (
    <span className="inline-flex flex-col items-start gap-1">
      {children}
      <span className="text-xs text-muted-foreground">Needs a connection</span>
    </span>
  );
}
