"use client";

import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { Keyboard, MailPlus, PanelLeft, Pencil, Settings } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton";
import { PageContent } from "@/components/app/page";
import { cn } from "@/lib/utils";
import ProtectedRoute from "../auth/protected-route";
import { useAuth } from "../auth/auth-context";
import AppShell from "../components/app-shell";
import { EmptyState, ErrorState, UnauthorizedState } from "../components/ui/page-states";
import { invalidateWorkspaceCounts } from "../lib/workspace-counts";
import { openCompose } from "./compose-store";
import { FolderPane } from "./mail-folders";
import { ThreadListPane } from "./thread-list";
import { composeReply, ReaderPane, replyTarget } from "./thread-reader";
import { useMailActions, useMailSummary, useMailThread, useMailUrl, usePartyOptions, useThreadList } from "./useMailbox";

// /mail: Outlook-style three panes on desktop (folders · conversations · reader); on phones the list and the
// reader stack (?thread shows only the reader) and the folders open in a sheet.

const PANE_HEIGHT = "h-[calc(100svh-3.5rem)] md:h-[calc(100svh-4.5rem)]";
const G_SEQUENCE_MS = 1200;

export function MailboxPage() {
  return <ProtectedRoute><AppShell title="Mail">
    <Suspense fallback={<MailboxSkeleton />}><Mailbox /></Suspense>
  </AppShell></ProtectedRoute>;
}

function Mailbox() {
  const { can } = useAuth();
  if (!can("mail.view")) return <PageContent><UnauthorizedState title="You don't have access to mail" permission="mail.view" backHref="/" backLabel="Go to dashboard" /></PageContent>;
  return <MailboxWorkspace />;
}

function useIsDesktop() {
  const [desktop, setDesktop] = useState(true);
  useEffect(() => {
    const query = window.matchMedia("(min-width: 1024px)");
    const update = () => setDesktop(query.matches);
    update();
    query.addEventListener("change", update);
    return () => query.removeEventListener("change", update);
  }, []);
  return desktop;
}

function useSelection(resetKey) {
  const [selected, setSelected] = useState(() => new Set());
  useEffect(() => setSelected(new Set()), [resetKey]);
  return useMemo(() => ({
    selected,
    toggle: (id) => setSelected((current) => {
      const next = new Set(current);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    }),
    selectAll: (ids) => setSelected(new Set(ids)),
    clear: () => setSelected(new Set()),
  }), [selected]);
}

// Mailbox keys stay out of the way while typing, while a dialog/menu is open, and right after "g" (shell go-to keys).
function mailKeysBlocked(event, gPressedAt) {
  const active = document.activeElement;
  const typing = ["INPUT", "TEXTAREA", "SELECT"].includes(active?.tagName) || active?.isContentEditable;
  const overlayOpen = document.querySelector("[role=dialog][data-state=open], [role=menu][data-state=open], [role=alertdialog][data-state=open], [role=listbox][data-state=open]");
  return typing || overlayOpen || event.metaKey || event.ctrlKey || event.altKey || Date.now() - gPressedAt < G_SEQUENCE_MS;
}

