// frontend/components/mail/MailClient.tsx — 메일 페이지 오케스트레이터 (Topbar 는 layout)
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import { useParams } from "next/navigation";
import useSWR, { useSWRConfig } from "swr";
import { tasksApi } from "@/lib/tasks/api";
import { mailApi } from "@/lib/mail/api";
import { useMail } from "@/lib/hooks/useMail";
import { useDialog } from "@/components/Dialog";
import type { AiReply, EmailDetail, EmailRow, ExtractKind, MailAccount, Person } from "@/lib/types";
import { MailSidebar } from "./MailSidebar";
import { MailList } from "./MailList";
import { Reader } from "./Reader";
import { Triage } from "./Triage";
import { Compose, type ComposeState } from "./Compose";

// 계정 구분 색 — 계정 tone 이 서로 겹쳐(둘 다 blue) dot 으로 구분이 안 되므로 팔레트에서 배정.
const ACC_PALETTE = ["var(--blue)", "var(--violet)", "var(--coral)", "var(--green)", "var(--amber)"];

const FOLDER_META: Record<string, string> = {
  smart: "아리 정리",
  inbox: "받은편지함",
  others: "기타",
  starred: "별표",
  sent: "보낸편지함",
  drafts: "임시보관함",
  archive: "보관함",
};

type Toast = { id: number; msg: string; tone: string };

function toneColor(tone: string): string {
  return tone === "coral"
    ? "var(--coral)"
    : tone === "violet"
      ? "var(--violet)"
      : tone === "green"
        ? "var(--green)"
        : "var(--blue)";
}

