"use client";

import {
  ChangeEvent,
  FormEvent,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from "react";
import {
  formatShanghaiDate as formatDate,
  formatShanghaiToday,
} from "../lib/date-utils";
import type { AuthUser } from "../lib/user-auth";
import UsersPage from "./users-page";

type Partner = {
  id: string;
  name: string;
  wecom_name: string;
  owner: string;
  claimed_total: number;
  completed_total: number;
};

type Task = {
  id: string;
  name: string;
  brand: string;
  quantity: number;
  claimed_quantity: number;
  due_at: string;
  status: string;
  source_url: string;
  source_sheet_id: string;
  source_sheet_name: string;
  source_synced_at: string | null;
  share_token: string;
  collection_start_date: string | null;
  collection_days: string;
  collection_schedule_updated_at: string | null;
};

type Account = {
  id: string;
  platform: string;
  platform_uid: string;
  public_account_id: string;
  nickname: string;
  profile_url: string;
  ip_location: string;
  followers: number;
  post_count: number;
  avg_views: number;
  first_seen_at: string;
  last_seen_at: string;
};

type Distribution = {
  id: string;
  task_id: string;
  content_id: string;
  partner_id: string;
  delegation_bundle_id: string | null;
  account_id: string | null;
  publish_url: string | null;
  publish_time: string | null;
  status: string;
  claimed_at: string;
  screenshot_key: string | null;
  ocr_status: string;
  exposure: number | null;
  views: number | null;
  d2_likes: number | null;
  d2_comments: number | null;
  d2_collects: number | null;
  d5_likes: number | null;
  d5_comments: number | null;
  d5_collects: number | null;
  d7_likes: number | null;
  d7_comments: number | null;
  d7_collects: number | null;
  latest_likes: number | null;
  latest_comments: number | null;
  latest_collects: number | null;
  collection_status: string;
  collection_status_description: string | null;
  collection_updated_at: string | null;
  last_collection_day: number | null;
  updated_at: string;
  content_title: string;
  partner_name: string;
  account_nickname: string | null;
  account_platform: string | null;
  task_name: string;
  task_brand: string;
  due_at: string;
};

type DashboardData = {
  partners: Partner[];
  tasks: Task[];
  accounts: Account[];
  distributions: Distribution[];
  portal_url: string;
};

type FeishuPreview = {
  sheetId: string;
  sheetName: string;
  syncedAt: string;
  rowCount: number;
  columns: string[];
  preview: Array<{
    sourceRow: number;
    title: string;
    body: string;
  }>;
};

type NavKey =
  | "overview"
  | "tasks"
  | "distributions"
  | "resources"
  | "recovery"
  | "users";

const NAV_ITEMS: Array<{ key: NavKey; label: string; mark: string }> = [
  { key: "overview", label: "工作台", mark: "⌂" },
  { key: "tasks", label: "内容任务", mark: "▤" },
  { key: "distributions", label: "内容分发", mark: "↗" },
  { key: "resources", label: "KOC资源", mark: "◎" },
  { key: "recovery", label: "数据回收", mark: "◫" },
  { key: "users", label: "用户管理", mark: "♙" },
];

const EMPTY_DATA: DashboardData = {
  partners: [],
  tasks: [],
  accounts: [],
  distributions: [],
  portal_url: "",
};

async function readApiResponse<T>(response: Response, fallback: string) {
  const text = await response.text();
  const contentType = response.headers.get("content-type") ?? "";
  if (!contentType.includes("json")) {
    if (response.status === 403) {
      throw new Error("请求被安全网关拦截，请刷新页面后重试");
    }
    throw new Error(`${fallback}（服务返回异常，HTTP ${response.status}）`);
  }
  try {
    return JSON.parse(text) as T & { error?: string };
  } catch {
    throw new Error(`${fallback}（服务返回的数据无法解析）`);
  }
}

function formatNumber(value: number | null | undefined) {
  if (value === null || value === undefined) return "—";
  if (value >= 10000) return `${(value / 10000).toFixed(value >= 100000 ? 0 : 1)}万`;
  return new Intl.NumberFormat("zh-CN").format(value);
}

function statusLabel(status: string) {
  const labels: Record<string, string> = {
    claimed: "待发布",
    published: "已发布",
    collecting: "采集中",
    complete: "已完成",
    active: "分发中",
    closed: "已结束",
  };
  return labels[status] || status;
}

function latestPublicMetrics(distribution: Distribution) {
  const legacyDay =
    distribution.d7_likes !== null
      ? 7
      : distribution.d5_likes !== null
        ? 5
        : distribution.d2_likes !== null
          ? 2
          : null;
  const likes =
    distribution.latest_likes ??
    (legacyDay ? distribution[`d${legacyDay}_likes`] : null);
  const comments =
    distribution.latest_comments ??
    (legacyDay ? distribution[`d${legacyDay}_comments`] : null);
  const collects =
    distribution.latest_collects ??
    (legacyDay ? distribution[`d${legacyDay}_collects`] : null);
  return {
    likes,
    comments,
    collects,
    total:
      likes === null
        ? null
        : likes + (comments ?? 0) + (collects ?? 0),
  };
}

function hasCreatorMetrics(distribution: Distribution) {
  return distribution.exposure !== null && distribution.views !== null;
}

function parseCollectionDays(value: string | null | undefined) {
  try {
    const parsed = JSON.parse(value || "[]") as unknown;
    if (!Array.isArray(parsed)) return [];
    return [...new Set(parsed.map(Number))]
      .filter((day) => Number.isInteger(day) && day >= 1 && day <= 7)
      .sort((a, b) => a - b);
  } catch {
    return [];
  }
}

function todayInputValue() {
  const parts = new Intl.DateTimeFormat("zh-CN", {
    timeZone: "Asia/Shanghai",
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  }).formatToParts(new Date());
  const value = Object.fromEntries(
    parts.map((part) => [part.type, part.value]),
  );
  return `${value.year}-${value.month}-${value.day}`;
}

function addCalendarDays(value: string, days: number) {
  const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!match) return "";
  const date = new Date(
    Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]) + days),
  );
  return date.toISOString().slice(0, 10);
}

function xhsPublishUrl(value: string | null) {
  if (!value) return null;
  try {
    const url = new URL(value);
    const hostname = url.hostname.toLowerCase();
    const isXhsHost =
      hostname === "xiaohongshu.com" ||
      hostname.endsWith(".xiaohongshu.com") ||
      hostname === "xhslink.com" ||
      hostname.endsWith(".xhslink.com");
    return ["http:", "https:"].includes(url.protocol) && isXhsHost
      ? url.toString()
      : null;
  } catch {
    return null;
  }
}

function collectionStatusLabel(status: string, hasMetrics: boolean) {
  if (hasMetrics && (!status || status === "pending")) return "采集成功";
  const labels: Record<string, string> = {
    pending: "待设置",
    scheduled: "已计划",
    collecting: "采集中",
    success: "采集成功",
    failed: "采集失败",
  };
  return labels[status] || status || "待设置";
}

function avatarColor(value: string) {
  const colors = ["coral", "cyan", "violet", "amber", "mint"];
  const index = [...value].reduce((sum, char) => sum + char.charCodeAt(0), 0);
  return colors[index % colors.length];
}

