/**
 * The design system's component vocabulary, in one place. Every other file
 * composes these rather than re-spelling the class strings.
 */
import type { ReactNode } from "react";

/**
 * Every control in the app is this tall. Width still grows with the label —
 * only the height is fixed, so a row of mixed buttons lines up.
 */
export const CONTROL_H = "h-[30px]";

/** Square version, for icon-only buttons. */
export const ICON_BTN =
  `grid ${CONTROL_H} w-[30px] shrink-0 place-items-center rounded-lg cursor-pointer ` +
  "disabled:cursor-not-allowed disabled:opacity-40";

export const HEADING =
  "text-[11px] font-bold uppercase tracking-widest text-slate-500 dark:text-slate-400";
export const LABEL = "text-[12px] text-slate-500 dark:text-slate-400";
export const HELP =
  "text-[11px] leading-snug text-slate-500 dark:text-slate-400";
export const PANEL =
  "bg-white dark:bg-slate-900 border-slate-300 dark:border-slate-800";
export const SECTION =
  "border-b border-slate-200 px-4 py-4 dark:border-slate-800";
export const INPUT =
  `w-full ${CONTROL_H} rounded-lg border border-slate-300 bg-white px-3 text-[12px] outline-none ` +
  "placeholder:text-slate-400 dark:border-slate-700 dark:bg-slate-800 dark:placeholder:text-slate-500";
export const SUBPANEL = "rounded-lg bg-slate-50 p-3 dark:bg-slate-800/50";

const BTN_BASE =
  `inline-flex ${CONTROL_H} items-center justify-center rounded-lg text-[12px] ` +
  "disabled:cursor-not-allowed";

export const BTN =
  `${BTN_BASE} border border-slate-300 px-2.5 font-medium ` +
  "hover:border-sky-500 hover:text-sky-600 disabled:opacity-40 " +
  "dark:border-slate-700 dark:hover:border-sky-500 dark:hover:text-sky-400";

export const BTN_PRIMARY =
  `${BTN_BASE} bg-sky-500 px-3 font-semibold text-white ` +
  "hover:bg-sky-400 disabled:opacity-40";

export const BTN_DANGER =
  `${BTN_BASE} bg-red-600 px-3 font-semibold text-white hover:bg-red-500`;

/** Header actions: quieter than a secondary button, still a real target. */
export const BTN_CHROME =
  `inline-flex ${CONTROL_H} items-center rounded-lg px-2 text-[12px] font-medium text-slate-500 ` +
  "hover:bg-slate-100 hover:text-slate-900 disabled:opacity-40 disabled:hover:bg-transparent " +
  "dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white";

/** Reads as a link; sits at the edge of a group. */
export const BTN_QUIET =
  "text-[11px] text-slate-500 underline underline-offset-2 " +
  "hover:text-sky-600 dark:text-slate-400 dark:hover:text-sky-400";

/** The only spinning thing in the app; used where a wait has no known length. */
export function Spinner({ className = "size-4" }: { className?: string }) {
  return (
    <svg className={`${className} animate-spin`} viewBox="0 0 24 24" fill="none" aria-hidden>
      <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2.5" className="opacity-25" />
      <path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
    </svg>
  );
}

export function SectionHeading({
  step,
  children,
}: {
  step?: number;
  children: ReactNode;
}) {
  return (
    <h2 className={`flex items-center gap-2 ${HEADING}`}>
      {step !== undefined && (
        <span className="flex size-4 shrink-0 items-center justify-center rounded-full bg-sky-500 text-[9px] font-bold leading-none text-white">
          {step}
        </span>
      )}
      {children}
    </h2>
  );
}