function MailboxWorkspace() {
  const { can } = useAuth();
  const url = useMailUrl();
  const { state } = url;
  const isDesktop = useIsDesktop();
  const [foldersOpen, setFoldersOpen] = useState(false);
  const [helpOpen, setHelpOpen] = useState(false);
  const summary = useMailSummary(state.account);
  const noAccounts = Boolean(summary.summary && !(summary.summary.accounts || []).length);
  const list = useThreadList(state, !noAccounts && !summary.error);
  const listRef = useRef(list);
  listRef.current = list;
  const reader = useMailThread(state.thread, {
    onLoaded: (thread) => {
      // Opening marks the thread read on the server; mirror it in the list and the badges.
      const row = listRef.current.items.find((item) => item.id === thread.id);
      if (row?.unreadCount > 0) {
        listRef.current.applyLocal([thread.id], { unreadCount: 0 });
        summary.refresh();
        invalidateWorkspaceCounts();
      }
    },
  });
  const actions = useMailActions({ list, reader, summary, onThreadGone: url.closeThread });
  const selection = useSelection(`${state.folder}|${state.account}|${state.q}|${state.buyer}|${state.factory}|${state.record}|${state.unread}|${state.attachments}`);
  const partyOptions = usePartyOptions({ buyers: can("buyers.view"), factories: can("factories.view"), enabled: list.hasLoaded });
  const rowRefs = useRef(new Map());
  const canSend = can("mail.send");
  const canSaveFiles = can("documents.upload");
  const accounts = useMemo(() => summary.summary?.accounts || [], [summary.summary]);
  const readable = accounts.length > 0;

  // New mail found by the summary poll (or a sync): refresh the list so it shows without a manual reload.
  // Only growth counts; reading or archiving here already updated the rows.
  const folders = summary.summary?.folders;
  const incoming = folders ? [folders.inbox?.total || 0, folders.inbox?.unread || 0, folders.outbox?.failed || 0].join("|") : "";
  const lastIncoming = useRef("");
  useEffect(() => {
    if (!incoming) return;
    const previous = lastIncoming.current ? lastIncoming.current.split("|").map(Number) : null;
    if (previous && incoming.split("|").map(Number).some((value, index) => value > previous[index])) listRef.current.refresh();
    lastIncoming.current = incoming;
  }, [incoming]);

  const openThread = useCallback((thread) => {
    url.openThread(thread.id, { push: !isDesktop });
  }, [isDesktop, url]);

  const compose = useCallback(() => {
    setFoldersOpen(false);
    openCompose({ accountId: state.account ? Number(state.account) : undefined });
  }, [state.account]);

  // Next row after `id`, else the previous one: where the reader goes after archiving.
  const neighbourOf = useCallback((id) => {
    const items = listRef.current.items;
    const index = items.findIndex((item) => item.id === id);
    return items[index + 1] || items[index - 1] || null;
  }, []);

  // Keyboard: j/k move · Enter/o open · x select · e archive · s star · Shift+U/Shift+I unread/read · r/a/f reply · Esc back.
  const gPressedAt = useRef(0);
  const keyState = useRef({});
  keyState.current = { list, reader, state, selection, actions, isDesktop, openThread, neighbourOf, url, canSend };
  useEffect(() => {
    const focusRow = (id) => rowRefs.current.get(id)?.focus({ preventScroll: false });
    const onKeyDown = (event) => {
      if (event.key === "g" && !event.shiftKey) { gPressedAt.current = Date.now(); return; }
      if (mailKeysBlocked(event, gPressedAt.current)) return;
      const { list: rows, reader: current, state: urlState, selection: picked, actions: act, isDesktop: desktop, openThread: open, neighbourOf: neighbour, url: nav, canSend: sendable } = keyState.current;
      const items = rows.items;
      const focusedId = document.activeElement?.dataset?.threadId;
      const currentId = urlState.thread || focusedId;
      const index = items.findIndex((item) => item.id === (focusedId || urlState.thread));
      const key = event.key;

      if (key === "j" || key === "k") {
        if (!items.length) return;
        event.preventDefault();
        const next = items[Math.min(items.length - 1, Math.max(0, index < 0 ? 0 : index + (key === "j" ? 1 : -1)))];
        focusRow(next.id);
        if (desktop) open(next);
      } else if ((key === "Enter" || key === "o") && focusedId) {
        event.preventDefault();
        open(items.find((item) => item.id === focusedId));
      } else if (key === "Escape") {
        if (picked.selected.size) picked.clear();
        else if (urlState.thread) { nav.closeThread(); window.requestAnimationFrame(() => focusRow(urlState.thread)); }
      } else if (key === "x" && currentId) {
        event.preventDefault();
        picked.toggle(currentId);
      } else if (key === "e") {
        const ids = picked.selected.size ? [...picked.selected] : currentId ? [currentId] : [];
        if (!ids.length) return;
        event.preventDefault();
        const action = urlState.folder === "archived" ? "unarchive" : "archive";
        const after = urlState.thread && ids.includes(urlState.thread) ? neighbour(urlState.thread) : null;
        act.bulk(ids, action).then((ok) => {
          if (!ok) return;
          picked.clear();
          if (urlState.thread && ids.includes(urlState.thread)) {
            if (after && desktop) { open(after); focusRow(after.id); } else nav.closeThread();
          }
        });
      } else if (key === "s" && currentId) {
        event.preventDefault();
        const row = items.find((item) => item.id === currentId) || current.thread;
        act.bulk([currentId], row?.isStarred ? "unstar" : "star");
      } else if ((key === "U" || key === "I") && event.shiftKey) {
        const ids = picked.selected.size ? [...picked.selected] : currentId ? [currentId] : [];
        if (!ids.length) return;
        event.preventDefault();
        act.bulk(ids, key === "U" ? "unread" : "read", { quiet: true }).then(() => {
          if (key === "U" && urlState.thread && ids.includes(urlState.thread)) nav.closeThread();
        });
      } else if (["r", "a", "f"].includes(key) && current.thread && sendable && current.thread.permissions?.reply !== false) {
        event.preventDefault();
        composeReply(current.thread, replyTarget(current.thread), { r: "reply", a: "replyAll", f: "forward" }[key]);
      }
    };
    document.addEventListener("keydown", onKeyDown);
    return () => document.removeEventListener("keydown", onKeyDown);
  }, []);

  const folderPane = <FolderPane
    className="flex-1"
    state={state}
    summary={summary.summary}
    isLoading={summary.isLoading}
    onSelectFolder={(folder) => { setFoldersOpen(false); url.update({ folder, thread: "" }); }}
    onSelectAccount={(account) => { setFoldersOpen(false); url.update({ account, thread: "" }); }}
    canCompose={canSend && readable}
    onCompose={compose}
    onSync={actions.syncNow}
    isSyncing={actions.isPending("sync")}
    onResume={actions.resumeAccount}
    isResuming={(account) => actions.isPending(`resume:${account.id}`)}
    footer={<Button variant="ghost" size="xs" className="justify-start text-muted-foreground" onClick={() => setHelpOpen(true)}><Keyboard /> Keyboard shortcuts</Button>}
  />;

  if (summary.error && !summary.summary) {
    return <PageContent><ErrorState error={summary.error} title="Mail could not be loaded" onRetry={summary.refresh} /></PageContent>;
  }
  if (summary.isLoading && !summary.summary) return <MailboxSkeleton />;
  // No mailbox of their own, but a conversation linked to a record they may view (opened from its Emails section).
  if (noAccounts && state.thread) {
    return <div className={cn("min-h-[28rem] overflow-hidden", PANE_HEIGHT)}>
      <ReaderPane className="h-full min-h-0 min-w-0" threadId={state.thread} reader={reader} actions={actions} canSend={canSend} canSaveFiles={canSaveFiles} onBack={url.closeThread} showBack />
    </div>;
  }
  if (noAccounts) return <NoMailboxes canConnect={canSend || can("mail.manage_accounts")} />;

  return <div className={cn("grid min-h-[28rem] overflow-hidden lg:grid-cols-[220px_minmax(320px,420px)_minmax(0,1fr)]", PANE_HEIGHT)}>
    <aside className="hidden min-h-0 overflow-y-auto border-r p-3 lg:flex lg:flex-col">{folderPane}</aside>

    <div className={cn("flex min-h-0 min-w-0 flex-col lg:border-r", state.thread && "hidden lg:flex")}>
      <div className="flex items-center gap-2 border-b px-2 py-1.5 lg:hidden">
        <Button variant="ghost" size="sm" onClick={() => setFoldersOpen(true)}><PanelLeft /> Folders</Button>
        {canSend && <Button size="sm" className="ml-auto" onClick={compose}><Pencil /> Compose</Button>}
      </div>
      <ThreadListPane
        className="min-h-0 flex-1"
        url={url}
        list={list}
        summary={summary.summary}
        selection={selection}
        activeId={state.thread}
        onOpen={openThread}
        actions={actions}
        partyOptions={partyOptions}
        canAct
        rowRefs={rowRefs}
      />
    </div>

    <ReaderPane
      className={cn("min-h-0 min-w-0", !state.thread && "hidden lg:flex")}
      threadId={state.thread}
      reader={reader}
      actions={actions}
      canSend={canSend}
      canSaveFiles={canSaveFiles}
      onBack={url.closeThread}
      showBack={!isDesktop}
    />

    <Sheet open={foldersOpen} onOpenChange={setFoldersOpen}>
      <SheetContent side="left" className="w-72 gap-0 p-0">
        <SheetHeader className="border-b">
          <SheetTitle>Mail</SheetTitle>
          <SheetDescription>Folders and mailboxes</SheetDescription>
        </SheetHeader>
        <div className="flex min-h-0 flex-1 flex-col overflow-y-auto p-3">{folderPane}</div>
      </SheetContent>
    </Sheet>
    <MailShortcutsDialog open={helpOpen} onOpenChange={setHelpOpen} />
  </div>;
}

