/**
 * The navigation table.
 *
 * Lifted out of Shell.tsx because four things now read it — the sidebar, the
 * phone tab bar, the command palette and the header's page title — and a table
 * that four consumers each filter differently is a table that ends up
 * duplicated.
 *
 * Visibility is a *capability* question, not a cosmetic one. The convention in
 * this codebase is to disable a control rather than hide it, so that the
 * interface tells you the same story regardless of who you are. Navigation is
 * the exception: a destination someone cannot use is not a disabled control,
 * it is a page that answers 403, and offering it is worse than omitting it.
 */
import { createElement, forwardRef } from 'react';
import {
  BookUser,
  Boxes,
  Building2,
  CalendarClock,
  FileSpreadsheet,
  FileText,
  GraduationCap,
  Handshake,
  LayoutDashboard,
  Library,
  Route,
  Server,
  Settings,
  ShieldCheck,
  Target,
  TrendingUp,
  Users,
  type LucideIcon,
  type LucideProps,
} from 'lucide-react';
import type { Capability, Team } from '@pig/core';
import { PiggyMark } from '@/components/PiggyMark';
import { canAny, type PermissionIdentity } from './permissions';

/**
 * Piggy's own mark, wearing a Lucide icon's shape.
 *
 * Four consumers read this table — the sidebar, the phone tab bar, the command
 * palette and the header — and every one of them renders `<Icon className
 * aria-hidden />` against a `LucideIcon`. Widening that type would have made
 * all four declare a new one for the sake of a single row, so the adapter lives
 * here instead: it takes the two props those call sites actually pass and drops
 * the rest, because `size`, `color` and `strokeWidth` have no meaning for a
 * filled two-tone mark. The ref goes nowhere for the same reason nothing in
 * this product has ever taken a ref to a nav glyph.
 *
 * Written with `createElement` rather than JSX only because this module is a
 * plain `.ts` table that four `.tsx` files import.
 */
const PiggyNavIcon: LucideIcon = forwardRef<SVGSVGElement, Omit<LucideProps, 'ref'>>(
  ({ className }, _ref) => createElement(PiggyMark, { className }),
);
PiggyNavIcon.displayName = 'PiggyNavIcon';

export const NAV_GROUPS = ['Workspace', 'Intelligence', 'Motion', 'Marketplace', 'Records', 'Control'] as const;
export type NavGroup = (typeof NAV_GROUPS)[number];

/**
 * The heading a group prints above its rows in the sidebar, or `null` for a
 * group that leads the list and needs none.
 *
 * Workspace is that group. It holds one row — Piggy — and a "WORKSPACE"
 * heading over a single row named Piggy says nothing the row does not; worse,
 * it makes the front door look like one section among five rather than the
 * thing the product opens on. Rendered unlabelled and followed by a rule, it
 * reads as what it is. The command palette still groups by the same name,
 * where a heading is doing real work because the list there is flat.
 */
export const NAV_GROUP_HEADING: Record<NavGroup, string | null> = {
  Workspace: null,
  Intelligence: 'Intelligence',
  Motion: 'Motion',
  Marketplace: 'Marketplace',
  Records: 'Records',
  Control: 'Control',
};

export interface NavItem {
  to: string;
  label: string;
  icon: LucideIcon;
  shortcut?: string;
  group: NavGroup;
  /** Shown in the phone tab bar. Space there is scarce, so only five fit. */
  primary?: boolean;
  /**
   * Hide the item unless this capability is granted somewhere. Absent means
   * the page is readable by any member — which includes Settings, where the
   * appearance controls and Sign out live for everybody, admin or not.
   */
  requires?: Capability;
  /** Narrows `requires` to one team, where the API enforces one. */
  requiresTeam?: Team;
}