async function compressScreenshot(file: File) {
  if (file.size < 850_000 || !file.type.startsWith("image/")) return file;
  const bitmap = await createImageBitmap(file);
  const maxEdge = 1600;
  const scale = Math.min(1, maxEdge / Math.max(bitmap.width, bitmap.height));
  const canvas = document.createElement("canvas");
  canvas.width = Math.max(1, Math.round(bitmap.width * scale));
  canvas.height = Math.max(1, Math.round(bitmap.height * scale));
  const context = canvas.getContext("2d");
  if (!context) {
    bitmap.close();
    return file;
  }
  context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  bitmap.close();
  const blob = await new Promise<Blob | null>((resolve) =>
    canvas.toBlob(resolve, "image/jpeg", 0.82),
  );
  if (!blob) return file;
  return new File([blob], `${file.name.replace(/\.[^.]+$/, "")}.jpg`, {
    type: "image/jpeg",
  });
}

export default function Home({ currentUser }: { currentUser: AuthUser }) {
  const [data, setData] = useState<DashboardData>(EMPTY_DATA);
  const [activeNav, setActiveNav] = useState<NavKey>("overview");
  const [loading, setLoading] = useState(true);
  const [working, setWorking] = useState(false);
  const [error, setError] = useState("");
  const [toast, setToast] = useState("");
  const [menuOpen, setMenuOpen] = useState(false);
  const [taskModalOpen, setTaskModalOpen] = useState(false);
  const [metricModal, setMetricModal] = useState<Distribution | null>(null);
  const [taskForm, setTaskForm] = useState({
    name: "",
    brand: "",
    dueAt: "2026-08-12",
    feishuUrl: "",
  });
  const [sourcePreview, setSourcePreview] = useState<FeishuPreview | null>(null);
  const [sourceWorking, setSourceWorking] = useState(false);
  const [metricForm, setMetricForm] = useState({ exposure: "", views: "" });
  const [exportingTaskId, setExportingTaskId] = useState<string | null>(null);
  const [exportingResources, setExportingResources] = useState(false);
  const isManager = currentUser.role === "super_admin" || currentUser.role === "admin";
  const visibleNavItems = NAV_ITEMS.filter((item) => {
    if (item.key === "resources" && currentUser.role === "user") return false;
    if (item.key === "users" && !isManager) return false;
    return true;
  });

  const loadData = useCallback(async () => {
    try {
      setLoading(true);
      const response = await fetch("/api/bootstrap", { cache: "no-store" });
      const result = await readApiResponse<DashboardData>(response, "加载失败");
      if (!response.ok) throw new Error(result.error || "加载失败");
      setData(result);
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "加载失败");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    const timer = window.setTimeout(() => {
      void loadData();
    }, 0);
    return () => window.clearTimeout(timer);
  }, [loadData]);

  useEffect(() => {
    if (!toast) return;
    const timer = window.setTimeout(() => setToast(""), 2600);
    return () => window.clearTimeout(timer);
  }, [toast]);

  const runAction = async (
    payload: Record<string, unknown>,
    successMessage: string,
  ) => {
    try {
      setWorking(true);
      const response = await fetch("/api/action", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      const result = await readApiResponse<DashboardData>(response, "操作失败");
      if (!response.ok) throw new Error(result.error || "操作失败");
      setData(result);
      setToast(successMessage);
      return true;
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "操作失败");
      return false;
    } finally {
      setWorking(false);
    }
  };

  const stats = useMemo(() => {
    const published = data.distributions.filter((item) =>
      ["published", "collecting", "complete"].includes(item.status),
    ).length;
    const completed = data.distributions.filter(
      (item) => item.status === "complete",
    ).length;
    const pendingRecovery = data.distributions.filter(
      (item) =>
        item.publish_url &&
        (latestPublicMetrics(item).likes === null ||
          !hasCreatorMetrics(item)),
    ).length;
    return {
      resources: data.accounts.length,
      activeTasks: data.tasks.filter((task) => task.status === "active").length,
      published,
      completed,
      pendingRecovery,
    };
  }, [data]);

  const recentDistributions = data.distributions.slice(0, 6);

  const navigate = (key: NavKey) => {
    if (key === "resources" && currentUser.role === "user") return;
    if (key === "users" && !isManager) return;
    setActiveNav(key);
    setMenuOpen(false);
  };

  const submitTask = async (event: FormEvent) => {
    event.preventDefault();
    if (!sourcePreview) {
      setToast("请先读取飞书表格");
      return;
    }
    const success = await runAction(
      { action: "create_task", ...taskForm },
      `任务已创建，${sourcePreview.rowCount} 篇内容进入可领取池`,
    );
    if (success) {
      setTaskModalOpen(false);
      setSourcePreview(null);
      setTaskForm({
        name: "",
        brand: "",
        dueAt: "2026-08-12",
        feishuUrl: "",
      });
    }
  };

  const inspectFeishu = async () => {
    if (!taskForm.feishuUrl.trim()) {
      setToast("请先粘贴飞书表格链接");
      return;
    }
    try {
      setSourceWorking(true);
      const response = await fetch("/api/action", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "inspect_feishu",
          feishuUrl: taskForm.feishuUrl,
        }),
      });
      const result = await readApiResponse<FeishuPreview>(
        response,
        "读取飞书表格失败",
      );
      if (!response.ok) throw new Error(result.error || "读取飞书表格失败");
      setSourcePreview(result);
      setTaskForm((current) => ({
        ...current,
        name: current.name || result.sheetName.replace(/500篇$/, "分发任务"),
      }));
      setToast(`已读取 ${result.rowCount} 篇有效内容`);
    } catch (reason) {
      setSourcePreview(null);
      setToast(reason instanceof Error ? reason.message : "读取飞书表格失败");
    } finally {
      setSourceWorking(false);
    }
  };

  const copyTaskShareLink = async (task: Task) => {
    if (!data.portal_url || !task.share_token) {
      setToast("KOC外部站点尚未发布");
      return;
    }
    const url = `${data.portal_url.replace(/\/$/, "")}/?task=${task.share_token}`;
    try {
      await navigator.clipboard.writeText(url);
      setToast("KOC领取链接已复制");
    } catch {
      window.prompt("复制KOC领取链接", url);
    }
  };

  const collectMetrics = async (distribution: Distribution) => {
    await runAction(
      { action: "collect_now", distributionId: distribution.id },
      "公开数据已更新",
    );
  };

  const retryFailedMetrics = async (taskId: string, failedCount: number) => {
    if (failedCount === 0) {
      setToast("当前没有采集异常的笔记");
      return;
    }
    await runAction(
      { action: "retry_failed_collections", taskId },
      `已完成 ${failedCount} 篇异常笔记的一键补采`,
    );
  };

  const saveCollectionSchedule = async (
    taskId: string,
    startDate: string,
    days: number[],
  ) => {
    return runAction(
      {
        action: "save_collection_schedule",
        taskId,
        startDate,
        days,
      },
      `采集任务已创建，将在所选日期10:00自动执行`,
    );
  };

  const exportRecoveryData = async (task: Task) => {
    try {
      setExportingTaskId(task.id);
      const response = await fetch(
        `/api/recovery-export?task=${encodeURIComponent(task.id)}`,
        { cache: "no-store" },
      );
      if (!response.ok) {
        const result = await readApiResponse<{ error?: string }>(
          response,
          "导出失败",
        );
        throw new Error(result.error || "导出失败");
      }
      const blob = await response.blob();
      const objectUrl = URL.createObjectURL(blob);
      const anchor = document.createElement("a");
      anchor.href = objectUrl;
      anchor.download = `${task.name}-数据回收.xlsx`;
      document.body.appendChild(anchor);
      anchor.click();
      anchor.remove();
      window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
      setToast("Excel 已导出，图片和截图已嵌入表格");
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "导出失败");
    } finally {
      setExportingTaskId(null);
    }
  };

  const exportResources = async (accountIds: string[]) => {
    if (accountIds.length === 0) {
      setToast("当前筛选结果为空");
      return;
    }
    try {
      setExportingResources(true);
      const response = await fetch("/api/resources-export", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ accountIds }),
      });
      if (!response.ok) {
        const result = await readApiResponse<{ error?: string }>(
          response,
          "导出失败",
        );
        throw new Error(result.error || "导出失败");
      }
      const blob = await response.blob();
      const objectUrl = URL.createObjectURL(blob);
      const anchor = document.createElement("a");
      anchor.href = objectUrl;
      anchor.download = `KOC资源库-${todayInputValue().replaceAll("-", "")}.xlsx`;
      document.body.appendChild(anchor);
      anchor.click();
      anchor.remove();
      window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
      setToast(`已导出 ${accountIds.length} 个KOC账号`);
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "导出失败");
    } finally {
      setExportingResources(false);
    }
  };

  const uploadScreenshot = async (
    distribution: Distribution,
    event: ChangeEvent<HTMLInputElement>,
  ) => {
    const file = event.target.files?.[0];
    if (!file) return;
    try {
      setWorking(true);
      const uploadFile = await compressScreenshot(file);
      const form = new FormData();
      form.append("distributionId", distribution.id);
      form.append("file", uploadFile);
      const response = await fetch("/api/upload", { method: "POST", body: form });
      const result = await readApiResponse<DashboardData>(response, "上传失败");
      if (!response.ok) throw new Error(result.error || "上传失败");
      setData(result);
      setToast("创作者中心截图已收回");
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "上传失败");
    } finally {
      setWorking(false);
      event.target.value = "";
    }
  };

  const submitManualMetrics = async (event: FormEvent) => {
    event.preventDefault();
    if (!metricModal) return;
    const success = await runAction(
      {
        action: "manual_metrics",
        distributionId: metricModal.id,
        exposure: metricForm.exposure,
        views: metricForm.views,
      },
      "创作者中心数据已确认",
    );
    if (success) {
      setMetricModal(null);
      setMetricForm({ exposure: "", views: "" });
    }
  };

  const openMetricFallback = (distribution: Distribution) => {
    setMetricModal(distribution);
    setMetricForm({
      exposure: distribution.exposure?.toString() || "",
      views: distribution.views?.toString() || "",
    });
  };

  const pageTitle: Record<NavKey, { title: string; subtitle: string }> = {
    overview: {
      title: "分发工作台",
      subtitle: "用最少动作，跑完内容领取、发布与数据回收。",
    },
    tasks: {
      title: "内容任务",
      subtitle: "从飞书同步内容，按数量开放给合作方领取。",
    },
    distributions: {
      title: "内容分发",
      subtitle: "先按任务查看进度，再进入单个任务处理每篇笔记。",
    },
    resources: {
      title: "KOC资源",
      subtitle: "只沉淀真实发布过的账号，不要求提前登记。",
    },
    recovery: {
      title: "数据回收",
      subtitle: "按任务设置自动采集日，集中查看最新公开数据与创作者截图。",
    },
    users: {
      title: "用户管理",
      subtitle: "由后台统一创建账号和重置密码，不开放自助注册与个人改密。",
    },
  };

  const logout = async () => {
    await fetch("/api/auth/logout", { method: "POST" }).catch(() => null);
    window.location.assign("/login");
  };

  return (
    <div className="app-shell">
      <aside className={`sidebar ${menuOpen ? "open" : ""}`}>
        <div className="brand-lockup">
          <div className="brand-symbol"><span>K</span></div>
          <div>
            <strong>KOC LOOP</strong>
            <span>内容分发闭环</span>
          </div>
        </div>

        <nav className="main-nav" aria-label="主导航">
          <p className="nav-caption">运营中心</p>
          {visibleNavItems.map((item) => (
            <button
              key={item.key}
              className={activeNav === item.key ? "active" : ""}
              onClick={() => navigate(item.key)}
            >
              <span className="nav-mark">{item.mark}</span>
              {item.label}
              {item.key === "recovery" && stats.pendingRecovery > 0 && (
                <span className="nav-badge">{stats.pendingRecovery}</span>
              )}
            </button>
          ))}
        </nav>

        <div className="sidebar-summary">
          <div className="pulse-dot" />
          <div>
            <strong>闭环运行中</strong>
            <span>{stats.activeTasks} 个任务正在分发</span>
          </div>
        </div>
        <button className="sidebar-user" onClick={() => void logout()} title="退出登录">
          <div className="avatar mint">{currentUser.username.slice(0, 1).toUpperCase()}</div>
          <div>
            <strong>{currentUser.username}</strong>
            <span>{currentUser.role === "super_admin" ? "超级管理员" : currentUser.role === "admin" ? "管理员" : "普通用户"}</span>
          </div>
          <span className="chevron">退出</span>
        </button>
      </aside>

      {menuOpen && <button className="sidebar-scrim" onClick={() => setMenuOpen(false)} aria-label="关闭菜单" />}

      <main className="main-area">
        <header className="topbar">
          <button className="menu-button" onClick={() => setMenuOpen(true)} aria-label="打开菜单">
            ☰
          </button>
          <div className="breadcrumb">
            <span>KOC LOOP</span>
            <i>/</i>
            <strong>{pageTitle[activeNav].title}</strong>
          </div>
          <div className="top-actions">
            <span className="sync-state"><i />飞书内容表已接入</span>
            <button className="ghost-button" onClick={() => setActiveNav("tasks")}>
              获取KOC领取链接
            </button>
            <button className="primary-button" onClick={() => setTaskModalOpen(true)}>
              <span>＋</span> 新建任务
            </button>
          </div>
        </header>

        <section className="content-frame">
          <div className="page-heading">
            <div>
              <p className="eyebrow">MVP · 核心闭环</p>
              <h1>{pageTitle[activeNav].title}</h1>
              <p>{pageTitle[activeNav].subtitle}</p>
            </div>
            {activeNav === "overview" && (
              <div className="date-chip">
                <span>今天</span>
                <strong>{formatShanghaiToday()}</strong>
              </div>
            )}
          </div>

          {loading ? (
            <LoadingState />
          ) : error ? (
            <div className="empty-state">
              <div className="empty-icon">!</div>
              <h3>暂时无法打开工作台</h3>
              <p>{error}</p>
              <button className="primary-button" onClick={() => void loadData()}>
                重新加载
              </button>
            </div>
          ) : (
            <>
              {activeNav === "overview" && (
                <OverviewPage
                  stats={stats}
                  distributions={recentDistributions}
                  tasks={data.tasks}
                  onNavigate={navigate}
                  showResources={currentUser.role !== "user"}
                />
              )}
              {activeNav === "tasks" && (
                <TasksPage
                  tasks={data.tasks}
                  portalUrl={data.portal_url}
                  onCreate={() => setTaskModalOpen(true)}
                  onCopyShare={copyTaskShareLink}
                />
              )}
              {activeNav === "distributions" && (
                <DistributionPage
                  tasks={data.tasks}
                  distributions={data.distributions}
                />
              )}
              {activeNav === "resources" && (
                <ResourcesPage
                  accounts={data.accounts}
                  distributions={data.distributions}
                  exporting={exportingResources}
                  onExport={exportResources}
                />
              )}
              {activeNav === "users" && isManager && (
                <UsersPage currentUser={currentUser} />
              )}
              {activeNav === "recovery" && (
                <RecoveryPage
                  tasks={data.tasks}
                  distributions={data.distributions.filter((item) => item.publish_url)}
                  working={working}
                  onCollect={collectMetrics}
                  onSaveSchedule={saveCollectionSchedule}
                  onUpload={uploadScreenshot}
                  onFallback={openMetricFallback}
                  onRetryFailed={retryFailedMetrics}
                  onExport={exportRecoveryData}
                  exportingTaskId={exportingTaskId}
                />
              )}
            </>
          )}
        </section>
      </main>

      {taskModalOpen && (
        <div className="modal-backdrop" role="presentation" onMouseDown={() => setTaskModalOpen(false)}>
          <form className="modal-card" onSubmit={submitTask} onMouseDown={(event) => event.stopPropagation()}>
            <div className="modal-heading">
              <div>
                <p className="eyebrow">飞书链接导入</p>
                <h2>新建分发任务</h2>
              </div>
              <button type="button" onClick={() => setTaskModalOpen(false)} aria-label="关闭">×</button>
            </div>
            <label>
              <span>飞书内容表链接</span>
              <div className="source-url-row">
                <input
                  value={taskForm.feishuUrl}
                  onChange={(event) => {
                    setTaskForm({ ...taskForm, feishuUrl: event.target.value });
                    setSourcePreview(null);
                  }}
                  placeholder="粘贴飞书 Wiki / Sheets 链接"
                  required
                />
                <button
                  type="button"
                  className="ghost-button source-read-button"
                  disabled={sourceWorking || !taskForm.feishuUrl.trim()}
                  onClick={() => void inspectFeishu()}
                >
                  {sourceWorking ? "读取中…" : "读取表格"}
                </button>
              </div>
              <small>自动读取“标题、正文/笔记内容、标签和全部配图”，空标题行不会进入任务。</small>
            </label>
            {sourcePreview && (
              <div className="source-preview">
                <div className="source-preview-head">
                  <div>
                    <span className="source-check">✓</span>
                    <div>
                      <strong>{sourcePreview.sheetName}</strong>
                      <small>工作表 {sourcePreview.sheetId} · 已匹配 {sourcePreview.columns.length} 个字段</small>
                    </div>
                  </div>
                  <b>{sourcePreview.rowCount} 篇</b>
                </div>
                <div className="source-columns">
                  {sourcePreview.columns.map((column) => <span key={column}>{column}</span>)}
                </div>
                <div className="source-samples">
                  {sourcePreview.preview.map((row) => (
                    <div key={row.sourceRow}>
                      <span>{row.sourceRow}</span>
                      <strong>{row.title}</strong>
                    </div>
                  ))}
                </div>
              </div>
            )}
            <div className="form-grid two">
              <label>
                <span>任务名称</span>
                <input
                  value={taskForm.name}
                  onChange={(event) => setTaskForm({ ...taskForm, name: event.target.value })}
                  placeholder="例如：夏日轻盈计划"
                  required
                />
              </label>
              <label>
                <span>品牌/项目</span>
                <input
                  value={taskForm.brand}
                  onChange={(event) => setTaskForm({ ...taskForm, brand: event.target.value })}
                  placeholder="例如：青柠实验室"
                  required
                />
              </label>
            </div>
            <label>
              <span>发布截止日期</span>
              <input
                type="date"
                value={taskForm.dueAt}
                onChange={(event) => setTaskForm({ ...taskForm, dueAt: event.target.value })}
                required
              />
            </label>
            <div className="modal-actions">
              <button type="button" className="ghost-button" onClick={() => setTaskModalOpen(false)}>取消</button>
              <button className="primary-button" disabled={working || !sourcePreview}>{working ? "创建中…" : "创建并开放领取"}</button>
            </div>
          </form>
        </div>
      )}

      {metricModal && (
        <div className="modal-backdrop" role="presentation" onMouseDown={() => setMetricModal(null)}>
          <form className="modal-card compact" onSubmit={submitManualMetrics} onMouseDown={(event) => event.stopPropagation()}>
            <div className="modal-heading">
              <div>
                <p className="eyebrow">OCR异常兜底</p>
                <h2>确认创作者数据</h2>
              </div>
              <button type="button" onClick={() => setMetricModal(null)} aria-label="关闭">×</button>
            </div>
            <div className="context-strip">
              <strong>{metricModal.account_nickname || "待识别账号"}</strong>
              <span>{metricModal.content_title}</span>
            </div>
            <div className="form-grid two">
              <label>
                <span>曝光量</span>
                <input type="number" min="0" value={metricForm.exposure} onChange={(event) => setMetricForm({ ...metricForm, exposure: event.target.value })} required />
              </label>
              <label>
                <span>阅读量</span>
                <input type="number" min="0" value={metricForm.views} onChange={(event) => setMetricForm({ ...metricForm, views: event.target.value })} required />
              </label>
            </div>
            <div className="modal-actions">
              <button type="button" className="ghost-button" onClick={() => setMetricModal(null)}>取消</button>
              <button className="primary-button" disabled={working}>确认数据</button>
            </div>
          </form>
        </div>
      )}

      {toast && <div className="toast"><span>✓</span>{toast}</div>}
      {working && <div className="working-bar" />}
    </div>
  );
}

