"use client";

/**
 * BeebBeeb shared UI primitives.
 *
 * This is the ONLY place a UI primitive may be defined, see CLAUDE-uiux-addition.md.
 * Visual contract: documents/design-system.md. Keep this file identical across
 * superadmin/ and investor/ (website/ deliberately does not use it).
 *
 * House rules encoded here:
 *  - buttons are always pills
 *  - every tinted surface goes through `tone` + the .tint formula, never a hand-picked hex
 *  - a tile's label is always --muted; color identifies the metric, not the text
 *  - numerals are tabular + LTR-isolated (.numeric) so Arabic never reorders money
 */

import { useEffect, useId } from "react";
import { useLocale } from "@/i18n/LocaleProvider";
import { TranslationKey } from "@/i18n/dictionaries";

export type Tone =
  | "brand"
  | "success"
  | "danger"
  | "info"
  | "accent"
  | "warning"
  | "neutral";

const toneClass: Record<Tone, string> = {
  brand: "tone-brand",
  success: "tone-success",
  danger: "tone-danger",
  info: "tone-info",
  accent: "tone-accent",
  warning: "tone-warning",
  neutral: "tone-neutral",
};

const toneText: Record<Tone, string> = {
  brand: "text-brand",
  success: "text-success",
  danger: "text-danger",
  info: "text-info",
  accent: "text-accent",
  warning: "text-warning",
  neutral: "",
};

/* ------------------------------------------------------------------ layout */

export function PageHeader({
  titleKey,
  subtitle,
  actions,
}: {
  titleKey: TranslationKey;
  /** One line under the title, for the count or state the page is about. */
  subtitle?: React.ReactNode;
  actions?: React.ReactNode;
}) {
  const { t } = useLocale();
  return (
    // items-start, not items-center: once a subtitle exists the title block is two lines tall and
    // centring would drag the actions into the middle of it.
    <div className="mb-6 flex flex-wrap items-start justify-between gap-4">
      <div>
        <h1 className="text-2xl font-bold tracking-tight">{t(titleKey)}</h1>
        {subtitle && <p className="mt-1 text-sm text-muted">{subtitle}</p>}
      </div>
      {actions && <div className="flex flex-wrap items-center gap-3">{actions}</div>}
    </div>
  );
}

export function Card({
  children,
  className = "",
}: {
  children: React.ReactNode;
  className?: string;
}) {
  return <div className={`card p-5 ${className}`}>{children}</div>;
}

export function SectionTitle({ children }: { children: React.ReactNode }) {
  return <h2 className="mb-3 text-lg font-semibold">{children}</h2>;
}

/* ----------------------------------------------------------------- actions */

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: "primary" | "secondary" | "ghost" | "danger";
};

/**
 * Variant → class, written out in full.
 *
 * These MUST be literal strings. Tailwind scans source text, so a constructed name like
 * `btn-${variant}` is invisible to it and the rule is silently never emitted, the button
 * then renders as unstyled text.
 */
const buttonClass: Record<NonNullable<ButtonProps["variant"]>, string> = {
  primary: "btn-primary",
  secondary: "btn-secondary",
  ghost: "btn-ghost",
  danger: "btn-danger",
};

/** Always a pill. Exactly one `primary` per screen. */
export function Button({
  variant = "secondary",
  className = "",
  ...props
}: ButtonProps) {
  return <button className={`${buttonClass[variant]} ${className}`} {...props} />;
}

/* -------------------------------------------------------------- indicators */

export function IconChip({
  children,
  tone = "neutral",
  className = "",
}: {
  children: React.ReactNode;
  tone?: Tone;
  className?: string;
}) {
  return (
    <span className={`icon-chip ${toneClass[tone]} ${className}`}>{children}</span>
  );
}

export function Badge({ children }: { children: React.ReactNode }) {
  return (
    <span className="inline-flex items-center rounded-full border border-line bg-background px-2 py-0.5 text-xs font-medium text-muted">
      {children}
    </span>
  );
}

