import { Link, useRouterState } from "@tanstack/react-router";
import {
  CalendarClock,
  CheckSquare,
  FolderKanban,
  GitBranch,
  Hourglass,
  LayoutDashboard,
  Menu,
  Moon,
  PenLine,
  Plus,
  Search,
  StickyNote,
  Sun,
  X,
} from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { Toaster } from "sonner";
import { Button } from "@/components/ui/button";
import { CommandPalette } from "@/components/command-palette";
import { SaveBanner, SaveStatus } from "@/components/save-status";
import { exportBook } from "@/lib/backup";
import { startSync } from "@/lib/book-sync";
import { QuickAdd } from "@/components/quick-add";
import { cn } from "@/lib/cn";
import { dueKind, followUpDue } from "@/lib/format";
import { applyTheme, readTheme, toggleTheme, type Theme } from "@/lib/theme";
import { useWorkspace } from "@/lib/store";

const NAV = [
  { to: "/", label: "Today", icon: LayoutDashboard, group: "Book" },
  { to: "/projects", label: "Projects", icon: FolderKanban, group: "Book" },
  { to: "/tasks", label: "Tasks", icon: CheckSquare, group: "Book" },
  { to: "/notes", label: "Notes", icon: StickyNote, group: "Book" },
  { to: "/reminders", label: "Reminders", icon: CalendarClock, group: "Time" },
  { to: "/waiting", label: "Waiting on", icon: Hourglass, group: "Time" },
  { to: "/boards", label: "Boards", icon: PenLine, group: "Draw" },
  { to: "/flows", label: "Flows", icon: GitBranch, group: "Draw" },
] as const;

/** Bottom bar on phones. By path, not index, so adding a NAV entry can't reshuffle it. */
const MOBILE_NAV = ["/", "/tasks", "/waiting", "/reminders", "/notes"] as const;