/** Bordered container, borderless children — the group's outline does the framing. */
export function Segmented<T extends string>({
  options,
  value,
  onChange,
}: {
  options: Array<{ value: T; label: ReactNode; title?: string }>;
  value: T;
  onChange: (v: T) => void;
}) {
  return (
    <div
      role="group"
      className={`flex ${CONTROL_H} items-center rounded-lg border border-slate-300 p-0.5 dark:border-slate-700`}
    >
      {options.map((o) => {
        const active = o.value === value;
        return (
          <button
            key={o.value}
            onClick={() => onChange(o.value)}
            title={o.title}
            className={
              "inline-flex h-full items-center justify-center rounded-md px-2.5 text-[12px] " +
              "font-medium transition-colors cursor-pointer " +
              (active
                ? "bg-slate-900! text-white! dark:bg-white! dark:text-slate-900!"
                : "text-slate-500 hover:bg-slate-100 hover:text-slate-900 " +
                  "dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white")
            }
          >
            {o.label}
          </button>
        );
      })}
    </div>
  );
}

/**
 * Success confirmation of something that already happened, so it never asks to
 * be dismissed. Making someone dismiss a box to be told it worked is a bug.
 */
export function Toast({ message }: { message: string | null }) {
  return (
    <div className="pointer-events-none fixed inset-x-0 bottom-6 z-[80] flex flex-col items-center gap-2">
      {message && (
        <div
          role="status"
          className="pointer-events-auto rounded-full border border-slate-300 bg-white px-4 py-2
                     text-[12.5px] shadow-xl transition-opacity duration-300
                     dark:border-slate-700 dark:bg-slate-800"
        >
          {message}
        </div>
      )}
    </div>
  );
}

/** A channel name that takes you to its channel. Quiet until hovered. */
export const CHANNEL_LINK =
  "block w-full cursor-pointer truncate text-left text-[12px] text-slate-500 " +
  "transition-colors hover:text-sky-600 dark:text-slate-400 dark:hover:text-sky-400";

/** States a fact in passing — a quality, a status. Never a control. */
export function Badge({
  children,
  tone = "neutral",
  title,
}: {
  children: ReactNode;
  tone?: "neutral" | "accent" | "danger";
  title?: string;
}) {
  const skin =
    tone === "accent"
      ? "bg-sky-500/15 text-sky-700 dark:text-sky-300"
      : tone === "danger"
        ? "bg-red-500/15 text-red-600 dark:text-red-400"
        : "bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300";
  return (
    <span
      title={title}
      className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5
                  text-[11px] font-medium ${skin}`}
    >
      {children}
    </span>
  );
}

/** A modal for anything needing a decision or reporting a failure. */
export function Dialog({
  title,
  children,
  onCancel,
  confirmLabel,
  onConfirm,
  destructive,
  wide,
  footer,
}: {
  title: string;
  children: ReactNode;
  onCancel: () => void;
  confirmLabel?: string;
  onConfirm?: () => void;
  destructive?: boolean;
  wide?: boolean;
  /** Replaces Cancel/Confirm, for a dialog whose buttons have their own state. */
  footer?: ReactNode;
}) {
  return (
    <div
      className="fixed inset-0 z-[70] flex items-center justify-center bg-slate-950/70 p-5"
      onClick={onCancel}
    >
      <div
        role="dialog"
        aria-modal="true"
        onClick={(e) => e.stopPropagation()}
        className={`w-full ${wide ? "max-w-lg max-h-[82vh] overflow-y-auto" : "max-w-sm"}
                    rounded-2xl border border-slate-300 bg-white p-5 shadow-2xl
                    dark:border-slate-700 dark:bg-slate-900`}
      >
        <h2 className="mb-1.5 text-[15px] font-semibold tracking-tight">{title}</h2>
        <div className="mb-4 text-[13px] leading-relaxed text-slate-600 dark:text-slate-300">
          {children}
        </div>
        {footer ?? (
          <div className="flex justify-end gap-2">
            <button onClick={onCancel} className={`${BTN} cursor-pointer`}>
              {onConfirm ? "Cancel" : "Close"}
            </button>
            {onConfirm && (
              <button
                onClick={onConfirm}
                className={`${destructive ? BTN_DANGER : BTN_PRIMARY} cursor-pointer`}
              >
                {confirmLabel ?? "Continue"}
              </button>
            )}
          </div>
        )}
      </div>
    </div>
  );
}