/** Tinted status pill. Always carries text, color is never the only signal. */
export function StatusBadge({
  children,
  tone = "neutral",
}: {
  children: React.ReactNode;
  tone?: Tone;
}) {
  return (
    <span className={`status-badge ${toneClass[tone]}`}>
      <span className="h-1.5 w-1.5 rounded-full bg-current" aria-hidden />
      {children}
    </span>
  );
}

/** Tabular, LTR-isolated numerals, money and timers must not reorder under RTL. */
export function Numeric({
  children,
  className = "",
}: {
  children: React.ReactNode;
  className?: string;
}) {
  return <span className={`numeric ${className}`}>{children}</span>;
}

/** Signed amount, colored by direction, with the currency after the figure. */
export function MoneyText({
  amount,
  currency = "SAR",
  signed = false,
  className = "",
}: {
  amount: number;
  currency?: string;
  signed?: boolean;
  className?: string;
}) {
  const tone =
    !signed || amount === 0 ? "" : amount > 0 ? "text-success" : "text-danger";
  const sign = signed && amount > 0 ? "+" : "";
  return (
    <span className={`font-semibold ${tone} ${className}`}>
      <Numeric>
        {sign}
        {amount.toFixed(2)}
      </Numeric>{" "}
      <span className="text-xs font-normal text-muted">{currency}</span>
    </span>
  );
}

/** Prototype form: icon chip on top, one big tabular value, muted caption. */
export function StatTile({
  label,
  value,
  tone = "neutral",
  icon,
  accent = false,
}: {
  label: string;
  value: string | number;
  tone?: Tone;
  icon?: React.ReactNode;
  /** @deprecated use `tone="brand"` */
  accent?: boolean;
}) {
  const resolved: Tone = accent && tone === "neutral" ? "brand" : tone;
  return (
    <div className="card p-5">
      {icon && (
        <IconChip tone={resolved} className="mb-3">
          {icon}
        </IconChip>
      )}
      <div className={`text-3xl font-bold ${toneText[resolved]}`}>
        <Numeric>{value}</Numeric>
      </div>
      <div className="mt-1 text-[13px] text-muted">{label}</div>
    </div>
  );
}

/**
 * Single-select from a handful of options, laid out as a grid of cards.
 *
 * <p>Exists because a pill {@link SegmentedControl} stops working past about three options in a
 * narrow column: it either wraps into ragged rows inside one rounded container, which reads as a
 * broken control, or it scrolls and hides choices the user needs to compare. A grid keeps every
 * option visible and the same size.
 *
 * <p>The optional {@code dot} carries a meaning the label cannot, e.g. the colour a zone type is
 * drawn in on the map, so choosing is not a guess about which colour you are about to get.
 */
export function ChoiceGrid<T extends string>({
  options,
  value,
  onChange,
  columns = 2,
}: {
  options: { value: T; label: string; dot?: string }[];
  value: T;
  onChange: (v: T) => void;
  columns?: 2 | 3;
}) {
  return (
    <div
      role="radiogroup"
      className={`grid gap-2 ${columns === 3 ? "grid-cols-3" : "grid-cols-2"}`}
    >
      {options.map((o) => {
        const selected = o.value === value;
        return (
          <button
            key={o.value}
            type="button"
            role="radio"
            aria-checked={selected}
            onClick={() => onChange(o.value)}
            className={`flex items-center gap-2 rounded-card border px-3 py-2.5 text-start text-sm transition ${
              selected
                ? "border-brand bg-brand/10 font-medium text-foreground"
                : "border-line text-muted hover:border-line-strong hover:text-foreground"
            }`}
          >
            {o.dot && (
              <span
                className="h-2.5 w-2.5 shrink-0 rounded-full"
                style={{ backgroundColor: o.dot }}
                aria-hidden
              />
            )}
            <span className="truncate">{o.label}</span>
          </button>
        );
      })}
    </div>
  );
}

