import { useCallback, useEffect, useMemo, useState } from "react";
import { Navigate } from "react-router-dom";
import { useAuth } from "../auth/AuthContext";
import { PageHeader } from "../components/layout/PageHeader";
import { Button, Card, EmptyState, Input, Select } from "../components/ui";
import { useData, useRepos } from "../data/DataContext";
import type { InviteMemberBrief, InviteTreeNode, MyInviteNetwork } from "../domain/types";
import { useI18n } from "../i18n/I18nContext";
import { memberRoleSummary } from "../lib/memberRole";
import { formatLocalDateTime } from "../lib/time";

type Tab = "mine" | "tree";

type FlatIndex = Map<string, InviteTreeNode>;

function inviteErrorMessage(
  e: unknown,
  t: (k: string, p?: Record<string, string | number>) => string,
  fallbackKey: string,
): string {
  if (!(e instanceof Error) || !e.message) return t(fallbackKey);
  if (e.message.startsWith("invite.")) return t(e.message);
  return e.message;
}

function indexForest(nodes: InviteTreeNode[], map: FlatIndex = new Map()): FlatIndex {
  for (const n of nodes) {
    map.set(n.uid, n);
    if (n.children.length) indexForest(n.children, map);
  }
  return map;
}

function countForestNodes(nodes: InviteTreeNode[]): number {
  let n = 0;
  for (const x of nodes) n += 1 + countForestNodes(x.children);
  return n;
}

function nodeMatches(n: Pick<InviteTreeNode, "display_name" | "email" | "invite_code" | "uid">, q: string): boolean {
  if (!q) return false;
  return (
    n.display_name.toLowerCase().includes(q) ||
    n.email.toLowerCase().includes(q) ||
    n.invite_code.toLowerCase().includes(q) ||
    n.uid.toLowerCase().includes(q)
  );
}

/** 從節點走到根的路徑（含自己，根在前） */
function pathToRoot(uid: string, byUid: FlatIndex): InviteTreeNode[] {
  const chain: InviteTreeNode[] = [];
  let cur = byUid.get(uid);
  const seen = new Set<string>();
  while (cur && !seen.has(cur.uid)) {
    seen.add(cur.uid);
    chain.push(cur);
    cur = cur.parent_uid ? byUid.get(cur.parent_uid) : undefined;
  }
  return chain.reverse();
}

/** 扁平搜尋結果 */
function searchHits(byUid: FlatIndex, q: string): InviteTreeNode[] {
  if (!q) return [];
  const out: InviteTreeNode[] = [];
  for (const n of byUid.values()) {
    if (nodeMatches(n, q)) out.push(n);
  }
  out.sort((a, b) => a.display_name.localeCompare(b.display_name, "zh-Hant"));
  return out;
}

function PersonChip({
  node,
  active,
  dim,
  onClick,
  sub,
}: {
  node: InviteTreeNode;
  active?: boolean;
  dim?: boolean;
  onClick?: () => void;
  sub?: string;
}) {
  return (
    <button
      type="button"
      className={[
        "hb-org-chip",
        active ? "is-active" : "",
        dim ? "is-dim" : "",
        onClick ? "is-clickable" : "",
      ]
        .filter(Boolean)
        .join(" ")}
      onClick={onClick}
      disabled={!onClick}
    >
      <span className="hb-org-chip__name">{node.display_name}</span>
      <span className="hb-org-chip__code">{node.invite_code}</span>
      {sub ? <span className="hb-org-chip__sub">{sub}</span> : null}
      {node.status === "suspended" ? <span className="hb-org-chip__flag">·</span> : null}
    </button>
  );
}

/**
 * 邀請關係
 *
 * 管理端採用「焦點 + 鑽取」而非整棵展開：
 * - 一次只看：邀請人 → 當前 → 直邀成員
 * - 搜尋找人，點進焦點
 * - 大樹（百人／深鏈）才可用
 */