function LoadingState() {
  return (
    <div className="loading-layout" aria-label="正在加载">
      <div className="loading-card wide" />
      <div className="loading-card" />
      <div className="loading-card" />
      <div className="loading-table" />
    </div>
  );
}

function OverviewPage({
  stats,
  distributions,
  tasks,
  onNavigate,
  showResources,
}: {
  stats: {
    resources: number;
    activeTasks: number;
    published: number;
    completed: number;
    pendingRecovery: number;
  };
  distributions: Distribution[];
  tasks: Task[];
  onNavigate: (key: NavKey) => void;
  showResources: boolean;
}) {
  const cards = [
    {
      label: "已沉淀账号资源",
      value: stats.resources,
      hint: "均来自真实发布",
      tone: "ink",
    },
    {
      label: "正在分发任务",
      value: stats.activeTasks,
      hint: `${tasks.reduce((sum, task) => sum + task.claimed_quantity, 0)} 篇已被领取`,
      tone: "green",
    },
    {
      label: "已回填发布",
      value: stats.published,
      hint: "链接自动识别账号",
      tone: "blue",
    },
    {
      label: "待完成数据回收",
      value: stats.pendingRecovery,
      hint: stats.pendingRecovery > 0 ? "需要跟进" : "全部按时完成",
      tone: "orange",
    },
  ].filter((card) => showResources || card.label !== "已沉淀账号资源");
  return (
    <>
      <div className="stat-grid">
        {cards.map((card) => (
          <article className="stat-card" key={card.label}>
            <div className={`stat-icon ${card.tone}`}><span /></div>
            <p>{card.label}</p>
            <div className="stat-value">
              <strong>{card.value}</strong>
              <span>个</span>
            </div>
            <small>{card.hint}</small>
          </article>
        ))}
      </div>

      <div className="overview-grid">
        <section className="panel workflow-panel">
          <div className="panel-heading">
            <div>
              <h2>当前闭环</h2>
              <p>每篇内容只沿着一条记录向前走</p>
            </div>
            <button className="text-button" onClick={() => onNavigate("distributions")}>查看全部</button>
          </div>
          <div className="workflow-track">
            <div className="workflow-step done">
              <span>01</span>
              <div><strong>内容已同步</strong><small>{tasks.reduce((sum, task) => sum + task.quantity, 0)} 篇</small></div>
            </div>
            <i />
            <div className="workflow-step active">
              <span>02</span>
              <div><strong>KOC领取</strong><small>{tasks.reduce((sum, task) => sum + task.claimed_quantity, 0)} 篇</small></div>
            </div>
            <i />
            <div className="workflow-step">
              <span>03</span>
              <div><strong>发布回填</strong><small>{stats.published} 篇</small></div>
            </div>
            <i />
            <div className="workflow-step">
              <span>04</span>
              <div><strong>数据回收</strong><small>{stats.completed} 篇完成</small></div>
            </div>
          </div>
          <div className="focus-callout">
            <div className="callout-ring"><strong>{stats.pendingRecovery}</strong><span>待回收</span></div>
            <div>
              <p className="eyebrow">今天最需要推进</p>
              <h3>把已发布笔记的数据收回来</h3>
              <p>按任务选择采集日期，系统在当天10:00更新点赞、收藏、评论和总互动。</p>
            </div>
            <button className="primary-button soft" onClick={() => onNavigate("recovery")}>进入数据回收</button>
          </div>
        </section>

        <section className="panel task-mini-panel">
          <div className="panel-heading">
            <div><h2>进行中的任务</h2><p>按截止时间排序</p></div>
          </div>
          <div className="task-mini-list">
            {tasks.map((task) => {
              const progress = Math.round((task.claimed_quantity / task.quantity) * 100);
              return (
                <article key={task.id}>
                  <div className="task-mini-top">
                    <div className={`avatar ${avatarColor(task.name)}`}>{task.brand.slice(0, 1)}</div>
                    <div><strong>{task.name}</strong><span>{task.brand}</span></div>
                    <b>{progress}%</b>
                  </div>
                  <div className="progress"><span style={{ width: `${progress}%` }} /></div>
                  <div className="task-mini-meta">
                    <span>{task.claimed_quantity}/{task.quantity} 篇已领取</span>
                    <span>{formatDate(task.due_at)}截止</span>
                  </div>
                </article>
              );
            })}
          </div>
          <button className="full-ghost-button" onClick={() => onNavigate("tasks")}>管理全部任务</button>
        </section>
      </div>

      <section className="panel table-panel">
        <div className="panel-heading">
          <div><h2>最近分发</h2><p>领取、发布账号与回收状态一眼看清</p></div>
          <button className="text-button" onClick={() => onNavigate("distributions")}>查看全部</button>
        </div>
        <DistributionTable distributions={distributions} compact />
      </section>
    </>
  );
}