export function Toggle({
  checked,
  onChange,
  label,
}: {
  checked: boolean;
  onChange: (v: boolean) => void;
  label: string;
}) {
  return (
    <button
      type="button"
      role="switch"
      aria-checked={checked}
      aria-label={label}
      onClick={() => onChange(!checked)}
      className={`relative h-6 w-11 shrink-0 rounded-full transition ${
        checked ? "bg-brand" : "bg-line-strong"
      }`}
    >
      <span
        className={`absolute top-0.5 h-5 w-5 rounded-full bg-white transition-all ${
          checked ? "start-[22px]" : "start-0.5"
        }`}
      />
    </button>
  );
}

/* ------------------------------------------------------------------ inputs */

export function Field({
  label,
  error,
  icon,
  className = "",
  datalist,
  ...props
}: React.InputHTMLAttributes<HTMLInputElement> & {
  label?: string;
  error?: string;
  icon?: React.ReactNode;
  /** Optional suggestion list  turns the input into a combobox (dropdown + free text). */
  datalist?: string[];
}) {
  const listId = useId();
  return (
    <div className={className}>
      {label && <label className="field-label">{label}</label>}
      <div className="relative">
        <input
          className={`field ${icon ? "pe-10" : ""} ${
            error ? "border-danger" : ""
          }`}
          aria-invalid={!!error}
          list={datalist ? listId : undefined}
          {...props}
        />
        {datalist && (
          <datalist id={listId}>
            {datalist.map((o) => (
              <option key={o} value={o} />
            ))}
          </datalist>
        )}
        {icon && (
          <span className="pointer-events-none absolute inset-y-0 end-3 flex items-center text-muted">
            {icon}
          </span>
        )}
      </div>
      {error && <p className="mt-1.5 text-xs text-danger">{error}</p>}
    </div>
  );
}

/** A styled native dropdown  same field styling as {@link Field}, for enumerable values. */
export function Select({
  label,
  error,
  className = "",
  options,
  placeholder,
  ...props
}: React.SelectHTMLAttributes<HTMLSelectElement> & {
  label?: string;
  error?: string;
  /** Each option is a value + label; a plain string is used for both. */
  options: (string | { value: string; label: string })[];
  placeholder?: string;
}) {
  return (
    <div className={className}>
      {label && <label className="field-label">{label}</label>}
      <select className={`field ${error ? "border-danger" : ""}`} aria-invalid={!!error} {...props}>
        {placeholder !== undefined && <option value="">{placeholder}</option>}
        {options.map((o) => {
          const value = typeof o === "string" ? o : o.value;
          const text = typeof o === "string" ? o : o.label;
          return (
            <option key={value} value={value}>
              {text}
            </option>
          );
        })}
      </select>
      {error && <p className="mt-1.5 text-xs text-danger">{error}</p>}
    </div>
  );
}

export function SegmentedControl<T extends string>({
  options,
  value,
  onChange,
  layout = "wrap",
}: {
  options: { value: T; label: string }[];
  value: T;
  onChange: (v: T) => void;
  /**
   * How to cope when the options are wider than the space.
   *
   * <p>"wrap" suits a narrow side panel, where a second line is better than a cut-off option.
   * "scroll" suits a full-width tab bar, where wrapping leaves one lonely tab on its own line and
   * shoves everything below it down.
   */
  layout?: "wrap" | "scroll";
}) {
  return (
    // max-w-full either way: without it a long option list pushes the whole page into a
    // horizontal scroll instead of managing itself.
    <div
      role="tablist"
      className={`inline-flex max-w-full gap-1 rounded-full border border-line bg-surface p-1 ${
        layout === "wrap"
          ? "flex-wrap"
          : "flex-nowrap overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
      }`}
    >
      {options.map((o) => (
        <button
          key={o.value}
          // Without this a segmented button inside a <form> defaults to type="submit", so
          // choosing an option submits the form instead of changing the selection.
          type="button"
          role="tab"
          aria-selected={o.value === value}
          onClick={() => onChange(o.value)}
          className={`whitespace-nowrap rounded-full px-4 py-1.5 text-sm font-medium transition ${
            o.value === value
              ? "bg-brand text-brand-ink"
              : "text-muted hover:text-foreground"
          }`}
        >
          {o.label}
        </button>
      ))}
    </div>
  );
}