function NoMailboxes({ canConnect }) {
  return <PageContent className="max-w-3xl">
    <EmptyState
      icon={MailPlus}
      title="Connect a mailbox to send and receive buyer and factory email here"
      detail={canConnect
        ? "Use your company mailbox (Gmail, Microsoft 365, cPanel webmail and others). Replies thread automatically and link to the right buyer, factory and order."
        : "No mailbox is shared with you yet. Ask an admin to connect the team mailbox and add you as a member."}
      action={canConnect ? <Button size="sm" asChild><Link href="/settings/email-accounts"><Settings /> Add mailbox</Link></Button> : null}
    />
  </PageContent>;
}

const SHORTCUTS = [
  { keys: ["J"], label: "Next conversation" },
  { keys: ["K"], label: "Previous conversation" },
  { keys: ["Enter"], label: "Open the focused conversation" },
  { keys: ["X"], label: "Select or unselect" },
  { keys: ["E"], label: "Archive (or move back to the inbox)" },
  { keys: ["S"], label: "Star or unstar" },
  { keys: ["Shift", "U"], label: "Mark unread" },
  { keys: ["Shift", "I"], label: "Mark read" },
  { keys: ["R"], label: "Reply" },
  { keys: ["A"], label: "Reply all" },
  { keys: ["F"], label: "Forward" },
  { keys: ["Esc"], label: "Clear selection, or back to the list" },
  { keys: ["G", "M"], label: "Go to mail from anywhere" },
];

