"use client";

import * as React from "react";
import Link from "next/link";
import { BellIcon, CheckCheckIcon, CircleCheckIcon, InfoIcon, OctagonAlertIcon, RotateCcwIcon, SettingsIcon, TriangleAlertIcon, type LucideIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { cn } from "@/lib/utils";

import { TONE_TEXT, type Tone } from "./tones";

/**
 * An in-app alert raised under Settings → 9.9 (ST-14): low stock, expiry, overdue balances, approvals,
 * eTIMS rejections. Alerts about a condition (stock is low) become `resolved` when the condition clears,
 * rather than disappearing, so the record of what happened stays (lifetime is still open: OQ-11).
 */
export type Notice = {
  id: string;
  tone: Tone;
  /** Status word shown with the icon: "Low stock", "Expiring", "Overdue". Never colour alone. */
  word: string;
  text: string;
  /** ISO date-time. */
  at: string;
  href?: string;
  read?: boolean;
  resolved?: boolean;
};

const ICON: Record<Tone, LucideIcon> = { warning: TriangleAlertIcon, error: OctagonAlertIcon, info: InfoIcon, success: CircleCheckIcon, neutral: BellIcon };

const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

/** "Just now", "12 min ago", "3 h ago", "Yesterday 16:40", "18 Oct 09:15". `now` is passed in so server and client agree. */
export function noticeTime(at: string, now: string) {
  const a = new Date(at).getTime();
  const n = new Date(now).getTime();
  const min = Math.round((n - a) / 60000);
  const hhmm = at.slice(11, 16);
  if (min < 1) return "Just now";
  if (min < 60) return `${min} min ago`;
  if (at.slice(0, 10) === now.slice(0, 10)) return `${Math.floor(min / 60)} h ago`;
  const y = new Date(n - 86400000).toISOString().slice(0, 10);
  if (at.slice(0, 10) === y) return `Yesterday ${hhmm}`;
  return `${Number(at.slice(8, 10))} ${MONTHS[Number(at.slice(5, 7)) - 1]} ${hhmm}`;
}

function NoticeRow({ n, now, onOpen }: { n: Notice; now: string; onOpen: (id: string) => void }) {
  const Icon = ICON[n.tone];
  const unread = !n.read && !n.resolved;
  const body = (
    <>
      <span className={cn("mt-0.5 shrink-0", n.resolved ? "text-muted-foreground" : TONE_TEXT[n.tone])} aria-hidden>
        {n.resolved ? <CircleCheckIcon className="size-4" /> : <Icon className="size-4" />}
      </span>
      <span className="min-w-0 flex-1">
        <span className="flex items-baseline gap-2">
          <span className={cn("text-xs font-semibold", n.resolved ? "text-muted-foreground" : TONE_TEXT[n.tone])}>{n.resolved ? `${n.word}, resolved` : n.word}</span>
          <span className="ml-auto shrink-0 text-xs text-muted-foreground tabular-nums">{noticeTime(n.at, now)}</span>
        </span>
        <span className={cn("mt-0.5 block text-sm", unread ? "font-medium text-foreground" : "text-muted-foreground")}>{n.text}</span>
      </span>
      {/* Unread marker uses the brand colour, not a status colour: being unread isn't a status. */}
      <span className={cn("mt-1.5 size-2 shrink-0 rounded-full", unread ? "bg-primary" : "bg-transparent")} aria-hidden />
      <span className="sr-only">{unread ? ", unread" : ""}</span>
    </>
  );
  const cls = "flex w-full gap-3 rounded-md px-3 py-2.5 text-left outline-none hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring";
  return (
    <li>
      {n.href ? (
        <Link href={n.href} className={cls} onClick={() => onOpen(n.id)}>
          {body}
        </Link>
      ) : (
        <button type="button" className={cn(cls, "cursor-pointer")} onClick={() => onOpen(n.id)}>
          {body}
        </button>
      )}
    </li>
  );
}

/**
 * Notifications (spec 03 §3, C48): the bell in the top bar and its panel. The count badge uses the brand
 * colour (unread isn't a status) and caps at "9+". The panel lists alerts newest first, grouped Today /
 * Earlier, with All / Unread, Mark all as read, and a footer link to alert settings only for roles that may
 * open them. Don't render the bell at all for a role with no alerts configured.
 */
export function NotificationsBell({
  items,
  now,
  state = "ready",
  onOpenNotice,
  onReadAll,
  onRetry,
  settingsHref,
  defaultOpen = false,
}: {
  items: Notice[];
  now: string;
  state?: "ready" | "loading" | "error";
  /** Opening one marks it read (and follows its link, when it has one). */
  onOpenNotice: (id: string) => void;
  onReadAll: () => void;
  onRetry?: () => void;
  /** Settings → 9.9; omit for roles that can't open it. */
  settingsHref?: string;
  defaultOpen?: boolean;
}) {
  const [filter, setFilter] = React.useState<"all" | "unread">("all");
  const sorted = [...items].sort((a, b) => b.at.localeCompare(a.at));
  const unread = sorted.filter((n) => !n.read && !n.resolved);
  const shown = filter === "unread" ? unread : sorted;
  const today = shown.filter((n) => n.at.slice(0, 10) === now.slice(0, 10));
  const earlier = shown.filter((n) => n.at.slice(0, 10) !== now.slice(0, 10));
  const count = unread.length;

  return (
    <Popover defaultOpen={defaultOpen}>
      <PopoverTrigger
        render={<Button variant="ghost" size="icon" className="relative" aria-label={count ? `Notifications, ${count} unread` : "Notifications"} />}
      >
        <BellIcon />
        {count ? (
          <span className="absolute -top-0.5 -right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[11px] leading-none font-semibold text-primary-foreground tabular-nums" aria-hidden>
            {count > 9 ? "9+" : count}
          </span>
        ) : null}
      </PopoverTrigger>
      <PopoverContent align="end" className="w-[400px] max-w-[calc(100vw-2rem)] gap-0 p-0">
        <div className="flex items-center gap-2 border-b border-border px-3 py-2.5">
          <h2 className="text-sm font-semibold">Notifications</h2>
          <ToggleGroup variant="outline" size="sm" spacing={0} aria-label="Show" value={[filter]} onValueChange={(v: string[]) => v[0] && setFilter(v[0] as "all" | "unread")} className="ml-auto">
            <ToggleGroupItem value="all">All</ToggleGroupItem>
            <ToggleGroupItem value="unread">Unread{count ? ` (${count})` : ""}</ToggleGroupItem>
          </ToggleGroup>
        </div>

        <div className="max-h-[min(440px,60vh)] overflow-y-auto p-1" aria-busy={state === "loading"}>
          {state === "loading" ? (
            <ul aria-label="Loading notifications">
              {[0, 1, 2].map((i) => (
                <li key={i} className="flex gap-3 px-3 py-2.5">
                  <span className="size-4 animate-pulse rounded-full bg-muted" />
                  <span className="flex-1 space-y-1.5">
                    <span className="block h-3 w-24 animate-pulse rounded-sm bg-muted" />
                    <span className="block h-3.5 w-full animate-pulse rounded-sm bg-muted" />
                  </span>
                </li>
              ))}
            </ul>
          ) : state === "error" ? (
            <div role="alert" className="flex flex-col items-center gap-2 px-4 py-8 text-center">
              <OctagonAlertIcon className="size-5 text-status-error-text" aria-hidden />
              <p className="text-sm font-medium">Couldn&apos;t load notifications</p>
              <p className="text-xs text-muted-foreground">Check the connection, then try again.</p>
              {onRetry ? (
                <Button size="sm" variant="outline" onClick={onRetry}>
                  <RotateCcwIcon data-icon="inline-start" />
                  Try again
                </Button>
              ) : null}
            </div>
          ) : shown.length === 0 ? (
            <div className="flex flex-col items-center gap-1 px-4 py-8 text-center">
              <CheckCheckIcon className="size-5 text-muted-foreground" aria-hidden />
              <p className="text-sm font-medium">{filter === "unread" && items.length ? "You've read everything" : "No alerts"}</p>
              <p className="text-xs text-muted-foreground">Low stock, expiry and overdue balances show here when they need attention.</p>
            </div>
          ) : (
            [
              ["Today", today],
              ["Earlier", earlier],
            ].map(([label, list]) =>
              (list as Notice[]).length ? (
                <section key={label as string} aria-label={label as string}>
                  <h3 className="px-3 pt-2 pb-1 text-xs font-medium text-muted-foreground uppercase">{label as string}</h3>
                  <ul>
                    {(list as Notice[]).map((n) => (
                      <NoticeRow key={n.id} n={n} now={now} onOpen={onOpenNotice} />
                    ))}
                  </ul>
                </section>
              ) : null,
            )
          )}
        </div>

        {state === "ready" && (count > 0 || settingsHref) ? (
          <div className="flex items-center gap-2 border-t border-border px-2 py-1.5">
            {count > 0 ? (
              <Button variant="ghost" size="sm" onClick={onReadAll}>
                <CheckCheckIcon data-icon="inline-start" />
                Mark all as read
              </Button>
            ) : null}
            {settingsHref ? (
              <Button variant="ghost" size="sm" className="ml-auto" render={<Link href={settingsHref} />} nativeButton={false}>
                <SettingsIcon data-icon="inline-start" />
                Alert settings
              </Button>
            ) : null}
          </div>
        ) : null}
      </PopoverContent>
    </Popover>
  );
}

/** State helper for screens and demos: read one, read all. */
export function useNotices(initial: Notice[]) {
  const [items, setItems] = React.useState(initial);
  return {
    items,
    setItems,
    open: (id: string) => setItems((xs) => xs.map((n) => (n.id === id ? { ...n, read: true } : n))),
    readAll: () => setItems((xs) => xs.map((n) => ({ ...n, read: true }))),
  };
}