function TasksPage({
  tasks,
  portalUrl,
  onCreate,
  onCopyShare,
}: {
  tasks: Task[];
  portalUrl: string;
  onCreate: () => void;
  onCopyShare: (task: Task) => void;
}) {
  return (
    <div className="stack">
      <section className="task-hero">
        <div>
          <p className="eyebrow">内容从飞书来</p>
          <h2>一行内容，一份可领取笔记</h2>
          <p>不在这里重复编辑正文。这里只控制任务数量、领取进度和截止时间。</p>
        </div>
        <button className="primary-button light" onClick={onCreate}>＋ 新建任务</button>
      </section>
      <section className="panel table-panel">
        <div className="panel-heading">
          <div><h2>任务列表</h2><p>{tasks.length} 个任务</p></div>
        </div>
        <div className="task-list">
          {tasks.map((task) => {
            const progress = Math.round((task.claimed_quantity / task.quantity) * 100);
            return (
              <article className="task-row" key={task.id}>
                <div className={`avatar large ${avatarColor(task.name)}`}>{task.brand.slice(0, 1)}</div>
                <div className="task-row-main">
                  <div><h3>{task.name}</h3><span>{task.brand}</span></div>
                  <small>{task.source_sheet_name ? `飞书 · ${task.source_sheet_name}` : "历史示例内容表"}</small>
                  <div className="progress"><span style={{ width: `${progress}%` }} /></div>
                </div>
                <div className="task-number"><strong>{task.claimed_quantity}</strong><span>/ {task.quantity} 已领取</span></div>
                <div className="task-date"><span>截止日期</span><strong>{formatDate(task.due_at)}</strong></div>
                <span className={`status-pill ${task.status}`}>{statusLabel(task.status)}</span>
                <div className="task-share-actions">
                  <button
                    className="share-link-button"
                    onClick={() => onCopyShare(task)}
                    disabled={!portalUrl || !task.share_token}
                  >
                    复制领取链接
                  </button>
                  {portalUrl && task.share_token && (
                    <a
                      className="round-arrow"
                      href={`${portalUrl.replace(/\/$/, "")}/?task=${task.share_token}`}
                      target="_blank"
                      rel="noreferrer"
                      aria-label="打开外部KOC领取页"
                    >
                      ↗
                    </a>
                  )}
                </div>
              </article>
            );
          })}
        </div>
      </section>
    </div>
  );
}