export function Stepper({ step, total }: { step: number; total: number }) {
  return (
    <div className="flex items-center gap-3" aria-label={`${step} / ${total}`}>
      {Array.from({ length: total }, (_, i) => i + 1).map((n) => (
        <div key={n} className="flex flex-1 items-center gap-3 last:flex-none">
          <span
            className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-bold ${
              n <= step
                ? "bg-brand text-brand-ink"
                : "border border-line text-muted"
            }`}
          >
            <Numeric>{n}</Numeric>
          </span>
          {n < total && (
            <span
              className={`h-px flex-1 ${n < step ? "bg-brand" : "bg-line"}`}
            />
          )}
        </div>
      ))}
    </div>
  );
}

export function Rating({
  value,
  onChange,
  label,
}: {
  value: number;
  onChange: (v: number) => void;
  label: string;
}) {
  return (
    <div className="flex gap-2" role="radiogroup" aria-label={label}>
      {[1, 2, 3, 4, 5].map((n) => (
        <button
          key={n}
          role="radio"
          aria-checked={n === value}
          aria-label={`${n}`}
          onClick={() => onChange(n)}
          className={`text-2xl transition ${
            n <= value ? "text-brand" : "text-line-strong"
          }`}
        >
          ★
        </button>
      ))}
    </div>
  );
}

/** Icon chip + title + trailing value. The settings/list-row workhorse. */
export function ListRow({
  icon,
  tone = "neutral",
  title,
  value,
  onClick,
}: {
  icon?: React.ReactNode;
  tone?: Tone;
  title: string;
  value?: React.ReactNode;
  onClick?: () => void;
}) {
  const className =
    "flex w-full items-center gap-3 border-b border-line px-5 py-4 text-start last:border-b-0";
  const inner = (
    <>
      {icon && <IconChip tone={tone}>{icon}</IconChip>}
      <span className="flex-1 text-sm font-medium">{title}</span>
      {value && <span className="text-sm text-muted">{value}</span>}
    </>
  );
  return onClick ? (
    <button onClick={onClick} className={className}>
      {inner}
    </button>
  ) : (
    <div className={className}>{inner}</div>
  );
}

/* ------------------------------------------------------------ three states */

export function Spinner() {
  const { t } = useLocale();
  return (
    <div className="flex items-center gap-2 text-sm text-muted">
      <span className="h-3 w-3 animate-spin rounded-full border-2 border-line border-t-brand" />
      {t("common.loading")}
    </div>
  );
}

/** Loading state must match the final layout's shape, never a spinner on a blank page. */
export function Skeleton({ className = "" }: { className?: string }) {
  return (
    <span
      aria-hidden
      className={`relative block overflow-hidden rounded-chip bg-line ${className}`}
    >
      <span className="absolute inset-0 -translate-x-full animate-shimmer bg-gradient-to-r from-transparent via-white/10 to-transparent" />
    </span>
  );
}

export function SkeletonCard({ rows = 3 }: { rows?: number }) {
  return (
    <div className="card space-y-3 p-5">
      <Skeleton className="h-4 w-1/3" />
      {Array.from({ length: rows }).map((_, i) => (
        <Skeleton key={i} className="h-3 w-full" />
      ))}
    </div>
  );
}

export function SkeletonTable({ rows = 5 }: { rows?: number }) {
  return (
    <div className="card divide-y divide-line">
      {Array.from({ length: rows }).map((_, i) => (
        <div key={i} className="flex items-center gap-4 p-4">
          <Skeleton className="h-9 w-9 rounded-chip" />
          <Skeleton className="h-3 flex-1" />
          <Skeleton className="h-3 w-16" />
        </div>
      ))}
    </div>
  );
}

/** Real empty state, icon + copy + optional action, never a bare table header. */
export function EmptyState({
  icon,
  title,
  description,
  action,
}: {
  icon?: React.ReactNode;
  title: string;
  description?: string;
  action?: React.ReactNode;
}) {
  return (
    <div className="card flex flex-col items-center gap-3 p-10 text-center">
      {icon && (
        <IconChip tone="neutral" className="h-12 w-12">
          {icon}
        </IconChip>
      )}
      <p className="font-semibold">{title}</p>
      {description && (
        <p className="max-w-sm text-sm text-muted">{description}</p>
      )}
      {action}
    </div>
  );
}

/** Error state always offers a retry, never a raw error string. */
export function ErrorState({
  onRetry,
  message,
}: {
  onRetry?: () => void;
  message?: string;
}) {
  const { t } = useLocale();
  return (
    <div className="card tint tone-danger p-5">
      <p className="text-sm font-medium">{message ?? t("common.error")}</p>
      {onRetry && (
        <Button className="mt-3" onClick={onRetry}>
          {t("common.retry")}
        </Button>
      )}
    </div>
  );
}

/* ------------------------------------------------------------------ layers */

function useLockedScroll(open: boolean) {
  useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = prev;
    };
  }, [open]);
}

function Scrim({ onClose }: { onClose: () => void }) {
  return (
    <div
      className="absolute inset-0 animate-fade-in bg-black/60"
      onClick={onClose}
    />
  );
}

/** Bottom sheet, grabber, top-rounded, scrim. The mobile-shaped disclosure. */
export function Sheet({
  open,
  onClose,
  title,
  children,
}: {
  open: boolean;
  onClose: () => void;
  title?: string;
  children: React.ReactNode;
}) {
  useLockedScroll(open);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-end justify-center">
      <Scrim onClose={onClose} />
      <div
        role="dialog"
        aria-modal="true"
        aria-label={title}
        className="relative flex max-h-[92vh] w-full max-w-lg animate-sheet-in flex-col rounded-t-sheet border border-line bg-surface-raised"
      >
        {/* Fixed header (drag handle + title); the body below scrolls when content is tall. */}
        <div className="shrink-0 px-5 pt-5">
          <div className="mx-auto mb-4 h-1 w-10 rounded-full bg-line-strong" />
          {title && (
            <h2 className="mb-2 text-center text-lg font-semibold">{title}</h2>
          )}
        </div>
        <div className="overflow-y-auto overscroll-contain px-5 pb-8 pt-2">{children}</div>
      </div>
    </div>
  );
}

/**
 * Full-height panel anchored to the side of the window.
 *
 * The counterpart to {@link Sheet}: use this when the content is dense enough that a bottom sheet
 * fights it. A vehicle's detail is tabbed, holds a searchable list and a log, and a sheet capped at
 * 92vh with a phone-width column left most of it below the fold on a desktop screen.
 *
 * Anchored with logical positioning so it arrives from the inline-end edge: the right in English,
 * the left in Arabic, without a second component.
 */
export function SidePanel({
  open,
  onClose,
  title,
  subtitle,
  children,
}: {
  open: boolean;
  onClose: () => void;
  title?: string;
  subtitle?: string;
  children: React.ReactNode;
}) {
  useLockedScroll(open);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex justify-end">
      <Scrim onClose={onClose} />
      <div
        role="dialog"
        aria-modal="true"
        aria-label={title}
        className="relative flex h-full w-full max-w-2xl animate-panel-in flex-col border-s border-line bg-surface-raised shadow-2xl"
      >
        <div className="flex shrink-0 items-start justify-between gap-4 border-b border-line px-6 py-5">
          <div className="min-w-0">
            {title && <h2 className="truncate text-xl font-semibold">{title}</h2>}
            {subtitle && <p className="mt-0.5 text-sm text-muted">{subtitle}</p>}
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="Close"
            className="shrink-0 rounded-full border border-line px-3 py-1.5 text-sm text-muted transition hover:bg-surface hover:text-fg"
          >
            &#10005;
          </button>
        </div>
        <div className="flex-1 overflow-y-auto overscroll-contain px-6 py-5">{children}</div>
      </div>
    </div>
  );
}

/** Centered confirm dialog, icon on top, then title, body, actions. */
export function Dialog({
  open,
  onClose,
  icon,
  tone = "neutral",
  title,
  description,
  children,
  actions,
}: {
  open: boolean;
  onClose: () => void;
  icon?: React.ReactNode;
  tone?: Tone;
  title: string;
  description?: string;
  children?: React.ReactNode;
  actions?: React.ReactNode;
}) {
  useLockedScroll(open);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
      <Scrim onClose={onClose} />
      <div
        role="dialog"
        aria-modal="true"
        aria-label={title}
        className="relative w-full max-w-sm animate-dialog-in rounded-card border border-line bg-surface-raised p-6 text-center"
      >
        {icon && (
          <IconChip tone={tone} className="mx-auto mb-4 h-12 w-12">
            {icon}
          </IconChip>
        )}
        <h2 className="text-lg font-semibold">{title}</h2>
        {description && <p className="mt-1 text-sm text-muted">{description}</p>}
        {children && <div className="mt-4 text-start">{children}</div>}
        {actions && <div className="mt-6 flex gap-3">{actions}</div>}
      </div>
    </div>
  );
}

/**
 * Wide centered modal for a task, as opposed to {@link Dialog} (a narrow confirm) or
 * {@link Sheet} (the mobile-shaped disclosure). Use it when the content is a form or a table that
 * needs room, e.g. previewing an import.
 *
 * <p>Header and footer stay put; only the body scrolls, so the action buttons never drift off a
 * long error list.
 */
export function Modal({
  open,
  onClose,
  title,
  description,
  children,
  actions,
  wide = false,
}: {
  open: boolean;
  onClose: () => void;
  title: string;
  description?: string;
  children: React.ReactNode;
  actions?: React.ReactNode;
  wide?: boolean;
}) {
  const { t } = useLocale();
  useLockedScroll(open);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
      <Scrim onClose={onClose} />
      <div
        role="dialog"
        aria-modal="true"
        aria-label={title}
        className={`relative flex max-h-[88vh] w-full ${
          wide ? "max-w-4xl" : "max-w-2xl"
        } animate-dialog-in flex-col rounded-card border border-line bg-surface-raised`}
      >
        <div className="flex shrink-0 items-start gap-4 border-b border-line p-5">
          <div className="min-w-0 flex-1">
            <h2 className="text-lg font-semibold">{title}</h2>
            {description && (
              <p className="mt-1 text-sm text-muted">{description}</p>
            )}
          </div>
          <button
            onClick={onClose}
            aria-label={t("common.close")}
            className="-me-1 -mt-1 rounded-full p-2 text-muted transition hover:bg-surface hover:text-foreground"
          >
            <svg
              width="18"
              height="18"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
            >
              <path d="M18 6 6 18M6 6l12 12" />
            </svg>
          </button>
        </div>
        <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-5">
          {children}
        </div>
        {actions && (
          <div className="flex shrink-0 justify-end gap-3 border-t border-line p-5">
            {actions}
          </div>
        )}
      </div>
    </div>
  );
}

/* ------------------------------------------------------------ placeholders */

/** Placeholders stay honest, this is still a surface a stakeholder may see. */
export function ComingSoon({ titleKey }: { titleKey: TranslationKey }) {
  const { t } = useLocale();
  return (
    <div>
      <PageHeader titleKey={titleKey} />
      <EmptyState
        icon={<span className="text-xl">🛠</span>}
        title={t("common.comingSoon")}
      />
    </div>
  );
}
