import { useEffect, useRef, useState, useSyncExternalStore } from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import {
  Activity,
  BarChart3,
  Bell,
  ChevronLeft,
  ChevronRight,
  Clapperboard,
  Download,
  Home,
  Info,
  ListVideo,
  LogOut,
  MessageSquare,
  ScrollText,
  Settings,
  Shield,
  SlidersHorizontal,
  Users,
  Tv,
  UserPlus,
} from "lucide-react";
import { api, clearActiveAccount, setActiveAccount, type Me } from "../lib/api";
import * as e2ee from "../lib/e2ee";
import { useLiveQuery } from "../lib/useLiveQuery";
import { getUnreadCount, subscribe } from "../lib/notifications";
import type { Page } from "../lib/urlState";
import { type LangCode } from "../i18n";
import AvatarImg from "./Avatar";
import LanguageSwitcher from "./LanguageSwitcher";
import SyncStatus from "./SyncStatus";

// Primary app navigation: a collapsible left rail with icon+label entries (Design C). The
// modules used to hide under the avatar dropdown; they now live here. Collapsed, it becomes
// a thin icon-only rail. Account actions sit at the bottom in a popover.
export default function NavSidebar({
  me,
  page,
  setPage,
  onOpenAbout,
  onChangeLanguage,
  language,
  collapsed,
  onToggleCollapse,
  onGoToFullHistory,
}: {
  me: Me;
  page: Page;
  setPage: (p: Page) => void;
  onOpenAbout: () => void;
  onChangeLanguage: (code: LangCode) => void;
  language: LangCode;
  // Collapse state is owned by App (persisted to the user's preferences); the rail just reflects
  // it and asks App to flip it.
  collapsed: boolean;
  onToggleCollapse: () => void;
  onGoToFullHistory: () => void;
}) {
  const { t } = useTranslation();
  const [acctOpen, setAcctOpen] = useState(false);
  const acctBtnRef = useRef<HTMLButtonElement | null>(null);
  const acctPanelRef = useRef<HTMLDivElement | null>(null);
  // Popover position (fixed, viewport coords). It's portaled to <body> so it escapes the
  // nav's backdrop-filter, which would otherwise trap fixed positioning + stacking and let
  // clicks fall through to the controls behind it.
  const [acctPos, setAcctPos] = useState<{ left: number; bottom: number }>({ left: 0, bottom: 0 });

  function toggleAccount() {
    if (!acctOpen) {
      const r = acctBtnRef.current?.getBoundingClientRect();
      if (r) setAcctPos({ left: r.right + 8, bottom: window.innerHeight - r.bottom });
    }
    setAcctOpen((o) => !o);
  }

  // Dismiss on outside click / Escape via document listeners — NOT a full-screen backdrop
  // div, which (sitting between the popover and the page) would break the popover's
  // backdrop-filter and make it look solid.
  useEffect(() => {
    if (!acctOpen) return;
    function onDoc(e: MouseEvent) {
      const t = e.target as Node;
      if (acctPanelRef.current?.contains(t) || acctBtnRef.current?.contains(t)) return;
      setAcctOpen(false);
    }
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") setAcctOpen(false);
    }
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [acctOpen]);

  async function logout() {
    // Signs THIS tab's active account out of the browser wallet (server is per-tab aware); then
    // drops this tab's override and reloads onto whatever account remains the default.
    try {
      await api.logout();
    } catch {
      /* clear locally and reload regardless */
    }
    // Forget this device's E2EE private key on sign-out: it's otherwise persisted in IndexedDB
    // and auto-unlocks on the next visit, so on a shared machine the conversations would stay
    // decryptable after logout. Re-entering the message passphrase is required next time.
    try {
      await e2ee.clearDevice(me.id);
    } catch {
      /* IndexedDB unavailable (e.g. private mode) — never let it block the logout + reload */
    }
    clearActiveAccount();
    location.reload();
  }

  // Accounts that have signed in on this browser (loaded only while the popover is open).
  const accountsQuery = useQuery({
    queryKey: ["accounts"],
    queryFn: api.accounts,
    enabled: acctOpen,
  });
  const otherAccounts = (accountsQuery.data ?? []).filter((a) => !a.active);

  function switchTo(id: number) {
    // Per-tab switch: point THIS tab at the account and reload; other tabs keep their own
    // identity. No server round-trip — req() sends the account header (validated against the
    // browser wallet server-side), so the cookie's default account is left untouched.
    setActiveAccount(id);
    // Drop the previous account's in-module sub-view/overlay before the reload (which would
    // otherwise preserve history.state across the swap). Otherwise the new account lands on a
    // stale sub-view — e.g. an open chat thread whose partnerId belongs to the OLD identity
    // (often the new account's own id → an empty self-thread). Start at the module root instead.
    const st = { ...(window.history.state || {}) };
    delete st._sub;
    delete st._ov;
    window.history.replaceState(st, "");
    location.reload(); // cleanest way to reload all per-user state for the new account
  }

  // Durable, server-backed unread count for the inbox badge. Polled live (pauses when the
  // tab is hidden); coexists with the client-side transient bell at the bottom of the rail.
  const unreadQuery = useLiveQuery(
    ["notif-unread"],
    api.notificationUnreadCount,
    { intervalMs: 30000 }
  );
  // One indicator for both layers: durable server notifications + the client-side transient
  // events (the former separate bell is now folded into the inbox page).
  const clientUnread = useSyncExternalStore(subscribe, getUnreadCount, getUnreadCount);
  const unread = (unreadQuery.data?.count ?? 0) + clientUnread;
  // Direct messages are their own module with their own unread badge (demo can't message).
  const msgUnreadQuery = useLiveQuery(
    ["message-unread"],
    api.messageUnreadCount,
    { intervalMs: 30000, enabled: !me.is_demo }
  );
  const msgUnread = msgUnreadQuery.data?.count ?? 0;
  // Download center badge = items still working (queued/running/paused). Reuses the same
  // lightweight per-user index the feed cards poll, so no extra request shape.
  const dlIndexQuery = useLiveQuery(["download-index"], api.downloadIndex, {
    intervalMs: 30000,
    enabled: !me.is_demo,
  });
  const dlActive = Object.values(dlIndexQuery.data ?? {}).filter((s) => s !== "done").length;

  type NavItem = { page: Page; icon: typeof Home; label: string; badge?: number };
  // User-facing content modules vs. admin/system modules, separated by a divider in the rail.
  const userItems: NavItem[] = [
    { page: "feed", icon: Home, label: t("header.account.feed") },
    { page: "channels", icon: Tv, label: t("header.account.channels") },
    { page: "playlists", icon: ListVideo, label: t("header.account.playlists") },
    // Optional Plex module — browse/play your Plex library. Shown only when the admin enabled it.
    ...(me.plex_enabled ? [{ page: "plex" as Page, icon: Clapperboard, label: t("plex.navLabel") }] : []),
    { page: "notifications", icon: Bell, label: t("inbox.navLabel"), badge: unread },
    // Direct messaging — its own module; hidden for the shared demo account.
    ...(me.is_demo
      ? []
      : [{ page: "messages" as Page, icon: MessageSquare, label: t("messages.navLabel"), badge: msgUnread }]),
    // Download center — YouTube → server → your device. Hidden for the shared demo account
    // (spends server disk + needs a real identity).
    ...(me.is_demo
      ? []
      : [{ page: "downloads" as Page, icon: Download, label: t("downloads.navLabel"), badge: dlActive }]),
    // Per-user sync status + your own API usage (admins get an extra system-wide tab inside).
    { page: "stats", icon: BarChart3, label: t("header.account.stats") },
  ];
  const systemItems: NavItem[] =
    me.role === "admin"
      ? [
          { page: "scheduler", icon: Activity, label: t("header.account.scheduler") },
          { page: "config", icon: SlidersHorizontal, label: t("header.account.config") },
          { page: "users", icon: Users, label: t("header.account.users") },
          { page: "audit", icon: ScrollText, label: t("header.account.audit") },
        ]
      : [];

  const rowBase =
    "w-full flex items-center gap-3 rounded-lg px-2.5 py-2 text-sm transition";
  const name = me.display_name ?? me.email.split("@")[0];
  // Role shown next to the name (expanded) / as an avatar dot (collapsed). Demo isn't a real
  // role (it's a flag on a `user` row), so surface it first.
  const roleKey: "admin" | "user" | "demo" = me.is_demo
    ? "demo"
    : me.role === "admin"
      ? "admin"
      : "user";
  const roleChipCls =
    roleKey === "admin"
      ? "bg-accent/15 text-accent"
      : roleKey === "demo"
        ? "bg-amber-500/20 text-amber-500"
        : "bg-muted/15 text-muted";
  const roleDotCls =
    roleKey === "admin" ? "bg-accent" : roleKey === "demo" ? "bg-amber-500" : "bg-muted";

  const renderItem = ({ page: p, icon: Icon, label, badge }: NavItem) => {
    const active = page === p;
    // On the active row the background is the accent colour, so a same-accent badge would be
    // red-on-red. Invert it there (light pill, accent-coloured number) for clean contrast.
    const badgeColor = active ? "bg-accent-fg text-accent" : "bg-accent text-accent-fg";
    const show = !!badge && badge > 0;
    return (
      <button
        key={p}
        onClick={() => setPage(p)}
        title={collapsed ? label : undefined}
        aria-current={active ? "page" : undefined}
        className={`${rowBase} ${collapsed ? "justify-center px-0" : ""} relative ${
          active ? "bg-accent text-accent-fg" : "text-muted hover:text-fg hover:bg-card"
        }`}
      >
        <span className="relative shrink-0">
          <Icon className="w-[18px] h-[18px]" />
          {/* Collapsed rail: a numeric badge centred on a circle at the icon corner; the ring
              matches the row background so it reads cleanly whether the row is active or not. */}
          {show && collapsed && (
            <span
              className={`absolute -top-2 -right-2 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold leading-none grid place-items-center ring-2 ${badgeColor} ${
                active ? "ring-accent" : "ring-bg"
              }`}
            >
              {badge > 9 ? "9+" : badge}
            </span>
          )}
        </span>
        {!collapsed && <span className="truncate">{label}</span>}
        {show && !collapsed && (
          <span
            className={`ml-auto min-w-[18px] h-[18px] px-1.5 rounded-full text-[11px] font-semibold leading-none inline-flex items-center justify-center tabular-nums ${badgeColor}`}
          >
            {badge > 99 ? "99+" : badge}
          </span>
        )}
      </button>
    );
  };

  return (
    <nav
      className={`glass relative shrink-0 border-r border-border flex flex-col py-3 transition-[width] ${
        collapsed ? "w-[56px] px-2" : "w-52 px-3"
      }`}
      aria-label={t("nav.primary")}
    >
      <div className={`flex items-center mb-3 ${collapsed ? "justify-center" : "justify-between"}`}>
        {!collapsed && (
          <button
            onClick={() => setPage("feed")}
            className="text-lg font-bold tracking-tight select-none cursor-pointer rounded-md -mx-1 px-1 hover:bg-card hover:opacity-90 transition"
            title={t("header.feed")}
          >
            Sift<span className="text-accent">lode</span>
          </button>
        )}
        <button
          onClick={onToggleCollapse}
          title={collapsed ? t("nav.expand") : t("nav.collapse")}
          aria-label={collapsed ? t("nav.expand") : t("nav.collapse")}
          className="p-1 rounded-md text-muted hover:text-fg hover:bg-card transition"
        >
          {collapsed ? (
            <ChevronRight className="w-4 h-4" />
          ) : (
            <ChevronLeft className="w-4 h-4" />
          )}
        </button>
      </div>

      {/* Per-user sync status (video counts + live sync state), moved out of the old top bar. */}
      <div className="mb-3 pb-3 border-b border-border">
        <SyncStatus
          isAdmin={me.role === "admin"}
          onGoToFullHistory={onGoToFullHistory}
          variant="rail"
          collapsed={collapsed}
        />
      </div>

      <div className="flex-1 min-h-0 overflow-y-auto flex flex-col gap-1">
        {userItems.map(renderItem)}
        {systemItems.length > 0 &&
          (collapsed ? (
            // Collapsed rail: a short, thicker centred rule is the clearest "new section" cue
            // when there's no room for a label.
            <div className="my-2 mx-auto w-7 border-t-2 border-border" />
          ) : (
            <div className="mt-4 mb-1 px-2.5 flex items-center gap-2">
              <span className="text-[10px] font-semibold uppercase tracking-wider text-muted/70">
                {t("nav.adminSection")}
              </span>
              <span className="flex-1 border-t border-border" />
            </div>
          ))}
        {systemItems.map(renderItem)}
      </div>

      <div className="mt-2 pt-2 border-t border-border flex flex-col gap-1">
        <div
          className={`flex items-center justify-center gap-2 pb-2 mb-1 border-b border-border ${
            collapsed ? "flex-col" : "flex-row"
          }`}
        >
          <LanguageSwitcher variant="rail" value={language} onChange={onChangeLanguage} />
          <button
            onClick={onOpenAbout}
            title={t("header.account.about")}
            aria-label={t("header.account.about")}
            className="p-2 rounded-lg text-muted hover:text-fg hover:bg-card transition"
          >
            <Info className="w-5 h-5" />
          </button>
        </div>
        <button
          onClick={() => setPage("settings")}
          title={collapsed ? t("header.account.settings") : undefined}
          aria-current={page === "settings" ? "page" : undefined}
          className={`${rowBase} ${collapsed ? "justify-center px-0" : ""} ${
            page === "settings"
              ? "bg-accent text-accent-fg"
              : "text-muted hover:text-fg hover:bg-card"
          }`}
        >
          <Settings className="w-[18px] h-[18px] shrink-0" />
          {!collapsed && <span className="truncate">{t("header.account.settings")}</span>}
        </button>

        <div className="relative">
          <button
            ref={acctBtnRef}
            onClick={toggleAccount}
            title={collapsed ? name : undefined}
            className={`${rowBase} ${collapsed ? "justify-center px-0" : ""} text-muted hover:text-fg hover:bg-card`}
          >
            <span className="relative shrink-0">
              <AvatarImg
                src={me.avatar_url}
                fallback={name}
                className="w-[22px] h-[22px] rounded-full text-[10px]"
              />
              {collapsed && (
                <span
                  className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full ring-2 ring-bg ${roleDotCls}`}
                  title={t(`nav.role.${roleKey}`)}
                />
              )}
            </span>
            {!collapsed && <span className="truncate flex-1 text-left">{name}</span>}
            {!collapsed && (
              <span
                className={`shrink-0 text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded ${roleChipCls}`}
              >
                {t(`nav.role.${roleKey}`)}
              </span>
            )}
          </button>

          {acctOpen &&
            createPortal(
              <div
                ref={acctPanelRef}
                style={{ position: "fixed", left: acctPos.left, bottom: acctPos.bottom }}
                className="glass w-60 rounded-xl p-3 z-50 animate-[popIn_0.16s_ease]"
              >
                <div className="flex items-center gap-3 pb-3 border-b border-border">
                  <AvatarImg
                    src={me.avatar_url}
                    fallback={name}
                    className="w-10 h-10 rounded-full text-sm shrink-0"
                  />
                  <div className="min-w-0">
                    <div className="text-sm font-semibold truncate">{name}</div>
                    <div className="text-xs text-muted truncate">{me.email}</div>
                  </div>
                </div>
                {me.role === "admin" && (
                  <div className="flex items-center gap-1.5 mt-2 text-[11px] font-medium text-accent">
                    <Shield className="w-3.5 h-3.5" />
                    {t("header.account.admin")}
                  </div>
                )}
                {otherAccounts.length > 0 && (
                  <div className="mt-2 pt-2 border-t border-border flex flex-col gap-0.5">
                    {otherAccounts.map((a) => (
                      <button
                        key={a.id}
                        onClick={() => switchTo(a.id)}
                        title={t("header.account.switchTo", { name: a.display_name ?? a.email })}
                        className="w-full flex items-center gap-2.5 px-2 py-1.5 rounded-lg hover:bg-card transition"
                      >
                        <AvatarImg
                          src={a.avatar_url}
                          fallback={a.display_name ?? a.email}
                          className="w-6 h-6 rounded-full text-[10px] shrink-0"
                        />
                        <span className="min-w-0 text-left">
                          <span className="block text-[13px] text-fg truncate">
                            {a.display_name ?? a.email.split("@")[0]}
                          </span>
                          <span className="block text-[11px] text-muted truncate">{a.email}</span>
                        </span>
                      </button>
                    ))}
                  </div>
                )}
                <div className="mt-2 pt-2 border-t border-border flex flex-col">
                  <button
                    onClick={() => {
                      // Adding an account switches THIS tab to it: drop the tab's pin so that on
                      // return from Google it adopts the freshly-added account (the new default)
                      // and pins that. Other tabs keep their own pinned account.
                      clearActiveAccount();
                      window.location.href = "/auth/login";
                    }}
                    className="w-full flex items-center gap-2 text-sm px-2 py-2 rounded-lg text-muted hover:text-fg hover:bg-card transition"
                  >
                    <UserPlus className="w-4 h-4" />
                    {t("header.account.addAccount")}
                  </button>
                  <button
                    onClick={logout}
                    className="w-full flex items-center gap-2 text-sm px-2 py-2 rounded-lg text-muted hover:text-fg hover:bg-card transition"
                  >
                    <LogOut className="w-4 h-4" />
                    {t("header.account.signOut")}
                  </button>
                </div>
              </div>,
              document.body
            )}
        </div>
      </div>
    </nav>
  );
}