function TaskScopeList({
  tasks,
  distributions,
  mode,
  onOpen,
}: {
  tasks: Task[];
  distributions: Distribution[];
  mode: "distribution" | "recovery";
  onOpen: (taskId: string) => void;
}) {
  return (
    <section className="task-scope-grid">
      {tasks.map((task) => {
        const taskDistributions = distributions.filter(
          (item) => item.task_id === task.id,
        );
        const published = taskDistributions.filter((item) => item.publish_url);
        const publicDataComplete = published.filter(
          (item) => latestPublicMetrics(item).likes !== null,
        ).length;
        const creatorComplete = published.filter(
          (item) => hasCreatorMetrics(item),
        ).length;
        const progress = task.quantity
          ? Math.round((task.claimed_quantity / task.quantity) * 100)
          : 0;
        return (
          <article className="task-scope-card" key={task.id}>
            <div className="task-scope-head">
              <div className={`avatar large ${avatarColor(task.name)}`}>
                {task.brand.slice(0, 1)}
              </div>
              <div>
                <h3>{task.name}</h3>
                <span>{task.brand} · {formatDate(task.due_at)} 截止</span>
              </div>
              <span className={`status-pill ${task.status}`}>{statusLabel(task.status)}</span>
            </div>
            <div className="task-source-line">
              <span>飞书来源</span>
              {task.source_url ? (
                <a href={task.source_url} target="_blank" rel="noreferrer">
                  {task.source_sheet_name || `工作表 ${task.source_sheet_id}`} ↗
                </a>
              ) : (
                <strong>历史示例内容表</strong>
              )}
            </div>
            {mode === "distribution" ? (
              <>
                <div className="task-scope-metrics">
                  <div><span>内容总量</span><strong>{task.quantity}</strong></div>
                  <div><span>已领取</span><strong>{task.claimed_quantity}</strong></div>
                  <div><span>已发布</span><strong>{published.length}</strong></div>
                </div>
                <div className="progress task-scope-progress">
                  <span style={{ width: `${progress}%` }} />
                </div>
              </>
            ) : (
              <div className="task-scope-metrics">
                <div><span>已发布</span><strong>{published.length}</strong></div>
                <div><span>公开数据</span><strong>{publicDataComplete}</strong></div>
                <div><span>创作者数据</span><strong>{creatorComplete}</strong></div>
              </div>
            )}
            <button className="task-scope-open" onClick={() => onOpen(task.id)}>
              <span>{mode === "distribution" ? "查看分发明细" : "进入回收队列"}</span>
              <b>→</b>
            </button>
          </article>
        );
      })}
    </section>
  );
}

