import {
  Activity,
  BarChart3,
  Bell,
  Clapperboard,
  Download,
  Home,
  ListVideo,
  MessageSquare,
  ScrollText,
  Settings,
  SlidersHorizontal,
  Tv,
  Users,
  type LucideIcon,
} from "lucide-react";
import type { Me } from "./api";
import type { Page } from "./urlState";

// ── The module registry ───────────────────────────────────────────────────────────────────────
// One ordered source of truth for every navigable module: its nav label, icon, admin/system
// placement, whether it carries a live badge, and its availability gate. The nav rail, the header's
// ◀/▶ stepper, and (as the registry grows) the page render all read from here, so a new or newly
// gated module shows up everywhere at once — nothing hard-codes the sequence or duplicates the icon.
export type ModuleDef = {
  id: Page;
  // i18n key for the SHORT display name — the same label the nav rail and the header pill show.
  labelKey: string;
  // i18n key for the LONGER, descriptive browser-tab title (e.g. "Channel manager" vs the rail's
  // "Channels"). Absent → the tab title falls back to `labelKey`. Read via `pageTitleKey`.
  titleKey?: string;
  icon: LucideIcon;
  // Admin/system modules render in their own section (below a divider) in the nav rail.
  system?: boolean;
  // Carries a live unread/active count in the rail (the number itself is computed in NavSidebar,
  // which owns the polling queries; this only flags which modules have one).
  hasBadge?: boolean;
  // Availability for THIS account, derived from language-independent account state (plex toggle,
  // demo restrictions, admin role). Absent = always available. Drives both the reachable module
  // set (nav + stepper) and, later, the render gate.
  gate?: (me: Me) => boolean;
};

const isAdmin = (me: Me) => me.role === "admin";
const notDemo = (me: Me) => !me.is_demo;

// Ordered in nav-rail order — this array IS the sequence the rail and the stepper walk.
const MODULES: readonly ModuleDef[] = [
  { id: "feed", labelKey: "header.account.feed", icon: Home },
  {
    id: "channels",
    labelKey: "header.account.channels",
    titleKey: "header.channelManager",
    icon: Tv,
  },
  { id: "playlists", labelKey: "header.account.playlists", icon: ListVideo },
  { id: "plex", labelKey: "plex.navLabel", icon: Clapperboard, gate: (me) => me.plex_enabled },
  { id: "notifications", labelKey: "inbox.navLabel", icon: Bell, hasBadge: true },
  {
    id: "messages",
    labelKey: "messages.navLabel",
    icon: MessageSquare,
    hasBadge: true,
    gate: notDemo,
  },
  {
    id: "downloads",
    labelKey: "downloads.navLabel",
    icon: Download,
    hasBadge: true,
    gate: notDemo,
  },
  { id: "stats", labelKey: "header.account.stats", titleKey: "header.usageStats", icon: BarChart3 },
  {
    id: "scheduler",
    labelKey: "header.account.scheduler",
    titleKey: "header.scheduler",
    icon: Activity,
    system: true,
    gate: isAdmin,
  },
  {
    id: "config",
    labelKey: "header.account.config",
    titleKey: "header.configuration",
    icon: SlidersHorizontal,
    system: true,
    gate: isAdmin,
  },
  {
    id: "users",
    labelKey: "header.account.users",
    titleKey: "header.users",
    icon: Users,
    system: true,
    gate: isAdmin,
  },
  {
    id: "audit",
    labelKey: "header.account.audit",
    titleKey: "audit.title",
    icon: ScrollText,
    system: true,
    gate: isAdmin,
  },
];

// Settings is reachable (its own rail button + the ◀/▶ wrap target) but is not a primary module in
// the ordered nav list, so it lives beside the array rather than in it.
const SETTINGS_MODULE: ModuleDef = {
  id: "settings",
  labelKey: "header.account.settings",
  titleKey: "settings.title",
  icon: Settings,
};

// Registering a NEW Page means adding it to MODULES (or SETTINGS_MODULE) above — that's the single
// place. If a Page is left unregistered, moduleDef()/moduleLabelKey[] return undefined and NavSidebar
// throws on `.icon`; the coverage isn't compiler-enforced here because the ordered array can't also
// be an exhaustive Page map, so treat the array as the checklist when the Page union grows.
const BY_ID = Object.fromEntries([...MODULES, SETTINGS_MODULE].map((m) => [m.id, m])) as Record<
  Page,
  ModuleDef
>;

// Look up a module's definition by page id (every Page is covered, incl. settings).
export function moduleDef(id: Page): ModuleDef {
  return BY_ID[id];
}

// The i18n key for each module's SHORT display name. Single source so the nav rail and the header's
// ModuleName pill always read identically (the longer, descriptive `pageTitleKey` names are kept
// only for the browser tab title).
export const moduleLabelKey: Record<Page, string> = Object.fromEntries(
  [...MODULES, SETTINGS_MODULE].map((m) => [m.id, m.labelKey]),
) as Record<Page, string>;

// Admin/system modules — rendered in their own section (below a divider) in the nav rail.
export const SYSTEM_PAGES: readonly Page[] = MODULES.filter((m) => m.system).map((m) => m.id);

// The ordered list of primary module pages available to THIS user, in nav-rail order. Single
// source of truth for both the nav rail (NavSidebar) and the header's ◀/▶ module stepper, so a
// new module (or a newly-gated one) shows up in both at once. Availability is derived from the
// account via each module's `gate`, so the reachable set changes with the account state, not the UI.
export function moduleOrder(me: Me): Page[] {
  return MODULES.filter((m) => !m.gate || m.gate(me)).map((m) => m.id);
}

// Step to the next/previous module cyclically (wraps at both ends). `dir` = +1 forward, -1 back.
// If the current page isn't a primary module (e.g. Settings), forward lands on the first module
// and back on the last.
export function stepModule(me: Me, current: Page, dir: 1 | -1): Page {
  const order = moduleOrder(me);
  if (order.length === 0) return current;
  const i = order.indexOf(current);
  // order is non-empty (guarded above) and every index below is in-bounds, so none are undefined.
  if (i === -1) return dir === 1 ? order[0]! : order[order.length - 1]!;
  return order[(i + dir + order.length) % order.length]!;
}