function MailShortcutsDialog({ open, onOpenChange }) {
  return <Dialog open={open} onOpenChange={onOpenChange}>
    <DialogContent className="sm:max-w-md">
      <DialogHeader>
        <DialogTitle>Mail shortcuts</DialogTitle>
        <DialogDescription>Work through the inbox without the mouse. Keys are ignored while you type.</DialogDescription>
      </DialogHeader>
      <ul className="grid gap-1 text-sm">
        {SHORTCUTS.map((row) => <li key={row.label} className="flex items-center justify-between gap-4 rounded-md px-2 py-1.5 odd:bg-muted/40">
          <span>{row.label}</span>
          <KbdGroup>{row.keys.map((key) => <Kbd key={key}>{key}</Kbd>)}</KbdGroup>
        </li>)}
      </ul>
    </DialogContent>
  </Dialog>;
}

function MailboxSkeleton() {
  return <div className={cn("grid overflow-hidden lg:grid-cols-[220px_minmax(320px,420px)_minmax(0,1fr)]", PANE_HEIGHT)} role="status" aria-label="Loading mail">
    <div className="hidden gap-2 border-r p-3 lg:grid lg:content-start">
      <Skeleton className="h-9 w-full" /><Skeleton className="h-9 w-full" />
      {Array.from({ length: 7 }).map((_, index) => <Skeleton key={index} className="h-8 w-full" />)}
    </div>
    <div className="grid content-start gap-3 border-r p-3">
      <Skeleton className="h-5 w-24" /><Skeleton className="h-9 w-full" /><div className="flex gap-2"><Skeleton className="h-8 flex-1" /><Skeleton className="h-8 flex-1" /></div>
      {Array.from({ length: 7 }).map((_, index) => <div key={index} className="flex gap-2.5 py-1"><Skeleton className="size-8 rounded-full" /><div className="grid flex-1 gap-1.5"><Skeleton className="h-4 w-1/2" /><Skeleton className="h-4 w-4/5" /><Skeleton className="h-3 w-full" /></div></div>)}
    </div>
    <div className="hidden content-start gap-4 p-6 lg:grid"><Skeleton className="h-6 w-2/3" /><Skeleton className="h-40 rounded-xl" /></div>
  </div>;
}