function TaskDetailHeader({
  task,
  label,
  onBack,
}: {
  task: Task;
  label: string;
  onBack: () => void;
}) {
  return (
    <section className="task-detail-header">
      <button className="detail-back" onClick={onBack}>← 返回任务列表</button>
      <div className={`avatar large ${avatarColor(task.name)}`}>{task.brand.slice(0, 1)}</div>
      <div>
        <p>{label}</p>
        <h2>{task.name}</h2>
        <span>{task.brand} · {task.quantity} 篇 · {formatDate(task.due_at)} 截止</span>
      </div>
      {task.source_url && (
        <a href={task.source_url} target="_blank" rel="noreferrer">
          打开飞书原表 ↗
        </a>
      )}
    </section>
  );
}

function DistributionPage({
  tasks,
  distributions,
}: {
  tasks: Task[];
  distributions: Distribution[];
}) {
  const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
  const selectedTask = tasks.find((task) => task.id === selectedTaskId);
  if (!selectedTask) {
    return (
      <div className="stack">
        <div className="section-intro">
          <div><p className="eyebrow">按任务管理</p><h2>选择一个分发任务</h2></div>
          <p>每个任务独立查看领取、发布和账号识别进度，不混排不同项目的笔记。</p>
        </div>
        <TaskScopeList
          tasks={tasks}
          distributions={distributions}
          mode="distribution"
          onOpen={setSelectedTaskId}
        />
      </div>
    );
  }

  const taskDistributions = distributions.filter(
    (item) => item.task_id === selectedTask.id,
  );
  const published = taskDistributions.filter((item) => item.publish_url).length;
  return (
    <div className="stack">
      <TaskDetailHeader
        task={selectedTask}
        label="内容分发"
        onBack={() => setSelectedTaskId(null)}
      />
      <div className="inline-summary">
        <div><span>已领取</span><strong>{taskDistributions.length}</strong></div>
        <i />
        <div><span>已回填链接</span><strong>{published}</strong></div>
        <i />
        <div><span>待发布</span><strong>{taskDistributions.length - published}</strong></div>
        <div className="summary-note"><span className="pulse-dot" />当前仅展示“{selectedTask.name}”</div>
      </div>
      <section className="panel table-panel">
        <div className="panel-heading">
          <div><h2>内容分发表</h2><p>实际发布账号在链接回填后自动补齐</p></div>
          <div className="filter-chips"><button className="active">全部</button><button>待发布</button><button>采集中</button></div>
        </div>
        <DistributionTable distributions={taskDistributions} />
      </section>
    </div>
  );
}

function DistributionTable({
  distributions,
  compact = false,
}: {
  distributions: Distribution[];
  compact?: boolean;
}) {
  return (
    <div className="table-scroll">
      <table className="data-table">
        <thead>
          <tr>
            <th>内容</th>
            <th>领取合作方</th>
            <th>实际发布账号</th>
            <th>发布时间</th>
            <th>数据状态</th>
            <th>状态</th>
          </tr>
        </thead>
        <tbody>
          {distributions.map((item) => (
            <tr key={item.id}>
              <td>
                <div className="content-cell">
                  <span className="content-id">{item.content_id.split("-").at(-1)?.toUpperCase()}</span>
                  <div><strong>{item.content_title}</strong><span>{item.task_name} · {item.task_brand}</span></div>
                </div>
              </td>
              <td><strong className="plain-strong">{item.partner_name}</strong></td>
              <td>
                {item.account_nickname ? (
                  <div className="account-cell">
                    <div className={`avatar small ${avatarColor(item.account_nickname)}`}>{item.account_nickname.slice(0, 1)}</div>
                    <div><strong>{item.account_nickname}</strong><span>{item.account_platform}</span></div>
                  </div>
                ) : (
                  <span className="muted">回填链接后识别</span>
                )}
              </td>
              <td><span className="date-value">{formatDate(item.publish_time, true)}</span></td>
              <td>
                {item.d7_likes !== null ? (
                  <span className="metric-ready">D7 已回收</span>
                ) : item.d2_likes !== null ? (
                  <span className="metric-waiting">采集中</span>
                ) : (
                  <span className="muted">{item.publish_url ? "等待D2" : "尚未发布"}</span>
                )}
              </td>
              <td><span className={`status-pill ${item.status}`}>{statusLabel(item.status)}</span></td>
            </tr>
          ))}
        </tbody>
      </table>
      {!compact && distributions.length === 0 && <div className="table-empty">暂无分发记录</div>}
    </div>
  );
}