export const NAV: NavItem[] = [
  /*
   * First row, own group, and the destination `/` redirects to: Piggy is where
   * the product starts now. It was the fourth row of Intelligence, which put
   * the agent below three reports — a filing that made sense when Piggy could
   * only read and answer, and stopped making sense the moment it could act.
   *
   * The agent's own mark, not a stock glyph. Piggy was drawn five different
   * ways — Sparkles here, Sparkles again for the model picker, a robot on
   * every answer, a speech bubble on the page buttons — so the one thing a
   * reader could not learn from the interface was what Piggy looks like.
   * `PiggyMark` is now the agent everywhere and nothing else, which is what
   * lets this row, the header control and the signature on every answer read
   * as one thing. Nothing else in this table uses it, which is the constraint
   * that matters — the sidebar collapses to icons alone, and two rows sharing
   * a glyph are two rows you have to expand the sidebar to tell apart.
   */
  { to: '/piggy', label: 'Piggy', icon: PiggyNavIcon, group: 'Workspace', primary: true },
  // Still first in Intelligence and still in the phone tab bar. Losing `/` cost
  // it a URL, not its prominence: it is one click from anywhere, and it remains
  // the page an exec opens to see whether the business is working.
  { to: '/overview', label: 'Overview', icon: LayoutDashboard, group: 'Intelligence', primary: true },
  { to: '/growth', label: 'Growth', icon: Target, group: 'Intelligence' },
  { to: '/calendar', label: 'Calendar', icon: CalendarClock, group: 'Intelligence' },
  { to: '/learn', label: 'Learn', icon: GraduationCap, group: 'Intelligence' },
  { to: '/margin', label: 'Margin', icon: TrendingUp, group: 'Intelligence', primary: true },
  // None of these is `primary`: the phone tab bar holds five and the ledger
  // pages already hold all five. The three routes are reachable from the
  // sidebar and from ⌘K, which a NavItem earns by structurally satisfying
  // CommandDestination. Reads are gated on `book:read` like Accounts and
  // Contracts, so no `requires` — the write capabilities are enforced per
  // control on the page, not by hiding the destination.
  { to: '/motion', label: 'Motion', icon: Route, group: 'Motion' },
  { to: '/motion/library', label: 'Library', icon: Library, group: 'Motion' },
  { to: '/motion/engagements', label: 'Engagements', icon: Handshake, group: 'Motion' },
  { to: '/capacity', label: 'Capacity', icon: Server, group: 'Marketplace', primary: true },
  { to: '/demand', label: 'Demand', icon: Building2, group: 'Marketplace', primary: true },
  { to: '/supply', label: 'Supply', icon: Boxes, group: 'Marketplace', primary: true },
  // BookUser rather than a second Building2: the sidebar collapses to icons
  // only, and Demand already owns the office block. Two rows sharing a glyph
  // are two rows you have to expand the sidebar to tell apart. It is also the
  // truer icon — this page is the directory of accounts *and* their people.
  { to: '/accounts', label: 'Accounts', icon: BookUser, group: 'Records' },
  { to: '/contracts', label: 'Contracts', icon: FileText, group: 'Records' },
  {
    to: '/imports',
    label: 'Import',
    icon: FileSpreadsheet,
    group: 'Records',
    requires: 'data:import',
  },
  { to: '/team', label: 'Team', icon: Users, group: 'Control' },
  {
    to: '/facts',
    label: 'Fact review',
    icon: ShieldCheck,
    group: 'Control',
    // The API gates fact review on data:import for the research team
    // specifically (routes/facts.ts), so the nav has to ask the same question.
    requires: 'data:import',
    requiresTeam: 'research',
  },
  { to: '/settings', label: 'Settings', icon: Settings, group: 'Control' },
];

export function visibleNav(identity: PermissionIdentity | undefined): NavItem[] {
  return NAV.filter((item) => {
    if (!item.requires) return true;
    if (!item.requiresTeam) return canAny(identity, item.requires);
    return Boolean(
      identity &&
        identity.permissions.some(
          (grant) =>
            grant.capability === item.requires &&
            (grant.team === null || grant.team === item.requiresTeam),
        ),
    );
  });
}

/**
 * The nav entry a pathname belongs to, for the header title and active state.
 *
 * The longest match wins, not the first. Motion is the first group whose own
 * destinations nest — `/motion` is a prefix of `/motion/library` — and a
 * first-match scan titles the library page "Motion". Longest-match is a no-op
 * for every other route in the table.
 */
export function activeNavItem(items: readonly NavItem[], pathname: string): NavItem | undefined {
  let match: NavItem | undefined;
  for (const item of items) {
    if (item.to === '/' ? pathname !== '/' : pathname !== item.to && !pathname.startsWith(`${item.to}/`)) continue;
    if (!match || item.to.length > match.to.length) match = item;
  }
  return match;
}