export function Shell({ children }: { children: React.ReactNode }) {
  const pathname = useRouterState({ select: (s) => s.location.pathname });
  const hydrated = useWorkspace((s) => s.hydrated);
  const setHydrated = useWorkspace((s) => s.setHydrated);
  const tasks = useWorkspace((s) => s.tasks);
  const notes = useWorkspace((s) => s.notes);
  const reminders = useWorkspace((s) => s.reminders);
  const projects = useWorkspace((s) => s.projects);
  const boards = useWorkspace((s) => s.boards);
  const waiting = useWorkspace((s) => s.waiting);
  const resetSeed = useWorkspace((s) => s.resetSeed);
  const [paletteOpen, setPaletteOpen] = useState(false);
  const [quickOpen, setQuickOpen] = useState(false);
  const [mobileNav, setMobileNav] = useState(false);
  const [theme, setTheme] = useState<Theme>("light");

  useEffect(() => {
    applyTheme(readTheme());
    setTheme(readTheme());
    // Rehydrate the local cache first, then hand over to the server copy.
    // startSync() only arms saving after it has read the server, so a stale
    // cache can never be written over a good server-side book.
    // rehydrate() is typed `void | Promise<void>` (it is sync for sync
    // storage), so normalise before chaining.
    void Promise.resolve(useWorkspace.persist.rehydrate()).finally(() => void startSync());
    const unsub = useWorkspace.persist.onFinishHydration(() => setHydrated(true));
    // Fallback so the UI is never stuck on a skeleton. This fires a store
    // write, which is harmless now: store.ts refuses to persist until a read
    // has actually succeeded.
    const t = window.setTimeout(() => setHydrated(true), 600);
    return () => {
      unsub();
      window.clearTimeout(t);
    };
  }, [setHydrated]);

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        setPaletteOpen(true);
      }
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "n") {
        e.preventDefault();
        setQuickOpen(true);
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const counts = useMemo(
    () => ({
      "/": reminders.filter((r) => !r.done && dueKind(r.dueAt) !== "later").length,
      "/projects": projects.filter((p) => p.status === "active").length,
      "/tasks": tasks.filter((t) => t.status !== "done").length,
      "/notes": notes.length,
      "/reminders": reminders.filter((r) => !r.done).length,
      "/boards": boards.filter((b) => b.kind === "board").length,
      "/flows": boards.filter((b) => b.kind === "flow").length,
      "/waiting": waiting.filter((w) => !w.done).length,
    }),
    [boards, notes.length, projects, reminders, tasks, waiting],
  );

  const overdue = reminders.filter((r) => !r.done && dueKind(r.dueAt) === "overdue").length;
  const chaseCount = waiting.filter(followUpDue).length;

  return (
    <div className="min-h-dvh bg-paper text-ink">
      <header className="sticky top-0 z-30 flex items-center justify-between gap-3 h-[60px] px-3 md:px-6 border-b border-line bg-surface">
        <div className="flex items-center gap-2.5 min-w-0">
          <button
            type="button"
            className="md:hidden size-11 inline-flex items-center justify-center text-ink-2"
            aria-label={mobileNav ? "Close menu" : "Open menu"}
            onClick={() => setMobileNav((v) => !v)}
          >
            {mobileNav ? <X className="size-5" /> : <Menu className="size-5" />}
          </button>
          <span className="brand-mark" aria-hidden="true" />
          <h1 className="text-[18px] font-semibold tracking-tight leading-none">
            Field<em className="not-italic text-accent">book</em>
          </h1>
          <span className="hidden sm:inline text-[12px] text-ink-2 pl-2 ml-1 border-l border-line">
            working ledger
          </span>
        </div>
        <div className="flex items-center gap-2 min-w-0">
          <button
            type="button"
            onClick={() => setPaletteOpen(true)}
            className="hidden md:flex items-center gap-2 min-w-0 w-[min(320px,32vw)] text-left text-[13px] text-ink-3 border border-line rounded-md px-2.5 py-[5px] bg-surface hover:border-ink-2"
          >
            <Search className="size-3.5" />
            Search the book
            <span className="ml-auto font-mono text-[11px] text-ink-3">⌘K</span>
          </button>
          <SaveStatus />
          {overdue > 0 ? (
            <Link
              to="/reminders"
              className="hidden sm:inline font-mono text-[12px] tabular-nums text-error bg-error-muted rounded-full px-2 py-0.5 hover:no-underline"
            >
              {overdue} overdue
            </Link>
          ) : null}
          <button
            type="button"
            className="size-9 inline-flex items-center justify-center rounded-md text-ink-2 hover:bg-neutral hover:text-ink"
            aria-label="Toggle light or dark theme"
            title="Toggle light / dark"
            onClick={() => setTheme(toggleTheme())}
          >
            {theme === "dark" ? <Sun className="size-4" /> : <Moon className="size-4" />}
          </button>
          <Button variant="primary" size="sm" onClick={() => setQuickOpen(true)}>
            <Plus className="size-3.5" />
            New
          </Button>
        </div>
      </header>
      <SaveBanner />

      {mobileNav ? (
        <div className="md:hidden border-b border-line bg-sunken grid grid-cols-2">
          {NAV.map((item) => {
            const Icon = item.icon;
            const active =
              item.to === "/"
                ? pathname === "/"
                : pathname === item.to || pathname.startsWith(`${item.to}/`);
            return (
              <Link
                key={item.to}
                to={item.to}
                onClick={() => setMobileNav(false)}
                className={cn(
                  "flex items-center gap-2 px-4 py-3 text-[13px] text-ink-2 border-b border-line-hair min-h-11 hover:no-underline",
                  active && "text-ink bg-accent-soft",
                )}
              >
                <Icon className="size-4" />
                {item.label}
                <span className="ml-auto font-mono text-[12px] text-ink-3">{counts[item.to]}</span>
              </Link>
            );
          })}
        </div>
      ) : null}

      <div className="md:grid md:grid-cols-[260px_1fr] min-h-[calc(100dvh-60px)]">
        <aside className="hidden md:flex sticky top-[60px] h-[calc(100dvh-60px)] flex-col border-r border-line bg-sunken">
          <nav className="flex-1 py-3 overflow-y-auto">
            {(["Book", "Time", "Draw"] as const).map((group) => (
              <div key={group} className="mb-2">
                <div className="px-4 pt-3 pb-1 text-[12px] font-semibold text-ink-2">
                  {group}
                </div>
                {NAV.filter((n) => n.group === group).map((item) => {
                  const active =
                    item.to === "/"
                      ? pathname === "/"
                      : pathname === item.to || pathname.startsWith(`${item.to}/`);
                  const Icon = item.icon;
                  return (
                    <Link
                      key={item.to}
                      to={item.to}
                      className={cn(
                        "flex items-center justify-between gap-2 mx-2 px-3 py-[6px] text-[13px] text-ink-2 rounded-md hover:bg-surface hover:text-ink hover:no-underline",
                        active && "text-ink font-semibold bg-surface shadow-[inset_2px_0_0_var(--color-accent)]",
                      )}
                    >
                      <span className="inline-flex items-center gap-2">
                        <Icon className="size-3.5" strokeWidth={1.75} />
                        {item.label}
                      </span>
                      <span
                        className={cn(
                          "font-mono text-[12px] tabular-nums text-ink-3 bg-neutral rounded-full px-1.5 leading-[18px]",
                          item.to === "/reminders" && overdue > 0 && "text-error",
                          item.to === "/waiting" && chaseCount > 0 && "text-error",
                        )}
                      >
                        {counts[item.to]}
                      </span>
                    </Link>
                  );
                })}
              </div>
            ))}
          </nav>
          <div className="border-t border-line px-4 py-3 flex flex-col gap-2">
            <Button variant="ghost" onClick={() => setPaletteOpen(true)}>
              <Search className="size-3.5" />
              Search
              <kbd className="ml-auto text-[11px] text-ink-3">⌘K</kbd>
            </Button>
            <button
              type="button"
              className="text-left text-[12px] text-ink-3 hover:text-ink-2"
              onClick={exportBook}
              title="Download the whole book as a JSON file"
            >
              Export a backup
            </button>
            <button
              type="button"
              className="text-left text-[12px] text-ink-3 hover:text-ink-2"
              onClick={() => {
                // Two steps on purpose: this button discards the entire book,
                // and it sits one click away from everything else.
                if (!window.confirm("Replace the book with the sample ledger?")) return;
                if (!window.confirm("This deletes everything currently in the book. Export a backup first if you are not sure.")) return;
                resetSeed();
              }}
            >
              Restore sample book
            </button>
          </div>
        </aside>

        <main className="min-w-0 pb-20 md:pb-0">
          {hydrated ? (
            children
          ) : (
            <div className="p-10 text-center text-ink-3 text-[13px]">Opening the ledger…</div>
          )}
        </main>
      </div>

      <nav className="md:hidden fixed bottom-0 inset-x-0 z-30 border-t border-line bg-surface grid grid-cols-5">
        {MOBILE_NAV.map((to) => NAV.find((n) => n.to === to)!).map((item) => {
          const Icon = item.icon;
          const active =
            item.to === "/"
              ? pathname === "/"
              : pathname === item.to || pathname.startsWith(`${item.to}/`);
          return (
            <Link
              key={item.to}
              to={item.to}
              className={cn(
                "flex flex-col items-center justify-center gap-0.5 min-h-14 text-[10px] font-medium text-ink-3 hover:no-underline",
                active && "text-accent",
              )}
            >
              <Icon className="size-4" strokeWidth={1.75} />
              {item.label}
            </Link>
          );
        })}
      </nav>

      <CommandPalette open={paletteOpen} onOpenChange={setPaletteOpen} />
      <QuickAdd open={quickOpen} onOpenChange={setQuickOpen} />
      <Toaster position="bottom-right" offset={24} />
    </div>
  );
}