function ResourcesPage({
  accounts,
  distributions,
  exporting,
  onExport,
}: {
  accounts: Account[];
  distributions: Distribution[];
  exporting: boolean;
  onExport: (accountIds: string[]) => void;
}) {
  const [query, setQuery] = useState("");
  const [platformFilter, setPlatformFilter] = useState("全部平台");
  const [ipFilter, setIpFilter] = useState("");
  const [sourceFilter, setSourceFilter] = useState("");
  const resourceAccounts = useMemo(() => {
    const deliveriesByAccount = new Map<string, Distribution[]>();
    distributions.forEach((distribution) => {
      if (!distribution.account_id) return;
      const current = deliveriesByAccount.get(distribution.account_id) ?? [];
      current.push(distribution);
      deliveriesByAccount.set(distribution.account_id, current);
    });
    return accounts.map((account) => {
      const deliveries = deliveriesByAccount.get(account.id) ?? [];
      return {
        account,
        sources: [
          ...new Set(
            deliveries.map((item) => item.partner_name).filter(Boolean),
          ),
        ].sort((left, right) => left.localeCompare(right, "zh-CN")),
        partnerManagedOnly:
          deliveries.some((item) => item.delegation_bundle_id) &&
          deliveries.every((item) => item.delegation_bundle_id),
      };
    });
  }, [accounts, distributions]);
  const platformOptions = useMemo(
    () =>
      [...new Set(accounts.map((account) => account.platform).filter(Boolean))]
        .sort((left, right) => left.localeCompare(right, "zh-CN")),
    [accounts],
  );
  const ipOptions = useMemo(
    () =>
      [
        ...new Set(
          accounts.map((account) => account.ip_location || "待识别"),
        ),
      ].sort((left, right) => {
        if (left === "待识别") return 1;
        if (right === "待识别") return -1;
        return left.localeCompare(right, "zh-CN");
      }),
    [accounts],
  );
  const sourceOptions = useMemo(
    () =>
      [...new Set(resourceAccounts.flatMap((item) => item.sources))].sort(
        (left, right) => left.localeCompare(right, "zh-CN"),
      ),
    [resourceAccounts],
  );
  const filteredAccounts = useMemo(() => {
    const keyword = query.trim().toLocaleLowerCase("zh-CN");
    const ipKeyword = ipFilter.trim().toLocaleLowerCase("zh-CN");
    const sourceKeyword = sourceFilter.trim().toLocaleLowerCase("zh-CN");
    return resourceAccounts.filter(({ account, sources }) => {
      const searchable = `${account.nickname} ${account.public_account_id}`.toLocaleLowerCase(
        "zh-CN",
      );
      const ipLocation = (account.ip_location || "待识别").toLocaleLowerCase(
        "zh-CN",
      );
      return (
        (!keyword || searchable.includes(keyword)) &&
        (platformFilter === "全部平台" ||
          account.platform === platformFilter) &&
        (!ipKeyword || ipLocation.includes(ipKeyword)) &&
        (!sourceKeyword ||
          sources.some((source) =>
            source.toLocaleLowerCase("zh-CN").includes(sourceKeyword),
          ))
      );
    });
  }, [ipFilter, platformFilter, query, resourceAccounts, sourceFilter]);
  const hasActiveFilters =
    Boolean(query.trim()) ||
    platformFilter !== "全部平台" ||
    Boolean(ipFilter.trim()) ||
    Boolean(sourceFilter.trim());
  const clearFilters = () => {
    setQuery("");
    setPlatformFilter("全部平台");
    setIpFilter("");
    setSourceFilter("");
  };
  return (
    <div className="stack">
      <section className="resource-intro">
        <div className="resource-figure">
          <strong>{accounts.length}</strong>
          <span>个真实发布账号</span>
        </div>
        <div>
          <h2>资源从合作中自然长出来</h2>
          <p>不向KOC索要名单。发布链接第一次出现时建档，再次出现时更新合作次数与效果。</p>
        </div>
        <div className="resource-rule">
          <span>唯一去重规则</span>
          <strong>平台 + 账号主页</strong>
        </div>
      </section>
      <section className="panel table-panel">
        <div className="panel-heading">
          <div><h2>账号资源库</h2><p>只展示真实交付过的账号</p></div>
          <div className="filter-chips">
            <button
              className={platformFilter === "全部平台" ? "active" : ""}
              onClick={() => setPlatformFilter("全部平台")}
            >
              全部平台
            </button>
            {platformOptions.map((platform) => (
              <button
                className={platformFilter === platform ? "active" : ""}
                key={platform}
                onClick={() => setPlatformFilter(platform)}
              >
                {platform}
              </button>
            ))}
          </div>
        </div>
        <div className="resource-toolbar">
          <div className="resource-search">
            <span aria-hidden="true">⌕</span>
            <input
              aria-label="搜索账号名称或账号ID"
              onChange={(event) => setQuery(event.target.value)}
              placeholder="搜索账号名称 / 账号ID"
              type="search"
              value={query}
            />
          </div>
          <div className="resource-filter-search">
            <span aria-hidden="true">⌕</span>
            <input
              aria-label="模糊搜索IP地区"
              list="resource-ip-options"
              onChange={(event) => setIpFilter(event.target.value)}
              placeholder="搜索IP地区"
              type="search"
              value={ipFilter}
            />
            <datalist id="resource-ip-options">
              {ipOptions.map((ip) => <option key={ip} value={ip} />)}
            </datalist>
          </div>
          <div className="resource-filter-search">
            <span aria-hidden="true">⌕</span>
            <input
              aria-label="模糊搜索合作来源"
              list="resource-source-options"
              onChange={(event) => setSourceFilter(event.target.value)}
              placeholder="搜索合作来源"
              type="search"
              value={sourceFilter}
            />
            <datalist id="resource-source-options">
              {sourceOptions.map((source) => (
                <option key={source} value={source} />
              ))}
            </datalist>
          </div>
          <button
            className="resource-clear-button"
            disabled={!hasActiveFilters}
            onClick={clearFilters}
          >
            清空筛选
          </button>
          <div className="resource-toolbar-summary">
            <span>共 <b>{filteredAccounts.length}</b> 个结果</span>
            <button
              className="export-data-button"
              disabled={exporting || filteredAccounts.length === 0}
              onClick={() =>
                onExport(filteredAccounts.map((item) => item.account.id))
              }
            >
              {exporting ? "正在导出…" : "导出筛选结果"}
            </button>
          </div>
        </div>
        <div className="resource-grid">
          {filteredAccounts.map(({ account, sources, partnerManagedOnly }) => (
            <article className="resource-card" key={account.id}>
              <div className="resource-card-head">
                <div className={`avatar xlarge ${avatarColor(account.nickname)}`}>{account.nickname.slice(0, 1)}</div>
                <div><h3>{account.nickname}</h3><span>{account.platform} · {account.ip_location}</span></div>
                <span className="verified-dot">✓</span>
              </div>
              <div className="resource-account-id">
                <span>{account.platform === "小红书" ? "小红书号" : account.platform === "抖音" ? "抖音号" : "账号"}</span>
                <strong>{account.public_account_id || "待识别"}</strong>
              </div>
              <div className="resource-metrics">
                <div><span>粉丝</span><strong>{formatNumber(account.followers)}</strong></div>
                <div><span>合作发布</span><strong>{account.post_count}</strong></div>
              </div>
              <div className="resource-source">
                <span>历史合作来源</span>
                <div>
                  {sources.map((source) => <b key={source}>{source}</b>)}
                  {partnerManagedOnly && (
                    <b className="partner-managed">合作社资源 · 不可直联</b>
                  )}
                </div>
              </div>
              <div className="resource-card-foot">
                <span>最近合作 {formatDate(account.last_seen_at)}</span>
                {account.profile_url && <a href={account.profile_url} target="_blank" rel="noreferrer">查看主页 ↗</a>}
              </div>
            </article>
          ))}
        </div>
        {filteredAccounts.length === 0 && (
          <div className="resource-empty">
            <strong>没有符合条件的KOC</strong>
            <span>可以调整搜索内容或清空筛选条件</span>
            <button className="ghost-button" onClick={clearFilters}>
              清空筛选
            </button>
          </div>
        )}
      </section>
    </div>
  );
}

