import { useEffect, useMemo, useState } from 'react';
import {
  AppShell,
  Badge,
  Collapse,
  Group,
  NavLink,
  Popover,
  ScrollArea,
  Stack,
  Text,
  Tooltip,
  UnstyledButton,
  rem,
  useComputedColorScheme,
} from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import {
  IconChevronLeft,
  IconChevronRight,
} from '@tabler/icons-react';
import type { Icon } from '@tabler/icons-react';
import type { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import {
  badgeLabel,
  isBranchActive,
  isItemActive,
  toSections,
  type NavEntries,
} from './nav-utils';

export interface NavItem {
  label: string;
  icon: Icon;
  to?: string;
  /**
   * One level only. A parent with children is a disclosure, not a destination,
   * so give it either `to` or `children` — not both.
   */
  children?: NavItem[];
  /**
   * Pending count, or `'dot'` for "something is waiting" without a number.
   * Zero renders nothing: a badge reading 0 is worse than no badge.
   */
  badge?: number | 'dot';
  /** Hidden unless the user holds at least one of these. */
  permissions?: string[];
  /** Not yet connected to real data — surfaced as a "Soon" badge. */
  soon?: boolean;
}

/** A labelled group of nav items. */
export interface NavSection {
  /** i18n key or literal heading. Omit for an ungrouped leading block. */
  label?: string;
  items: NavItem[];
}

export type { NavEntries } from './nav-utils';

/** Shown when an item is disabled, so no control is ever dead without a reason. */
function itemTooltip(item: NavItem, soonLabel: string): string | undefined {
  return item.soon ? soonLabel : undefined;
}

interface SidebarItemProps {
  item: NavItem;
  collapsed: boolean;
  activePath: string;
  onNavigate: (item: NavItem) => void;
  /** Whether this item's children are expanded. Ignored when it has none. */
  opened: boolean;
  /** Called with the new expanded state when the header is toggled. */
  onToggle: (opened: boolean) => void;
}

/**
 * One nav entry, at either level.
 *
 * A parent with children renders as a Mantine NavLink disclosure that starts
 * open when the current route is inside it, so the user can always see where
 * they are without hunting. Collapsed to the icon rail there is no room to
 * nest, so a parent becomes a hover flyout instead of silently losing its
 * children.
 */
function SidebarItem({ item, collapsed, activePath, onNavigate, opened, onToggle }: SidebarItemProps) {
  const { t } = useTranslation();
  const ItemIcon = item.icon;
  const hasChildren = Boolean(item.children?.length);
  const active = isItemActive(item, activePath);
  const branchActive = isBranchActive(item, activePath);
  const soonLabel = `${t(item.label)} — ${t('nav.soon', 'Soon')}`;
  const badge = badgeLabel(item.badge);

  const badgeNode = badge !== null && (
    <Badge
      size="xs"
      circle={item.badge === 'dot'}
      variant="filled"
      color="red"
      radius="sm"
      aria-label={
        item.badge === 'dot'
          ? t('nav.pending', 'Items pending')
          : t('nav.pendingCount', { count: Number(item.badge), defaultValue: '{{count}} pending' })
      }
    >
      {badge}
    </Badge>
  );

  // Parent headers always carry a chevron so the expand/collapse state is
  // never ambiguous, even when a badge is also present. One icon rotated
  // (rather than swapping icons) so the toggle animates instead of jumping.
  const chevron = hasChildren ? (
    <IconChevronRight
      size={14}
      stroke={1.8}
      color="var(--mantine-color-gray-5)"
      style={{
        transform: opened ? 'rotate(90deg)' : 'rotate(0deg)',
        transition: 'transform 200ms ease',
      }}
    />
  ) : null;

  const soonBadge = (
    <Badge size="xs" variant="light" color="gray" radius="sm">
      {t('nav.soon', 'Soon')}
    </Badge>
  );

  const rightSection = hasChildren ? (
    <Group gap={4} wrap="nowrap">
      {item.soon ? soonBadge : badgeNode}
      {chevron}
    </Group>
  ) : item.soon ? (
    soonBadge
  ) : (
    badgeNode || undefined
  );

  if (collapsed) {
    const trigger = (
      <UnstyledButton
        onClick={() => !hasChildren && onNavigate(item)}
        aria-label={t(item.label)}
        aria-current={active ? 'page' : undefined}
        style={{
          position: 'relative',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          width: '100%',
          height: rem(40),
          borderRadius: rem(10),
          opacity: item.soon ? 0.55 : 1,
          color: branchActive ? 'var(--mantine-primary-color-filled)' : undefined,
          backgroundColor: branchActive ? 'var(--mantine-primary-color-light)' : undefined,
        }}
      >
        <ItemIcon size={20} stroke={1.6} />
        {badge !== null && (
          <Badge
            size="xs"
            circle
            variant="filled"
            color="red"
            style={{ position: 'absolute', top: rem(6), right: rem(10) }}
          />
        )}
      </UnstyledButton>
    );

    // Children would be unreachable behind an icon, so open them in a flyout.
    if (hasChildren) {
      return (
        <Popover position="right-start" withArrow shadow="md" trapFocus>
          <Popover.Target>{trigger}</Popover.Target>
          <Popover.Dropdown p={4}>
            <Text size="xs" fw={700} c="dimmed" px="xs" py={4}>
              {t(item.label)}
            </Text>
            {item.children?.map((child) => (
              <NavLink
                key={child.label}
                active={isItemActive(child, activePath)}
                label={t(child.label)}
                leftSection={<child.icon size={17} stroke={1.6} />}
                onClick={() => onNavigate(child)}
                variant="light"
                styles={{ root: { borderRadius: rem(8) } }}
              />
            ))}
          </Popover.Dropdown>
        </Popover>
      );
    }

    return (
      <Tooltip
        label={itemTooltip(item, soonLabel) ?? t(item.label)}
        position="right"
        withArrow
      >
        {trigger}
      </Tooltip>
    );
  }

  return (
    <NavLink
      active={hasChildren ? branchActive && !active : active}
      label={t(item.label)}
      leftSection={<ItemIcon size={19} stroke={1.6} />}
      rightSection={rightSection}
      disableRightSectionRotation
      // Controlled so the group re-opens if the active route moves inside it
      // later (see the auto-expand effect in AppSidebar), not just on mount.
      opened={hasChildren ? opened : undefined}
      onChange={hasChildren ? onToggle : undefined}
      onClick={() => !hasChildren && onNavigate(item)}
      // A leaf that is the current page is filled in the app's primary; a
      // branch whose child is current stays a tint, so the filled item is
      // always exactly the page you are on.
      variant={hasChildren ? 'light' : 'filled'}
      styles={{
        root: { borderRadius: rem(10), opacity: item.soon ? 0.7 : 1 },
        label: { fontWeight: 500 },
      }}
    >
      {hasChildren
        ? item.children?.map((child) => (
            <NavLink
              key={child.label}
              active={isItemActive(child, activePath)}
              label={t(child.label)}
              leftSection={<child.icon size={17} stroke={1.6} />}
              rightSection={
                child.soon ? (
                  <Badge size="xs" variant="light" color="gray" radius="sm">
                    {t('nav.soon', 'Soon')}
                  </Badge>
                ) : (
                  badgeLabel(child.badge) !== null && (
                    <Badge size="xs" variant="filled" color="red" radius="sm">
                      {badgeLabel(child.badge)}
                    </Badge>
                  )
                ) || undefined
              }
              onClick={() => onNavigate(child)}
              variant="light"
              styles={{ root: { borderRadius: rem(8) }, label: { fontWeight: 500 } }}
            />
          ))
        : null}
    </NavLink>
  );
}

interface AppSidebarProps {
  navItems: NavEntries;
  collapsed: boolean;
  activePath: string;
  onToggleCollapse: () => void;
  onNavigate: (item: NavItem) => void;
  brandName: string;
  brandSubtitle: string;
  /**
   * Rendered in the brand header. Passed in rather than imported so this
   * library stays free of a dependency on `@ema-platform/auth`, which depends
   * on it — the two formed an import cycle.
   */
  brandLogo?: ReactNode;
}

export function AppSidebar({
  navItems,
  collapsed: collapsedProp,
  activePath,
  onToggleCollapse,
  onNavigate,
  brandName,
  brandSubtitle,
  brandLogo,
}: AppSidebarProps) {
  const { t } = useTranslation();
  const colorScheme = useComputedColorScheme('light', { getInitialValueInEffect: true });
  // The icon-rail toggle is a desktop-only affordance (its button is
  // `visibleFrom="sm"`) — on mobile the sidebar renders in a full-width
  // drawer, so a `collapsed` state carried over from a prior desktop session
  // would otherwise show icon-only with no way back. Mobile always expands.
  const isMobile = useMediaQuery('(max-width: 48em)');
  const collapsed = collapsedProp && !isMobile;
  const hoverBg = colorScheme === 'dark'
    ? 'var(--mantine-color-dark-6)'
    : 'var(--mantine-color-gray-0)';
  const hoverColor = colorScheme === 'dark'
    ? 'var(--mantine-color-gray-3)'
    : 'var(--mantine-color-gray-7)';

  const sections = useMemo(() => toSections(navItems), [navItems]);

  // Expand/collapse state per collapsible header, keyed by section label for
  // NavSection headings and by item label for a parent item's children.
  // Sections default open (today every section is always visible); a nested
  // item's children default to open only when the active route is inside it,
  // matching the previous `defaultOpened` behaviour.
  const [openMap, setOpenMap] = useState<Record<string, boolean>>({});

  // If the active route moves into a header the user had collapsed, expand
  // it back open so the highlighted item stays visible. Never collapses
  // anything — that stays purely a manual, per-header action.
  useEffect(() => {
    setOpenMap((prev) => {
      let changed = false;
      const next = { ...prev };
      sections.forEach((section, sectionIndex) => {
        const sectionKey = section.label ?? `section-${sectionIndex}`;
        if (
          section.label &&
          next[sectionKey] !== true &&
          section.items.some((item) => isBranchActive(item, activePath))
        ) {
          next[sectionKey] = true;
          changed = true;
        }
        section.items.forEach((item) => {
          if (item.children?.length && next[item.label] !== true && isBranchActive(item, activePath)) {
            next[item.label] = true;
            changed = true;
          }
        });
      });
      return changed ? next : prev;
    });
  }, [activePath, sections]);

  const toggleSection = (key: string) =>
    setOpenMap((prev) => ({ ...prev, [key]: !(prev[key] ?? true) }));
  const setItemOpened = (key: string, next: boolean) =>
    setOpenMap((prev) => ({ ...prev, [key]: next }));

  return (
    <>
      {/* Brand header */}
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: rem(12),
          height: 74,
          padding: collapsed ? '0 12px' : '0 20px',
          borderBottom: '1px solid var(--mantine-color-gray-2)',
          justifyContent: collapsed ? 'center' : 'flex-start',
          flexShrink: 0,
        }}
      >
        {brandLogo}
        {!collapsed && (
          <div style={{ minWidth: 0 }}>
            <Text
              fw={800}
              size="xs"
              style={{
                textTransform: 'uppercase',
                letterSpacing: '0.08em',
                lineHeight: 1.2,
                color: 'var(--mantine-color-primary-7)',
              }}
            >
              {brandName}
            </Text>
            <Text
              size="xs"
              c="dimmed"
              style={{
                textTransform: 'uppercase',
                letterSpacing: '0.05em',
                fontSize: rem(10),
                lineHeight: 1.3,
              }}
            >
              {brandSubtitle}
            </Text>
          </div>
        )}
      </div>

      {/* Navigation items */}
      <AppShell.Section grow component={ScrollArea} p="md">
        <Stack gap={2}>
          {sections.map((section, sectionIndex) => {
            const sectionKey = section.label ?? `section-${sectionIndex}`;
            const sectionOpened = openMap[sectionKey] ?? true;
            return (
              <Stack gap={2} key={sectionKey}>
                {/* Headings are noise when only icons are visible. */}
                {section.label && !collapsed && (
                  <UnstyledButton
                    onClick={() => toggleSection(sectionKey)}
                    aria-expanded={sectionOpened}
                    style={{
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'space-between',
                      width: '100%',
                      padding: `0 ${rem(12)}`,
                      marginTop: sectionIndex === 0 ? 0 : rem(14),
                    }}
                  >
                    <Text
                      size="xs"
                      fw={700}
                      c="dimmed"
                      style={{ textTransform: 'uppercase', letterSpacing: '0.06em' }}
                    >
                      {t(section.label)}
                    </Text>
                    <IconChevronRight
                      size={14}
                      stroke={1.8}
                      color="var(--mantine-color-gray-5)"
                      style={{
                        transform: sectionOpened ? 'rotate(90deg)' : 'rotate(0deg)',
                        transition: 'transform 200ms ease',
                      }}
                    />
                  </UnstyledButton>
                )}
                {section.label && collapsed && sectionIndex > 0 && (
                  <div
                    style={{
                      height: 1,
                      margin: `${rem(8)} ${rem(6)}`,
                      backgroundColor: 'var(--mantine-color-gray-2)',
                    }}
                  />
                )}
                <Collapse in={!section.label || sectionOpened || collapsed}>
                  <Stack gap={2}>
                    {section.items.map((item) => (
                      <SidebarItem
                        key={item.label}
                        item={item}
                        collapsed={collapsed}
                        activePath={activePath}
                        onNavigate={onNavigate}
                        opened={openMap[item.label] ?? isBranchActive(item, activePath)}
                        onToggle={(next) => setItemOpened(item.label, next)}
                      />
                    ))}
                  </Stack>
                </Collapse>
              </Stack>
            );
          })}
        </Stack>
      </AppShell.Section>

      {/* Collapse toggle */}
      <div
        style={{
          padding: rem(8),
          borderTop: '1px solid var(--mantine-color-gray-2)',
          flexShrink: 0,
        }}
      >
        <Tooltip
          label={collapsed ? t('nav.expandSidebar', 'Expand sidebar') : t('nav.collapseSidebar', 'Collapse sidebar')}
          position="right"
          withArrow
          disabled={!collapsed}
        >
          <UnstyledButton
            onClick={onToggleCollapse}
            visibleFrom="sm"
            style={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: collapsed ? 'center' : 'flex-start',
              gap: rem(10),
              width: '100%',
              padding: '10px 12px',
              borderRadius: rem(12),
              color: 'var(--mantine-color-gray-5)',
              transition: 'all 150ms ease',
              cursor: 'pointer',
              border: 'none',
              background: 'transparent',
              fontSize: rem(13),
              fontWeight: 500,
            }}
            onMouseEnter={(e) => {
              e.currentTarget.style.background = hoverBg;
              e.currentTarget.style.color = hoverColor;
            }}
            onMouseLeave={(e) => {
              e.currentTarget.style.background = 'transparent';
              e.currentTarget.style.color = 'var(--mantine-color-gray-5)';
            }}
          >
            {collapsed ? (
              <IconChevronRight size={18} stroke={1.6} />
            ) : (
              <>
                <IconChevronLeft size={18} stroke={1.6} />
                <span>{t('nav.collapseSidebar', 'Collapse')}</span>
              </>
            )}
          </UnstyledButton>
        </Tooltip>
      </div>
    </>
  );
}