export function MailClient() {
  // 기본은 통합 받은편지함(전체 받은편지함). 아리 정리(smart)는 사이드바에서 선택.
  // folder 는 폴더 슬러그(inbox/spam/trash/starred/cat:…/label:…/of:…) — 사이드바 트리가 지정.
  const [folder, setFolder] = useState<string>("inbox");
  const [acctFilter, setAcctFilter] = useState<string | null>(null);
  const [query, setQuery] = useState("");
  const [debouncedQuery, setDebouncedQuery] = useState("");
  const [selId, setSelId] = useState<string | null>(null);
  const [detail, setDetail] = useState<EmailDetail | null>(null);
  const [detailLoading, setDetailLoading] = useState(false);
  const [added, setAdded] = useState<Record<string, boolean>>({});
  const [compose, setCompose] = useState<ComposeState | null>(null);
  const [toasts, setToasts] = useState<Toast[]>([]);
  const [syncing, setSyncing] = useState(false);
  const [analyzing, setAnalyzing] = useState(false);
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const shellRef = useRef<HTMLDivElement>(null);
  const tid = useRef(0);
  const didMount = useRef(false);

  // URL 로 열린 메일을 표현 — /mail/<id> 의 <id> 세그먼트(옵셔널 캐치올).
  // 주소 갱신은 router 대신 history.replaceState(얕은 갱신) — router.replace 는 이 라우트를
  // 리마운트시켜 folder/검색 상태가 날아가고 openMail 루프가 발생하기 때문.
  const params = useParams();
  const routeId = Array.isArray(params.slug) ? params.slug[0] : undefined;

  // 검색은 서버측(백엔드 q 파라미터). 입력은 250ms 디바운스해 키 입력마다 호출하지 않음.
  useEffect(() => {
    const h = setTimeout(() => setDebouncedQuery(query), 250);
    return () => clearTimeout(h);
  }, [query]);

  const { emails, accounts, folders, mutate } = useMail(acctFilter, folder, debouncedQuery);
  const { mutate: globalMutate } = useSWRConfig();
  const { confirm } = useDialog();
  const { data: people } = useSWR<Person[]>("people", () => tasksApi.people(), {
    revalidateOnFocus: false,
  });
  // 아리 정리 기준 시각(이 시각 이후 새 메일만 정리) + 새 안읽음 수(사이드바 배지).
  // 배지는 어느 폴더 보기에서도 필요하므로 항상 조회한다.
  const { data: baseline, mutate: mutateBaseline } = useSWR(
    "mail/triage/baseline",
    () => mailApi.triageBaseline(),
    { revalidateOnFocus: false, refreshInterval: 30000 },
  );

  const peopleMap = useMemo<Record<string, Person>>(() => {
    const m: Record<string, Person> = {};
    (people || []).forEach((p) => (m[p.id] = p));
    return m;
  }, [people]);
  const accMap = useMemo<Record<string, MailAccount>>(() => {
    const m: Record<string, MailAccount> = {};
    (accounts || []).forEach((a) => (m[a.id] = a));
    return m;
  }, [accounts]);

  useEffect(() => {
    const el = shellRef.current;
    const id = requestAnimationFrame(() => el && el.classList.add("entered"));
    return () => cancelAnimationFrame(id);
  }, [emails]);

  const toast = (msg: string, tone: string) => {
    const id = ++tid.current;
    setToasts((t) => [...t, { id, msg, tone }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2600);
  };

  // 목록 + 사이드바 배지(계정/폴더 안읽음)까지 함께 갱신.
  // 읽음/별표/보관/삭제 등은 백엔드가 즉시 반영하지만, 계정/폴더 SWR 키는 별도라
  // mutate() 만으론 사이드바 숫자가 안 줄어든다 → 함께 revalidate.
  const revalidateAll = async () => {
    await Promise.all([
      mutate(),
      globalMutate("mail/accounts"),
      globalMutate("mail/folders"),
      globalMutate("mail/triage/baseline"), // 아리 정리 배지(새 안읽음)
    ]);
  };

  const openMail = async (id: string) => {
    // 보낸편지함/임시보관함 행(ob-…)은 상세 조회 대상이 아니라 목록 표시 전용.
    if (folder === "sent" || folder === "drafts") return;
    setSelId(id);
    setDetail(null);
    setDetailLoading(true);
    try {
      const d = await mailApi.detail(id);
      setDetail(d);
    } catch {
      toast("메일을 불러오지 못했어요", "coral");
    } finally {
      setDetailLoading(false);
      revalidateAll(); // 읽음 처리 + 사이드바 배지 반영
    }
  };

  // URL → 상세: routeId(주소의 <id>)가 바뀌면 해당 메일을 연다(직접 입력·뒤로가기·딥링크).
  useEffect(() => {
    if (!routeId) {
      setSelId(null);
      setDetail(null);
      return;
    }
    if (routeId === selId) return; // 이미 열려 있음
    openMail(routeId);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [routeId]);

  // 상세 → URL: selId 가 바뀌면 주소를 맞춘다(초기 마운트는 위 effect 가 처리하므로 skip).
  useEffect(() => {
    if (!didMount.current) {
      didMount.current = true;
      return;
    }
    const target = selId ? `/mail/${selId}` : "/mail";
    if (window.location.pathname !== target) window.history.replaceState(null, "", target);
  }, [selId]);

  // 사이드바 트리 선택 = (계정 범위, 폴더 슬러그) 동시 지정.
  const pickScope = (acct: string | null, f: string) => {
    setAcctFilter(acct);
    setFolder(f);
    setSelId(null);
    setDetail(null);
    setSelected(new Set());
  };

  const onStar = async (id: string) => {
    const cur = emails?.find((m) => m.id === id);
    const next = !(cur?.starred ?? detail?.starred ?? false);
    // 낙관적 토글 → 서버 반영 → 실패 시 롤백
    if (detail && detail.id === id) setDetail({ ...detail, starred: next });
    mutate((list) => (list || []).map((m) => (m.id === id ? { ...m, starred: next } : m)), false);
    try {
      await mailApi.star(id, next);
      revalidateAll();
    } catch (e) {
      if (detail && detail.id === id) setDetail({ ...detail, starred: !next });
      revalidateAll();
      toast(e instanceof Error ? e.message : "별표를 바꾸지 못했어요", "coral");
    }
  };

  const onArchive = async (id: string) => {
    setSelId(null);
    setDetail(null);
    mutate((list) => (list || []).filter((m) => m.id !== id), false);
    try {
      await mailApi.archive(id);
      revalidateAll();
      toast("보관함으로 옮겼어요", "green");
    } catch (e) {
      revalidateAll();
      toast(e instanceof Error ? e.message : "보관하지 못했어요", "coral");
    }
  };

  const onMarkUnread = async (id: string) => {
    // 안 읽음 처리 → 목록 낙관적 갱신 → 리더 닫고 목록으로 복귀
    mutate((list) => (list || []).map((m) => (m.id === id ? { ...m, read: false } : m)), false);
    setSelId(null);
    setDetail(null);
    try {
      await mailApi.setRead(id, false);
      revalidateAll();
      toast("안 읽음으로 표시했어요", "blue");
    } catch (e) {
      revalidateAll();
      toast(e instanceof Error ? e.message : "안 읽음으로 표시하지 못했어요", "coral");
    }
  };

  const onDelete = async (id: string) => {
    const ok = await confirm({
      title: "메일 삭제",
      message: "이 메일을 삭제할까요?",
      confirmText: "삭제",
      tone: "danger",
    });
    if (!ok) return;
    setSelId(null);
    setDetail(null);
    mutate((list) => (list || []).filter((m) => m.id !== id), false);
    try {
      await mailApi.remove(id);
      revalidateAll();
      toast("메일을 삭제했어요", "green");
    } catch (e) {
      revalidateAll();
      toast(e instanceof Error ? e.message : "삭제하지 못했어요", "coral");
    }
  };

  // ── 여러 메일 선택 + 일괄 처리 ──
  const toggleSelect = (id: string) =>
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  const clearSelected = () => setSelected(new Set());
  const onBulk = async (action: string) => {
    const ids = [...selected];
    if (!ids.length) return;
    if (action === "trash") {
      const ok = await confirm({
        title: "메일 삭제",
        message: `${ids.length}개 메일을 삭제할까요?`,
        confirmText: "삭제",
        tone: "danger",
      });
      if (!ok) return;
    }
    // 즉시 목록에서 반영(삭제/보관은 사라지고, 읽음/별표는 토글)
    mutate((list) => {
      if (action === "trash" || action === "archive")
        return (list || []).filter((m) => !selected.has(m.id));
      return (list || []).map((m) =>
        selected.has(m.id)
          ? {
              ...m,
              read: action === "read" ? true : action === "unread" ? false : m.read,
              starred: action === "star" ? true : action === "unstar" ? false : m.starred,
            }
          : m,
      );
    }, false);
    clearSelected();
    try {
      const res = await mailApi.bulk(ids, action);
      revalidateAll();
      toast(`${res.count}개 메일을 처리했어요`, "green");
    } catch (e) {
      revalidateAll();
      toast(e instanceof Error ? e.message : "일괄 처리에 실패했어요", "coral");
    }
  };

  // 전체 답장 / 전달 — 기존 발송(결재) 경로 재사용해 Compose 채움.
  const onReplyAll = (m: EmailDetail) => {
    const sender = peopleMap[m.from];
    const to = sender?.email ? `${sender.name} <${sender.email}>` : (sender?.name ?? m.from);
    setCompose({
      from: m.account,
      to,
      subject: "Re: " + m.subject,
      body: "",
      inReplyTo: m.id,
    });
  };
  const onForward = (m: EmailDetail) => {
    const quoted = (m.body || []).map((p) => "> " + p).join("\n");
    setCompose({
      from: m.account,
      to: "",
      subject: "Fwd: " + m.subject,
      body: `\n\n---------- 전달된 메일 ----------\n${quoted}`,
    });
  };

  // 추출(할일/일정/파일) — mailId 지정(없으면 현재 열린 메일 selId). 리더·아리비서 공용.
  const doAddFor = async (mailId: string, key: string, kind: ExtractKind, index: number) => {
    if (added[key]) return;
    setAdded((a) => ({ ...a, [key]: true })); // 낙관적
    try {
      const res = await mailApi.extract(mailId, kind, index);
      toast(res.message, kind === "event" ? "coral" : kind === "file" ? "violet" : "blue");
      if (kind === "task") globalMutate("/api/tasks");
      if (kind === "event") globalMutate("/api/calendar");
    } catch {
      setAdded((a) => ({ ...a, [key]: false })); // 롤백
      toast("추가하지 못했어요", "coral");
    }
  };
  const doAdd = (key: string, kind: ExtractKind, index: number) =>
    doAddFor(selId!, key, kind, index);

  // 아리 비서: 새 메일을 가져와(sync) 미분석분을 LLM 으로 정리. 먼저 동기화해
  // 기준 시각 이후 새로 도착한 메일을 받고, 작은 배치(2통)로 나눠 반복 분석 —
  // 요청을 짧게 유지하고 라운드마다 목록을 갱신해 카드를 점진적으로 노출한다.
  const runAnalyze = async () => {
    if (analyzing) return;
    setAnalyzing(true);
    let total = 0;
    try {
      await mailApi.sync().catch(() => {}); // 새 메일 먼저 가져오기(실패해도 분석 진행)
      await revalidateAll();
      for (let round = 0; round < 8; round++) {
        const res = await mailApi.triageAnalyze(2);
        total += res.analyzed;
        await mutate(); // 라운드마다 카드 갱신
        if (res.remaining === 0 || res.analyzed === 0) break;
      }
      toast(total > 0 ? `새 메일 ${total}통을 정리했어요` : "정리할 새 메일이 없어요", "blue");
    } catch {
      if (total > 0) await mutate();
      toast("정리하지 못했어요 (아리 모델 확인)", "coral");
    } finally {
      setAnalyzing(false);
    }
  };

  // 기준 시각 재설정 — 지금 이후 도착하는 새 메일부터 다시 정리.
  const onResetBaseline = async () => {
    try {
      await mailApi.triageBaselineReset();
      await Promise.all([mutateBaseline(), mutate()]);
      toast("지금부터 도착하는 새 메일만 정리할게요", "blue");
    } catch {
      toast("기준 시각을 바꾸지 못했어요", "coral");
    }
  };

  const addAll = async (m: EmailRow) => {
    if (!m.ai) return;
    const next = { ...added };
    m.ai.tasks.forEach((_, i) => (next["t:" + m.id + ":" + i] = true));
    m.ai.events.forEach((_, i) => (next["e:" + m.id + ":" + i] = true));
    if (m.ai.file) next["f:" + m.id] = true;
    setAdded(next);
    try {
      const res = await mailApi.extractAll(m.id);
      toast(res.message, "green");
      globalMutate("/api/tasks");
      globalMutate("/api/calendar");
    } catch {
      toast("적용하지 못했어요", "coral");
    }
  };

  // 답장: AriPanel/Triage 의 reply 선택 → 서버 초안(AI) + 원본 인용 → Compose 채움
  const onReply = async (
    m: EmailRow | EmailDetail,
    replyIndex: number | null,
    _r: AiReply | null,
  ) => {
    void _r;
    try {
      const d = await mailApi.replyDraft(m.id, replyIndex);
      // 원본 본문: 열린 상세(detail) 우선, 없으면 m 자체가 상세면 그 body 사용
      const bodyLines =
        detail && detail.id === m.id
          ? detail.body
          : "body" in m && Array.isArray(m.body)
            ? m.body
            : [];
      const quoted = bodyLines.map((p) => "> " + p).join("\n");
      const draft = d.body || "";
      const body = quoted
        ? `${draft}\n\n--------\n원본 메일:\n${quoted}`
        : draft;
      setCompose({
        from: d.from_account,
        to: d.to,
        subject: d.subject,
        body,
        inReplyTo: m.id,
      });
    } catch {
      toast("초안을 만들지 못했어요", "coral");
    }
  };

  const onSend = async (c: ComposeState) => {
    try {
      const res = await mailApi.send({
        from_account: c.from,
        to: c.to ?? "",
        cc: c.cc ?? "",
        bcc: c.bcc ?? "",
        subject: c.subject ?? "",
        body: c.body ?? "",
        in_reply_to: c.inReplyTo ?? null,
        attachments: c.attachments ?? [],
      });
      setCompose(null);
      toast(res.message, "violet"); // "결재함에서 확인하세요"
      globalMutate("/api/approvals");
    } catch {
      toast("보내지 못했어요", "coral");
    }
  };

  if (!emails || !accounts || !folders) {
    return (
      <div className="mailpage">
        <div className="mwork" aria-busy="true">
          <aside className="subnav" />
          <div className="mlist" />
          <div className="reader" />
        </div>
      </div>
    );
  }

  // 목록은 서버가 폴더 슬러그로 이미 필터·정렬 — 그대로 표시.
  const visible = emails;

  // 폴더를 계정별로 그룹화(사이드바 계정 노드 서브트리). 정렬 키대로.
  const foldersByAccount: Record<string, typeof folders> = {};
  folders.forEach((f) => (foldersByAccount[f.account] ||= []).push(f));
  Object.values(foldersByAccount).forEach((list) =>
    list.sort((a, b) => a.sort_order - b.sort_order),
  );
  // 통합 받은편지함 노드: 계정 공통(시스템·카테고리) 폴더만 슬러그별 합산(커스텀/라벨 제외).
  const unifiedMap: Record<string, (typeof folders)[number]> = {};
  folders.forEach((f) => {
    // 통합 상단 목록은 공통 시스템 폴더만(받은편지함/별표/보낸/임시/보관/스팸/휴지통).
    // 카테고리(Gmail 프로모션·Outlook 기타)/라벨/커스텀은 각 계정 노드에서만 표시.
    if (f.kind !== "system") return;
    const cur = unifiedMap[f.slug];
    if (cur) {
      cur.unread += f.unread;
      cur.total += f.total;
    } else unifiedMap[f.slug] = { ...f, account: "" };
  });
  const unifiedFolders = Object.values(unifiedMap).sort((a, b) => a.sort_order - b.sort_order);

  // 아리 정리 배지 = 기준 시각 이후 도착한 새 메일 수(읽음 무관) — 백엔드가 baseline 으로 산출.
  const smartCount = baseline?.count ?? 0;
  // 사이드바 트리 배지(집중 안읽음)는 계정 목록의 unread(백엔드 산출)에서 — 현재 보기와 무관하게 정확.
  const accUnread = (id: string) => accMap[id]?.unread ?? 0;
  const unifiedUnread = accounts.reduce((sum, a) => sum + a.unread, 0);
  // 기타(Outlook other) 안읽음 — 통합/계정별. 사이드바 기타 행·받은편지함 탭 카운트에 사용.
  const unifiedOthersUnread = accounts.reduce((sum, a) => sum + (a.others ?? 0), 0);
  const focusedUnread = acctFilter ? (accMap[acctFilter]?.unread ?? 0) : unifiedUnread;
  const othersUnread = acctFilter ? (accMap[acctFilter]?.others ?? 0) : unifiedOthersUnread;
  const accColor = (id: string) => {
    const i = accounts.findIndex((a) => a.id === id);
    return ACC_PALETTE[(i < 0 ? 0 : i) % ACC_PALETTE.length];
  };

  // 목록 제목: 폴더 슬러그 → 표시명(시스템은 META, 커스텀/카테고리/라벨은 폴더명에서).
  const curFolder =
    (acctFilter ? foldersByAccount[acctFilter] : unifiedFolders)?.find((f) => f.slug === folder) ||
    folders.find((f) => f.slug === folder);
  const listTitle = FOLDER_META[folder] ?? curFolder?.name ?? "받은편지함";

  const sel = detail && detail.id === selId ? detail : null;
  // 아리 비서(정리) 보기 = smart 폴더. 메일 리스트 대신 비서 다이제스트를 보여준다.
  const digestView = folder === "smart";

  const readerEl = (
    <Reader
      m={sel}
      person={sel ? peopleMap[sel.from] : undefined}
      account={sel ? accMap[sel.account] : undefined}
      added={added}
      loading={detailLoading}
      onAdd={doAdd}
      onReply={onReply}
      onReplyAll={onReplyAll}
      onForward={onForward}
      onAddAll={addAll}
      onArchive={onArchive}
      onStar={onStar}
      onMarkUnread={onMarkUnread}
      onDelete={onDelete}
      onOpenMsg={openMail}
    />
  );

  return (
    <div className="mailpage" ref={shellRef}>
      <div
        className={
          "mwork" + (digestView ? " digest" : "") + (digestView && selId ? " reading" : "")
        }
      >
        <MailSidebar
          accounts={accounts}
          folder={folder}
          acctFilter={acctFilter}
          smartCount={smartCount}
          unifiedUnread={unifiedUnread}
          accUnread={accUnread}
          unifiedFolders={unifiedFolders}
          foldersByAccount={foldersByAccount}
          accColor={accColor}
          onCompose={() => setCompose({ from: acctFilter || accounts[0]?.id || "" })}
          onSelectSmart={() => pickScope(null, "smart")}
          onSelectScope={pickScope}
          onToast={toast}
        />

        {digestView ? (
          <>
            <Triage
              emails={emails}
              people={peopleMap}
              accColor={accColor}
              added={added}
              analyzing={analyzing}
              since={baseline?.since}
              onAnalyze={runAnalyze}
              onResetBaseline={onResetBaseline}
              onOpen={openMail}
              onAdd={doAddFor}
              onAddAll={addAll}
              onReply={onReply}
            />
            {selId && readerEl}
          </>
        ) : (
          <>
            <MailList
              title={listTitle}
              metaLabel={acctFilter ? (accMap[acctFilter]?.name ?? "") + " · " : ""}
              count={visible.length}
              folder={folder}
              focusedUnread={focusedUnread}
              othersUnread={othersUnread}
              onSelectTab={(slug) => pickScope(acctFilter, slug)}
              query={query}
              syncing={syncing}
              emails={visible}
              people={peopleMap}
              accounts={accMap}
              selId={selId}
              hasSel={!!selId}
              selected={selected}
              onToggleSelect={toggleSelect}
              onClearSelected={clearSelected}
              onBulk={onBulk}
              onQuery={setQuery}
              onSync={async () => {
                setSyncing(true);
                try {
                  const res = await mailApi.sync();
                  await revalidateAll();
                  toast(
                    res.upserted > 0 ? `새 메일 ${res.upserted}통을 가져왔어요` : "새 메일이 없어요",
                    "blue",
                  );
                } catch {
                  toast("메일을 가져오지 못했어요", "coral");
                } finally {
                  setSyncing(false);
                }
              }}
              onOpen={openMail}
              onStar={onStar}
              accColor={accColor}
            />
            {readerEl}
          </>
        )}
      </div>

      {compose && (
        <Compose
          init={compose}
          accounts={accounts}
          onClose={() => setCompose(null)}
          onSend={onSend}
        />
      )}

      <div className="toast-wrap">
        {toasts.map((t) => (
          <div className="toast" key={t.id}>
            <span className="t-dot" style={{ background: toneColor(t.tone) }} />
            {t.msg}
          </div>
        ))}
      </div>
    </div>
  );
}