function RecoveryPage({
  tasks,
  distributions,
  working,
  onCollect,
  onSaveSchedule,
  onUpload,
  onFallback,
  onRetryFailed,
  onExport,
  exportingTaskId,
}: {
  tasks: Task[];
  distributions: Distribution[];
  working: boolean;
  onCollect: (distribution: Distribution) => void;
  onSaveSchedule: (
    taskId: string,
    startDate: string,
    days: number[],
  ) => Promise<boolean>;
  onUpload: (distribution: Distribution, event: ChangeEvent<HTMLInputElement>) => void;
  onFallback: (distribution: Distribution) => void;
  onRetryFailed: (taskId: string, failedCount: number) => void;
  onExport: (task: Task) => void;
  exportingTaskId: string | null;
}) {
  const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
  const selectedTask = tasks.find((task) => task.id === selectedTaskId);
  if (!selectedTask) {
    return (
      <div className="stack">
        <div className="section-intro">
          <div><p className="eyebrow">按任务回收</p><h2>选择一个数据回收任务</h2></div>
          <p>进入任务后设置自动采集日期，统一查看每篇笔记的最新互动数据。</p>
        </div>
        <TaskScopeList
          tasks={tasks}
          distributions={distributions}
          mode="recovery"
          onOpen={setSelectedTaskId}
        />
      </div>
    );
  }

  const taskDistributions = distributions.filter(
    (item) => item.task_id === selectedTask.id,
  );
  const failedCollectionCount = taskDistributions.filter(
    (item) => item.collection_status === "failed",
  ).length;
  return (
    <div className="stack">
      <TaskDetailHeader
        task={selectedTask}
        label="数据回收"
        onBack={() => setSelectedTaskId(null)}
      />
      <CollectionScheduleCard
        key={`${selectedTask.id}:${selectedTask.collection_start_date}:${selectedTask.collection_days}`}
        task={selectedTask}
        working={working}
        onSave={onSaveSchedule}
      />
      <section className="panel table-panel recovery-panel">
        <div className="panel-heading">
          <div>
            <h2>数据回收队列</h2>
            <p>{taskDistributions.length} 篇已发布笔记 · 展示最近一次采集结果</p>
          </div>
          <div className="recovery-panel-actions">
            <button
              className="export-data-button"
              disabled={working || exportingTaskId === selectedTask.id}
              onClick={() => onExport(selectedTask)}
            >
              {exportingTaskId === selectedTask.id
                ? "正在生成 Excel…"
                : "导出全部数据"}
            </button>
            <button
              className="retry-failed-button"
              disabled={working || failedCollectionCount === 0}
              onClick={() =>
                onRetryFailed(selectedTask.id, failedCollectionCount)
              }
            >
              一键补采异常数据
              {failedCollectionCount > 0 && <b>{failedCollectionCount}</b>}
            </button>
          </div>
        </div>
        <div className="table-scroll">
          <table className="data-table recovery-table">
            <thead>
              <tr>
                <th>内容 / 发布账号</th>
                <th>发布时间</th>
                <th>点赞</th>
                <th>收藏</th>
                <th>评论</th>
                <th>总互动</th>
                <th>数据更新时间</th>
                <th>采集状态</th>
                <th>创作者截图</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              {taskDistributions.map((item) => {
                const metrics = latestPublicMetrics(item);
                const noteUrl = xhsPublishUrl(item.publish_url);
                const hasMetrics = metrics.likes !== null;
                const collectionStatus =
                  item.collection_status ||
                  (hasMetrics ? "success" : "pending");
                return (
                  <tr key={item.id}>
                    <td>
                      <div className="account-cell recovery-content">
                        <div className={`avatar small ${avatarColor(item.content_title)}`}>{Array.from(item.content_title)[0] || "笔"}</div>
                        <div>
                          {noteUrl ? (
                            <a
                              className="recovery-title-link"
                              href={noteUrl}
                              target="_blank"
                              rel="noopener noreferrer"
                              aria-label={`打开小红书笔记：${item.content_title}`}
                              title="打开小红书笔记"
                            >
                              <strong>{item.content_title}</strong>
                            </a>
                          ) : (
                            <strong>{item.content_title}</strong>
                          )}
                          <span>
                            {item.account_nickname || "待识别账号"}
                            {item.account_platform ? ` · ${item.account_platform}` : ""}
                          </span>
                        </div>
                      </div>
                    </td>
                    <td>
                      <div className="metric-cell publish-time">
                        <strong>{formatDate(item.publish_time, true)}</strong>
                      </div>
                    </td>
                    <td><div className="metric-cell"><strong>{formatNumber(metrics.likes)}</strong></div></td>
                    <td><div className="metric-cell"><strong>{formatNumber(metrics.collects)}</strong></div></td>
                    <td><div className="metric-cell"><strong>{formatNumber(metrics.comments)}</strong></div></td>
                    <td>
                      <div className="metric-cell total">
                        <strong>{formatNumber(metrics.total)}</strong>
                        <span>赞 + 藏 + 评</span>
                      </div>
                    </td>
                    <td>
                      <div className="metric-cell update-time">
                        <strong>{formatDate(item.collection_updated_at || (hasMetrics ? item.updated_at : null), true)}</strong>
                        {item.last_collection_day && <span>第{item.last_collection_day}天采集</span>}
                      </div>
                    </td>
                    <td>
                      <div className="collection-status-cell">
                        <span className={`collection-status ${collectionStatus}`}>
                          {collectionStatusLabel(collectionStatus, hasMetrics)}
                        </span>
                        <small>
                          {item.collection_status_description ||
                            (hasMetrics ? "成功" : "等待采集计划")}
                        </small>
                      </div>
                    </td>
                    <td>
                      <div className="creator-screenshot-cell">
                        {item.screenshot_key ? (
                          <a
                            className="creator-screenshot-link"
                            href={`/api/creator-screenshot?distribution=${encodeURIComponent(item.id)}`}
                            target="_blank"
                            rel="noopener noreferrer"
                          >
                            查看截图 ↗
                          </a>
                        ) : (
                          <span className="creator-screenshot-pending">待KOC上传</span>
                        )}
                        {hasCreatorMetrics(item) ? (
                          <span className="creator-data-summary">
                            <b>曝光 {formatNumber(item.exposure)}</b>
                            <b>阅读 {formatNumber(item.views)}</b>
                          </span>
                        ) : item.screenshot_key ? (
                          <span className="creator-ocr-failed">
                            待KOC填写数据
                          </span>
                        ) : null}
                        {item.screenshot_key &&
                          !hasCreatorMetrics(item) && (
                            <label className="upload-button retry-upload">
                              <input
                                type="file"
                                accept="image/*"
                                onChange={(event) => onUpload(item, event)}
                              />
                              重新上传
                            </label>
                          )}
                      </div>
                    </td>
                    <td>
                      {item.screenshot_key && !hasCreatorMetrics(item) ? (
                        <button className="text-button" onClick={() => onFallback(item)}>手动填写</button>
                      ) : (
                        <button
                          className="collect-button"
                          disabled={working}
                          onClick={() => onCollect(item)}
                        >
                          立即采集
                        </button>
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          {taskDistributions.length === 0 && (
            <div className="table-empty compact">当前任务还没有已发布笔记</div>
          )}
        </div>
      </section>
    </div>
  );
}

function CollectionScheduleCard({
  task,
  working,
  onSave,
}: {
  task: Task;
  working: boolean;
  onSave: (
    taskId: string,
    startDate: string,
    days: number[],
  ) => Promise<boolean>;
}) {
  const [startDate, setStartDate] = useState(
    task.collection_start_date || todayInputValue(),
  );
  const [days, setDays] = useState<number[]>(
    parseCollectionDays(task.collection_days),
  );

  const toggleDay = (day: number) => {
    setDays((current) =>
      current.includes(day)
        ? current.filter((item) => item !== day)
        : [...current, day].sort((a, b) => a - b),
    );
  };

  return (
    <section className="panel collection-schedule-panel">
      <div className="collection-schedule-heading">
        <div>
          <p className="eyebrow">自动采集计划</p>
          <h2>选择开始日期与采集日</h2>
          <span>勾选第1天到第7天，所选日期均在北京时间10:00自动采集。</span>
        </div>
        <div className="schedule-save-area">
          <span>已选择 {days.length} 天</span>
          <button
            className="primary-button"
            disabled={working || !startDate || days.length === 0}
            onClick={() => void onSave(task.id, startDate, days)}
          >
            {working ? "保存中…" : "保存采集计划"}
          </button>
        </div>
      </div>
      <div className="schedule-grid">
        <label className="schedule-start">
          <span>开始采集日期</span>
          <input
            type="date"
            value={startDate}
            onChange={(event) => setStartDate(event.target.value)}
          />
        </label>
        <div className="schedule-days" role="group" aria-label="选择自动采集日">
          {Array.from({ length: 7 }, (_, index) => index + 1).map((day) => {
            const selected = days.includes(day);
            const date = addCalendarDays(startDate, day - 1);
            return (
              <button
                key={day}
                type="button"
                className={selected ? "selected" : ""}
                aria-pressed={selected}
                onClick={() => toggleDay(day)}
              >
                <span>第{day}天</span>
                <b aria-hidden="true">{selected ? "✓" : ""}</b>
                <small>{date ? date.slice(5).replace("-", "/") : "—"}</small>
              </button>
            );
          })}
        </div>
        <span className="schedule-mobile-hint">左右滑动查看第1—7天</span>
      </div>
      {task.collection_schedule_updated_at && (
        <p className="schedule-updated">
          当前计划更新于 {formatDate(task.collection_schedule_updated_at, true)}
        </p>
      )}
    </section>
  );
}