export function InvitePage() {
  const repos = useRepos();
  const { member } = useAuth();
  const { t } = useI18n();
  const { tick, refresh } = useData();
  const isAdmin = memberRoleSummary(member, t).isAdmin;

  const [tab, setTab] = useState<Tab>("mine");
  const [network, setNetwork] = useState<MyInviteNetwork | null>(null);
  const [forest, setForest] = useState<InviteTreeNode[]>([]);
  const [error, setError] = useState("");
  const [message, setMessage] = useState("");
  const [copyHint, setCopyHint] = useState("");
  const [busy, setBusy] = useState(false);

  const [focusUid, setFocusUid] = useState<string | null>(null);
  const [parentDraft, setParentDraft] = useState("");
  const [candidates, setCandidates] = useState<InviteMemberBrief[]>([]);
  const [treeQuery, setTreeQuery] = useState("");
  /** 補填邀請碼草稿 */
  const [inviteCodeDraft, setInviteCodeDraft] = useState("");
  const [claimBusy, setClaimBusy] = useState(false);

  const inviteRepo = repos.invite;

  const loadMine = useCallback(async () => {
    if (!inviteRepo?.getMyNetwork) {
      setError(t("invite.err.repoMissing"));
      return;
    }
    try {
      setNetwork(await inviteRepo.getMyNetwork());
    } catch (e) {
      setError(inviteErrorMessage(e, t, "invite.loadFail"));
    }
  }, [inviteRepo, t]);

  const loadTree = useCallback(async () => {
    if (!isAdmin || !inviteRepo?.getTree) return;
    try {
      const tree = await inviteRepo.getTree();
      setForest(tree);
    } catch (e) {
      setError(inviteErrorMessage(e, t, "invite.loadFail"));
    }
  }, [inviteRepo, isAdmin, t]);

  useEffect(() => {
    setError("");
    void loadMine();
    if (isAdmin && tab === "tree") void loadTree();
  }, [loadMine, loadTree, isAdmin, tab, tick]);

  const byUid = useMemo(() => indexForest(forest), [forest]);
  const forestSize = useMemo(() => countForestNodes(forest), [forest]);
  const roots = forest;

  const q = treeQuery.trim().toLowerCase();
  const searching = q.length > 0;
  const hits = useMemo(() => searchHits(byUid, q), [byUid, q]);

  const focus = focusUid ? byUid.get(focusUid) ?? null : null;
  const inviter = focus?.parent_uid ? byUid.get(focus.parent_uid) ?? null : null;
  const path = focusUid ? pathToRoot(focusUid, byUid) : [];
  const directs = focus?.children ?? [];

  useEffect(() => {
    if (!isAdmin || !focusUid || !inviteRepo?.listParentCandidates) {
      setCandidates([]);
      setParentDraft("");
      return;
    }
    void (async () => {
      try {
        const list = await inviteRepo.listParentCandidates(focusUid);
        setCandidates(list);
        const node = byUid.get(focusUid);
        setParentDraft(node?.parent_uid || "");
      } catch {
        setCandidates([]);
      }
    })();
  }, [focusUid, isAdmin, inviteRepo, byUid]);

  async function onCopyCode(code: string) {
    setCopyHint("");
    try {
      await navigator.clipboard.writeText(code);
      setCopyHint(t("invite.copied"));
    } catch {
      setCopyHint(t("invite.copyFail"));
    }
  }

  async function onClaimInvite(e: React.FormEvent) {
    e.preventDefault();
    if (!inviteRepo?.applyInviteCode) {
      setError(t("invite.err.repoMissing"));
      return;
    }
    setClaimBusy(true);
    setError("");
    setMessage("");
    try {
      const next = await inviteRepo.applyInviteCode(inviteCodeDraft);
      setNetwork(next);
      setInviteCodeDraft("");
      setMessage(
        next.upline
          ? t("invite.claimOk", { name: next.upline.display_name })
          : t("invite.claimOkGeneric"),
      );
      refresh();
    } catch (err) {
      setError(inviteErrorMessage(err, t, "invite.claimFail"));
    } finally {
      setClaimBusy(false);
    }
  }

  async function onMove() {
    if (!focusUid || !isAdmin || !inviteRepo?.moveMember) return;
    setBusy(true);
    setError("");
    setMessage("");
    try {
      const newParent = parentDraft.trim() || null;
      const brief = await inviteRepo.moveMember(focusUid, newParent);
      const parentLabel = newParent
        ? candidates.find((c) => c.uid === newParent)?.display_name || newParent
        : t("invite.root");
      setMessage(t("invite.moved", { name: brief.display_name, parent: parentLabel }));
      refresh();
      await loadTree();
      await loadMine();
    } catch (e) {
      setError(inviteErrorMessage(e, t, "invite.moveFail"));
    } finally {
      setBusy(false);
    }
  }

  if (!member) return <Navigate to="/login" replace />;

  const showMine = tab === "mine" || !isAdmin;

  return (
    <>
      <PageHeader title={t("invite.title")} />

      {message ? (
        <p className="hb-banner-ok" role="status">
          {message}
        </p>
      ) : null}
      {error ? (
        <p className="hb-form-error" role="alert">
          {error}
        </p>
      ) : null}

      {isAdmin ? (
        <div className="hb-tabs" role="tablist" aria-label={t("invite.tabs")}>
          <button
            type="button"
            role="tab"
            className={`hb-tab ${tab === "mine" ? "is-active" : ""}`}
            onClick={() => setTab("mine")}
          >
            {t("invite.tab.mine")}
          </button>
          <button
            type="button"
            role="tab"
            className={`hb-tab ${tab === "tree" ? "is-active" : ""}`}
            onClick={() => setTab("tree")}
          >
            {t("invite.tab.tree")}
          </button>
        </div>
      ) : null}

      {showMine ? (
        <div className="hb-stack">
          <Card title={t("invite.myCode")}>
            {!network ? (
              <p className="text-muted">{t("common.loading")}</p>
            ) : (
              <div className="hb-invite-code">
                <div className="hb-invite-code__display">
                  <code className="hb-invite-code__value">{network.me.invite_code}</code>
                </div>
                <div className="hb-invite-code__actions">
                  <Button type="button" onClick={() => void onCopyCode(network.me.invite_code)}>
                    {t("invite.copyCode")}
                  </Button>
                  {copyHint ? <span className="text-muted">{copyHint}</span> : null}
                </div>
                <p className="hb-invite-stats">
                  {t("invite.stats", {
                    direct: network.downlines.length,
                    total: network.total_downline_count,
                  })}
                </p>
                {network.rewards ? (
                  <p className="text-muted" style={{ marginTop: "0.75rem" }}>
                    {t("invite.rewards.summary", {
                      total: network.rewards.total_points,
                      month: network.rewards.month_points,
                      cap: network.rewards.month_cap,
                    })}
                  </p>
                ) : null}
              </div>
            )}
          </Card>

          {network?.rewards && network.rewards.recent.length > 0 ? (
            <Card title={t("invite.rewards.title")}>
              <ul className="hb-compact-list">
                {network.rewards.recent.map((r) => (
                  <li key={r.id} className="hb-compact-row">
                    <div className="hb-compact-row__body">
                      <strong>+{r.points}</strong>
                      <span className="text-muted">
                        {" "}
                        · {r.plan_id} · {r.status}
                      </span>
                    </div>
                  </li>
                ))}
              </ul>
            </Card>
          ) : null}

          <Card title={t("invite.upline")}>
            {!network ? (
              <p className="text-muted">{t("common.loading")}</p>
            ) : network.upline ? (
              <div className="hb-stack hb-stack--tight">
                <p className="hb-invite-simple">
                  <strong>{network.upline.display_name}</strong>
                  <span className="text-muted"> · {network.upline.invite_code}</span>
                </p>
                <p className="text-muted hb-invite-claim-note">{t("invite.claimLocked")}</p>
              </div>
            ) : (
              <form className="hb-invite-claim" onSubmit={(ev) => void onClaimInvite(ev)}>
                <p className="text-muted hb-invite-claim__hint">{t("invite.claimHint")}</p>
                <Input
                  label={t("invite.claimLabel")}
                  value={inviteCodeDraft}
                  onChange={(e) => setInviteCodeDraft(e.target.value)}
                  placeholder={t("invite.claimPh")}
                  autoComplete="off"
                  spellCheck={false}
                />
                <div className="hb-invite-claim__actions">
                  <Button type="submit" disabled={claimBusy || !inviteCodeDraft.trim()}>
                    {claimBusy ? t("common.loading") : t("invite.claimSubmit")}
                  </Button>
                </div>
              </form>
            )}
          </Card>

          <Card title={t("invite.downlines", { n: network?.downlines.length ?? 0 })}>
            {!network ? (
              <p className="text-muted">{t("common.loading")}</p>
            ) : network.downlines.length === 0 ? (
              <p className="text-muted">{t("invite.noDownline")}</p>
            ) : (
              <ul className="hb-invite-plain">
                {network.downlines.map((d) => (
                  <li key={d.uid}>
                    <strong>{d.display_name}</strong>
                    <span className="text-muted">
                      {" "}
                      · {d.invite_code}
                      {d.downline_count > 0
                        ? ` · ${t("invite.directN", { n: d.downline_count })}`
                        : ""}
                      {` · ${formatLocalDateTime(d.joined_at)}`}
                    </span>
                  </li>
                ))}
              </ul>
            )}
          </Card>
        </div>
      ) : (
        <div className="hb-org">
          {/* 搜尋永遠第一：大樹靠找人，不靠展開 */}
          <div className="hb-org__bar">
            <Input
              value={treeQuery}
              onChange={(e) => setTreeQuery(e.target.value)}
              placeholder={t("invite.treeSearchPh")}
              autoComplete="off"
              aria-label={t("invite.treeSearch")}
            />
            <span className="text-muted hb-org__meta">
              {searching
                ? t("invite.treeMatchCount", { n: hits.length, total: forestSize })
                : t("invite.treeCount", { n: forestSize })}
            </span>
            {searching ? (
              <Button type="button" variant="ghost" onClick={() => setTreeQuery("")}>
                {t("invite.clearSearch")}
              </Button>
            ) : null}
          </div>

          {forest.length === 0 ? (
            <EmptyState title={t("invite.treeEmpty")} />
          ) : searching ? (
            /* —— 搜尋結果：扁平列表 + 路徑 —— */
            hits.length === 0 ? (
              <EmptyState title={t("invite.treeNoMatch")} description={t("invite.treeNoMatchHint")} />
            ) : (
              <ul className="hb-org-hits">
                {hits.map((n) => {
                  const crumb = pathToRoot(n.uid, byUid)
                    .map((p) => p.display_name)
                    .join(" › ");
                  return (
                    <li key={n.uid}>
                      <button
                        type="button"
                        className="hb-org-hits__row"
                        onClick={() => {
                          setFocusUid(n.uid);
                          setTreeQuery("");
                        }}
                      >
                        <span className="hb-org-hits__name">{n.display_name}</span>
                        <span className="hb-org-hits__code">{n.invite_code}</span>
                        <span className="hb-org-hits__path text-muted">{crumb}</span>
                      </button>
                    </li>
                  );
                })}
              </ul>
            )
          ) : !focus ? (
            /* —— 起點：各獨立根（無人邀請進來的）—— */
            <div className="hb-org-start">
              <p className="hb-org-start__hint text-muted">{t("invite.orgPickRoot")}</p>
              <div className="hb-org-chips">
                {roots.map((r) => (
                  <PersonChip
                    key={r.uid}
                    node={r}
                    onClick={() => setFocusUid(r.uid)}
                    sub={
                      r.total_downline_count > 0
                        ? t("invite.orgChainN", { n: r.total_downline_count })
                        : undefined
                    }
                  />
                ))}
              </div>
            </div>
          ) : (
            /* —— 焦點圖：上邀請人 · 中本人 · 下直邀 —— */
            <div className="hb-org-focus">
              <nav className="hb-org-crumb" aria-label={t("invite.orgPath")}>
                <button type="button" className="hb-org-crumb__home" onClick={() => setFocusUid(null)}>
                  {t("invite.orgRoots")}
                </button>
                {path.map((p) => (
                  <span key={p.uid} className="hb-org-crumb__seg">
                    <span className="hb-org-crumb__sep" aria-hidden>
                      ›
                    </span>
                    <button
                      type="button"
                      className={`hb-org-crumb__link${p.uid === focus.uid ? " is-current" : ""}`}
                      onClick={() => setFocusUid(p.uid)}
                    >
                      {p.display_name}
                    </button>
                  </span>
                ))}
              </nav>

              <div className="hb-org-graph">
                {/* 邀請人 */}
                <div className="hb-org-graph__layer">
                  {inviter ? (
                    <PersonChip
                      node={inviter}
                      dim
                      onClick={() => setFocusUid(inviter.uid)}
                      sub={t("invite.parentField")}
                    />
                  ) : (
                    <span className="hb-org-graph__none text-muted">{t("invite.root")}</span>
                  )}
                </div>

                <div className="hb-org-graph__edge" aria-hidden>
                  ↓
                </div>

                {/* 焦點 */}
                <div className="hb-org-graph__layer">
                  <PersonChip
                    node={focus}
                    active
                    sub={t("invite.orgFocusStats", {
                      direct: focus.downline_count,
                      total: focus.total_downline_count,
                    })}
                  />
                </div>

                {directs.length > 0 ? (
                  <>
                    <div className="hb-org-graph__edge" aria-hidden>
                      ↓
                    </div>
                    <div className="hb-org-graph__kids">
                      <p className="hb-org-graph__kids-label">
                        {t("invite.orgDirects", { n: directs.length })}
                      </p>
                      <div className="hb-org-chips">
                        {directs.map((c) => (
                          <PersonChip
                            key={c.uid}
                            node={c}
                            onClick={() => setFocusUid(c.uid)}
                            sub={
                              c.downline_count > 0
                                ? t("invite.directN", { n: c.downline_count })
                                : undefined
                            }
                          />
                        ))}
                      </div>
                    </div>
                  </>
                ) : (
                  <p className="hb-org-graph__empty text-muted">{t("invite.orgNoDirects")}</p>
                )}
              </div>

              {/* 調整歸屬：只在有焦點時 */}
              <div className="hb-org-reassign">
                <Select
                  label={t("invite.newParent")}
                  value={parentDraft}
                  onChange={(e) => setParentDraft(e.target.value)}
                >
                  <option value="">{t("invite.root")}</option>
                  {candidates.map((c) => (
                    <option key={c.uid} value={c.uid}>
                      {c.display_name} · {c.invite_code}
                    </option>
                  ))}
                </Select>
                <div className="hb-org-reassign__actions">
                  <Button type="button" disabled={busy} onClick={() => void onMove()}>
                    {busy ? t("common.loading") : t("invite.confirmMove")}
                  </Button>
                </div>
              </div>
            </div>
          )}
        </div>
      )}
    </>
  );
}
