"use client";

import { zipSync } from "fflate";
import { ChangeEvent, FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import {
  formatShanghaiDate as formatDate,
  parseStoredDate,
} from "./date-utils";

type Assignment = {
  id: string;
  status: string;
  publish_url: string | null;
  publish_time: string | null;
  publish_screenshot_key: string | null;
  creator_screenshot_key: string | null;
  ocr_status: string;
  exposure: number | null;
  views: number | null;
  title: string;
  body: string;
  source_row: number | null;
  account_nickname: string | null;
  delegation_bundle_id?: string | null;
  delegation_label?: string | null;
  images: Array<{
    index: number;
    width: number | null;
    height: number | null;
  }>;
};

type DelegationSummary = {
  id: string;
  label: string;
  share_token: string;
  quantity: number;
  status: "active" | "revoked";
  created_at: string;
  revoked_at: string | null;
  completed_count: number;
  creator_completed_count: number;
};

type RecoveredClaim = {
  claimToken: string;
  quantity: number;
  completedCount: number;
  createdAt: string;
  firstTitle: string;
};

type TaskPayload = {
  task: {
    name: string;
    brand: string;
    quantity?: number;
    claimedQuantity?: number;
    availableQuantity?: number;
    dueAt: string;
    status: string;
  };
  claim: null | {
    id: string;
    claimantName: string;
    quantity: number;
    createdAt: string;
    assignments: Assignment[];
    delegations: DelegationSummary[];
  };
  delegation: null | {
    id: string;
    label: string;
    quantity: number;
    createdAt: string;
    assignments: Assignment[];
  };
};

const PRODUCTION_ADMIN_ORIGIN =
  "https://koc-loop-mvp-wufp.pyeongwu.chatgpt.site";

function partnerApi(path: "/api/partner" | "/api/partner-upload") {
  const origin =
    window.location.hostname === "localhost"
      ? "http://localhost:3001"
      : PRODUCTION_ADMIN_ORIGIN;
  return `${origin}${path}`;
}

function statusLabel(item: Assignment) {
  return item.publish_url ? "已回填" : "待发布";
}

function creatorScreenshotDueAt(publishTime?: string | null) {
  if (!publishTime) return null;
  const date = parseStoredDate(publishTime);
  if (Number.isNaN(date.getTime())) return null;
  date.setDate(date.getDate() + 7);
  return date.toISOString();
}

function imageExtension(contentType: string) {
  const extensionByType: Record<string, string> = {
    "image/avif": "avif",
    "image/gif": "gif",
    "image/jpeg": "jpg",
    "image/png": "png",
    "image/webp": "webp",
  };
  return extensionByType[contentType] ?? "jpg";
}

function safeFileBase(item: Assignment) {
  return (
    item.title.replace(/[\\/:*?"<>|]/g, "").trim().slice(0, 30) ||
    `笔记-${item.source_row ?? "配图"}`
  );
}

function exactArrayBuffer(bytes: Uint8Array) {
  const copy = new Uint8Array(bytes.byteLength);
  copy.set(bytes);
  return copy.buffer;
}

function downloadBlob(blob: Blob, fileName: string) {
  const objectUrl = URL.createObjectURL(blob);
  const anchor = document.createElement("a");
  anchor.href = objectUrl;
  anchor.download = fileName;
  document.body.appendChild(anchor);
  anchor.click();
  anchor.remove();
  window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
}

async function compressScreenshot(file: File) {
  if (file.size < 900_000 || !file.type.startsWith("image/")) return file;
  const bitmap = await createImageBitmap(file);
  const maxEdge = 1800;
  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.84),
  );
  if (!blob) return file;
  return new File([blob], `${file.name.replace(/\.[^.]+$/, "")}.jpg`, {
    type: "image/jpeg",
  });
}

export default function Home() {
  const [taskToken, setTaskToken] = useState("");
  const [claimToken, setClaimToken] = useState("");
  const [delegationToken, setDelegationToken] = useState("");
  const [selectedId, setSelectedId] = useState("");
  const [payload, setPayload] = useState<TaskPayload | null>(null);
  const [loading, setLoading] = useState(true);
  const [working, setWorking] = useState(false);
  const [error, setError] = useState("");
  const [toast, setToast] = useState("");
  const [claimantName, setClaimantName] = useState("");
  const [quantity, setQuantity] = useState(1);
  const [accountNickname, setAccountNickname] = useState("");
  const [publishUrl, setPublishUrl] = useState("");
  const [screenshot, setScreenshot] = useState<File | null>(null);
  const [creatorScreenshot, setCreatorScreenshot] = useState<File | null>(null);
  const [creatorExposure, setCreatorExposure] = useState("");
  const [creatorViews, setCreatorViews] = useState("");
  const [entryMode, setEntryMode] = useState<"claim" | "recover">("claim");
  const [recoveredClaims, setRecoveredClaims] = useState<RecoveredClaim[]>([]);
  const [adminOrigin, setAdminOrigin] = useState(PRODUCTION_ADMIN_ORIGIN);
  const [downloadingImage, setDownloadingImage] = useState<number | null>(null);
  const [batchDownloading, setBatchDownloading] = useState(false);
  const [creatorWorking, setCreatorWorking] = useState(false);
  const [creatorStage, setCreatorStage] = useState("");
  const [selectedForShare, setSelectedForShare] = useState<string[]>([]);
  const [shareLabel, setShareLabel] = useState("");
  const [shareComposerOpen, setShareComposerOpen] = useState(false);
  const [shareWorking, setShareWorking] = useState(false);

  const updateUrl = useCallback(
    (nextClaim: string, nextNote = "", nextDelegation = "") => {
      const url = new URL(window.location.href);
      if (nextClaim) url.searchParams.set("claim", nextClaim);
      else url.searchParams.delete("claim");
      if (nextDelegation) url.searchParams.set("share", nextDelegation);
      else url.searchParams.delete("share");
      if (nextNote) url.searchParams.set("note", nextNote);
      else url.searchParams.delete("note");
      if (nextDelegation) url.searchParams.delete("task");
      window.history.replaceState({}, "", url);
    },
    [],
  );

  const loadTask = useCallback(async (
    task: string,
    claim: string,
    delegation = "",
  ) => {
    try {
      setLoading(true);
      setError("");
      const params = new URLSearchParams();
      if (task) params.set("task", task);
      if (claim) params.set("claim", claim);
      if (delegation) params.set("share", delegation);
      const response = await fetch(`${partnerApi("/api/partner")}?${params}`, {
        cache: "no-store",
      });
      const result = (await response.json()) as TaskPayload & { error?: string };
      if (!response.ok) throw new Error(result.error || "暂时无法打开任务");
      setPayload(result);
      if (result.claim) setClaimantName(result.claim.claimantName);
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "暂时无法打开任务");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    const timer = window.setTimeout(() => {
      if (window.location.hostname === "localhost") {
        setAdminOrigin("http://localhost:3001");
      }
      const params = new URLSearchParams(window.location.search);
      const task = params.get("task") ?? "";
      const claim = params.get("claim") ?? "";
      const delegation = params.get("share") ?? "";
      const note = params.get("note") ?? "";
      setTaskToken(task);
      setClaimToken(claim);
      setDelegationToken(delegation);
      setSelectedId(note);
      if (!task && !delegation) {
        setLoading(false);
        setError("任务链接不完整，请向运营重新获取");
        return;
      }
      void loadTask(task, claim, delegation);
    }, 0);
    return () => window.clearTimeout(timer);
  }, [loadTask]);

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

  const activeBatch = payload?.claim ?? payload?.delegation ?? null;
  const selected = useMemo(
    () => activeBatch?.assignments.find((item) => item.id === selectedId) ?? null,
    [activeBatch, selectedId],
  );

  useEffect(() => {
    if (!selected) return;
    const timer = window.setTimeout(() => {
      setAccountNickname(selected.account_nickname ?? "");
      setPublishUrl(selected.publish_url ?? "");
      setScreenshot(null);
      setCreatorScreenshot(null);
      setCreatorExposure(
        selected.exposure === null ? "" : String(selected.exposure),
      );
      setCreatorViews(selected.views === null ? "" : String(selected.views));
    }, 0);
    return () => window.clearTimeout(timer);
  }, [selected]);

  const claimNotes = async (event: FormEvent) => {
    event.preventDefault();
    try {
      setWorking(true);
      const response = await fetch(partnerApi("/api/partner"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "claim",
          taskToken,
          claimantIdentifier: claimantName,
          quantity,
        }),
      });
      const result = (await response.json()) as {
        claimToken?: string;
        claimedCount?: number;
        error?: string;
      };
      if (!response.ok || !result.claimToken) {
        throw new Error(result.error || "领取失败");
      }
      setClaimToken(result.claimToken);
      setDelegationToken("");
      updateUrl(result.claimToken);
      await loadTask(taskToken, result.claimToken);
      setToast(`已领取 ${result.claimedCount} 篇，逐篇发布即可`);
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "领取失败");
    } finally {
      setWorking(false);
    }
  };

  const openRecoveredClaim = async (nextClaimToken: string) => {
    setClaimToken(nextClaimToken);
    setDelegationToken("");
    setSelectedId("");
    setShareComposerOpen(false);
    setSelectedForShare([]);
    updateUrl(nextClaimToken);
    await loadTask(taskToken, nextClaimToken);
  };

  const recoverClaims = async (event: FormEvent) => {
    event.preventDefault();
    try {
      setWorking(true);
      const response = await fetch(partnerApi("/api/partner"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "recover",
          taskToken,
          claimantIdentifier: claimantName,
        }),
      });
      const result = (await response.json()) as {
        claims?: RecoveredClaim[];
        error?: string;
      };
      if (!response.ok || !result.claims?.length) {
        throw new Error(result.error || "没有找到领取记录");
      }
      setRecoveredClaims(result.claims);
      if (result.claims.length === 1) {
        await openRecoveredClaim(result.claims[0].claimToken);
      }
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "领取记录查找失败");
    } finally {
      setWorking(false);
    }
  };

  const showRecovery = async () => {
    setEntryMode("recover");
    setRecoveredClaims([]);
    setClaimToken("");
    setDelegationToken("");
    setSelectedId("");
    setShareComposerOpen(false);
    setSelectedForShare([]);
    updateUrl("");
    await loadTask(taskToken, "");
  };

  const noteImageUrl = (item: Assignment, imageIndex: number) => {
    const params = new URLSearchParams({
      distribution: item.id,
      index: String(imageIndex),
    });
    if (delegationToken) params.set("share", delegationToken);
    else {
      params.set("task", taskToken);
      params.set("claim", claimToken);
    }
    return `${adminOrigin}/api/partner-image?${params}`;
  };

  const openNote = (item: Assignment) => {
    setSelectedId(item.id);
    updateUrl(claimToken, item.id, delegationToken);
  };

  const closeNote = () => {
    setSelectedId("");
    updateUrl(claimToken, "", delegationToken);
  };

  const copyText = async (value: string, successMessage: string) => {
    try {
      await navigator.clipboard.writeText(value);
      setToast(successMessage);
    } catch {
      setToast("复制失败，请长按对应文字复制");
    }
  };

  const delegationUrl = (shareToken: string) => {
    const url = new URL(window.location.origin);
    url.searchParams.set("share", shareToken);
    return url.toString();
  };

  const toggleShareSelection = (distributionId: string) => {
    setSelectedForShare((current) =>
      current.includes(distributionId)
        ? current.filter((id) => id !== distributionId)
        : [...current, distributionId],
    );
  };

  const createDelegation = async (event: FormEvent) => {
    event.preventDefault();
    if (!payload?.claim || selectedForShare.length === 0) return;
    try {
      setShareWorking(true);
      const response = await fetch(partnerApi("/api/partner"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "create_delegation",
          taskToken,
          claimToken,
          delegationLabel: shareLabel,
          distributionIds: selectedForShare,
        }),
      });
      const result = (await response.json()) as {
        delegation?: {
          id: string;
          label: string;
          shareToken: string;
          quantity: number;
        };
        error?: string;
      };
      if (!response.ok || !result.delegation) {
        throw new Error(result.error || "分享链接生成失败");
      }
      await copyText(
        delegationUrl(result.delegation.shareToken),
        `已生成给 ${result.delegation.label} 的分享链接并复制`,
      );
      setSelectedForShare([]);
      setShareLabel("");
      setShareComposerOpen(false);
      await loadTask(taskToken, claimToken);
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "分享链接生成失败");
    } finally {
      setShareWorking(false);
    }
  };

  const revokeDelegation = async (bundle: DelegationSummary) => {
    if (!window.confirm(`确认撤销“${bundle.label}”的分享链接吗？`)) return;
    try {
      setShareWorking(true);
      const response = await fetch(partnerApi("/api/partner"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "revoke_delegation",
          taskToken,
          claimToken,
          delegationBundleId: bundle.id,
        }),
      });
      const result = (await response.json()) as {
        revoked?: boolean;
        error?: string;
      };
      if (!response.ok || !result.revoked) {
        throw new Error(result.error || "撤销失败");
      }
      await loadTask(taskToken, claimToken);
      setSelectedForShare([]);
      setToast("分享链接已撤销，相关笔记可以重新转派");
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "撤销失败");
    } finally {
      setShareWorking(false);
    }
  };

  const prepareImage = async (item: Assignment, imageIndex: number) => {
    const response = await fetch(noteImageUrl(item, imageIndex));
    if (!response.ok) throw new Error("图片读取失败");
    const blob = await response.blob();
    const extension = imageExtension(blob.type);
    const fileName = `${safeFileBase(item)}-图片${imageIndex}.${extension}`;
    const buffer = await blob.arrayBuffer();
    return {
      bytes: new Uint8Array(buffer),
      file: new File([buffer], fileName, { type: blob.type || "image/jpeg" }),
    };
  };

  const shareImages = async (files: File[], title: string) => {
    if (
      typeof navigator.share !== "function" ||
      typeof navigator.canShare !== "function" ||
      !navigator.canShare({ files })
    ) {
      return false;
    }
    await navigator.share({ files, title });
    return true;
  };

  const downloadImage = async (item: Assignment, imageIndex: number) => {
    try {
      setDownloadingImage(imageIndex);
      const prepared = await prepareImage(item, imageIndex);
      try {
        if (await shareImages([prepared.file], item.title)) {
          setToast("请在系统菜单中选择“存储图像”");
          return;
        }
      } catch (reason) {
        if (reason instanceof DOMException && reason.name === "AbortError") return;
      }
      downloadBlob(
        new Blob([exactArrayBuffer(prepared.bytes)], {
          type: prepared.file.type,
        }),
        prepared.file.name,
      );
      setToast(`图片 ${imageIndex} 已下载到文件`);
    } catch {
      setToast("图片下载失败，请点击图片后保存");
    } finally {
      setDownloadingImage(null);
    }
  };

  const downloadAllImages = async (item: Assignment) => {
    try {
      setBatchDownloading(true);
      const prepared = await Promise.all(
        item.images.map((image) => prepareImage(item, image.index)),
      );
      const files = prepared.map((image) => image.file);
      try {
        if (await shareImages(files, `${item.title}｜全部配图`)) {
          setToast("请在系统菜单中选择“存储图像”批量保存");
          return;
        }
      } catch (reason) {
        if (reason instanceof DOMException && reason.name === "AbortError") return;
      }
      const archive = zipSync(
        Object.fromEntries(
          prepared.map((image) => [image.file.name, image.bytes]),
        ),
        { level: 0 },
      );
      downloadBlob(
        new Blob([exactArrayBuffer(archive)], { type: "application/zip" }),
        `${safeFileBase(item)}-全部配图.zip`,
      );
      setToast("当前浏览器不支持直接存相册，已下载全部配图 ZIP");
    } catch {
      setToast("批量保存失败，请尝试用系统浏览器打开");
    } finally {
      setBatchDownloading(false);
    }
  };

  const chooseScreenshot = (event: ChangeEvent<HTMLInputElement>) => {
    setScreenshot(event.target.files?.[0] ?? null);
  };

  const chooseCreatorScreenshot = (event: ChangeEvent<HTMLInputElement>) => {
    setCreatorScreenshot(event.target.files?.[0] ?? null);
  };

  const uploadEvidence = async (
    selectedItem: Assignment,
    file: File,
    kind: "publish" | "creator-center",
  ) => {
    const compressed = await compressScreenshot(file);
    const headers: Record<string, string> = {
      "Content-Type": compressed.type || "image/jpeg",
      "X-KOC-Distribution": selectedItem.id,
      "X-KOC-File-Name": encodeURIComponent(compressed.name),
      "X-KOC-Upload-Kind": kind,
    };
    if (delegationToken) {
      headers["X-KOC-Delegation"] = delegationToken;
    } else {
      headers["X-KOC-Task"] = taskToken;
      headers["X-KOC-Claim"] = claimToken;
    }
    const uploadResponse = await fetch(partnerApi("/api/partner-upload"), {
      method: "POST",
      headers,
      body: compressed,
    });
    const uploadResult = (await uploadResponse.json()) as { error?: string };
    if (!uploadResponse.ok) {
      throw new Error(
        uploadResult.error ||
          (kind === "creator-center"
            ? "创作者中心截图上传失败"
            : "发布截图上传失败"),
      );
    }
  };

  const submitNote = async (event: FormEvent) => {
    event.preventDefault();
    if (!selected) return;
    try {
      setWorking(true);
      if (screenshot) {
        await uploadEvidence(selected, screenshot, "publish");
      } else if (!selected.publish_screenshot_key) {
        throw new Error("请上传这篇笔记的发布截图");
      }

      const response = await fetch(partnerApi("/api/partner"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "submit",
          taskToken,
          claimToken,
          delegationToken,
          distributionId: selected.id,
          accountNickname,
          publishUrl,
        }),
      });
      const result = (await response.json()) as { error?: string };
      if (!response.ok) throw new Error(result.error || "回填失败");
      await loadTask(taskToken, claimToken, delegationToken);
      setScreenshot(null);
      setToast("这篇笔记已回填，不会与其他笔记错配");
    } catch (reason) {
      setToast(reason instanceof Error ? reason.message : "回填失败");
    } finally {
      setWorking(false);
    }
  };

  const submitCreatorData = async () => {
    if (!selected) return;
    if (!creatorScreenshot && !selected.creator_screenshot_key) {
      setToast("请选择创作者中心截图");
      return;
    }
    if (
      !/^\d{1,12}$/.test(creatorExposure) ||
      !/^\d{1,12}$/.test(creatorViews)
    ) {
      setToast("请填写正确的曝光量和阅读量");
      return;
    }
    try {
      setCreatorWorking(true);
      if (creatorScreenshot) {
        setCreatorStage("正在上传截图…");
        await uploadEvidence(
          selected,
          creatorScreenshot,
          "creator-center",
        );
      }
      setCreatorStage("正在保存数据…");
      const response = await fetch(partnerApi("/api/partner"), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "submit_creator_metrics",
          taskToken,
          claimToken,
          delegationToken,
          distributionId: selected.id,
          exposure: creatorExposure,
          views: creatorViews,
        }),
      });
      const result = (await response.json()) as { error?: string };
      if (!response.ok) {
        throw new Error(result.error || "创作者数据提交失败");
      }
      await loadTask(taskToken, claimToken, delegationToken);
      setCreatorScreenshot(null);
      setToast("创作者截图、曝光量和阅读量已提交");
    } catch (reason) {
      setToast(
        reason instanceof Error ? reason.message : "创作者数据提交失败",
      );
    } finally {
      setCreatorWorking(false);
      setCreatorStage("");
    }
  };

  if (loading) {
    return (
      <main className="portal-shell loading-shell">
        <div className="loading-mark">K</div>
        <p>正在打开任务…</p>
      </main>
    );
  }

  if (error || !payload) {
    return (
      <main className="portal-shell">
        <section className="message-card">
          <div className="message-icon">!</div>
          <p className="micro">KOC LOOP</p>
          <h1>暂时无法打开任务</h1>
          <p>{error || "任务不存在"}</p>
          <button onClick={() => window.location.reload()}>重新加载</button>
        </section>
      </main>
    );
  }

  if (selected && activeBatch) {
    const creatorDueAt = creatorScreenshotDueAt(selected.publish_time);
    const creatorDataReady =
      Boolean(selected.creator_screenshot_key) &&
      selected.exposure !== null &&
      selected.views !== null;
    return (
      <main className="portal-shell detail-shell">
        <header className="portal-topbar">
          <div className="portal-brand"><span>K</span><strong>KOC LOOP</strong></div>
          <div className="claimant-chip">
            {payload.claim ? payload.claim.claimantName : "合作社转派 · 无需登录"}
          </div>
        </header>
        <button className="back-link" onClick={closeNote}>← 返回我的笔记</button>
        <div className="detail-grid">
          <article className="note-document">
            <div className="note-document-meta">
              <span>笔记 {activeBatch.assignments.findIndex((item) => item.id === selected.id) + 1}</span>
              <span>飞书源行 {selected.source_row ?? "—"}</span>
            </div>
            <div className="note-title-row">
              <h1>{selected.title}</h1>
              <button
                type="button"
                className="inline-copy"
                onClick={() => void copyText(selected.title, "标题已复制")}
              >
                复制标题
              </button>
            </div>
            <div className="document-rule" />
            <div className="document-label-row">
              <p className="document-label">正文与话题</p>
              <button
                type="button"
                className="inline-copy"
                onClick={() => void copyText(selected.body, "文案已复制")}
              >
                复制文案
              </button>
            </div>
            <div className="note-body">{selected.body || "暂无正文"}</div>
            {selected.images.length > 0 && (
              <section className="note-images">
                <div className="note-images-heading">
                  <div>
                    <p className="document-label">发布配图</p>
                    <span>手机优先打开系统保存菜单，其他浏览器会下载文件</span>
                  </div>
                  <div className="note-images-tools">
                    <b>{selected.images.length} 张</b>
                    {selected.images.length > 1 && (
                      <button
                        type="button"
                        disabled={batchDownloading}
                        onClick={() => void downloadAllImages(selected)}
                      >
                        {batchDownloading ? "正在准备…" : "批量保存图片"}
                      </button>
                    )}
                  </div>
                </div>
                <div className="note-image-grid">
                  {selected.images.map((image, index) => (
                    <div className="note-image-card" key={image.index}>
                      <a
                        className="note-image-preview"
                        href={noteImageUrl(selected, image.index)}
                        target="_blank"
                        rel="noreferrer"
                      >
                        {/* Private claim-bound images intentionally bypass the public image optimizer. */}
                        {/* eslint-disable-next-line @next/next/no-img-element */}
                        <img
                          src={noteImageUrl(selected, image.index)}
                          alt={`${selected.title} 配图 ${index + 1}`}
                          width={image.width ?? undefined}
                          height={image.height ?? undefined}
                          loading={index === 0 ? "eager" : "lazy"}
                        />
                      </a>
                      <div className="note-image-actions">
                        <span>图片 {index + 1}</span>
                        <button
                          type="button"
                          disabled={downloadingImage === image.index}
                          onClick={() => void downloadImage(selected, image.index)}
                        >
                          {downloadingImage === image.index ? "下载中…" : "下载原图"}
                        </button>
                      </div>
                    </div>
                  ))}
                </div>
              </section>
            )}
          </article>

          <form className="submit-card" onSubmit={submitNote}>
            <div className="submit-heading">
              <div>
                <p className="micro">逐篇回填</p>
                <h2>填写这篇笔记的发布信息</h2>
              </div>
              <span className={selected.publish_url ? "done" : "pending"}>
                {statusLabel(selected)}
              </span>
            </div>
            <div className="mapping-note">
              <span>1 : 1</span>
              <p>以下信息只会绑定到当前笔记，不按顺序批量匹配。</p>
            </div>
            <label>
              <span>发布账号昵称</span>
              <input
                value={accountNickname}
                onChange={(event) => setAccountNickname(event.target.value)}
                placeholder="例如：小满的轻生活"
                required
              />
            </label>
            <label>
              <span>发布链接</span>
              <input
                type="text"
                inputMode="url"
                value={publishUrl}
                onChange={(event) => setPublishUrl(event.target.value)}
                placeholder="可粘贴小红书长链、短链或整段分享文案"
                required
              />
              <small className="field-hint">
                系统会自动提取分享文案中的小红书链接
              </small>
            </label>
            <label>
              <span>发布截图</span>
              <div className={`screenshot-picker ${screenshot || selected.publish_screenshot_key ? "has-file" : ""}`}>
                <input type="file" accept="image/*" onChange={chooseScreenshot} />
                <b>{screenshot ? "✓" : selected.publish_screenshot_key ? "✓" : "＋"}</b>
                <strong>
                  {screenshot
                    ? screenshot.name
                    : selected.publish_screenshot_key
                      ? "发布截图已上传"
                      : "点击上传这篇笔记的发布截图"}
                </strong>
                <small>支持 JPG、PNG，最大8MB</small>
              </div>
            </label>
            <button className="submit-primary" disabled={working}>
              {working ? "正在提交…" : selected.publish_url ? "更新这篇笔记" : "确认回填这篇笔记"}
            </button>
            {selected.publish_time && (
              <p className="submitted-at">上次回填：{formatDate(selected.publish_time, true)}</p>
            )}
            {selected.publish_url && (
              <section className="creator-recovery">
                <div className="creator-recovery-heading">
                  <div>
                    <p className="micro">第7天数据回收</p>
                    <h3>上传截图并填写数据</h3>
                  </div>
                  <span className={creatorDataReady ? "done" : ""}>
                    {creatorDataReady ? "已提交" : "待提交"}
                  </span>
                </div>
                <p className="creator-recovery-tip">
                  请在发布满7天后，上传创作者中心数据页完整截图，并填写页面显示的数据。
                  {creatorDueAt && ` 建议上传日期：${formatDate(creatorDueAt)}。`}
                </p>
                <label>
                  <span>创作者中心截图</span>
                  <div
                    className={`screenshot-picker compact ${
                      creatorScreenshot || selected.creator_screenshot_key
                        ? "has-file"
                        : ""
                    }`}
                  >
                    <input
                      type="file"
                      accept="image/*"
                      onChange={chooseCreatorScreenshot}
                    />
                    <b>
                      {creatorScreenshot || selected.creator_screenshot_key
                        ? "✓"
                        : "＋"}
                    </b>
                    <strong>
                      {creatorScreenshot
                        ? creatorScreenshot.name
                        : selected.creator_screenshot_key
                          ? "创作者中心截图已提交"
                          : "点击选择第7天创作者中心截图"}
                    </strong>
                    <small>截图仅用于运营核对，不再自动OCR</small>
                  </div>
                </label>
                <div className="creator-metric-grid">
                  <label>
                    <span>曝光量</span>
                    <input
                      type="number"
                      inputMode="numeric"
                      min="0"
                      step="1"
                      max="999999999999"
                      value={creatorExposure}
                      onChange={(event) => setCreatorExposure(event.target.value)}
                      placeholder="填写截图中的曝光量"
                      required
                    />
                  </label>
                  <label>
                    <span>阅读量</span>
                    <input
                      type="number"
                      inputMode="numeric"
                      min="0"
                      step="1"
                      max="999999999999"
                      value={creatorViews}
                      onChange={(event) => setCreatorViews(event.target.value)}
                      placeholder="填写截图中的阅读量"
                      required
                    />
                  </label>
                </div>
                <button
                  type="button"
                  className="creator-submit"
                  disabled={
                    creatorWorking ||
                    (!creatorScreenshot && !selected.creator_screenshot_key) ||
                    !creatorExposure ||
                    !creatorViews
                  }
                  onClick={() => void submitCreatorData()}
                >
                  {creatorWorking
                    ? creatorStage || "正在处理…"
                    : creatorDataReady
                      ? "更新创作者数据"
                      : "提交创作者数据"}
                </button>
              </section>
            )}
          </form>
        </div>
        {toast && <div className="toast">{toast}</div>}
      </main>
    );
  }

  if (activeBatch) {
    const completed = activeBatch.assignments.filter((item) => item.publish_url).length;
    const isClaimOwner = Boolean(payload.claim);
    const shareableCount = payload.claim?.assignments.filter(
      (item) => !item.publish_url && !item.delegation_bundle_id,
    ).length ?? 0;
    return (
      <main className="portal-shell">
        <header className="portal-topbar">
          <div className="portal-brand"><span>K</span><strong>KOC LOOP</strong></div>
          {payload.claim ? (
            <div className="claim-tools">
              <button onClick={() => void showRecovery()}>找回其他批次</button>
              <div className="claimant-chip">{payload.claim.claimantName}</div>
            </div>
          ) : (
            <div className="claimant-chip">合作社转派 · 无需登录</div>
          )}
        </header>
        <section className="claimed-hero">
          <div>
            <p className="micro">
              {isClaimOwner ? "我的领取任务" : "合作社转派发布包"}
            </p>
            <h1>{payload.task.name}</h1>
            <p>
              {payload.task.brand} · {formatDate(payload.task.dueAt)}前发布
              {payload.delegation ? ` · ${payload.delegation.label}` : ""}
            </p>
          </div>
          <div className="completion-ring">
            <strong>{completed}/{activeBatch.assignments.length}</strong>
            <span>已回填</span>
          </div>
        </section>
        <section className="notes-section">
          <div className="section-heading">
            <div>
              <h2>{isClaimOwner ? "我领取的笔记" : "需要发布的笔记"}</h2>
              <p>
                {isClaimOwner
                  ? "打开一篇，查看内容并单独回填；也可以选择笔记转派给底层KOC"
                  : "打开一篇查看完整内容，发布后逐篇回填"}
              </p>
            </div>
            <div className="section-actions">
              <span>{activeBatch.assignments.length} 篇</span>
              {isClaimOwner && (
                <button
                  type="button"
                  className={shareComposerOpen ? "active" : ""}
                  disabled={shareableCount === 0 && !shareComposerOpen}
                  onClick={() => {
                    setShareComposerOpen((current) => !current);
                    setSelectedForShare([]);
                    setShareLabel("");
                  }}
                >
                  {shareComposerOpen ? "取消选择" : "选择笔记并分享"}
                </button>
              )}
            </div>
          </div>
          {payload.claim && shareComposerOpen && (
            <form className="share-composer" onSubmit={createDelegation}>
              <div>
                <p className="micro">合作社转派</p>
                <h3>生成免登录分享链接</h3>
                <p>
                  勾选待发布笔记，底层KOC打开链接即可查看内容并逐篇回填。
                </p>
              </div>
              <label>
                <span>接收人备注</span>
                <input
                  value={shareLabel}
                  maxLength={24}
                  onChange={(event) => setShareLabel(event.target.value)}
                  placeholder="例如：A、小王"
                  required
                />
              </label>
              <div className="share-composer-action">
                <strong>已选 {selectedForShare.length} 篇</strong>
                <button
                  className="share-primary"
                  disabled={
                    shareWorking ||
                    selectedForShare.length === 0 ||
                    !shareLabel.trim()
                  }
                >
                  {shareWorking ? "生成中…" : "生成并复制分享链接"}
                </button>
              </div>
              <small>
                对方无需登录，也不会填写手机号、微信或企微；分享链接可用于后续第7天数据回收。
              </small>
            </form>
          )}
          <div className="note-list">
            {activeBatch.assignments.map((item, index) => {
              const shareDisabled = Boolean(
                item.publish_url || item.delegation_bundle_id,
              );
              return (
              <article
                key={item.id}
                className={`note-row ${shareComposerOpen && isClaimOwner ? "share-selecting" : ""}`}
              >
                {shareComposerOpen && isClaimOwner && (
                  <label
                    className={`share-checkbox ${shareDisabled ? "disabled" : ""}`}
                    title={
                      item.publish_url
                        ? "已发布笔记不能转派"
                        : item.delegation_bundle_id
                          ? `已转派给 ${item.delegation_label}`
                          : "选择这篇笔记"
                    }
                  >
                    <input
                      type="checkbox"
                      checked={selectedForShare.includes(item.id)}
                      disabled={shareDisabled}
                      onChange={() => toggleShareSelection(item.id)}
                      aria-label={`选择笔记：${item.title}`}
                    />
                    <span>✓</span>
                  </label>
                )}
                <span className={`note-index ${item.publish_url ? "done" : ""}`}>
                  {String(index + 1).padStart(2, "0")}
                </span>
                {item.images[0] ? (
                  <>
                    {/* Private claim-bound images intentionally bypass the public image optimizer. */}
                    {/* eslint-disable-next-line @next/next/no-img-element */}
                    <img
                      className="note-thumb"
                      src={noteImageUrl(item, item.images[0].index)}
                      alt=""
                      loading="lazy"
                    />
                  </>
                ) : (
                  <span className="note-thumb empty">无图</span>
                )}
                <div>
                  <h3>{item.title}</h3>
                  <p>
                    {item.images.length} 张配图 · 飞书源行 {item.source_row ?? "—"} · {statusLabel(item)}
                    {item.creator_screenshot_key ? " · D7截图已交" : ""}
                    {item.delegation_label ? ` · 已转派给 ${item.delegation_label}` : ""}
                  </p>
                </div>
                <span
                  className={`note-status ${
                    item.publish_url
                      ? "done"
                      : item.delegation_bundle_id
                        ? "delegated"
                        : ""
                  }`}
                >
                  {item.publish_url
                    ? "已回填"
                    : item.delegation_bundle_id
                      ? "已转派"
                      : "待发布"}
                </span>
                <button onClick={() => openNote(item)}>
                  {item.publish_url ? "查看 / 修改" : "查看并回填"} →
                </button>
              </article>
              );
            })}
          </div>
          {payload.claim && payload.claim.delegations.length > 0 && (
            <section className="delegation-history">
              <div className="delegation-heading">
                <div>
                  <p className="micro">分享记录</p>
                  <h3>转派发布包</h3>
                </div>
                <span>{payload.claim.delegations.length} 个</span>
              </div>
              <div className="delegation-list">
                {payload.claim.delegations.map((bundle) => (
                  <article
                    key={bundle.id}
                    className={bundle.status === "revoked" ? "revoked" : ""}
                  >
                    <div>
                      <strong>{bundle.label}</strong>
                      <span>
                        {bundle.quantity} 篇 · 已发布{" "}
                        {Number(bundle.completed_count ?? 0)}/{bundle.quantity} ·
                        D7完成 {Number(bundle.creator_completed_count ?? 0)}
                      </span>
                    </div>
                    <small>{formatDate(bundle.created_at, true)}</small>
                    <span className={`delegation-status ${bundle.status}`}>
                      {bundle.status === "active" ? "分享中" : "已撤销"}
                    </span>
                    {bundle.status === "active" && (
                      <div className="delegation-actions">
                        <button
                          type="button"
                          onClick={() =>
                            void copyText(
                              delegationUrl(bundle.share_token),
                              `已复制给 ${bundle.label} 的分享链接`,
                            )
                          }
                        >
                          复制链接
                        </button>
                        {Number(bundle.completed_count ?? 0) === 0 && (
                          <button
                            type="button"
                            className="danger"
                            disabled={shareWorking}
                            onClick={() => void revokeDelegation(bundle)}
                          >
                            撤销
                          </button>
                        )}
                      </div>
                    )}
                  </article>
                ))}
              </div>
            </section>
          )}
        </section>
        <div className="safe-tip">
          {isClaimOwner
            ? "以后再次打开原任务链接，填写相同微信号或手机号即可找回全部领取批次和分享记录。"
            : "请保存当前分享链接；发布后仍需通过此链接回填第7天截图、曝光量和阅读量。"}
        </div>
        {toast && <div className="toast">{toast}</div>}
      </main>
    );
  }

  return (
    <main className="portal-shell">
      <header className="portal-topbar">
        <div className="portal-brand"><span>K</span><strong>KOC LOOP</strong></div>
        <span className="external-label">KOC领取页</span>
      </header>
      <section className="task-hero">
        <div className="hero-copy">
          <p className="micro">正在招募 · {payload.task.brand}</p>
          <h1>{payload.task.name}</h1>
          <p>领取后可查看完整笔记正文，每篇笔记独立回填发布信息。</p>
          <div className="hero-meta">
            <span>截止 {formatDate(payload.task.dueAt)}</span>
            <span>剩余 {payload.task.availableQuantity} 篇</span>
          </div>
        </div>
        <aside className="claim-card">
          <div className="entry-switch" aria-label="领取入口">
            <button
              type="button"
              className={entryMode === "claim" ? "active" : ""}
              onClick={() => {
                setEntryMode("claim");
                setRecoveredClaims([]);
              }}
            >
              领取新笔记
            </button>
            <button
              type="button"
              className={entryMode === "recover" ? "active" : ""}
              onClick={() => {
                setEntryMode("recover");
                setRecoveredClaims([]);
              }}
            >
              找回领取记录
            </button>
          </div>
          {entryMode === "claim" ? (
            <form onSubmit={claimNotes}>
              <div>
                <p className="micro">01 · 确认领取</p>
                <h2>你要领取多少篇？</h2>
              </div>
              <label>
                <span>微信号 / 手机号</span>
                <input
                  value={claimantName}
                  onChange={(event) => setClaimantName(event.target.value)}
                  placeholder="请输入微信号或手机号"
                  autoCapitalize="none"
                  autoCorrect="off"
                  spellCheck={false}
                  maxLength={64}
                  required
                />
              </label>
              <div className="quantity-control">
                <button type="button" onClick={() => setQuantity(Math.max(1, quantity - 1))}>−</button>
                <div><strong>{quantity}</strong><span>篇</span></div>
                <button
                  type="button"
                  onClick={() =>
                    setQuantity(Math.min(payload.task.availableQuantity || 1, quantity + 1))
                  }
                >
                  ＋
                </button>
              </div>
              <button
                className="claim-primary"
                disabled={working || payload.task.availableQuantity === 0}
              >
                {payload.task.availableQuantity === 0
                  ? "当前任务已领完"
                  : working
                    ? "领取中…"
                    : `确认领取 ${quantity} 篇`}
              </button>
              <small>领取后系统会生成你的专属笔记清单</small>
            </form>
          ) : (
            <form onSubmit={recoverClaims}>
              <div>
                <p className="micro">找回历史记录</p>
                <h2>继续之前领取的笔记</h2>
                <p className="recover-intro">
                  输入领取时使用的微信号或手机号；同一任务多次领取会分批展示。
                </p>
              </div>
              <label>
                <span>领取时的微信号 / 手机号</span>
                <input
                  value={claimantName}
                  onChange={(event) => setClaimantName(event.target.value)}
                  placeholder="需与领取时完全一致"
                  autoCapitalize="none"
                  autoCorrect="off"
                  spellCheck={false}
                  maxLength={64}
                  required
                />
              </label>
              <button className="claim-primary" disabled={working}>
                {working ? "正在查找…" : "查找我的领取记录"}
              </button>
              {recoveredClaims.length > 0 && (
                <div className="recovery-list">
                  {recoveredClaims.map((claim, index) => (
                    <button
                      type="button"
                      key={claim.claimToken}
                      onClick={() => void openRecoveredClaim(claim.claimToken)}
                    >
                      <span>第 {recoveredClaims.length - index} 批 · {formatDate(claim.createdAt, true)}</span>
                      <strong>{claim.firstTitle}</strong>
                      <small>{claim.completedCount}/{claim.quantity} 已回填</small>
                      <i>继续填写 →</i>
                    </button>
                  ))}
                </div>
              )}
            </form>
          )}
        </aside>
      </section>
      <section className="steps">
        <div><span>1</span><strong>选择数量</strong><p>一次可领多篇</p></div>
        <i />
        <div><span>2</span><strong>逐篇查看</strong><p>标题正文完整展示</p></div>
        <i />
        <div><span>3</span><strong>单篇回填</strong><p>昵称、链接、截图一一对应</p></div>
      </section>
      <footer>由 KOC LOOP 提供任务与数据安全保障</footer>
      {toast && <div className="toast">{toast}</div>}
    </main>
  );
}
