import {
  AppstoreOutlined,
  ClearOutlined,
  CloudUploadOutlined,
  CloseOutlined,
  DeleteOutlined,
  DownloadOutlined,
  EditOutlined,
  FireOutlined,
  FileImageOutlined,
  FolderOpenOutlined,
  FrownOutlined,
  GlobalOutlined,
  LayoutOutlined,
  LoadingOutlined,
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  PaperClipOutlined,
  PlusOutlined,
  QuestionCircleOutlined,
  ReloadOutlined,
  ScissorOutlined,
  SettingOutlined,
  SkinOutlined,
  TableOutlined,
  VideoCameraOutlined,
} from "@ant-design/icons";
import {
  ArrowsCounterClockwise,
  Fire,
  FrameCorners,
  Gift,
  MagicWand,
  Mountains,
  PaperPlaneRight,
  ShoppingBag,
  User,
  VideoCamera,
} from "@phosphor-icons/react";
import { Fragment, useEffect, useMemo, useRef, useState, type CSSProperties, type ChangeEvent, type DragEvent, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
import { createPortal } from "react-dom";
import { useTypewriter } from "../../hooks/useTypewriter";
import "../../styles/pages/ecommerce.css";
import "../../styles/pages/local-theme-parity.css";
import { ossAssets } from "../../data/ossAssets";
import { EcommerceProgressBar } from "./EcommerceProgressBar";
import ImageMentionMenu, { getImageMentionQuery, insertImageMentionValue, type MentionImageOption } from "./ImageMentionMenu";
import EcommerceVideoWorkspace from "./EcommerceVideoWorkspace";
import EcommerceVideoHistoryPanel from "./panels/EcommerceVideoHistoryPanel";
import EcommerceDetailPanel from "./panels/EcommerceDetailPanel";
import EcommerceSetPanel from "./panels/EcommerceSetPanel";
import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel";
import EcommerceClonePanel from "./panels/EcommerceClonePanel";
import EcommerceCopywritingPanel from "./panels/EcommerceCopywritingPanel";
import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
import { downloadResultAsset } from "../workbench/workbenchDownload";
import type { CloneOutputKey, ProductSetOutputKey } from "./utils/platformRules";

const smartCutoutColorPresets = [
  "#ffffff",
  "#111111",
  "#ff3131",
  "#ff7a1a",
  "#f7c600",
  "#29b34a",
  "#25a9e0",
  "#438df5",
  "#9029d9",
  "#8aa3ad",
  "#6b7b86",
  "#f46f7b",
  "#ff9451",
  "#f7d34f",
  "#55c66f",
  "#73c7f3",
  "#6dabf5",
  "#b45adb",
  "#bcc8ce",
  "#aeb7bd",
  "#ffbec4",
  "#ffd1ac",
  "#f8e69d",
  "#91de9e",
  "#b7e5fb",
  "#b9d9fb",
  "#d7abe8",
  "#dfe5e8",
  "#d7dde0",
  "#ffe2e4",
  "#ffe5d1",
  "#f8efcf",
  "#c9efcf",
  "#d8f0fb",
  "#d8eafa",
  "#ead2f1",
];

const smartCutoutSizeOptions = [
  { key: "original", label: "原尺寸", icon: "image", frameWidth: "min(520px, 78%)", frameAspect: "auto", imageMaxWidth: "78%", imageMaxHeight: "310px" },
  { key: "trim", label: "裁剪到边缘", icon: "crop", frameWidth: "min(420px, 70%)", frameAspect: "auto", imageMaxWidth: "92%", imageMaxHeight: "360px" },
  { key: "one-inch", label: "一寸头像", sizeLabel: "295*413", icon: "portrait", frameWidth: "min(290px, 50%)", frameAspect: "295 / 413", imageMaxWidth: "86%", imageMaxHeight: "86%", outputWidth: 295, outputHeight: 413 },
  { key: "two-inch", label: "二寸头像", sizeLabel: "413*579", icon: "portrait", frameWidth: "min(320px, 54%)", frameAspect: "413 / 579", imageMaxWidth: "86%", imageMaxHeight: "86%", outputWidth: 413, outputHeight: 579 },
  { key: "taobao-1-1", label: "淘宝1:1主图", sizeLabel: "800*800", icon: "shop", frameWidth: "min(430px, 72%)", frameAspect: "800 / 800", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 800, outputHeight: 800 },
  { key: "taobao-3-4", label: "淘宝3:4主图", sizeLabel: "750*1000", icon: "shop", frameWidth: "min(330px, 56%)", frameAspect: "750 / 1000", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 750, outputHeight: 1000 },
  { key: "pdd-main", label: "拼多多主图", sizeLabel: "800*800", icon: "pdd", frameWidth: "min(430px, 72%)", frameAspect: "800 / 800", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 800, outputHeight: 800 },
  { key: "xiaohongshu-cover", label: "小红书封面", sizeLabel: "1242*1660", icon: "text", frameWidth: "min(330px, 56%)", frameAspect: "1242 / 1660", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 1242, outputHeight: 1660 },
  { key: "ratio-1-1", label: "1:1", icon: "square", frameWidth: "min(430px, 72%)", frameAspect: "1 / 1", imageMaxWidth: "82%", imageMaxHeight: "82%" },
  { key: "ratio-3-2", label: "3:2", icon: "landscape", frameWidth: "min(520px, 78%)", frameAspect: "3 / 2", imageMaxWidth: "82%", imageMaxHeight: "82%" },
  { key: "ratio-2-3", label: "2:3", icon: "portrait-ratio", frameWidth: "min(330px, 56%)", frameAspect: "2 / 3", imageMaxWidth: "82%", imageMaxHeight: "82%" },
  { key: "ratio-4-3", label: "4:3", icon: "landscape", frameWidth: "min(520px, 78%)", frameAspect: "4 / 3", imageMaxWidth: "82%", imageMaxHeight: "82%" },
  { key: "ratio-3-4", label: "3:4", icon: "portrait-ratio", frameWidth: "min(330px, 56%)", frameAspect: "3 / 4", imageMaxWidth: "82%", imageMaxHeight: "82%" },
  { key: "ratio-16-9", label: "16:9", icon: "wide", frameWidth: "min(560px, 82%)", frameAspect: "16 / 9", imageMaxWidth: "82%", imageMaxHeight: "82%" },
  { key: "ratio-9-16", label: "9:16", icon: "phone", frameWidth: "min(260px, 46%)", frameAspect: "9 / 16", imageMaxWidth: "82%", imageMaxHeight: "82%" },
] as const;

type SmartCutoutSizeKey = (typeof smartCutoutSizeOptions)[number]["key"];
type SmartCutoutImageItem = { src: string; name: string; originalSrc?: string };

const ecommerceInspirationTabs = ["最近打开", "一键同款", "海报模板", "热门", "商品图", "模特穿戴"];
const ecommerceInspirationAssets = ossAssets.ecommerce.inspiration;

const ecommerceInspirationRows = [
  {
    title: "作品记录",
    desc: "沉淀最近生成的高转化素材，随时回看与复用。",
    variant: "team",
    cards: [
      { title: "指定ASIN，优化listing", meta: "竞品拆解 · 卖点重排 · 图文建议", mediaUrl: ecommerceInspirationAssets.asinListing, mediaType: "image" },
      { title: "TikTok美区爆品分析", meta: "脚本方向 · 人群洞察 · 素材策略", mediaUrl: ecommerceInspirationAssets.tiktokPreference, mediaType: "image" },
      { title: "竞品分析 + 全套listing", meta: "关键词 · 主图结构 · 转化建议", mediaUrl: ecommerceInspirationAssets.competitorListing, mediaType: "image" },
      { title: "世界杯属性快闪视频", meta: "热点追踪 · 模板复用 · 15秒短片", mediaUrl: ecommerceInspirationAssets.worldCupFlashVideo, mediaType: "video" },
    ],
  },
  {
    title: "电商套图",
    desc: "主图 / 详情图全套一次性生成。",
    variant: "listing",
    cards: [
      { title: "科技礼盒主图", meta: "高反差质感 · 参数卖点", mediaUrl: ecommerceInspirationAssets.officeStyleSet, mediaType: "image" },
      { title: "美妆节日套图", meta: "促销氛围 · 多规格展示", mediaUrl: ecommerceInspirationAssets.fathersDaySet, mediaType: "image" },
      { title: "防晒产品场景", meta: "户外光感 · 功效表达", mediaUrl: ecommerceInspirationAssets.sprayScene, mediaType: "image" },
      { title: "露营家具详情", meta: "场景组合 · 尺寸说明", mediaUrl: ecommerceInspirationAssets.campingCart, mediaType: "image" },
      { title: "香氛A+页面", meta: "材质细节 · 品牌氛围", mediaUrl: ecommerceInspirationAssets.perfumeSet, mediaType: "image" },
      { title: "童装listing组合", meta: "多角度 · 人群展示", mediaUrl: ecommerceInspirationAssets.cosmeticApplication, mediaType: "image" },
      { title: "高考文具淘宝套图", meta: "文具套装 · 淘宝主图 · 卖点陈列", mediaUrl: ecommerceInspirationAssets.stationeryTaobaoSet, mediaType: "image" },
      { title: "条纹单人沙发套图", meta: "家居场景 · 多角度展示 · 软装质感", mediaUrl: ecommerceInspirationAssets.stripedSingleSofaSet, mediaType: "image" },
      { title: "棕色皮夹克照片集", meta: "服饰套图 · 质感细节 · 穿搭展示", mediaUrl: ecommerceInspirationAssets.brownLeatherJacketPhotoSet, mediaType: "image" },
      { title: "防晒帽模特佩戴", meta: "真人试戴 · 户外防晒 · 穿戴效果", mediaUrl: ecommerceInspirationAssets.modelSunHatTryon, mediaType: "image" },
      { title: "淘宝耳机商品图", meta: "数码主图 · 参数卖点 · 平台套图", mediaUrl: ecommerceInspirationAssets.taobaoEarphoneProduct, mediaType: "image" },
      { title: "Etsy香薰蜡烛套图", meta: "香氛氛围 · 手作质感 · 跨境陈列", mediaUrl: ecommerceInspirationAssets.etsyScentedCandleSet, mediaType: "image" },
    ],
  },
  {
    title: "商品视频",
    desc: "口播模拟 / 商品展示视频 / 社媒短片。",
    variant: "video",
    cards: [
      { title: "口播种草短片", meta: "手持展示 · 真实推荐", mediaUrl: ecommerceInspirationAssets.spokenReview, mediaType: "video" },
      { title: "香水质感视频", meta: "光影旋转 · 高级静物", mediaUrl: ecommerceInspirationAssets.perfumeTexture, mediaType: "video" },
      { title: "玩具互动短视频", meta: "生活场景 · 情绪表达", mediaUrl: ecommerceInspirationAssets.toyInteraction, mediaType: "video" },
      { title: "器皿产品展示", meta: "极简背景 · 材质突出", mediaUrl: ecommerceInspirationAssets.vesselDisplay, mediaType: "video" },
      { title: "饰品模特试戴", meta: "近景特写 · 搭配建议", mediaUrl: ecommerceInspirationAssets.jewelryModel, mediaType: "video" },
      { title: "包袋生活方式", meta: "室内场景 · 组合展示", mediaUrl: ecommerceInspirationAssets.sofaLifestyle, mediaType: "video" },
      { title: "口红TikTok带货", meta: "UGC口播 · 真实推荐 · 社媒转化", mediaUrl: ecommerceInspirationAssets.lipstickUgcTiktokVideo, mediaType: "video" },
      { title: "小夜灯抖音开箱", meta: "开箱种草 · 暖光氛围 · 竖版短片", mediaUrl: ecommerceInspirationAssets.nightLightUnboxingDouyin, mediaType: "video" },
      { title: "清洁剂痛点解决", meta: "问题演示 · 功效对比 · 抖音素材", mediaUrl: ecommerceInspirationAssets.cleanerPainpointDouyin, mediaType: "video" },
      { title: "连衣裙穿搭视频", meta: "服饰上身 · 场景走动 · 穿搭展示", mediaUrl: ecommerceInspirationAssets.dressOutfitVideo, mediaType: "video" },
      { title: "防晒霜TikTok种草", meta: "UGC测评 · 户外防晒 · 平台短片", mediaUrl: ecommerceInspirationAssets.sunscreenUgcTiktokVideo, mediaType: "video" },
      { title: "世界杯属性快闪", meta: "热点短片 · 节奏快闪 · 活动素材", mediaUrl: ecommerceInspirationAssets.worldCupFlashVideo, mediaType: "video" },
    ],
  },
] as const;

// 把灵感卡片的标题 + 卖点要点合成一段可直接填入指令栏的提示词。
const buildInspirationPrompt = (title: string, meta: string): string => {
  const points = meta
    .split(/[·、,，]/)
    .map((part) => part.trim())
    .filter(Boolean);
  const base = title.trim();
  return points.length ? `${base}。风格要点：${points.join("、")}。` : `${base}。`;
};

const clampNumber = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));

const normalizeHexColor = (value: string) => {
  const clean = value.trim().replace(/^#/, "");
  if (!/^[0-9a-fA-F]{6}$/.test(clean)) return null;
  return `#${clean.toLowerCase()}`;
};

const hexToRgb = (value: string) => {
  const normalized = normalizeHexColor(value);
  if (!normalized) return null;
  const numeric = Number.parseInt(normalized.slice(1), 16);
  return {
    r: (numeric >> 16) & 255,
    g: (numeric >> 8) & 255,
    b: numeric & 255,
  };
};

const rgbToHex = (r: number, g: number, b: number) =>
  `#${[r, g, b].map((item) => clampNumber(Math.round(item), 0, 255).toString(16).padStart(2, "0")).join("")}`;

const parseSmartCutoutAspect = (aspect: string) => {
  const match = aspect.match(/(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)/);
  if (!match) return null;
  const width = Number(match[1]);
  const height = Number(match[2]);
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
  return width / height;
};

const parseSmartCutoutPercent = (value: string, fallback: number) => {
  const numeric = Number(value.replace("%", ""));
  if (!Number.isFinite(numeric)) return fallback;
  return clampNumber(numeric / 100, 0.05, 1);
};

const hsvToRgb = (h: number, s: number, v: number) => {
  const hue = ((h % 360) + 360) % 360;
  const saturation = clampNumber(s, 0, 100) / 100;
  const value = clampNumber(v, 0, 100) / 100;
  const chroma = value * saturation;
  const x = chroma * (1 - Math.abs(((hue / 60) % 2) - 1));
  const match = value - chroma;
  const [red, green, blue] =
    hue < 60
      ? [chroma, x, 0]
      : hue < 120
        ? [x, chroma, 0]
        : hue < 180
          ? [0, chroma, x]
          : hue < 240
            ? [0, x, chroma]
            : hue < 300
              ? [x, 0, chroma]
              : [chroma, 0, x];
  return {
    r: (red + match) * 255,
    g: (green + match) * 255,
    b: (blue + match) * 255,
  };
};

const hexToHsv = (value: string) => {
  const rgb = hexToRgb(value) ?? { r: 255, g: 255, b: 255 };
  const red = rgb.r / 255;
  const green = rgb.g / 255;
  const blue = rgb.b / 255;
  const max = Math.max(red, green, blue);
  const min = Math.min(red, green, blue);
  const delta = max - min;
  const hue =
    delta === 0
      ? 0
      : max === red
        ? 60 * (((green - blue) / delta) % 6)
        : max === green
          ? 60 * ((blue - red) / delta + 2)
          : 60 * ((red - green) / delta + 4);
  return {
    h: Math.round((hue + 360) % 360),
    s: max === 0 ? 0 : Math.round((delta / max) * 100),
    v: Math.round(max * 100),
  };
};
import { aiGenerationClient } from "../../api/aiGenerationClient";
import { ServerRequestError } from "../../api/serverConnection";
import { waitForTask } from "../../api/taskSubscription";
import { toast } from "../../components/toast/toastStore";
import { useGenerationTasks } from "../../hooks/useGenerationTasks";
import { useAppStore } from "../../stores";
import {
  normalizeEcommerceImageMime,
  summarizeRejectedImages,
  validateEcommerceImageFiles,
} from "./ecommerceImageValidation";


interface ProductClonePageProps {
  [key: string]: unknown;
}

type ProductCloneStatus = "idle" | "ready" | "generating" | "done" | "failed";
type CommerceScenarioKey = "popular" | "poster" | "mainImage" | "scene" | "festival" | "model" | "background" | "retouch" | "salesVideo";
type CloneSetCountKey = "selling" | "white" | "scene";
type CloneModelPanelTab = "scene" | "model";
type CloneVideoQualityKey = "standard" | "high" | "ultra";
type ProductSetStatus = "idle" | "ready" | "generating" | "done" | "failed";
type ProductKitToolKey = "set" | "detail" | "wear" | "clone";
type ComposerMenuKey = "mode" | "platform" | "language" | "ratio" | "settings" | "assetLibrary" | "workMode" | "aiWrite";
type ComposerAssetTabKey = "recent" | "recipe" | "model";
type ComposerWorkModeKey = "quick" | "think";
type CloneBasicSelectKey = "platform" | "market" | "language" | "ratio";
type CloneModelSelectKey = "gender" | "age" | "ethnicity" | "body";
type CloneReferenceMode = "upload" | "link";
type CloneReplicateLevelKey = "style" | "high";
type CloneTemplateAsset = {
  id: string;
  title: string;
  prompt: string;
  mediaUrl: string;
};
interface CommerceScenarioTemplate extends CloneTemplateAsset {
  scenario: Exclude<CommerceScenarioKey, "popular">;
  output: ProductSetOutputKey;
  desc: string;
  badge: string;
}
type TryOnModelSource = "ai" | "library";
type TryOnStatus = "idle" | "modeling" | "ready" | "generating" | "done" | "failed";
type DetailStatus = "idle" | "ready" | "generating" | "done" | "failed";

interface CloneImageItem {
  id: string;
  src: string;
  name: string;
  file?: File;
  width?: number;
  height?: number;
  format?: string;
  mimeType?: string;
  ossKey?: string;
}

interface CloneResult {
  id: string;
  src: string;
  label: string;
  type?: "image" | "video";
}

interface CanvasNode {
  id: string;
  mode: string;
  sourceImage?: string;
  results: CloneResult[];
  createdAt: number;
  x: number;
  y: number;
}

interface PreviewTouchPoint {
  id: number;
  x: number;
  y: number;
}

interface PreviewTouchGesture {
  mode: "none" | "pan" | "pinch";
  points: PreviewTouchPoint[];
  startOffset: { x: number; y: number };
  startZoom: number;
  startDistance: number;
  startCenter: { x: number; y: number };
}

interface CloneSavedSetting {
  id: string;
  name: string;
  savedAt: string;
  output: CloneOutputKey;
  platform: string;
  market: string;
  language: string;
  ratio: string;
  setCounts: Record<CloneSetCountKey, number>;
  detailModules: string[];
  modelPanelTab: CloneModelPanelTab;
  modelScenes: string[];
  modelCustomScene: string;
  modelGender: string;
  modelAge: string;
  modelEthnicity: string;
  modelBody: string;
  modelAppearance: string;
  videoQuality: CloneVideoQualityKey;
  videoDurationSeconds: number;
  videoSmart: boolean;
  referenceMode?: CloneReferenceMode;
  replicateLevel?: CloneReplicateLevelKey;
  requirement: string;
}

type EcommerceHistoryStatus = "generating" | "done" | "failed";

interface EcommerceHistoryTurn {
  id: string;
  createdAt: number;
  status: EcommerceHistoryStatus;
  errorMessage?: string;
  output: CloneOutputKey;
  platform: string;
  market: string;
  language: string;
  ratio: string;
  requirement: string;
  productImages: CloneImageItem[];
  results: CloneResult[];
  setResultImages: string[];
  setCounts: Record<CloneSetCountKey, number>;
  detailModules: string[];
  modelScenes: string[];
  referenceImages: CloneImageItem[];
  replicateLevel: CloneReplicateLevelKey;
}

interface EcommerceHistoryRecord {
  id: string;
  title: string;
  createdAt: number;
  status?: EcommerceHistoryStatus;
  errorMessage?: string;
  output: CloneOutputKey;
  platform: string;
  market: string;
  language: string;
  ratio: string;
  requirement: string;
  productImages: CloneImageItem[];
  results: CloneResult[];
  setResultImages: string[];
  setCounts: Record<CloneSetCountKey, number>;
  detailModules: string[];
  modelScenes: string[];
  referenceImages: CloneImageItem[];
  replicateLevel: CloneReplicateLevelKey;
  turns?: EcommerceHistoryTurn[];
}

interface ProductSetPreviewSelection {
  src: string;
  label: string;
  nodeId?: string;
  cardId?: string;
  removable?: boolean;
}

interface EcommerceImagePromptOptions {
  gender?: string;
  age?: string;
  ethnicity?: string;
  body?: string;
  appearance?: string;
  scenes?: string[];
  customScene?: string;
  smartScene?: boolean;
  detailModules?: string[];
}

type PlatformRatioModeKey = ProductSetOutputKey | "hot";

interface PlatformRatioGroup {
  ratios: string[];
  defaultRatio: string;
}

const sideTools: Array<{ key: ProductKitToolKey; label: string; icon: ReactNode }> = [
  { key: "set", label: "商品套图", icon: <AppstoreOutlined /> },
  { key: "detail", label: "A+详情", icon: <FileImageOutlined /> },
  { key: "wear", label: "服饰穿搭", icon: <SkinOutlined /> },
  { key: "clone", label: "电商AI作图", icon: <AppstoreOutlined /> },
];

const platformSpecOptions: Array<{
  label: string;
  ratios: string[];
  defaultRatio: string;
  ratioGroups?: Partial<Record<PlatformRatioModeKey, PlatformRatioGroup>>;
  specs: string[];
  tip?: string;
  aliases?: string[];
}> = [
  {
    label: "淘宝/天猫",
    ratios: ["淘宝主图 / SKU 图 800×800px", "详情页宽 750px", "详情页宽 790px"],
    defaultRatio: "淘宝主图 / SKU 图 800×800px",
    ratioGroups: {
      set: {
        ratios: ["1000×1000px\u00a0\u00a0\u00a01:1", "800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: [
          "750×1000px\u00a0\u00a0\u00a03:4",
          "790×1053px\u00a0\u00a0\u00a03:4",
          "750×1125px\u00a0\u00a0\u00a02:3",
          "790×1185px\u00a0\u00a0\u00a02:3",
        ],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      model: {
        ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1080×1440px\u00a0\u00a0\u00a03:4", "1080×1080px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["主图 / SKU 图 800×800px，≤3MB", "详情页宽 750px 或 790px，单张高度≤1546px"],
    tip: "建议主图 200-400KB JPG，超过 500KB 会影响加载速度。",
  },
  {
    label: "京东",
    ratios: ["京东主图 / SKU 图 800×800px", "详情页宽 750px", "首图主体占比 ≥80%"],
    defaultRatio: "京东主图 / SKU 图 800×800px",
    ratioGroups: {
      set: {
        ratios: ["1000×1000px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: [
          "750×1000px\u00a0\u00a0\u00a03:4",
          "990×1320px\u00a0\u00a0\u00a03:4",
          "750×1125px\u00a0\u00a0\u00a02:3",
          "990×1485px\u00a0\u00a0\u00a02:3",
        ],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      model: {
        ratios: ["750×1125px\u00a0\u00a0\u00a02:3", "990×1485px\u00a0\u00a0\u00a02:3"],
        defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1920×1080px\u00a0\u00a0\u00a016:9"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["主图 / SKU 图 800×800px，白底，≤3MB", "详情页宽 750px，首图主体占比 ≥80%"],
  },
  {
    label: "拼多多",
    ratios: ["主图 750×352px", "主图 800×800px", "详情页宽 750px"],
    defaultRatio: "主图 750×352px",
    ratioGroups: {
      set: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1", "750×1000px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      model: {
        ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["主图 750×352px 或 800×800px，≤1MB", "详情页宽 750px，要求纯白底、无水印、无拼接"],
  },
  {
    label: "抖音电商",
    ratios: ["短视频1080×1920px"],
    defaultRatio: "短视频1080×1920px",
    ratioGroups: {
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["短视频 1080×1920px，9:16", "30s 内最佳"],
  },
  {
    label: "亚马逊 Amazon",
    ratios: ["主图 ≥1600×1600px", "建议 2000×2000px+", "最小 500×500px"],
    defaultRatio: "主图 ≥1600×1600px",
    ratioGroups: {
      set: {
        ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["1600×1600px\u00a0\u00a0\u00a01:1", "1200×1800px\u00a0\u00a0\u00a02:3", "1200×1600px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "1200×1800px\u00a0\u00a0\u00a02:3",
      },
      model: {
        ratios: ["1200×1800px\u00a0\u00a0\u00a02:3"],
        defaultRatio: "1200×1800px\u00a0\u00a0\u00a02:3",
      },
      video: {
        ratios: ["1920×1080px\u00a0\u00a0\u00a016:9"],
        defaultRatio: "1920×1080px\u00a0\u00a0\u00a016:9",
      },
      hot: {
        ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["主图 1600×1600px+，纯白底，≤10MB", "最小 500×500px，建议 2000px+ 以支持缩放"],
    aliases: ["亚马逊"],
  },
  {
    label: "Shopee",
    ratios: ["商品主图 1024×1024px", "基础主图 800×800px"],
    defaultRatio: "商品主图 1024×1024px",
    ratioGroups: {
      set: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      model: {
        ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["商品主图推荐 1024×1024px，基础 800×800px", "≤2MB，白底或浅色底"],
    aliases: ["虾皮 Shopee/Lazada", "虾皮"],
  },
  {
    label: "Lazada",
    ratios: ["商品主图 800×800px"],
    defaultRatio: "商品主图 800×800px",
    ratioGroups: {
      set: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      model: {
        ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["商品主图 800×800px，1:1"],
  },
  {
    label: "Instagram",
    ratios: ["帖子 1080×1350px", "帖子 1080×1080px", "Stories / Reels 1080×1920px", "头像 320×320px"],
    defaultRatio: "帖子 1080×1350px",
    ratioGroups: {
      set: {
        ratios: ["1080×1080px\u00a0\u00a0\u00a01:1", "1080×1350px\u00a0\u00a0\u00a04:5"],
        defaultRatio: "1080×1080px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
        defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
      },
      model: {
        ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
        defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1080×1350px\u00a0\u00a0\u00a04:5"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
    },
    specs: ["帖子 1080×1350px 或 1080×1080px", "Stories / Reels 封面 1080×1920px，头像 320×320px"],
    tip: "建议 ≤8MB JPG。",
    aliases: ["Instagram Reels"],
  },
  {
    label: "速卖通",
    ratios: ["主图 800×800px", "主图 1000×1000px+"],
    defaultRatio: "主图 800×800px",
    ratioGroups: {
      set: {
        ratios: ["1000×1000px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["750×1125px\u00a0\u00a0\u00a02:3", "750×1000px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3",
      },
      model: {
        ratios: ["750×1125px\u00a0\u00a0\u00a02:3"],
        defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1920×1080px\u00a0\u00a0\u00a016:9"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["主图建议 800×800px 或更高，1:1", "适合跨境电商主图、SKU 图和场景图"],
  },
  {
    label: "eBay",
    ratios: ["商品图1:1", "白底多角度展示图 1:1"],
    defaultRatio: "商品图1:1",
    ratioGroups: {
      set: {
        ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["1000×1500px\u00a0\u00a0\u00a02:3", "1200×1600px\u00a0\u00a0\u00a03:4"],
        defaultRatio: "1000×1500px\u00a0\u00a0\u00a02:3",
      },
      model: {
        ratios: ["1000×1500px\u00a0\u00a0\u00a02:3"],
        defaultRatio: "1000×1500px\u00a0\u00a0\u00a02:3",
      },
      video: {
        ratios: ["1920×1080px\u00a0\u00a0\u00a016:9", "1080×1920px\u00a0\u00a0\u00a09:16"],
        defaultRatio: "1920×1080px\u00a0\u00a0\u00a016:9",
      },
      hot: {
        ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["商品图建议 1:1，主体清晰居中", "适合白底主图和多角度展示图"],
  },
  {
    label: "TikTok Shop",
    ratios: ["商品主图 1:1", "短视频/ 竖版封面 9:16"],
    defaultRatio: "商品主图 1:1",
    ratioGroups: {
      set: {
        ratios: ["1280×1280px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "1280×1280px\u00a0\u00a0\u00a01:1",
      },
      detail: {
        ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
        defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
      },
      model: {
        ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
        defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
      },
      video: {
        ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
        defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
      },
      hot: {
        ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
        defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
      },
    },
    specs: ["商品主图建议 1:1", "短视频竖版封面建议 9:16"],
  },
];
const platformOptions = platformSpecOptions.map((option) => option.label);
const getPlatformLogoText = (value: string) => {
  const normalized = value.toLowerCase();
  if (value.includes("淘宝") || value.includes("天猫")) return "淘";
  if (value.includes("京东")) return "京";
  if (value.includes("拼多多") || value.includes("鎷煎澶")) return "拼";
  if (value.includes("抖音")) return "抖";
  if (normalized.includes("amazon")) return "a";
  if (normalized.includes("shopee")) return "S";
  if (normalized.includes("lazada")) return "L";
  if (normalized.includes("instagram")) return "IG";
  if (value.includes("速卖通") || value.includes("閫熷崠閫")) return "AE";
  if (normalized.includes("ebay")) return "eB";
  if (normalized.includes("tiktok")) return "♪";
  return value.trim().slice(0, 1).toUpperCase() || "商";
};
const getPlatformLogoVariant = (value: string) => {
  const normalized = value.toLowerCase();
  if (value.includes("淘宝") || value.includes("天猫")) return "taobao";
  if (value.includes("京东")) return "jd";
  if (value.includes("拼多多") || value.includes("鎷煎澶")) return "pdd";
  if (value.includes("抖音")) return "douyin";
  if (normalized.includes("amazon")) return "amazon";
  if (normalized.includes("shopee")) return "shopee";
  if (normalized.includes("lazada")) return "lazada";
  if (normalized.includes("instagram")) return "instagram";
  if (value.includes("速卖通") || value.includes("閫熷崠閫")) return "aliexpress";
  if (normalized.includes("ebay")) return "ebay";
  if (normalized.includes("tiktok")) return "tiktok";
  return "default";
};
const getPlatformLogoMarks = (value: string) => {
  if (value.includes("淘宝") || value.includes("天猫")) return ["淘", "猫"];
  return [getPlatformLogoText(value)];
};
const renderPlatformLogo = (value: string) => {
  const marks = getPlatformLogoMarks(value);
  const variant = getPlatformLogoVariant(value);
  return (
    <span
      className={`ecom-platform-logo-mark ecom-platform-logo-mark--${variant}${marks.length > 1 ? " ecom-platform-logo-mark--duo" : ""}`}
      aria-hidden="true"
    >
      {marks.map((text) => (
        <span key={text} className={`ecom-platform-logo-mark__tile${text.length > 1 ? " ecom-platform-logo-mark__tile--wide" : ""}`}>
          {text}
        </span>
      ))}
    </span>
  );
};
const marketLanguageOptions: Array<{ country: string; languages: string[] }> = [
  { country: "中国", languages: ["中文"] },
  { country: "美国", languages: ["英文"] },
  { country: "加拿大", languages: ["英文", "法文"] },
  { country: "英国", languages: ["英文"] },
  { country: "德国", languages: ["德文"] },
  { country: "法国", languages: ["法文"] },
  { country: "意大利", languages: ["意大利语"] },
  { country: "西班牙", languages: ["西班牙语"] },
  { country: "日本", languages: ["日文"] },
  { country: "韩国", languages: ["韩文"] },
  { country: "澳大利亚", languages: ["英文"] },
  { country: "新加坡", languages: ["英文", "中文"] },
  { country: "马来西亚", languages: ["马来语", "英文", "中文"] },
  { country: "印尼", languages: ["印度尼西亚语", "英文"] },
  { country: "越南", languages: ["越南语", "英文"] },
  { country: "泰国", languages: ["泰语", "英文"] },
  { country: "菲律宾", languages: ["菲律宾语（他加禄语）", "英文"] },
  { country: "巴西", languages: ["葡萄牙语"] },
  { country: "墨西哥", languages: ["西班牙语"] },
  { country: "智利", languages: ["西班牙语"] },
  { country: "哥伦比亚", languages: ["西班牙语"] },
  { country: "阿联酋", languages: ["阿拉伯语", "英文"] },
  { country: "沙特阿拉伯", languages: ["阿拉伯语", "英文"] },
  { country: "俄罗斯", languages: ["俄语"] },
  { country: "波兰", languages: ["波兰语"] },
];
const marketOptions = marketLanguageOptions.map((option) => option.country);
const languageOptions = Array.from(new Set(marketLanguageOptions.flatMap((option) => option.languages)));
const languageAliases: Record<string, string> = {
  "英文": "英文",
  "中文": "中文",
  "英语": "英文",
  "日语": "日文",
  "日文": "日文",
  "德语": "德文",
  "德文": "德文",
  "法语": "法文",
  "法文": "法文",
  "韩语": "韩文",
  "韩文": "韩文",
  "西文": "西班牙语",
  "西班牙语": "西班牙语",
  "葡文": "葡萄牙语",
  "葡萄牙语": "葡萄牙语",
  "印尼语": "印度尼西亚语",
  "印度尼西亚语": "印度尼西亚语",
  "菲律宾语": "菲律宾语（他加禄语）",
  "菲律宾语（他加禄语）": "菲律宾语（他加禄语）",
};
const defaultPlatformSpec = platformSpecOptions[0]!;
const getPlatformSpec = (value: string) =>
  platformSpecOptions.find((option) => option.label === value || option.aliases?.includes(value)) ?? defaultPlatformSpec;
const legacyPlatformAliases: Record<string, string> = {
  "淘宝/天猫": "淘宝/天猫",
  "京东": "京东",
  "拼多多": "拼多多",
  "抖音电商": "抖音电商",
  "亚马逊Amazon": "亚马逊 Amazon",
  "速卖通": "速卖通",
};
const normalizePlatform = (value: string) => getPlatformSpec(legacyPlatformAliases[value] ?? value).label;
const domesticPlatformLabels = new Set(["淘宝/天猫", "京东", "拼多多", "抖音电商"]);
const domesticPlatformLanguages = ["中文"];
const isDomesticPlatform = (platformValue: string) => domesticPlatformLabels.has(normalizePlatform(platformValue));
const getPlatformRatioGroup = (value: string, mode?: PlatformRatioModeKey): PlatformRatioGroup => {
  const platformSpec = getPlatformSpec(value);
  return (mode ? platformSpec.ratioGroups?.[mode] : null) ?? {
    ratios: platformSpec.ratios,
    defaultRatio: platformSpec.defaultRatio,
  };
};
const getPlatformRatioOptions = (value: string, mode?: PlatformRatioModeKey) => getPlatformRatioGroup(value, mode).ratios;
const getPlatformDefaultRatio = (value: string, mode?: PlatformRatioModeKey) => getPlatformRatioGroup(value, mode).defaultRatio;
const getUniqueRatioOptions = (ratios: string[]) => Array.from(new Set(ratios));
const normalizeRatioToken = (value: string) =>
  value
    .replaceAll("\u00a0", " ")
    .replaceAll("脳", "×")
    .replaceAll("*", "×")
    .replaceAll("：", ":")
    .replace(/锛\?/g, ":")
    .replace(/\s+/g, " ")
    .trim();
const normalizeRatioForPlatform = (platformValue: string, ratioValue: string, mode?: PlatformRatioModeKey) => {
  const platformRatios = getPlatformRatioOptions(platformValue, mode);
  if (platformRatios.includes(ratioValue)) return ratioValue;
  const normalizedRatio = normalizeRatioToken(ratioValue);
  const matchedRatio = platformRatios.find((option) => normalizeRatioToken(option).includes(normalizedRatio));
  return matchedRatio ?? getPlatformDefaultRatio(platformValue, mode);
};
const quickSetRatioOptions = ["1:1", "3:4", "4:3", "9:16", "16:9"];
const getQuickSetRatioValue = (value: string) => {
  const normalizedValue = normalizeRatioToken(value);
  if (quickSetRatioOptions.includes(normalizedValue)) return normalizedValue;
  const sizeMatch = normalizedValue.match(/(\d+)\s*[×xX]\s*(\d+)/u);
  if (sizeMatch) {
    const width = Number(sizeMatch[1]);
    const height = Number(sizeMatch[2]);
    if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0) {
      const aspect = formatAspectRatio(width, height);
      if (quickSetRatioOptions.includes(aspect)) return aspect;
    }
  }
  const ratioMatch = normalizedValue.match(/(\d+)\s*[:：]\s*(\d+)/u);
  if (ratioMatch) {
    const aspect = `${Number(ratioMatch[1])}:${Number(ratioMatch[2])}`;
    if (quickSetRatioOptions.includes(aspect)) return aspect;
  }
  return quickSetRatioOptions[0]!;
};
const formatRatioDisplayValue = (value: string) => {
  const normalizedValue = normalizeRatioToken(value);
  const sizeMatch = normalizedValue.match(/(\d+)\s*[×xX]\s*(\d+)\s*px?/u);
  if (sizeMatch) {
    const width = Number(sizeMatch[1]);
    const height = Number(sizeMatch[2]);
    return `${width}×${height}px\u00a0\u00a0\u00a0${formatAspectRatio(width, height)}`;
  }
  return normalizedValue
    .replace("淘宝主图 / SKU 图 ", "淘宝主图 / SKU 图 ")
    .replace("京东主图 / SKU 图 ", "京东主图 / SKU 图 ")
    .replace("详情页宽", "详情页宽")
    .replace("短视频", "短视频")
    .replace("主图", "主图")
    .replace("商品主图", "商品主图")
    .replace("鍟嗗搧鍥?", "商品图")
    .replace(/\s+:/g, ":")
    .replace(/:\s+/g, ":");
};
const getRatioDisplayParts = (value: string) => {
  const display = formatRatioDisplayValue(value).replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
  const aspectMatch = display.match(/(\d+\s*[:：]\s*\d+)(?!.*\d+\s*[:：]\s*\d+)/u);
  const aspect = aspectMatch?.[1]?.replace(/\s+/g, "") ?? "自适应";
  const size = aspectMatch ? display.replace(aspectMatch[0], "").trim() : display;
  return {
    size: size || "原图比例",
    aspect,
  };
};
/** Extract CSS aspect-ratio from a ratio string like "1000x1000px   1:1" -> "1 / 1" */
const parseRatioToAspectCss = (ratioStr: string): string => {
  const match = ratioStr.match(/(\d+)\D+(\d+)/u);
  if (!match) return "1 / 1";
  return `${match[1]} / ${match[2]}`;
};
const supportedImageApiRatios = ["1:1", "3:4", "4:3", "9:16", "16:9"] as const;
type SupportedImageApiRatio = typeof supportedImageApiRatios[number];

const toSupportedImageApiRatio = (width: number, height: number): SupportedImageApiRatio => {
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return "1:1";
  let bestRatio: SupportedImageApiRatio = "1:1";
  let bestScore = Number.POSITIVE_INFINITY;
  const target = Math.log(width / height);
  for (const ratio of supportedImageApiRatios) {
    const [left, right] = ratio.split(":").map(Number);
    const score = Math.abs(target - Math.log(left / right));
    if (score < bestScore) {
      bestRatio = ratio;
      bestScore = score;
    }
  }
  return bestRatio;
};

/** Normalize ratio display string ("1000×1000px   1:1") to an image API aspect ratio ("1:1"). */
const normalizeRatioForApi = (ratioStr: string): string => {
  const normalizedValue = normalizeRatioToken(ratioStr);
  const explicitRatios = Array.from(normalizedValue.matchAll(/(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)/g));
  const explicitRatio = explicitRatios.at(-1);
  if (explicitRatio) {
    return toSupportedImageApiRatio(Number(explicitRatio[1]), Number(explicitRatio[2]));
  }

  const sizeMatch = normalizedValue.match(/(\d+(?:\.\d+)?)\s*[×xX*]\s*(\d+(?:\.\d+)?)/u);
  if (!sizeMatch) return "1:1";
  return toSupportedImageApiRatio(Number(sizeMatch[1]), Number(sizeMatch[2]));
};
const greatestCommonDivisor = (left: number, right: number): number => {
  let a = Math.abs(left);
  let b = Math.abs(right);
  while (b) {
    [a, b] = [b, a % b];
  }
  return a || 1;
};
const formatAspectRatio = (width: number, height: number) => {
  const divisor = greatestCommonDivisor(width, height);
  return `${Math.round(width / divisor)}:${Math.round(height / divisor)}`;
};
const formatUploadedImageRatio = (image?: CloneImageItem) => {
  if (!image) return null;
  const format = image.format ? `\u00a0\u00a0\u00a0${image.format}` : "";
  if (!image.width || !image.height) return `上传图片\u00a0\u00a0\u00a0原图比例${format}`;
  return `上传图片 ${image.width}×${image.height}px\u00a0\u00a0\u00a0${formatAspectRatio(image.width, image.height)}${format}`;
};
const defaultMarketLanguageOption = marketLanguageOptions[0]!;
const normalizeMarket = (value: string) =>
  marketLanguageOptions.some((option) => option.country === value) ? value : defaultMarketLanguageOption.country;
const normalizeLanguage = (value: string) => languageAliases[value] ?? value;
const uniqueLanguages = (languages: string[]) => Array.from(new Set(languages));
const appendEnglish = (languages: string[]) => Array.from(new Set([...languages, "英文"]));
const getMarketLanguageOptions = (marketValue: string) =>
  appendEnglish((marketLanguageOptions.find((option) => option.country === marketValue) ?? defaultMarketLanguageOption).languages);
const getPlatformLanguageOptions = (platformValue: string, marketValue: string) => {
  const marketLanguages = getMarketLanguageOptions(marketValue);
  if (!isDomesticPlatform(platformValue)) return marketLanguages;
  const localLanguages = marketLanguages.filter((item) => item !== "英文");
  return uniqueLanguages([...localLanguages, ...domesticPlatformLanguages, "英文"]);
};
const getPlatformDefaultLanguage = (platformValue: string, marketValue: string) =>
  isDomesticPlatform(platformValue) ? "中文" : (getPlatformLanguageOptions(platformValue, marketValue)[0] ?? languageOptions[0] ?? "英文");
const normalizeLanguageForPlatform = (platformValue: string, marketValue: string, languageValue: string) => {
  const normalizedLanguage = normalizeLanguage(languageValue);
  const platformLanguages = getPlatformLanguageOptions(platformValue, marketValue);
  return platformLanguages.includes(normalizedLanguage) ? normalizedLanguage : getPlatformDefaultLanguage(platformValue, marketValue);
};
const productSetOutputOptions: Array<{ key: ProductSetOutputKey; label: string; desc: string; icon: ReactNode }> = [
  { key: "set", label: "套图", desc: "主图/卖点/场景", icon: <AppstoreOutlined /> },
  { key: "detail", label: "详情图", desc: "长图模块化生成", icon: <LayoutOutlined /> },
  { key: "model", label: "模特图", desc: "真人穿搭展示", icon: <SkinOutlined /> },
  { key: "video", label: "短视频", desc: "分镜视频链路", icon: <VideoCameraOutlined /> },
];
const cloneOutputOptions: Array<{ key: ProductSetOutputKey; label: string; desc: string; icon: ReactNode }> = [
  ...productSetOutputOptions,
];
const commerceScenarioOptions: Array<{ key: CommerceScenarioKey; label: string; desc: string; icon: ReactNode }> = [
  { key: "popular", label: "热门", desc: "高频模板", icon: <span role="img" aria-label="fire">🔥</span> },
  { key: "poster", label: "海报生成", desc: "活动视觉", icon: <span role="img" aria-label="poster">🎨</span> },
  { key: "mainImage", label: "商品主图", desc: "主图转化", icon: <span role="img" aria-label="product">🛍️</span> },
  { key: "model", label: "模特图", desc: "真人展示", icon: <span role="img" aria-label="model">🕴️</span> },
  { key: "scene", label: "场景图", desc: "生活氛围", icon: <span role="img" aria-label="scene">🌅</span> },
  { key: "festival", label: "节日风格图", desc: "节点营销", icon: <span role="img" aria-label="festival">🎉</span> },
  { key: "salesVideo", label: "带货视频", desc: "短视频脚本", icon: <span role="img" aria-label="video">🎬</span> },
  { key: "background", label: "更换背景", desc: "背景重构", icon: <span role="img" aria-label="background">✨</span> },
  { key: "retouch", label: "无痕改图", desc: "精修优化", icon: <span role="img" aria-label="retouch">🪄</span> },
];
const primaryCommerceScenarioKeys: CommerceScenarioKey[] = ["popular", "poster", "mainImage", "model"];
const scenarioSettingsKeys: CommerceScenarioKey[] = ["poster", "mainImage", "model", "scene", "festival", "salesVideo"];
const scenarioAdvancedSettingsKeys: CommerceScenarioKey[] = ["model", "salesVideo"];
const commerceScenarioOutputMap: Record<Exclude<CommerceScenarioKey, "popular">, ProductSetOutputKey> = {
  poster: "set",
  mainImage: "set",
  scene: "set",
  festival: "set",
  model: "model",
  background: "set",
  retouch: "set",
  salesVideo: "video",
};
const commerceScenarioTemplates: CommerceScenarioTemplate[] = [
  {
    id: "poster-campaign-clean",
    scenario: "poster",
    output: "set",
    title: "新品活动海报",
    desc: "适合首发、上新、促销专题的主视觉",
    badge: "高频推荐",
    prompt: "帮我生成一张电商新品活动海报，突出产品主体、核心卖点和促销氛围，画面干净高级，适合店铺首页和广告投放。",
    mediaUrl: ossAssets.ecommerce.detail.longPage,
  },
  {
    id: "poster-social-drop",
    scenario: "poster",
    output: "set",
    title: "社媒种草海报",
    desc: "更适合小红书、朋友圈、站外广告",
    badge: "热门模板",
    prompt: "生成一张社媒种草风格商品海报，突出产品质感、生活方式和一句清晰卖点，画面轻盈、有品牌感。",
    mediaUrl: ossAssets.ecommerce.inspiration.officeStyleSet,
  },
  {
    id: "main-clean-product",
    scenario: "mainImage",
    output: "set",
    title: "高转化商品主图",
    desc: "白底/浅场景，主体清楚，卖点明确",
    badge: "高频推荐",
    prompt: "生成一张高转化商品主图，产品主体居中清晰，背景简洁，突出核心卖点和材质细节，适合电商搜索列表展示。",
    mediaUrl: ossAssets.ecommerce.productSet.main,
  },
  {
    id: "main-selling-point",
    scenario: "mainImage",
    output: "set",
    title: "卖点强化主图",
    desc: "适合列表点击率优化",
    badge: "点击率优先",
    prompt: "生成一张卖点强化商品主图，保留产品真实质感，加入清晰卖点表达和轻量信息层级，适合提升列表点击率。",
    mediaUrl: ossAssets.ecommerce.productSet.selling,
  },
  {
    id: "scene-lifestyle",
    scenario: "scene",
    output: "set",
    title: "生活方式场景图",
    desc: "把商品放进真实使用环境",
    badge: "高频推荐",
    prompt: "生成生活方式商品场景图，把产品自然放入真实使用环境，突出使用感、氛围和购买理由，画面真实且商业化。",
    mediaUrl: ossAssets.ecommerce.productSet.scene,
  },
  {
    id: "scene-premium",
    scenario: "scene",
    output: "set",
    title: "高级质感场景",
    desc: "适合品牌调性和详情页氛围图",
    badge: "品牌感",
    prompt: "生成高级质感商品场景图，背景克制、光影柔和，突出产品材质、轮廓和品牌调性，适合详情页和广告素材。",
    mediaUrl: ossAssets.ecommerce.detail.gridA,
  },
  {
    id: "festival-seasonal",
    scenario: "festival",
    output: "set",
    title: "节日营销图",
    desc: "适合大促、节庆、节点活动",
    badge: "节点营销",
    prompt: "生成节日营销风格商品图，结合节日氛围和促销视觉，但保持产品主体清晰、信息不过载，适合电商活动投放。",
    mediaUrl: ossAssets.ecommerce.detail.gridB,
  },
  {
    id: "festival-gift",
    scenario: "festival",
    output: "set",
    title: "礼赠氛围图",
    desc: "适合礼盒、礼品、节日送礼场景",
    badge: "热门模板",
    prompt: "生成礼赠氛围商品图，突出节日送礼感、包装质感和温暖情绪，画面高级克制，适合活动页与社媒投放。",
    mediaUrl: ossAssets.ecommerce.detail.gridC,
  },
  {
    id: "model-natural-fit",
    scenario: "model",
    output: "model",
    title: "自然穿搭模特图",
    desc: "突出上身效果、版型和真实穿着",
    badge: "高频推荐",
    prompt: "生成自然穿搭模特图，突出服饰上身效果、版型和整体气质，模特姿态自然，适合服饰电商详情与主图展示。",
    mediaUrl: ossAssets.ecommerce.tryOn.dressA,
  },
  {
    id: "model-street",
    scenario: "model",
    output: "model",
    title: "街拍模特场景",
    desc: "更适合年轻化、生活方式品牌",
    badge: "风格推荐",
    prompt: "生成街拍风格模特图，模特自然展示商品，背景有生活气息，突出穿搭氛围、比例和品牌调性。",
    mediaUrl: ossAssets.ecommerce.tryOn.modelWoman,
  },
  {
    id: "background-clean",
    scenario: "background",
    output: "set",
    title: "商品换浅色背景",
    desc: "保留主体，重构干净商业背景",
    badge: "高频推荐",
    prompt: "为商品更换干净浅色商业背景，保留产品主体、边缘和材质细节，整体画面适合电商主图和广告素材。",
    mediaUrl: ossAssets.ecommerce.productSet.detail,
  },
  {
    id: "background-scene",
    scenario: "background",
    output: "set",
    title: "商品换场景背景",
    desc: "从普通拍摄变成真实使用场景",
    badge: "场景增强",
    prompt: "为商品更换真实使用场景背景，保持主体比例和边缘自然，增强生活化氛围和商业转化感。",
    mediaUrl: ossAssets.ecommerce.productSet.scene,
  },
  {
    id: "retouch-clean",
    scenario: "retouch",
    output: "set",
    title: "白底精修图",
    desc: "修正瑕疵、增强质感和边缘细节",
    badge: "高频推荐",
    prompt: "对商品图进行无痕精修，清理瑕疵、优化光影和边缘细节，保持商品真实结构，输出干净高级的电商图。",
    mediaUrl: ossAssets.ecommerce.productSet.main,
  },
  {
    id: "retouch-premium",
    scenario: "retouch",
    output: "set",
    title: "质感增强图",
    desc: "强化材质、反光和商品高级感",
    badge: "精修模板",
    prompt: "对商品图进行质感增强，强化材质、光泽、纹理和立体感，画面自然不过度修饰，适合商业广告素材。",
    mediaUrl: ossAssets.ecommerce.productSet.selling,
  },
  {
    id: "sales-video-hook",
    scenario: "salesVideo",
    output: "video",
    title: "带货视频开场",
    desc: "第一秒抓住注意力，快速进入卖点",
    badge: "高频推荐",
    prompt: "生成电商带货短视频脚本和分镜，第一秒突出产品和痛点，随后展示核心卖点、使用场景和行动引导。",
    mediaUrl: ossAssets.ecommerce.inspiration.tiktokPreference,
  },
  {
    id: "sales-video-demo",
    scenario: "salesVideo",
    output: "video",
    title: "使用演示视频",
    desc: "适合讲解型、种草型短视频",
    badge: "转化优先",
    prompt: "生成商品使用演示短视频分镜，围绕使用过程、关键卖点和效果对比展开，节奏清晰，适合带货转化。",
    mediaUrl: ossAssets.ecommerce.inspiration.asinListing,
  },
  {
    id: "poster-festival-gift",
    scenario: "poster",
    output: "set",
    title: "节日礼赠海报",
    desc: "适合父亲节、母亲节等节点礼赠氛围",
    badge: "节点营销",
    prompt: "生成一张节日礼赠风格电商海报，突出礼盒质感、温馨氛围和送礼情绪，画面高级克制，适合节日活动投放。",
    mediaUrl: ossAssets.ecommerce.inspiration.fathersDaySet,
  },
  {
    id: "poster-luxury-perfume",
    scenario: "poster",
    output: "set",
    title: "奢品香水海报",
    desc: "高端质感，适合美妆香氛品牌",
    badge: "品牌感",
    prompt: "生成一张奢品香水电商海报，突出瓶身质感、光影层次和高端氛围，画面精致有艺术感，适合品牌旗舰店投放。",
    mediaUrl: ossAssets.ecommerce.inspiration.perfumeSet,
  },
  {
    id: "main-image-model",
    scenario: "mainImage",
    output: "set",
    title: "模特展示主图",
    desc: "真人上身，提升列表点击率",
    badge: "点击率优先",
    prompt: "生成一张真人模特展示商品主图，突出上身效果、版型和搭配，背景简洁，适合提升搜索列表点击率和转化。",
    mediaUrl: ossAssets.ecommerce.productSet.model,
  },
  {
    id: "main-image-detail",
    scenario: "mainImage",
    output: "set",
    title: "细节质感主图",
    desc: "材质特写，强化购买信心",
    badge: "转化优先",
    prompt: "生成一张商品细节质感主图，突出材质纹理、工艺细节和真实触感，画面聚焦主体，适合强化用户购买信心。",
    mediaUrl: ossAssets.ecommerce.productSet.detail,
  },
  {
    id: "model-jacket",
    scenario: "model",
    output: "model",
    title: "男装夹克模特",
    desc: "硬朗风格，突出版型和质感",
    badge: "风格推荐",
    prompt: "生成男装夹克模特展示图，模特姿态自然有型，突出夹克版型、面料质感和整体搭配，适合男装电商详情和主图。",
    mediaUrl: ossAssets.ecommerce.tryOn.jacketResultA,
  },
  {
    id: "model-hat",
    scenario: "model",
    output: "model",
    title: "帽子配饰模特",
    desc: "细节展示，适合配饰品类",
    badge: "高频推荐",
    prompt: "生成帽子配饰模特展示图，突出帽型、佩戴效果和搭配细节，模特姿态自然，适合配饰、帽饰电商详情与主图。",
    mediaUrl: ossAssets.ecommerce.tryOn.hatResultA,
  },
  {
    id: "scene-camping",
    scenario: "scene",
    output: "set",
    title: "户外露营场景",
    desc: "把商品放进自然野趣环境",
    badge: "生活方式",
    prompt: "生成户外露营风格商品场景图，把产品自然融入露营环境，突出使用场景、自由氛围和生活品质，适合户外品类推广。",
    mediaUrl: ossAssets.ecommerce.inspiration.campingCart,
  },
  {
    id: "scene-beauty-spray",
    scenario: "scene",
    output: "set",
    title: "美妆喷雾场景",
    desc: "捕捉使用瞬间，增强氛围感",
    badge: "氛围感",
    prompt: "生成美妆喷雾使用场景图，捕捉产品使用瞬间和细腻喷雾，突出清爽感、仪式感和大片氛围，适合美妆护肤品类。",
    mediaUrl: ossAssets.ecommerce.inspiration.sprayScene,
  },
  {
    id: "festival-fathers-gift",
    scenario: "festival",
    output: "set",
    title: "父亲节礼盒图",
    desc: "礼赠场景，适合节日送礼营销",
    badge: "父亲节",
    prompt: "生成父亲节礼赠风格商品图，突出礼盒质感、沉稳色调和送礼仪式感，画面温暖有格调，适合父亲节活动投放。",
    mediaUrl: ossAssets.ecommerce.inspiration.fathersDaySet,
  },
  {
    id: "festival-candle-gift",
    scenario: "festival",
    output: "set",
    title: "香薰蜡烛礼盒",
    desc: "温暖氛围，适合节日礼赠场景",
    badge: "热门模板",
    prompt: "生成香薰蜡烛节日礼盒图，突出温暖烛光、包装质感和治愈氛围，画面柔和高级，适合节日礼赠和家居品类营销。",
    mediaUrl: ossAssets.ecommerce.inspiration.etsyScentedCandleSet,
  },
  {
    id: "background-premium-gray",
    scenario: "background",
    output: "set",
    title: "高级灰背景",
    desc: "简约商业，提升产品高级感",
    badge: "高频推荐",
    prompt: "为商品更换高级灰商业背景，保留产品主体和细节，背景简约有层次，突出产品轮廓和质感，适合电商主图和广告。",
    mediaUrl: ossAssets.ecommerce.detail.productA,
  },
  {
    id: "background-home-living",
    scenario: "background",
    output: "set",
    title: "居家背景",
    desc: "温馨生活场景，增强代入感",
    badge: "场景增强",
    prompt: "为商品更换温馨居家背景，保持主体自然融入，增强生活气息和使用代入感，适合家居、日用和生活方式品类。",
    mediaUrl: ossAssets.ecommerce.productSet.hosting,
  },
  {
    id: "retouch-color-correction",
    scenario: "retouch",
    output: "set",
    title: "色彩统一精修",
    desc: "多色校正，保持系列一致",
    badge: "精修模板",
    prompt: "对商品图进行色彩统一精修，校正色偏、统一光影和色调，保持系列素材一致性，画面自然真实，适合电商套图。",
    mediaUrl: ossAssets.ecommerce.detail.productB,
  },
  {
    id: "retouch-detail-sharpen",
    scenario: "retouch",
    output: "set",
    title: "细节锐化精修",
    desc: "纹理增强，提升商品质感",
    badge: "高频推荐",
    prompt: "对商品图进行细节锐化精修，增强纹理、边缘和材质细节，保持自然不过度，画面干净高级，适合主图和详情页。",
    mediaUrl: ossAssets.ecommerce.productSet.detail,
  },
  {
    id: "sales-video-painpoint",
    scenario: "salesVideo",
    output: "video",
    title: "痛点种草视频",
    desc: "直击痛点，快速建立购买动机",
    badge: "转化优先",
    prompt: "生成痛点种草风格带货短视频脚本和分镜，先抛出生活痛点再展示产品解决方案，节奏紧凑，适合清洁家电和功能性产品。",
    mediaUrl: ossAssets.ecommerce.inspiration.cleanerPainpointDouyin,
  },
  {
    id: "sales-video-unboxing",
    scenario: "salesVideo",
    output: "video",
    title: "温馨开箱视频",
    desc: "氛围产品，增强情感连接",
    badge: "热门模板",
    prompt: "生成温馨开箱风格带货短视频脚本和分镜，围绕拆箱仪式感、产品外观和初体验展开，画面温暖治愈，适合氛围类产品。",
    mediaUrl: ossAssets.ecommerce.inspiration.nightLightUnboxingDouyin,
  },
];
const popularCommerceScenarioTemplates = commerceScenarioOptions
  .filter((option): option is { key: Exclude<CommerceScenarioKey, "popular">; label: string; desc: string; icon: ReactNode } => option.key !== "popular")
  .map((option) => commerceScenarioTemplates.find((template) => template.scenario === option.key))
  .filter((template): template is CommerceScenarioTemplate => Boolean(template));
const cloneSetCountOptions: Array<{
  key: CloneSetCountKey;
  title: string;
  desc: string;
}> = [
  { key: "selling", title: "卖点图", desc: "展示商品核心卖点和细节特写" },
  { key: "white", title: "白底图", desc: "白底主图，多角度呈现商品细节" },
  { key: "scene", title: "场景图", desc: "展示商品生活使用场景和人物搭配" },
];
const cloneSetCountKeys = cloneSetCountOptions.map((option) => option.key);
const defaultCloneSetCounts: Record<CloneSetCountKey, number> = {
  selling: 3,
  white: 1,
  scene: 3,
};
const minCloneSetTotal = 1;
const maxCloneSetTotal = 16;
const maxCloneProductImages = 20;
const maxCloneReferenceImages = 20;
const cloneVideoDurationMin = 5;
const cloneVideoDurationMax = 45;
const composerDurationOptions = [5, 10, 15];
const defaultEcommercePlatform = "淘宝/天猫";
const defaultProductSetOutput: ProductSetOutputKey = "set";
const defaultCloneOutput: CloneOutputKey = "set";
const cloneLatestSettingStorageKey = "omniai.clone-ai.latest-setting";
const ecommerceHistoryStorageKey = "omniai.ecommerce.history.records";
const cloneVideoQualityOptions: Array<{ key: CloneVideoQualityKey; label: string; desc: string }> = [
  { key: "standard", label: "标准", desc: "快速出片" },
  { key: "high", label: "高清", desc: "推荐" },
  { key: "ultra", label: "超清", desc: "细节增强" },
];
const cloneReplicateLevelOptions: Array<{ key: CloneReplicateLevelKey; title: string; desc: string }> = [
  { key: "style", title: "参考风格", desc: "参考整体风格和结构，自动调整色彩和重构场景。" },
  { key: "high", title: "高度复刻", desc: "参考视觉结构替换产品和文案，保留主要场景细节。" },
];
const tryOnRatioOptions = ["3:4", "1:1", "9:16"];
const tryOnScenes = ["纯色棚拍", "都市街头", "街角咖啡", "自然草坪", "度假海滩", "温馨居家", "艺术展馆"];
const normalizeCloneModelSceneSelection = (scenes: string[] | null | undefined) => {
  const validScenes = (scenes ?? []).filter((scene) => typeof scene === "string" && scene.trim());
  const latestScene = validScenes[validScenes.length - 1];
  return latestScene ? [latestScene] : [];
};
const tryOnModelOptions = {
  gender: ["女", "男"],
  age: ["青年", "少年", "中年"],
  ethnicity: ["欧美白人", "亚洲人", "拉美裔", "非洲裔"],
  body: ["标准", "高挑", "微胖", "运动"],
};
const sampleResults = [
  ossAssets.ecommerce.slides.slide4,
  ossAssets.ecommerce.generated,
  ossAssets.ecommerce.slides.slide5,
];
const productSetAssets = ossAssets.ecommerce.productSet;
const productSetPreviewCards = [
  { id: "main", label: "01 主图 (白底/合规)", src: productSetAssets.main },
  { id: "scene", label: "02 场景展示", src: productSetAssets.scene },
  { id: "model", label: "03 模特场景图", src: productSetAssets.model },
  { id: "detail", label: "04 细节说明", src: productSetAssets.detail },
  { id: "selling", label: "05 卖点详解", src: productSetAssets.selling },
];
const tryOnAssets = ossAssets.ecommerce.tryOn;

const tryOnCards = [
  {
    title: "多件混搭自动融合",
    tone: "red",
    inputs: [tryOnAssets.dressA, tryOnAssets.dressB, tryOnAssets.modelWoman],
    results: [tryOnAssets.tryA, tryOnAssets.tryB],
  },
  {
    title: "一件也能出大片",
    tone: "brown",
    inputs: [tryOnAssets.jacket, tryOnAssets.modelMan],
    results: [tryOnAssets.jacketResultA, tryOnAssets.jacketResultB],
  },
  {
    title: "鞋帽饰品完美适配",
    tone: "gold",
    inputs: [tryOnAssets.hat, tryOnAssets.modelAsian],
    results: [tryOnAssets.hatResultA, tryOnAssets.hatResultB],
  },
];

const detailTypeOptions = ["普通A+", "品牌A+", "标准详情页", "移动端长图"];
const detailModules = [
  { id: "hero", title: "首页焦点图", desc: "集中呈现核心利益点" },
  { id: "selling", title: "卖点强化图", desc: "放大产品优势" },
  { id: "usage", title: "使用情境图", desc: "还原实际使用画面" },
  { id: "angle", title: "外观角度图", desc: "展示不同视角造型" },
  { id: "scene", title: "氛围场景图", desc: "营造产品应用环境" },
  { id: "detail", title: "细节特写图", desc: "突出材质和做工" },
  { id: "story", title: "品牌理念图", desc: "表达品牌主张" },
  { id: "size", title: "规格尺寸图", desc: "说明尺寸容量尺码" },
  { id: "compare", title: "效果对照图", desc: "呈现前后差异" },
  { id: "spec", title: "参数信息表", desc: "整理商品关键数据" },
  { id: "craft", title: "工艺流程图", desc: "说明制作与处理步骤" },
  { id: "gift", title: "清单配件图", desc: "展示包装内全部内容" },
  { id: "series", title: "SKU组合图", desc: "呈现颜色款式组合" },
  { id: "ingredient", title: "成分材质图", desc: "说明配方或材料构成" },
  { id: "service", title: "保障说明图", desc: "传达质保退换承诺" },
  { id: "tips", title: "使用提示图", desc: "提醒操作与保养要点" },
];
const defaultDetailModuleIds: string[] = [];
const defaultCloneDetailModuleIds = ["hero", "selling", "usage", "angle", "scene", "detail"];
const maxDetailModuleSelection = 6;
const cloneDetailModules = detailModules;
const detailAssets = ossAssets.ecommerce.detail;
const detailProductSamples = [detailAssets.productA, detailAssets.productB, detailAssets.productC];
const detailGridSamples = [detailAssets.gridA, detailAssets.gridB, detailAssets.gridC, detailAssets.gridD, detailAssets.gridE, detailAssets.gridF];

function getImageFileFormat(file: File) {
  const mimeFormat = file.type.split("/")[1]?.replace("jpeg", "jpg").toUpperCase();
  if (mimeFormat) return mimeFormat;
  return file.name.split(".").pop()?.toUpperCase() ?? "";
}

function getRemoteImageFormat(mimeType: string, imageUrl: string) {
  const mimeFormat = mimeType.split("/")[1]?.replace("jpeg", "jpg").toUpperCase();
  if (mimeFormat) return mimeFormat;
  return imageUrl.split("?")[0].split(".").pop()?.toUpperCase() ?? "IMAGE";
}

function getRemoteImageName(imageUrl: string, fallback: string) {
  try {
    const parsed = new URL(imageUrl);
    const filename = decodeURIComponent(parsed.pathname.split("/").filter(Boolean).pop() || "");
    return filename || fallback;
  } catch {
    return fallback;
  }
}

function readImageDimensions(src: string): Promise<{ width: number; height: number }> {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => resolve({ width: image.naturalWidth, height: image.naturalHeight });
    image.onerror = reject;
    image.src = src;
  });
}

const blobToDataUrl = (blob: Blob): Promise<string> =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result || ""));
    reader.onerror = () => reject(reader.error || new Error("文件读取失败"));
    reader.readAsDataURL(blob);
  });

function createLocalImageItems(files: File[], limit: number, prefix: string): CloneImageItem[] {
  const selectedFiles = Array.from(files).slice(0, limit);
  const stamp = Date.now();
  return selectedFiles.map((file, index) => {
    const localPreviewUrl = URL.createObjectURL(file);
    const mimeType = normalizeEcommerceImageMime(file.type);
    return {
      id: `${prefix}-${stamp}-${index}`,
      src: localPreviewUrl,
      name: file.name,
      file,
      format: getImageFileFormat(file),
      mimeType,
    };
  });
}

async function uploadImageItem(item: CloneImageItem): Promise<{ src?: string; ossKey?: string; width?: number; height?: number }> {
  if (!item.file) return {};
  const mimeType = normalizeEcommerceImageMime(item.file.type);
  try {
    const uploadBlob = item.file.type === mimeType ? item.file : new Blob([item.file], { type: mimeType });
    const [uploaded, dimensions] = await Promise.all([
      aiGenerationClient.uploadAssetBinary(uploadBlob, {
        name: item.file.name,
        mimeType,
        scope: ecommerceOssScopes.productSource,
      }),
      readImageDimensions(item.src).catch(() => ({})),
    ]);
    return { src: uploaded.url, ossKey: uploaded.ossKey, ...dimensions };
  } catch {
    return {};
  }
}

async function createUploadedImageItems(files: File[], limit: number, prefix: string): Promise<CloneImageItem[]> {
  const selectedFiles = Array.from(files).slice(0, limit);
  const stamp = Date.now();

  const items = await Promise.all(selectedFiles.map(async (file, index) => {
    const localPreviewUrl = URL.createObjectURL(file);
    let src = localPreviewUrl;
    let ossKey: string | undefined;
    let shouldRevokeLocalPreview = false;
    let dimensions: { width?: number; height?: number } = {};
    try {
      dimensions = await readImageDimensions(localPreviewUrl);
    } catch {
      dimensions = {};
    }

    const mimeType = normalizeEcommerceImageMime(file.type);
    try {
      const uploadBlob = file.type === mimeType ? file : new Blob([file], { type: mimeType });
      const uploaded = await aiGenerationClient.uploadAssetBinary(uploadBlob, {
        name: file.name,
        mimeType,
        scope: ecommerceOssScopes.productSource,
      });
      src = uploaded.url;
      ossKey = uploaded.ossKey;
      shouldRevokeLocalPreview = true;
    } catch {
      src = localPreviewUrl;
    } finally {
      if (shouldRevokeLocalPreview) URL.revokeObjectURL(localPreviewUrl);
    }

    return {
      id: `${prefix}-${stamp}-${index}`,
      src,
      name: file.name,
      file,
      format: getImageFileFormat(file),
      mimeType,
      ossKey,
      ...dimensions,
    };
  }));

  return items;
}

async function persistGeneratedImageUrl(sourceUrl: string, scope: string, namePrefix: string): Promise<string> {
  if (!sourceUrl) return sourceUrl;
  try {
    if (sourceUrl.startsWith("data:")) {
      const { url } = await aiGenerationClient.uploadAsset({
        dataUrl: sourceUrl,
        name: `${namePrefix}-${Date.now()}.png`,
        scope,
      });
      return url || sourceUrl;
    }

    if (sourceUrl.startsWith("blob:")) {
      const rawBlob = await fetch(sourceUrl).then((res) => res.blob());
      const mimeType = normalizeEcommerceImageMime(rawBlob.type);
      const blob = rawBlob.type === mimeType ? rawBlob : new Blob([rawBlob], { type: mimeType });
      const { url } = await aiGenerationClient.uploadAssetBinary(blob, {
        name: `${namePrefix}-${Date.now()}.png`,
        mimeType,
        scope,
      });
      return url;
    }

    const { url } = await aiGenerationClient.uploadAssetByUrl({
      sourceUrl,
      name: `${namePrefix}-${Date.now()}`,
      scope,
    });
    return url || sourceUrl;
  } catch {
    return sourceUrl;
  }
}

function notifyRejectedImages(files: File[]): File[] {
  const { accepted, rejected } = validateEcommerceImageFiles(files);
  const message = summarizeRejectedImages(rejected);
  if (message) toast.error(message);
  return accepted;
}

function isCloneSavedSetting(item: unknown): item is CloneSavedSetting {
  const candidate = item as Partial<CloneSavedSetting>;
  return (
    typeof candidate.id === "string" &&
    typeof candidate.name === "string" &&
    typeof candidate.savedAt === "string" &&
    typeof candidate.output === "string" &&
    typeof candidate.platform === "string" &&
    typeof candidate.market === "string" &&
    typeof candidate.language === "string" &&
    typeof candidate.ratio === "string" &&
    typeof candidate.videoDurationSeconds === "number"
  );
}

function readCloneLatestSetting() {
  if (typeof window === "undefined") return null;
  try {
    const rawValue = window.localStorage.getItem(cloneLatestSettingStorageKey);
    if (rawValue) {
      const parsedValue: unknown = JSON.parse(rawValue);
      if (isCloneSavedSetting(parsedValue)) return parsedValue;
    }
  } catch {
    return null;
  }
  return null;
}

function writeCloneLatestSetting(setting: CloneSavedSetting) {
  if (typeof window === "undefined") return;
  window.localStorage.setItem(cloneLatestSettingStorageKey, JSON.stringify(setting));
}

function isCloneImageItem(item: unknown): item is CloneImageItem {
  const candidate = item as Partial<CloneImageItem>;
  return typeof candidate.id === "string" && typeof candidate.src === "string" && typeof candidate.name === "string";
}

function isCloneResult(item: unknown): item is CloneResult {
  const candidate = item as Partial<CloneResult>;
  return typeof candidate.id === "string" && typeof candidate.src === "string" && typeof candidate.label === "string";
}

function isEcommerceHistoryRecord(item: unknown): item is EcommerceHistoryRecord {
  const candidate = item as Partial<EcommerceHistoryRecord>;
  return (
    typeof candidate.id === "string" &&
    typeof candidate.title === "string" &&
    typeof candidate.createdAt === "number" &&
    typeof candidate.output === "string" &&
    typeof candidate.platform === "string" &&
    typeof candidate.market === "string" &&
    typeof candidate.language === "string" &&
    typeof candidate.ratio === "string" &&
    typeof candidate.requirement === "string" &&
    Array.isArray(candidate.productImages) &&
    candidate.productImages.every(isCloneImageItem) &&
    Array.isArray(candidate.results) &&
    candidate.results.every(isCloneResult)
  );
}

function removeFilePayloadFromImages(images: CloneImageItem[]): CloneImageItem[] {
  return images.map(({ id, src, name, width, height, format, mimeType, ossKey }) => ({
    id,
    src,
    name,
    width,
    height,
    format,
    mimeType,
    ossKey,
  }));
}

function getTurnResults(turn: EcommerceHistoryTurn): CloneResult[] {
  if (turn.results?.length) return turn.results.filter((item) => item.src);
  if (turn.output !== "set") return [];
  return (turn.setResultImages ?? [])
    .filter(Boolean)
    .map((src, index) => ({ id: `${turn.id}-set-${index}`, src, label: `套图 ${index + 1}` }));
}

function buildHistoryTurnFromRecord(record: EcommerceHistoryRecord): EcommerceHistoryTurn {
  return {
    id: `${record.id}-turn-initial`,
    createdAt: record.createdAt,
    status: record.status ?? "done",
    errorMessage: record.status === "failed" ? record.errorMessage : undefined,
    output: record.output,
    platform: record.platform,
    market: record.market,
    language: record.language,
    ratio: record.ratio,
    requirement: record.requirement,
    productImages: record.productImages ?? [],
    results: record.results ?? [],
    setResultImages: record.setResultImages ?? [],
    setCounts: record.setCounts ?? defaultCloneSetCounts,
    detailModules: record.detailModules ?? defaultCloneDetailModuleIds,
    modelScenes: record.modelScenes ?? [],
    referenceImages: record.referenceImages ?? [],
    replicateLevel: record.replicateLevel ?? "high",
  };
}

function normalizeEcommerceHistoryTurn(turn: EcommerceHistoryTurn, fallback: EcommerceHistoryRecord, index: number): EcommerceHistoryTurn {
  const status = turn.status ?? fallback.status ?? "done";
  return {
    id: typeof turn.id === "string" && turn.id ? turn.id : `${fallback.id}-turn-${index + 1}`,
    createdAt: typeof turn.createdAt === "number" ? turn.createdAt : fallback.createdAt,
    status,
    errorMessage: status === "failed" ? turn.errorMessage ?? fallback.errorMessage : undefined,
    output: turn.output ?? fallback.output,
    platform: turn.platform ?? fallback.platform,
    market: turn.market ?? fallback.market,
    language: turn.language ?? fallback.language,
    ratio: turn.ratio ?? fallback.ratio,
    requirement: turn.requirement ?? fallback.requirement,
    productImages: removeFilePayloadFromImages(Array.isArray(turn.productImages) ? turn.productImages : fallback.productImages),
    results: Array.isArray(turn.results) ? turn.results.filter(isCloneResult) : [],
    setResultImages: Array.isArray(turn.setResultImages) ? turn.setResultImages.filter(Boolean) : [],
    setCounts: turn.setCounts ?? fallback.setCounts ?? defaultCloneSetCounts,
    detailModules: turn.detailModules ?? fallback.detailModules ?? defaultCloneDetailModuleIds,
    modelScenes: turn.modelScenes ?? fallback.modelScenes ?? [],
    referenceImages: removeFilePayloadFromImages(Array.isArray(turn.referenceImages) ? turn.referenceImages : fallback.referenceImages ?? []),
    replicateLevel: turn.replicateLevel ?? fallback.replicateLevel ?? "high",
  };
}

function normalizeEcommerceHistoryRecord(record: EcommerceHistoryRecord): EcommerceHistoryRecord {
  const status = record.status ?? "done";
  const baseRecord = {
    ...record,
    status,
    errorMessage: status === "failed" ? record.errorMessage : undefined,
    productImages: removeFilePayloadFromImages(record.productImages),
    referenceImages: removeFilePayloadFromImages(record.referenceImages ?? []),
    results: record.results ?? [],
    setResultImages: record.setResultImages ?? [],
    setCounts: record.setCounts ?? defaultCloneSetCounts,
    detailModules: record.detailModules ?? defaultCloneDetailModuleIds,
    modelScenes: record.modelScenes ?? [],
    replicateLevel: record.replicateLevel ?? "high",
  };
  const rawTurns = Array.isArray(record.turns) && record.turns.length
    ? record.turns
    : [buildHistoryTurnFromRecord(baseRecord)];
  const turns = rawTurns.map((turn, index) => normalizeEcommerceHistoryTurn(turn, baseRecord, index));
  return {
    ...baseRecord,
    turns,
  };
}

function readEcommerceHistoryRecords() {
  if (typeof window === "undefined") return [];
  try {
    const rawValue = window.localStorage.getItem(ecommerceHistoryStorageKey);
    if (!rawValue) return [];
    const parsedValue: unknown = JSON.parse(rawValue);
    if (!Array.isArray(parsedValue)) return [];
    return parsedValue
      .filter(isEcommerceHistoryRecord)
      .map(normalizeEcommerceHistoryRecord)
      .sort((a, b) => b.createdAt - a.createdAt)
      .slice(0, 30);
  } catch {
    return [];
  }
}

function writeEcommerceHistoryRecords(records: EcommerceHistoryRecord[]) {
  if (typeof window === "undefined") return;
  window.localStorage.setItem(ecommerceHistoryStorageKey, JSON.stringify(records.map(normalizeEcommerceHistoryRecord).slice(0, 30)));
}

function clampCloneVideoDuration(value: number) {
  return Math.min(cloneVideoDurationMax, Math.max(cloneVideoDurationMin, Math.round(value)));
}

function ProductClonePage(_props: ProductClonePageProps = {}) {
  const setInputRef = useRef<HTMLInputElement>(null);
  const productInputRef = useRef<HTMLInputElement>(null);
  const cloneReferenceInputRef = useRef<HTMLInputElement>(null);
  const smartCutoutInputRef = useRef<HTMLInputElement>(null);
  const imageWorkbenchInputRef = useRef<HTMLInputElement>(null);
  const imageWorkbenchUrlInputRef = useRef<HTMLInputElement>(null);
  const imageWorkbenchProgressRef = useRef<number | null>(null);
  const watermarkInputRef = useRef<HTMLInputElement>(null);
  const watermarkUrlInputRef = useRef<HTMLInputElement>(null);
  const watermarkProcessTimeoutRef = useRef<number | null>(null);
  const translateInputRef = useRef<HTMLInputElement>(null);
  const translateUrlInputRef = useRef<HTMLInputElement>(null);
  const translateProcessTimeoutRef = useRef<number | null>(null);
  const smartCutoutTransitionTimeoutRef = useRef<number | null>(null);
  const smartCutoutPendingUrlsRef = useRef<string[]>([]);
  const smartCutoutPaletteRef = useRef<HTMLDivElement>(null);
  const smartCutoutToolsRef = useRef<HTMLDivElement>(null);
  const composerMenuCloseTimeoutRef = useRef<number | null>(null);
  const requirementTextareaRef = useRef<HTMLTextAreaElement>(null);
  const commandComposerWrapRef = useRef<HTMLElement | null>(null);
  const templateStripRef = useRef<HTMLElement | null>(null);
  const garmentInputRef = useRef<HTMLInputElement>(null);
  const detailInputRef = useRef<HTMLInputElement>(null);
  const detailProgressRef = useRef<number | null>(null);
  const hotProgressRef = useRef<number | null>(null);
  const hotMaterialInputRef = useRef<HTMLInputElement>(null);
  const countHoldTimeoutRef = useRef<number | null>(null);
  const countHoldIntervalRef = useRef<number | null>(null);
  const isAuthenticated = Boolean((_props as Record<string, unknown>).isAuthenticated);
  const requestLogin = () => {
    const handler = (_props as Record<string, unknown>).onRequireLogin;
    if (typeof handler === "function") handler();
  };
  const imageGen = useGenerationTasks({ sourceView: "ecommerce" });
  const appUsage = useAppStore((s) => s.usage);
  const latestCloneSettingRef = useRef<CloneSavedSetting | null>(null);
  const skipInitialCloneAutoSaveRef = useRef(true);
  const skipNextCloneAutoSaveRef = useRef(false);
  const [activeTool, setActiveTool] = useState<ProductKitToolKey>("clone");
  useEffect(() => {
    setPreviewZoom(1);
    setIsCommandComposerCompact(false);
  }, [activeTool]);
  const [setImages, setSetImages] = useState<CloneImageItem[]>([]);
  const [productSetPlatform, setProductSetPlatform] = useState(defaultEcommercePlatform);
  const [productSetMarket, setProductSetMarket] = useState(marketOptions[0]);
  const [productSetLanguage, setProductSetLanguage] = useState(getPlatformDefaultLanguage(defaultEcommercePlatform, marketOptions[0]));
  const [productSetRatio, setProductSetRatio] = useState(getPlatformDefaultRatio(defaultEcommercePlatform, defaultProductSetOutput));
  const [productSetRequirement, setProductSetRequirement] = useState("");
  const [productSetOutput, setProductSetOutput] = useState<ProductSetOutputKey>(defaultProductSetOutput);
  const [productSetStatus, setProductSetStatus] = useState<ProductSetStatus>("idle");
  // 套图/图像生成的真实进度（0-100）：多张串行生成时按"已完成张数 + 当前张子进度"推进，
  // 替代进度条原先写死 50 导致卡在 75% 的假进度。
  const [generationProgress, setGenerationProgress] = useState(0);
  const [productSetResultImages, setProductSetResultImages] = useState<string[]>([]);
  const [isSetUploadDragging, setIsSetUploadDragging] = useState(false);
  const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<ProductSetPreviewSelection | null>(null);
  const [showHostingModal, setShowHostingModal] = useState(false);
  const [productImages, setProductImages] = useState<CloneImageItem[]>([]);
  const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | "quick-set" | "copywriting" | null>(null);
  const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
  const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
  const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
  const [smartCutoutBackgroundAlpha, setSmartCutoutBackgroundAlpha] = useState(100);
  const [smartCutoutHexDraft, setSmartCutoutHexDraft] = useState("#ffffff");
  const [isSmartCutoutPaletteOpen, setIsSmartCutoutPaletteOpen] = useState(false);
  const [smartCutoutSizeKey, setSmartCutoutSizeKey] = useState<SmartCutoutSizeKey>("original");
  const [isSmartCutoutDragging, setIsSmartCutoutDragging] = useState(false);
  const [isSmartCutoutComparing, setIsSmartCutoutComparing] = useState(false);
  const [isSmartCutoutTransitioning, setIsSmartCutoutTransitioning] = useState(false);
  const [smartCutoutTransitionMessage, setSmartCutoutTransitionMessage] = useState({
    title: "正在切换页面",
    subtitle: "请稍候",
  });
  const [watermarkImage, setWatermarkImage] = useState<{ src: string; name: string; format: string } | null>(null);
  const [watermarkStatus, setWatermarkStatus] = useState<"idle" | "processing" | "done" | "failed">("idle");
  const [isWatermarkDragging, setIsWatermarkDragging] = useState(false);
  const [watermarkResultUrl, setWatermarkResultUrl] = useState<string | null>(null);
  const [watermarkProgress, setWatermarkProgress] = useState(0);
  const [translateImage, setTranslateImage] = useState<{ src: string; name: string; format: string } | null>(null);
  const [translateStatus, setTranslateStatus] = useState<"idle" | "processing" | "done" | "failed">("idle");
  const [isTranslateDragging, setIsTranslateDragging] = useState(false);
  const [translateLanguage, setTranslateLanguage] = useState("zh");
  const [translateResultUrl, setTranslateResultUrl] = useState<string | null>(null);
  const [imageWorkbenchImage, setImageWorkbenchImage] = useState<{ src: string; name: string; format: string } | null>(null);
  const [imageWorkbenchPrompt, setImageWorkbenchPrompt] = useState("");
  const [imageWorkbenchBrushSize, setImageWorkbenchBrushSize] = useState(50);
  const [imageWorkbenchRatio, setImageWorkbenchRatio] = useState("1:1");
  const [imageWorkbenchStatus, setImageWorkbenchStatus] = useState<"idle" | "processing" | "done" | "failed">("idle");
  const [isImageWorkbenchDragging, setIsImageWorkbenchDragging] = useState(false);
  const [imageWorkbenchMaskStrokes, setImageWorkbenchMaskStrokes] = useState<Array<{ id: string; size: number; points: Array<{ x: number; y: number }> }>>([]);
  const [imageWorkbenchBrushCursor, setImageWorkbenchBrushCursor] = useState<{ x: number; y: number } | null>(null);
  const [imageWorkbenchResultUrl, setImageWorkbenchResultUrl] = useState<string | null>(null);
  const [imageWorkbenchProgress, setImageWorkbenchProgress] = useState(0);
  const [isProductUploadDragging, setIsProductUploadDragging] = useState(false);
  const [activeCommerceScenario, setActiveCommerceScenario] = useState<CommerceScenarioKey | null>(null);
  const [isCommerceScenarioMoreOpen, setIsCommerceScenarioMoreOpen] = useState(false);
  const [cloneOutput, setCloneOutput] = useState<CloneOutputKey>(defaultCloneOutput);
  const [isCloneTemplateStripVisible, setIsCloneTemplateStripVisible] = useState(false);
  const [videoHistoryVisible, setVideoHistoryVisible] = useState(false);
  const [isVideoWorkspaceVisible, setIsVideoWorkspaceVisible] = useState(false);
  const [videoPlanTrigger, setVideoPlanTrigger] = useState(0);
  const [openCloneBasicSelect, setOpenCloneBasicSelect] = useState<CloneBasicSelectKey | null>(null);
  const [openQuickSetSelect, setOpenQuickSetSelect] = useState<CloneBasicSelectKey | null>(null);
  const [visibleQuickSetSelect, setVisibleQuickSetSelect] = useState<CloneBasicSelectKey | null>(null);
  const [isQuickSetSelectClosing, setIsQuickSetSelectClosing] = useState(false);
  const [composerMenu, setComposerMenu] = useState<ComposerMenuKey | null>(null);
  const [visibleComposerMenu, setVisibleComposerMenu] = useState<ComposerMenuKey | null>(null);
  const [isComposerMenuClosing, setIsComposerMenuClosing] = useState(false);
  const [composerPopoverLeft, setComposerPopoverLeft] = useState(0);
  const [composerPopoverTop, setComposerPopoverTop] = useState(0);
  const [composerTooltip, setComposerTooltip] = useState<{ text: string; left: number; top: number } | null>(null);
  const [composerAssetTab, setComposerAssetTab] = useState<ComposerAssetTabKey>("recent");
  const [composerWorkMode, setComposerWorkMode] = useState<ComposerWorkModeKey>("quick");
  const [aiWriteDraft, setAiWriteDraft] = useState("");
  const [isCommandHistoryCollapsed, setIsCommandHistoryCollapsed] = useState(true);
  const [inspirationPreview, setInspirationPreview] = useState<{ mediaUrl: string; mediaType: "image" | "video"; prompt: string } | null>(null);
  const [isQuickPanelCollapsed, setIsQuickPanelCollapsed] = useState(false);
  const [openCloneModelSelect, setOpenCloneModelSelect] = useState<CloneModelSelectKey | null>(null);
  const [cloneModelSelectDropUp, setCloneModelSelectDropUp] = useState(false);
  const [cloneReferenceMode, setCloneReferenceMode] = useState<CloneReferenceMode>("upload");
  const [cloneReferenceImages, setCloneReferenceImages] = useState<CloneImageItem[]>([]);
  const [cloneReplicateLevel, setCloneReplicateLevel] = useState<CloneReplicateLevelKey>("high");
  const [cloneSetCounts, setCloneSetCounts] = useState(defaultCloneSetCounts);
  const [selectedCloneDetailModules, setSelectedCloneDetailModules] = useState<string[]>(defaultCloneDetailModuleIds);
  const [cloneModelPanelTab, setCloneModelPanelTab] = useState<CloneModelPanelTab>("scene");
  const [selectedCloneModelScenes, setSelectedCloneModelScenes] = useState<string[]>([]);
  const [cloneModelCustomScene, setCloneModelCustomScene] = useState("");
  const [cloneModelGender, setCloneModelGender] = useState(tryOnModelOptions.gender[0]);
  const [cloneModelAge, setCloneModelAge] = useState(tryOnModelOptions.age[0]);
  const [cloneModelEthnicity, setCloneModelEthnicity] = useState(tryOnModelOptions.ethnicity[0]);
  const [cloneModelBody, setCloneModelBody] = useState(tryOnModelOptions.body[0]);
  const [cloneModelAppearance, setCloneModelAppearance] = useState("");
  const [cloneVideoQuality, setCloneVideoQuality] = useState<CloneVideoQualityKey>("high");
  const [cloneVideoDuration, setCloneVideoDuration] = useState(10);
  const [cloneVideoSmart, setCloneVideoSmart] = useState(true);
  const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
  const [isCloneConversationCollapsed, setIsCloneConversationCollapsed] = useState(false);
  const [quickSetStatus, setQuickSetStatus] = useState<"idle" | "generating" | "done" | "failed">("idle");
  const [quickSetResultUrls, setQuickSetResultUrls] = useState<string[]>([]);
  const [quickSetProgress, setQuickSetProgress] = useState(0);
  const [quickSetRequirement, setQuickSetRequirement] = useState("");
  const quickSetProgressRef = useRef<number | null>(null);
  const [previewZoom, setPreviewZoom] = useState(1);
  const quickSetSelectTimerRef = useRef<number | null>(null);
  const openQuickSetSelectRef = useRef<CloneBasicSelectKey | null>(null);
  const visibleQuickSetSelectRef = useRef<CloneBasicSelectKey | null>(null);
  const [previewOffset, setPreviewOffset] = useState({ x: 0, y: 0 });
  const previewSurfaceRef = useRef<HTMLElement | null>(null);
  const previewZoomRef = useRef(previewZoom);
  const previewOffsetRef = useRef(previewOffset);
  const imageWorkbenchMaskPaintingRef = useRef(false);
  const imageWorkbenchActiveStrokeIdRef = useRef<string | null>(null);
  const imageWorkbenchMaskCanvasRef = useRef<HTMLCanvasElement>(null);
  const imageWorkbenchLastMaskPointRef = useRef<{ x: number; y: number } | null>(null);
  const previewPanRef = useRef<{ active: boolean; startX: number; startY: number; offsetX: number; offsetY: number }>({
    active: false,
    startX: 0,
    startY: 0,
    offsetX: 0,
    offsetY: 0,
  });
  const previewTouchGestureRef = useRef<PreviewTouchGesture>({
    mode: "none",
    points: [],
    startOffset: { x: 0, y: 0 },
    startZoom: 1,
    startDistance: 0,
    startCenter: { x: 0, y: 0 },
  });
  const nodeDragRef = useRef<{ active: boolean; nodeId: string; startX: number; startY: number; originX: number; originY: number }>({
    active: false,
    nodeId: "",
    startX: 0,
    startY: 0,
    originX: 0,
    originY: 0,
  });
  const [isCommandComposerCompact, setIsCommandComposerCompact] = useState(false);
  const typewriterText = useTypewriter("万物皆可AI，广告素材一键生成");

  useEffect(() => {
    previewZoomRef.current = previewZoom;
  }, [previewZoom]);

  useEffect(() => {
    previewOffsetRef.current = previewOffset;
  }, [previewOffset]);

  useEffect(() => {
    if (typeof window === "undefined") return undefined;
    // aside 默认收起，用户手动控制展开/收起
    return undefined;
  }, []);

  useEffect(() => {
    if (!inspirationPreview) return undefined;
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Escape") setInspirationPreview(null);
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [inspirationPreview]);

  const previewTransformStyle = useMemo<CSSProperties>(
    () => ({
      transform: `translate3d(${previewOffset.x}px, ${previewOffset.y}px, 0) scale(${previewZoom})`,
    }),
    [previewOffset.x, previewOffset.y, previewZoom],
  );

  const updatePreviewTransform = (nextZoom: number, nextOffset: { x: number; y: number }) => {
    previewZoomRef.current = nextZoom;
    previewOffsetRef.current = nextOffset;
    setPreviewZoom(nextZoom);
    setPreviewOffset(nextOffset);
  };

  const getPreviewGestureDistance = (points: PreviewTouchPoint[]) => {
    if (points.length < 2) return 0;
    return Math.hypot(points[0]!.x - points[1]!.x, points[0]!.y - points[1]!.y);
  };

  const getPreviewGestureCenter = (points: PreviewTouchPoint[]) => {
    if (points.length < 2) return points[0] ? { x: points[0].x, y: points[0].y } : { x: 0, y: 0 };
    return {
      x: (points[0]!.x + points[1]!.x) / 2,
      y: (points[0]!.y + points[1]!.y) / 2,
    };
  };

  const isPreviewTouchInteractiveTarget = (target: HTMLElement | null) =>
    Boolean(target?.closest(".ecom-command-composer-wrap, .clone-ai-preview-header, .clone-ai-source-corner-action, input, textarea, select, a, button"));

  const startPreviewTouchGesture = (event: ReactPointerEvent<HTMLElement>) => {
    if (event.pointerType === "mouse" || isPreviewTouchInteractiveTarget(event.target as HTMLElement | null)) return;
    event.preventDefault();
    event.currentTarget.setPointerCapture(event.pointerId);
    const points = [
      ...previewTouchGestureRef.current.points.filter((point) => point.id !== event.pointerId),
      { id: event.pointerId, x: event.clientX, y: event.clientY },
    ].slice(-2);
    const mode = points.length >= 2 ? "pinch" : "pan";
    previewTouchGestureRef.current = {
      mode,
      points,
      startOffset: previewOffsetRef.current,
      startZoom: previewZoomRef.current,
      startDistance: getPreviewGestureDistance(points),
      startCenter: getPreviewGestureCenter(points),
    };
    event.currentTarget.classList.add("is-touch-panning");
  };

  const movePreviewTouchGesture = (event: ReactPointerEvent<HTMLElement>) => {
    const gesture = previewTouchGestureRef.current;
    if (gesture.mode === "none" || event.pointerType === "mouse") return;
    event.preventDefault();
    const points = gesture.points.map((point) => point.id === event.pointerId ? { ...point, x: event.clientX, y: event.clientY } : point);
    if (!points.some((point) => point.id === event.pointerId)) return;

    if (gesture.mode === "pinch" && points.length >= 2 && gesture.startDistance > 0) {
      const rect = event.currentTarget.getBoundingClientRect();
      const center = getPreviewGestureCenter(points);
      const zoomRatio = getPreviewGestureDistance(points) / gesture.startDistance;
      const nextZoom = Math.min(2, Math.max(0.25, gesture.startZoom * zoomRatio));
      const startCenterX = gesture.startCenter.x - rect.left;
      const startCenterY = gesture.startCenter.y - rect.top;
      const currentCenterX = center.x - rect.left;
      const currentCenterY = center.y - rect.top;
      const contentX = (startCenterX - gesture.startOffset.x) / gesture.startZoom;
      const contentY = (startCenterY - gesture.startOffset.y) / gesture.startZoom;
      updatePreviewTransform(nextZoom, {
        x: currentCenterX - contentX * nextZoom,
        y: currentCenterY - contentY * nextZoom,
      });
    } else {
      const point = points[0]!;
      const startPoint = gesture.points[0]!;
      updatePreviewTransform(gesture.startZoom, {
        x: gesture.startOffset.x + point.x - startPoint.x,
        y: gesture.startOffset.y + point.y - startPoint.y,
      });
    }

    previewTouchGestureRef.current = { ...gesture, points };
  };

  const stopPreviewTouchGesture = (event: ReactPointerEvent<HTMLElement>) => {
    const gesture = previewTouchGestureRef.current;
    if (event.pointerType === "mouse" || gesture.mode === "none") return;
    const points = gesture.points.filter((point) => point.id !== event.pointerId);
    if (points.length) {
      previewTouchGestureRef.current = {
        mode: "pan",
        points,
        startOffset: previewOffsetRef.current,
        startZoom: previewZoomRef.current,
        startDistance: 0,
        startCenter: getPreviewGestureCenter(points),
      };
    } else {
      previewTouchGestureRef.current = {
        mode: "none",
        points: [],
        startOffset: previewOffsetRef.current,
        startZoom: previewZoomRef.current,
        startDistance: 0,
        startCenter: { x: 0, y: 0 },
      };
      event.currentTarget.classList.remove("is-touch-panning");
    }
    try {
      event.currentTarget.releasePointerCapture(event.pointerId);
    } catch {
      // Pointer capture can already be released by the browser after cancel.
    }
  };

  useEffect(() => {
    const container = previewSurfaceRef.current;
    if (!container) return undefined;

    const handleWheel = (event: WheelEvent) => {
      const target = event.target as HTMLElement | null;
      if (target?.closest(".ecom-inspiration-preview")) {
        event.preventDefault();
        return;
      }
      if (target?.closest(".ecom-command-composer-wrap, .clone-ai-preview-header")) return;
      event.preventDefault();
      const currentZoom = previewZoomRef.current;
      const rect = container.getBoundingClientRect();
      const cursorX = event.clientX - rect.left;
      const cursorY = event.clientY - rect.top;
      const zoomDelta = event.deltaY < 0 ? 1.08 : 0.92;
      const nextZoom = Math.min(2, Math.max(0.25, currentZoom * zoomDelta));
      if (nextZoom === currentZoom) return;

      const currentOffset = previewOffsetRef.current;
      const contentX = (cursorX - currentOffset.x) / currentZoom;
      const contentY = (cursorY - currentOffset.y) / currentZoom;
      const nextOffset = {
        x: cursorX - contentX * nextZoom,
        y: cursorY - contentY * nextZoom,
      };

      previewZoomRef.current = nextZoom;
      previewOffsetRef.current = nextOffset;
      setPreviewZoom(nextZoom);
      setPreviewOffset(nextOffset);
    };

    container.addEventListener("wheel", handleWheel, { passive: false, capture: true });
    return () => container.removeEventListener("wheel", handleWheel, { capture: true });
  }, [activeTool, cloneOutput]);

  useEffect(() => {
    const container = previewSurfaceRef.current;
    if (!container) return undefined;
    const listenerOptions = { capture: true };

    const handleMouseDown = (event: MouseEvent) => {
      if (event.button !== 0 && event.button !== 1) return;
      const target = event.target as HTMLElement | null;
      if (target?.closest(".ecom-command-composer-wrap, .clone-ai-preview-header, .clone-ai-preview-showcase, .clone-ai-main-result, .clone-ai-result-grid, .clone-ai-node-drag-handle, input, textarea, select, a, button, img")) return;
      event.preventDefault();
      const currentOffset = previewOffsetRef.current;
      previewPanRef.current = {
        active: true,
        startX: event.clientX,
        startY: event.clientY,
        offsetX: currentOffset.x,
        offsetY: currentOffset.y,
      };
      container.classList.add("is-middle-panning");
    };

    const handleMouseMove = (event: MouseEvent) => {
      const pan = previewPanRef.current;
      if (!pan.active) return;
      event.preventDefault();
      const nextOffset = {
        x: pan.offsetX + event.clientX - pan.startX,
        y: pan.offsetY + event.clientY - pan.startY,
      };
      previewOffsetRef.current = nextOffset;
      setPreviewOffset(nextOffset);
    };

    const stopMousePan = () => {
      const pan = previewPanRef.current;
      if (!pan.active) return;
      previewPanRef.current = { ...pan, active: false };
      container.classList.remove("is-middle-panning");
    };

    const preventAuxClick = (event: MouseEvent) => {
      if (event.button === 1) event.preventDefault();
    };

    container.addEventListener("mousedown", handleMouseDown, listenerOptions);
    container.addEventListener("auxclick", preventAuxClick, listenerOptions);
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mouseup", stopMousePan);
    window.addEventListener("blur", stopMousePan);

    return () => {
      container.removeEventListener("mousedown", handleMouseDown, listenerOptions);
      container.removeEventListener("auxclick", preventAuxClick, listenerOptions);
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("mouseup", stopMousePan);
      window.removeEventListener("blur", stopMousePan);
    };
  }, [activeTool, cloneOutput]);

  const bindPreviewSurface = (node: HTMLElement | null) => {
    previewSurfaceRef.current = node;
  };

  const getPreviewSurfaceProps = () => ({
    ref: bindPreviewSurface,
    onMouseDown: (event: ReactMouseEvent<HTMLElement>) => {
      if (event.button === 1) event.preventDefault();
    },
    onAuxClick: (event: ReactMouseEvent<HTMLElement>) => {
      if (event.button === 1) event.preventDefault();
    },
    onPointerDown: startPreviewTouchGesture,
    onPointerMove: movePreviewTouchGesture,
    onPointerUp: stopPreviewTouchGesture,
    onPointerCancel: stopPreviewTouchGesture,
  });

  const startCanvasNodeDrag = (event: ReactPointerEvent<HTMLElement>, node: CanvasNode) => {
    if (event.button !== 0 || event.pointerType === "mouse") return;
    if ((event.target as HTMLElement | null)?.closest("button, a, input, textarea, select")) return;
    event.preventDefault();
    event.stopPropagation();
    event.currentTarget.setPointerCapture(event.pointerId);
    nodeDragRef.current = { active: true, nodeId: node.id, startX: event.clientX, startY: event.clientY, originX: node.x, originY: node.y };
  };

  const moveCanvasNodeDrag = (event: ReactPointerEvent<HTMLElement>, nodeId: string) => {
    const drag = nodeDragRef.current;
    if (!drag.active || drag.nodeId !== nodeId || event.pointerType === "mouse") return;
    event.preventDefault();
    event.stopPropagation();
    const zoom = previewZoomRef.current;
    const dx = (event.clientX - drag.startX) / zoom;
    const dy = (event.clientY - drag.startY) / zoom;
    setCanvasNodes((prev) => prev.map((node) => node.id === nodeId ? { ...node, x: drag.originX + dx, y: drag.originY + dy } : node));
  };

  const stopCanvasNodeDrag = (event: ReactPointerEvent<HTMLElement>, nodeId: string) => {
    if (nodeDragRef.current.nodeId !== nodeId || event.pointerType === "mouse") return;
    nodeDragRef.current = { ...nodeDragRef.current, active: false };
    event.stopPropagation();
    try {
      event.currentTarget.releasePointerCapture(event.pointerId);
    } catch {
      // Pointer capture can already be released by the browser after cancel.
    }
  };

  const handlePreviewWheel = (event: React.WheelEvent<HTMLElement>) => {
    if (!event.currentTarget) return;
    const container = event.currentTarget as HTMLElement;
    const rect = container.getBoundingClientRect();
    const cursorX = event.clientX - rect.left;
    const cursorY = event.clientY - rect.top;
    const zoomDelta = event.deltaY < 0 ? 1.08 : 0.92;

    const nextZoom = Math.min(2, Math.max(0.25, previewZoom * zoomDelta));
    if (nextZoom === previewZoom) return;

    const contentX = (cursorX + container.scrollLeft) / previewZoom;
    const contentY = (cursorY + container.scrollTop) / previewZoom;

    setPreviewZoom(nextZoom);

    requestAnimationFrame(() => {
      container.scrollLeft = contentX * nextZoom - cursorX;
      container.scrollTop = contentY * nextZoom - cursorY;
    });
  };

  const handleQuickPreviewWheel = (event: React.WheelEvent<HTMLDivElement>) => {
    event.preventDefault();
    event.stopPropagation();
    const zoomDelta = event.deltaY < 0 ? 1.08 : 0.92;
    setPreviewZoom((value) => Math.min(2, Math.max(0.25, value * zoomDelta)));
  };

  const handleQuickPanelWheel = (event: React.WheelEvent<HTMLElement>) => {
    const panel = event.currentTarget;
    if (panel.scrollHeight <= panel.clientHeight) return;
    event.stopPropagation();
    panel.scrollTop += event.deltaY;
  };

  const [requirement, setRequirement] = useState("");
  const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState<string | null>(null);
  const [cloneSettingName, setCloneSettingName] = useState("新建创作");
  const [platform, setPlatform] = useState(defaultEcommercePlatform);
  const [market, setMarket] = useState(marketOptions[0]);
  const [language, setLanguage] = useState(getPlatformDefaultLanguage(defaultEcommercePlatform, marketOptions[0]));
  const [ratio, setRatio] = useState(getPlatformDefaultRatio(defaultEcommercePlatform, defaultCloneOutput));
  const [status, setStatus] = useState<ProductCloneStatus>("idle");
  const [results, setResults] = useState<CloneResult[]>([]);
  const [canvasNodes, setCanvasNodes] = useState<CanvasNode[]>([]);
  const [ecommerceHistoryRecords, setEcommerceHistoryRecords] = useState<EcommerceHistoryRecord[]>(() => readEcommerceHistoryRecords());
  const [activeHistoryRecordId, setActiveHistoryRecordId] = useState<string | null>(null);
  const [historyRefreshTick, setHistoryRefreshTick] = useState(0);
  const [isHistoryRefreshing, setIsHistoryRefreshing] = useState(false);
  const [historyRefreshMessage, setHistoryRefreshMessage] = useState("");
  const [historyRefreshStamp, setHistoryRefreshStamp] = useState(0);
  const historyRefreshLockRef = useRef(false);
  const lastSavedHistorySignatureRef = useRef("");
  const imageAbortRef = useRef({ current: false });
  const activeHistoryTurnIdRef = useRef<string | null>(null);
  const activeEcommerceTaskIdsRef = useRef<Set<string>>(new Set());
  const lastFailedActionRef = useRef<(() => void) | null>(null);
  const [garmentImages, setGarmentImages] = useState<CloneImageItem[]>([]);
  const [modelSource, setModelSource] = useState<TryOnModelSource>("ai");
  const [modelGender, setModelGender] = useState(tryOnModelOptions.gender[0]);
  const [modelAge, setModelAge] = useState(tryOnModelOptions.age[0]);
  const [modelEthnicity, setModelEthnicity] = useState(tryOnModelOptions.ethnicity[0]);
  const [modelBody, setModelBody] = useState(tryOnModelOptions.body[0]);
  const [appearance, setAppearance] = useState("");
  const [selectedScenes, setSelectedScenes] = useState<string[]>([]);

  useEffect(() => {
    if (status === "done") {
      setIsCommandComposerCompact(true);
    } else if (status === "generating" || status === "idle") {
      setIsCommandComposerCompact(false);
    }
  }, [status]);

  useEffect(() => {
    writeEcommerceHistoryRecords(ecommerceHistoryRecords);
  }, [ecommerceHistoryRecords]);
  const [customScene, setCustomScene] = useState("");
  const [smartScene, setSmartScene] = useState(false);
  const [tryOnRatio, setTryOnRatio] = useState(tryOnRatioOptions[0]);
  const [tryOnStatus, setTryOnStatus] = useState<TryOnStatus>("idle");
  const [tryOnResultImages, setTryOnResultImages] = useState<string[]>([]);
  const [detailProductImages, setDetailProductImages] = useState<CloneImageItem[]>([]);
  const [detailPlatform, setDetailPlatform] = useState(platformOptions[0]);
  const [detailMarket, setDetailMarket] = useState(marketOptions[0]);
  const [detailLanguage, setDetailLanguage] = useState(getPlatformDefaultLanguage(platformOptions[0], marketOptions[0]));
  const [detailRatio, setDetailRatio] = useState(getQuickSetRatioValue(getPlatformDefaultRatio(platformOptions[0], "detail")));
  const [detailType, setDetailType] = useState(detailTypeOptions[0]);
  const [detailRequirement, setDetailRequirement] = useState("");
  const [selectedDetailModules, setSelectedDetailModules] = useState<string[]>(defaultDetailModuleIds);
  const [detailStatus, setDetailStatus] = useState<DetailStatus>("idle");
  const [detailResultUrl, setDetailResultUrl] = useState<string | null>(null);
  const [detailProgress, setDetailProgress] = useState(0);
  const [hotRequirement, setHotRequirement] = useState("");
  const [isHotMaterialDragging, setIsHotMaterialDragging] = useState(false);
  const [hotMaterialHoverZoom, setHotMaterialHoverZoom] = useState<{ src: string; x: number; y: number; placement: "above" | "below" } | null>(null);
  const [hotPlatform, setHotPlatform] = useState(platformOptions[0]);
  const [hotMarket, setHotMarket] = useState(marketOptions[0]);
  const [hotLanguage, setHotLanguage] = useState(getPlatformDefaultLanguage(platformOptions[0], marketOptions[0]));
  const [hotRatio, setHotRatio] = useState(getQuickSetRatioValue(getPlatformDefaultRatio(platformOptions[0], "detail")));
  const [hotStatus, setHotStatus] = useState<DetailStatus>("idle");
  const [hotResultUrl, setHotResultUrl] = useState<string | null>(null);
  const [hotProgress, setHotProgress] = useState(0);
  const productSetRatioOptions = useMemo(
    () => getPlatformRatioOptions(productSetPlatform, productSetOutput),
    [productSetOutput, productSetPlatform],
  );
  const baseCloneRatioOptions = useMemo(
    () => getPlatformRatioOptions(platform, cloneOutput),
    [cloneOutput, platform],
  );
  const cloneRatioOptions = baseCloneRatioOptions;
  const composerRatioOptions = useMemo(
    () => [
      "1000×1000px\u00a0\u00a0\u00a01:1",
      "800×1200px\u00a0\u00a0\u00a02:3",
      "1200×800px\u00a0\u00a0\u00a03:2",
      "1200×900px\u00a0\u00a0\u00a04:3",
      "900×1200px\u00a0\u00a0\u00a03:4",
      "1080×1920px\u00a0\u00a0\u00a09:16",
      "1920×1080px\u00a0\u00a0\u00a016:9",
    ],
    [],
  );
  const productSetLanguageOptions = useMemo(
    () => getPlatformLanguageOptions(productSetPlatform, productSetMarket),
    [productSetMarket, productSetPlatform],
  );
  const cloneLanguageOptions = useMemo(
    () => getPlatformLanguageOptions(platform, market),
    [market, platform],
  );
  const detailLanguageOptions = useMemo(
    () => getPlatformLanguageOptions(detailPlatform, detailMarket),
    [detailMarket, detailPlatform],
  );
  const hotLanguageOptions = useMemo(
    () => getPlatformLanguageOptions(hotPlatform, hotMarket),
    [hotMarket, hotPlatform],
  );
  const ecommerceMentionImages: MentionImageOption[] = [
    ...productImages.map((image, index) => ({ ...image, label: `商品图 ${index + 1}` })),
    ...cloneReferenceImages.map((image, index) => ({ ...image, label: `参考图 ${index + 1}` })),
  ];

  const ecommerceVideoImageDataUrls = useMemo(
    () => productImages.map((img) => img.src),
    [productImages],
  );
  const ecommerceVideoImageFiles = useMemo(
    () => productImages.map((img) => img.file),
    [productImages],
  );

  const selectedProductSetOutput =
    productSetOutputOptions.find((option) => option.key === productSetOutput) ?? productSetOutputOptions[0]!;
  const selectedCloneOutput = cloneOutputOptions.find((option) => option.key === cloneOutput) ?? cloneOutputOptions[1]!;
  const visibleCommerceScenarioOptions = useMemo(
    () =>
      isCommerceScenarioMoreOpen
        ? commerceScenarioOptions
        : commerceScenarioOptions.filter((option) => primaryCommerceScenarioKeys.includes(option.key)),
    [isCommerceScenarioMoreOpen],
  );
  const activeCommerceScenarioTemplates = activeCommerceScenario === null
    ? []
    : activeCommerceScenario === "popular"
      ? popularCommerceScenarioTemplates
      : commerceScenarioTemplates.filter((template) => template.scenario === activeCommerceScenario);
  const shouldShowScenarioSettings = activeCommerceScenario !== null && scenarioSettingsKeys.includes(activeCommerceScenario);
  useEffect(() => {
    templateStripRef.current?.scrollTo({ left: 0, behavior: "auto" });
  }, [activeCommerceScenario, isCloneTemplateStripVisible]);
  const cloneRequirementPlaceholder =
    cloneOutput === "model"
      ? "建议包含以下信息：产品名称、核心卖点、期望场景、模特外貌描述（如小麦色皮肤、齐刘海、眼角有泪痣）、具体参数"
      : "建议包含以下信息：产品名称、核心卖点、期望场景、具体参数";
  const productSetPreviewReady = productSetStatus === "done";
  const cloneSetTotal = useMemo(
    () => Object.values(cloneSetCounts).reduce((sum, value) => sum + value, 0),
    [cloneSetCounts],
  );
  const canGenerateSet = setImages.length > 0 && productSetStatus !== "generating";
  const canGenerate = productImages.length > 0 && status !== "generating";
  const canGenerateTryOn = garmentImages.length > 0 && tryOnStatus !== "generating" && tryOnStatus !== "modeling";
  const canGenerateDetail = detailProductImages.length > 0 && detailStatus !== "generating";
  const canGenerateHot = cloneReferenceImages.length > 0 && hotStatus !== "generating";
  const canGenerateQuickSet = productImages.length > 0 && quickSetStatus !== "generating";
  const cloneVideoDurationProgress =
    ((cloneVideoDuration - cloneVideoDurationMin) / (cloneVideoDurationMax - cloneVideoDurationMin)) * 100;
  const cloneVideoDurationStyle: CSSProperties = useMemo(
    () => ({
      "--clone-video-duration-progress": `${cloneVideoDurationProgress}%`,
    }) as CSSProperties,
    [cloneVideoDurationProgress],
  );

  const trackEcommerceTask = (taskId: string) => {
    activeEcommerceTaskIdsRef.current.add(taskId);
  };

  const untrackEcommerceTask = (taskId: string) => {
    activeEcommerceTaskIdsRef.current.delete(taskId);
  };

  const handleCancelGenerate = () => {
    imageAbortRef.current.current = true;
    const taskIds = Array.from(activeEcommerceTaskIdsRef.current);
    activeEcommerceTaskIdsRef.current.clear();
    taskIds.forEach((taskId) => {
      aiGenerationClient.cancelTask(taskId).catch(() => {});
    });
    lastFailedActionRef.current = null;
    if (productSetStatus === "generating") setProductSetStatus("idle");
    if (status === "generating") {
      setStatus("idle");
      if (activeHistoryRecordId) {
        const turnId = activeHistoryTurnIdRef.current;
        if (turnId) {
          updateLocalEcommerceHistoryTurn(activeHistoryRecordId, turnId, (turn) => ({
            ...turn,
            status: "failed",
            errorMessage: "已取消生成",
          }));
        } else {
          updateLocalEcommerceHistoryRecord(activeHistoryRecordId, (record) => ({
            ...record,
            status: "failed",
            errorMessage: "已取消生成",
          }));
        }
      }
    }
    if (detailStatus === "generating") setDetailStatus("idle");
    if (tryOnStatus === "generating") setTryOnStatus("idle");
    if (tryOnStatus === "modeling") setTryOnStatus("ready");
    toast.info("\u5df2\u53d6\u6d88\u751f\u6210");
  };

  const syncRequirementMentionQuery = (value: string, selectionStart: number | null | undefined) => {
    setRequirementImageMentionQuery(ecommerceMentionImages.length ? getImageMentionQuery(value, selectionStart) : null);
  };

  const insertRequirementImageMention = (image: MentionImageOption) => {
    const textarea = requirementTextareaRef.current;
    const cursor = textarea?.selectionStart ?? requirement.length;
    const next = insertImageMentionValue(requirement, cursor, image.name, 500);
    setRequirement(next.value);
    setRequirementImageMentionQuery(null);
    window.requestAnimationFrame(() => {
      requirementTextareaRef.current?.focus();
      requirementTextareaRef.current?.setSelectionRange(next.selectionStart, next.selectionStart);
    });
  };

  const addSetImages = (files: File[]) => {
    if (setImages.length >= 3) return;
    const imageFiles = notifyRejectedImages(files);
    if (!imageFiles.length) return;
    const remainingSlots = 3 - setImages.length;
    if (remainingSlots <= 0) return;

    const localItems = createLocalImageItems(imageFiles, remainingSlots, "set");
    setSetImages((current) => [...current, ...localItems].slice(0, 3));
    setProductSetStatus("ready");

    Promise.all(localItems.map(async (item) => {
      const uploaded = await uploadImageItem(item);
      if (uploaded.src) URL.revokeObjectURL(item.src);
      return { id: item.id, uploaded };
    })).then((results) => {
      const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
      setSetImages((current) => current.map((item) => {
        const update = updateMap.get(item.id);
        return update ? { ...item, ...update } : item;
      }));
    }).catch(() => {
      toast.error("套图后台上传失败，请检查网络后重试");
    });
  };

  const handleSetUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files?.length) return;
    void addSetImages(Array.from(files));
    event.target.value = "";
  };

  const handleSetDrop = (event: DragEvent<HTMLButtonElement>) => {
    event.preventDefault();
    setIsSetUploadDragging(false);
    const files = Array.from(event.dataTransfer.files);
    if (files.length) void addSetImages(files);
  };

  const revokeSmartCutoutItem = (item: SmartCutoutImageItem | null) => {
    if (!item) return;
    URL.revokeObjectURL(item.src);
    if (item.originalSrc && item.originalSrc !== item.src) URL.revokeObjectURL(item.originalSrc);
  };

  const revokeSmartCutoutItems = (items: SmartCutoutImageItem[]) => {
    items.forEach(revokeSmartCutoutItem);
  };

  const clearSmartCutoutTransition = () => {
    if (smartCutoutTransitionTimeoutRef.current !== null) {
      window.clearTimeout(smartCutoutTransitionTimeoutRef.current);
      smartCutoutTransitionTimeoutRef.current = null;
    }
    if (smartCutoutPendingUrlsRef.current.length) {
      smartCutoutPendingUrlsRef.current.forEach((url) => URL.revokeObjectURL(url));
      smartCutoutPendingUrlsRef.current = [];
    }
    setIsSmartCutoutTransitioning(false);
  };

  const runSmartCutoutPageTransition = (message: { title: string; subtitle: string }, action: () => void, delay = 460) => {
    clearSmartCutoutTransition();
    setSmartCutoutTransitionMessage(message);
    setIsSmartCutoutTransitioning(true);
    smartCutoutTransitionTimeoutRef.current = window.setTimeout(() => {
      smartCutoutTransitionTimeoutRef.current = null;
      action();
      setIsSmartCutoutTransitioning(false);
    }, delay);
  };

  const openSmartCutoutUpload = () => {
    clearSmartCutoutTransition();
    setComposerMenu(null);
    toast.info("功能正在优化中");
  };

  const openWatermarkRemovalPage = () => {
    clearSmartCutoutTransition();
    setActiveQuickTool("watermark");
    setComposerMenu(null);
    setIsCloneSettingsCollapsed(false);
  };

  const loadRemoteImageFromInput = async (input: HTMLInputElement | null, fallbackName: string) => {
    const rawValue = input?.value.trim() ?? "";
    if (!rawValue) {
      toast.info("请先粘贴图片 URL");
      return null;
    }

    let imageUrl: string;
    try {
      const parsed = new URL(rawValue, window.location.href);
      if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
        throw new Error("仅支持 http 或 https 图片链接");
      }
      imageUrl = parsed.toString();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "图片 URL 不正确");
      return null;
    }

    try {
      const response = await fetch(imageUrl);
      if (!response.ok) throw new Error(`图片读取失败（${response.status}）`);
      const blob = await response.blob();
      if (!blob.type.startsWith("image/")) throw new Error("链接内容不是图片");
      const src = URL.createObjectURL(blob);
      try {
        await readImageDimensions(src);
      } catch {
        URL.revokeObjectURL(src);
        throw new Error("图片无法预览，请换一个链接");
      }
      if (input) input.value = "";
      return {
        src,
        name: getRemoteImageName(imageUrl, fallbackName),
        format: getRemoteImageFormat(blob.type, imageUrl),
      };
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "图片导入失败");
      return null;
    }
  };

  const closeWatermarkRemovalPage = () => {
    stopWatermarkProgress();
    setActiveQuickTool(null);
    setWatermarkStatus("idle");
    setWatermarkResultUrl(null);
    setWatermarkProgress(0);
    setWatermarkImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return null;
    });
  };

  const addWatermarkImage = (file: File) => {
    const nextImage = {
      src: URL.createObjectURL(file),
      name: file.name,
      format: getImageFileFormat(file) || "PNG / JPG / WebP",
    };
    setWatermarkImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return nextImage;
    });
    setWatermarkStatus("idle");
    setWatermarkResultUrl(null);
    setWatermarkProgress(0);
    setActiveQuickTool("watermark");
  };

  const removeWatermarkImage = () => {
    stopWatermarkProgress();
    setWatermarkStatus("idle");
    setWatermarkResultUrl(null);
    setWatermarkProgress(0);
    setWatermarkImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return null;
    });
  };

  const handleWatermarkUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;
    addWatermarkImage(file);
    event.target.value = "";
  };

  const handleWatermarkDrop = (event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setIsWatermarkDragging(false);
    const file = Array.from(event.dataTransfer.files).find((item) => item.type.startsWith("image/"));
    if (file) addWatermarkImage(file);
  };

  const handleWatermarkUrlImport = async () => {
    const nextImage = await loadRemoteImageFromInput(watermarkUrlInputRef.current, "watermark-source");
    if (!nextImage) return;
    setWatermarkImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return nextImage;
    });
    setWatermarkStatus("idle");
    setActiveQuickTool("watermark");
    toast.success("图片已导入");
  };

  const stopWatermarkProgress = () => {
    if (watermarkProcessTimeoutRef.current !== null) {
      window.clearInterval(watermarkProcessTimeoutRef.current);
      watermarkProcessTimeoutRef.current = null;
    }
  };

  const startWatermarkProgress = () => {
    stopWatermarkProgress();
    setWatermarkProgress(0);
    watermarkProcessTimeoutRef.current = window.setInterval(() => {
      setWatermarkProgress((prev) => {
        if (prev >= 90) {
          stopWatermarkProgress();
          return 90;
        }
        return prev + (90 - prev) * 0.06;
      });
    }, 500);
  };

  const handleWatermarkGenerate = async () => {
    if (!watermarkImage || watermarkStatus === "processing") return;
    setWatermarkStatus("processing");
    setWatermarkResultUrl(null);
    startWatermarkProgress();

    try {
      const sourceBlob = await fetch(watermarkImage.src).then((res) => res.blob());
      const sourceMime = normalizeEcommerceImageMime(sourceBlob.type || "image/png");
      const { url: imageUrl } = await aiGenerationClient.uploadAssetBinary(sourceBlob, {
        name: `watermark-source-${Date.now()}.png`,
        mimeType: sourceMime,
        scope: ecommerceOssScopes.productSource,
      });

      const { taskId } = await aiGenerationClient.createImageEditTask({
        imageUrl,
        function: "watermark-remove",
      });

      const resultUrl = await waitForTask(taskId, {
        abortRef: { current: false },
        onProgress: () => {},
      });

      if (resultUrl) {
        const persistedUrl = await persistGeneratedImageUrl(resultUrl, ecommerceOssScopes.cloneResult("watermark"), "ecommerce-watermark");
        setWatermarkResultUrl(persistedUrl);
        setWatermarkStatus("done");
        stopWatermarkProgress();
        setWatermarkProgress(100);
        toast.success("去水印处理完成");
        void saveUnifiedEcommerceGenerationRecord({
          clientRecordId: crypto.randomUUID(),
          title: `去水印 ${watermarkImage.name || ""}`.trim(),
          mode: "watermark",
          taskIds: [taskId],
          sourceImages: [{ url: imageUrl, label: watermarkImage.name || "watermark-source" }],
          results: [{ url: persistedUrl, label: "去水印结果", mediaType: "image", taskId }],
          createdAt: new Date().toISOString(),
        });
      } else {
        setWatermarkStatus("failed");
        stopWatermarkProgress();
        setWatermarkProgress(0);
        toast.error("去水印未返回结果");
      }
    } catch (err) {
      setWatermarkStatus("failed");
      stopWatermarkProgress();
      setWatermarkProgress(0);
      if (err instanceof ServerRequestError && err.status === 402) {
        toast.error("余额不足，请充值后继续");
      } else {
        toast.error(err instanceof Error ? err.message : "去水印失败");
      }
    }
  };

  const handleWatermarkDownload = () => {
    if (!watermarkResultUrl || watermarkStatus !== "done") {
      toast.info("请先完成去水印");
      return;
    }
    const link = document.createElement("a");
    const safeName = (watermarkImage?.name || "watermark-result").replace(/\.[^.]+$/, "").replace(/[\\/:*?"<>|]+/g, "-");
    link.href = watermarkResultUrl;
    link.download = `${safeName || "watermark-result"}-去水印.png`;
    document.body.appendChild(link);
    link.click();
    link.remove();
  };

  const openImageTranslatePage = () => {
    clearSmartCutoutTransition();
    setComposerMenu(null);
    toast.info("功能正在优化中");
  };

  const closeImageTranslatePage = () => {
    if (translateProcessTimeoutRef.current !== null) {
      window.clearTimeout(translateProcessTimeoutRef.current);
      translateProcessTimeoutRef.current = null;
    }
    setActiveQuickTool(null);
    setTranslateStatus("idle");
    setTranslateResultUrl(null);
    setTranslateImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return null;
    });
  };

  const addTranslateImage = (file: File) => {
    const nextImage = {
      src: URL.createObjectURL(file),
      name: file.name,
      format: getImageFileFormat(file) || "PNG / JPG / WebP",
    };
    setTranslateImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return nextImage;
    });
    setTranslateStatus("idle");
    setTranslateResultUrl(null);
    setActiveQuickTool("translate");
  };

  const removeTranslateImage = () => {
    if (translateProcessTimeoutRef.current !== null) {
      window.clearTimeout(translateProcessTimeoutRef.current);
      translateProcessTimeoutRef.current = null;
    }
    setTranslateStatus("idle");
    setTranslateResultUrl(null);
    setTranslateImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return null;
    });
  };

  const handleTranslateUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;
    addTranslateImage(file);
    event.target.value = "";
  };

  const handleTranslateDrop = (event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setIsTranslateDragging(false);
    const file = Array.from(event.dataTransfer.files).find((item) => item.type.startsWith("image/"));
    if (file) addTranslateImage(file);
  };

  const handleTranslateUrlImport = async () => {
    const nextImage = await loadRemoteImageFromInput(translateUrlInputRef.current, "translate-source");
    if (!nextImage) return;
    setTranslateImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return nextImage;
    });
    setTranslateStatus("idle");
    setTranslateResultUrl(null);
    toast.success("图片已导入");
  };

  const handleTranslateGenerate = async () => {
    if (!translateImage || translateStatus === "processing") return;
    const targetLabel = translateLanguageOptions.find((option) => option.value === translateLanguage)?.label || "中文";
    setTranslateStatus("processing");
    setTranslateResultUrl(null);

    try {
      const sourceBlob = await fetch(translateImage.src).then((res) => res.blob());
      const sourceMime = normalizeEcommerceImageMime(sourceBlob.type || "image/png");
      const { url: imageUrl } = await aiGenerationClient.uploadAssetBinary(sourceBlob, {
        name: `translate-source-${Date.now()}.png`,
        mimeType: sourceMime,
        scope: ecommerceOssScopes.productSource,
      });

      const prompt = `将图片中的所有文字翻译成${targetLabel}，保持原有的排版、字体风格、位置和整体设计不变，只替换文字内容。`;
      const { taskId } = await aiGenerationClient.createImageEditTask({
        imageUrl,
        function: "description_edit",
        prompt,
      });

      const resultUrl = await waitForTask(taskId, {
        kind: "image",
        abortRef: { current: false },
        onProgress: () => {},
      });

      if (resultUrl) {
        const persistedUrl = await persistGeneratedImageUrl(resultUrl, ecommerceOssScopes.cloneResult("translate"), "ecommerce-translate");
        setTranslateResultUrl(persistedUrl);
        setTranslateStatus("done");
        toast.success("图片翻译完成");
        void saveUnifiedEcommerceGenerationRecord({
          clientRecordId: crypto.randomUUID(),
          title: `图片翻译（${targetLabel}） ${translateImage.name || ""}`.trim(),
          mode: "translate",
          prompt,
          taskIds: [taskId],
          sourceImages: [{ url: imageUrl, label: translateImage.name || "translate-source" }],
          results: [{ url: persistedUrl, label: "翻译结果", mediaType: "image", taskId }],
          config: { targetLanguage: translateLanguage },
          createdAt: new Date().toISOString(),
        });
      } else {
        setTranslateStatus("failed");
        toast.error("翻译未返回结果");
      }
    } catch (err) {
      setTranslateStatus("failed");
      if (err instanceof ServerRequestError && err.status === 402) {
        toast.error("余额不足，请充值后继续");
      } else {
        toast.error(err instanceof Error ? err.message : "图片翻译失败");
      }
    }
  };

  const handleTranslateDownload = () => {
    if (!translateResultUrl || translateStatus !== "done") {
      toast.info("请先完成图片翻译");
      return;
    }
    const link = document.createElement("a");
    const safeName = (translateImage?.name || "translate-result").replace(/\.[^.]+$/, "").replace(/[\\/:*?"<>|]+/g, "-");
    link.href = translateResultUrl;
    link.download = `${safeName || "translate-result"}-翻译.png`;
    document.body.appendChild(link);
    link.click();
    link.remove();
  };

  const openImageWorkbenchPage = () => {
    clearSmartCutoutTransition();
    setActiveQuickTool("image-edit");
    setComposerMenu(null);
    setIsCloneSettingsCollapsed(false);
    setImageWorkbenchStatus("idle");
  };

  const closeImageWorkbenchPage = () => {
    setActiveQuickTool(null);
    setImageWorkbenchStatus("idle");
    setImageWorkbenchResultUrl(null);
    setImageWorkbenchPrompt("");
    setImageWorkbenchMaskStrokes([]);
    setImageWorkbenchBrushCursor(null);
    clearImageWorkbenchMaskCanvas();
    imageWorkbenchMaskPaintingRef.current = false;
    imageWorkbenchActiveStrokeIdRef.current = null;
    setImageWorkbenchImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return null;
    });
  };

  const addImageWorkbenchImage = (file: File) => {
    if (!file.type.startsWith("image/")) {
      toast.error("请上传图片文件");
      return;
    }
    const nextImage = {
      src: URL.createObjectURL(file),
      name: file.name,
      format: getImageFileFormat(file) || "PNG / JPG / WebP",
    };
    setImageWorkbenchImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return nextImage;
    });
    setImageWorkbenchStatus("idle");
    setImageWorkbenchResultUrl(null);
    setImageWorkbenchMaskStrokes([]);
    setImageWorkbenchBrushCursor(null);
    clearImageWorkbenchMaskCanvas();
    imageWorkbenchActiveStrokeIdRef.current = null;
    setActiveQuickTool("image-edit");
  };

  const removeImageWorkbenchImage = () => {
    setImageWorkbenchStatus("idle");
    setImageWorkbenchResultUrl(null);
    setImageWorkbenchMaskStrokes([]);
    setImageWorkbenchBrushCursor(null);
    clearImageWorkbenchMaskCanvas();
    imageWorkbenchMaskPaintingRef.current = false;
    imageWorkbenchActiveStrokeIdRef.current = null;
    setImageWorkbenchImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return null;
    });
  };

  const handleImageWorkbenchUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;
    addImageWorkbenchImage(file);
    event.target.value = "";
  };

  const handleImageWorkbenchDrop = (event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setIsImageWorkbenchDragging(false);
    const file = Array.from(event.dataTransfer.files).find((item) => item.type.startsWith("image/"));
    if (file) addImageWorkbenchImage(file);
  };

  const handleImageWorkbenchUrlImport = async () => {
    const nextImage = await loadRemoteImageFromInput(imageWorkbenchUrlInputRef.current, "image-workbench-source");
    if (!nextImage) return;
    setImageWorkbenchImage((current) => {
      if (current?.src) URL.revokeObjectURL(current.src);
      return nextImage;
    });
    setImageWorkbenchStatus("idle");
    setImageWorkbenchMaskStrokes([]);
    setImageWorkbenchBrushCursor(null);
    clearImageWorkbenchMaskCanvas();
    imageWorkbenchActiveStrokeIdRef.current = null;
    setActiveQuickTool("image-edit");
    toast.success("图片已导入");
  };

  const stopWorkbenchProgress = () => {
    if (imageWorkbenchProgressRef.current !== null) {
      window.clearInterval(imageWorkbenchProgressRef.current);
      imageWorkbenchProgressRef.current = null;
    }
  };

  const startWorkbenchProgress = () => {
    stopWorkbenchProgress();
    setImageWorkbenchProgress(0);
    imageWorkbenchProgressRef.current = window.setInterval(() => {
      setImageWorkbenchProgress((prev) => {
        if (prev >= 90) {
          stopWorkbenchProgress();
          return 90;
        }
        return prev + (90 - prev) * 0.06;
      });
    }, 500);
  };

  const exportWorkbenchMask = (): string | null => {
    const canvas = imageWorkbenchMaskCanvasRef.current;
    if (!canvas) return null;
    const ctx = canvas.getContext("2d");
    if (!ctx) return null;
    const w = canvas.width;
    const h = canvas.height;
    const maskCanvas = document.createElement("canvas");
    maskCanvas.width = w;
    maskCanvas.height = h;
    const maskCtx = maskCanvas.getContext("2d")!;
    maskCtx.fillStyle = "#000000";
    maskCtx.fillRect(0, 0, w, h);
    const imgData = ctx.getImageData(0, 0, w, h);
    const maskData = maskCtx.getImageData(0, 0, w, h);
    for (let i = 3; i < imgData.data.length; i += 4) {
      if (imgData.data[i] > 0) {
        const pi = i - 3;
        maskData.data[pi] = 255;
        maskData.data[pi + 1] = 255;
        maskData.data[pi + 2] = 255;
        maskData.data[pi + 3] = 255;
      }
    }
    maskCtx.putImageData(maskData, 0, 0);
    return maskCanvas.toDataURL("image/png");
  };

  const handleImageWorkbenchGenerate = async () => {
    if (!imageWorkbenchImage) {
      toast.info("请先上传图片");
      return;
    }
    setImageWorkbenchStatus("processing");
    setImageWorkbenchResultUrl(null);
    startWorkbenchProgress();

    try {
      const sourceBlob = await fetch(imageWorkbenchImage.src).then((res) => res.blob());
      const sourceMime = normalizeEcommerceImageMime(sourceBlob.type || "image/png");
      const { url: imageUrl } = await aiGenerationClient.uploadAssetBinary(sourceBlob, {
        name: `inpaint-source-${Date.now()}.png`,
        mimeType: sourceMime,
        scope: ecommerceOssScopes.productSource,
      });

      let maskUrl: string | undefined;
      if (imageWorkbenchMaskStrokes.length > 0) {
        const maskDataUrl = exportWorkbenchMask();
        if (maskDataUrl) {
          const { url } = await aiGenerationClient.uploadAsset({
            dataUrl: maskDataUrl,
            name: `inpaint-mask-${Date.now()}.png`,
            mimeType: "image/png",
            scope: ecommerceOssScopes.productSource,
          });
          maskUrl = url;
        }
      }

      const { taskId } = await aiGenerationClient.createImageEditTask({
        imageUrl,
        function: "inpaint",
        prompt: imageWorkbenchPrompt || undefined,
        maskUrl,
        ratio: imageWorkbenchRatio,
      });

      const resultUrl = await waitForTask(taskId, {
        abortRef: { current: false },
        onProgress: () => {},
      });

      if (resultUrl) {
        const persistedUrl = await persistGeneratedImageUrl(resultUrl, ecommerceOssScopes.cloneResult("inpaint"), "ecommerce-inpaint");
        setImageWorkbenchResultUrl(persistedUrl);
        setImageWorkbenchStatus("done");
        stopWorkbenchProgress();
        setImageWorkbenchProgress(100);
        toast.success("局部重绘已完成");
        void saveUnifiedEcommerceGenerationRecord({
          clientRecordId: crypto.randomUUID(),
          title: imageWorkbenchPrompt.trim() || `图片修改 ${imageWorkbenchImage.name || ""}`.trim(),
          mode: "inpaint",
          prompt: imageWorkbenchPrompt || undefined,
          taskIds: [taskId],
          sourceImages: [{ url: imageUrl, label: imageWorkbenchImage.name || "inpaint-source" }],
          results: [{ url: persistedUrl, label: "局部重绘结果", mediaType: "image", taskId }],
          config: { ratio: imageWorkbenchRatio },
          createdAt: new Date().toISOString(),
        });
      } else {
        setImageWorkbenchStatus("failed");
        stopWorkbenchProgress();
        setImageWorkbenchProgress(0);
        toast.error("重绘未返回结果");
      }
    } catch (err) {
      setImageWorkbenchStatus("failed");
      stopWorkbenchProgress();
      setImageWorkbenchProgress(0);
      if (err instanceof ServerRequestError && err.status === 402) {
        toast.error("余额不足，请充值后继续");
      } else {
        toast.error(err instanceof Error ? err.message : "重绘失败");
      }
    }
  };

  const syncImageWorkbenchMaskCanvas = () => {
    const canvas = imageWorkbenchMaskCanvasRef.current;
    if (!canvas) return null;
    const rect = canvas.getBoundingClientRect();
    const dpr = window.devicePixelRatio || 1;
    const width = Math.max(1, Math.round(rect.width * dpr));
    const height = Math.max(1, Math.round(rect.height * dpr));
    if (canvas.width !== width || canvas.height !== height) {
      canvas.width = width;
      canvas.height = height;
    }
    const context = canvas.getContext("2d");
    if (!context) return null;
    context.setTransform(dpr, 0, 0, dpr, 0, 0);
    context.lineCap = "round";
    context.lineJoin = "round";
    context.strokeStyle = "rgba(30, 189, 219, 0.52)";
    context.fillStyle = "rgba(30, 189, 219, 0.52)";
    context.lineWidth = imageWorkbenchBrushSize;
    return { canvas, context, rect };
  };

  const clearImageWorkbenchMaskCanvas = () => {
    const canvas = imageWorkbenchMaskCanvasRef.current;
    if (!canvas) return;
    const context = canvas.getContext("2d");
    if (!context) return;
    context.setTransform(1, 0, 0, 1, 0, 0);
    context.clearRect(0, 0, canvas.width, canvas.height);
  };

  const getImageWorkbenchMaskPoint = (event: ReactPointerEvent<HTMLElement>) => {
    const canvas = imageWorkbenchMaskCanvasRef.current;
    const rect = (canvas ?? event.currentTarget).getBoundingClientRect();
    const x = clampNumber(event.clientX - rect.left, 0, rect.width);
    const y = clampNumber(event.clientY - rect.top, 0, rect.height);
    return { x, y };
  };

  const appendImageWorkbenchMaskPoint = (event: ReactPointerEvent<HTMLElement>) => {
    const point = getImageWorkbenchMaskPoint(event);
    const rect = imageWorkbenchMaskCanvasRef.current?.getBoundingClientRect();
    setImageWorkbenchBrushCursor(rect ? { x: (point.x / Math.max(rect.width, 1)) * 100, y: (point.y / Math.max(rect.height, 1)) * 100 } : null);
    setImageWorkbenchStatus((current) => (current === "done" ? "idle" : current));
    const activeStrokeId = imageWorkbenchActiveStrokeIdRef.current;
    if (!activeStrokeId) return;
    const lastPoint = imageWorkbenchLastMaskPointRef.current;
    if (lastPoint && Math.hypot(point.x - lastPoint.x, point.y - lastPoint.y) < 1.5) return;
    const synced = syncImageWorkbenchMaskCanvas();
    if (!synced) return;
    synced.context.beginPath();
    synced.context.moveTo(lastPoint?.x ?? point.x, lastPoint?.y ?? point.y);
    synced.context.lineTo(point.x, point.y);
    synced.context.stroke();
    imageWorkbenchLastMaskPointRef.current = point;
  };

  const handleImageWorkbenchMaskPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (!imageWorkbenchImage || event.button !== 0) return;
    event.preventDefault();
    event.currentTarget.setPointerCapture(event.pointerId);
    syncImageWorkbenchMaskCanvas();
    const point = getImageWorkbenchMaskPoint(event);
    const rect = imageWorkbenchMaskCanvasRef.current?.getBoundingClientRect();
    const strokeId = `mask-${Date.now()}`;
    imageWorkbenchMaskPaintingRef.current = true;
    imageWorkbenchActiveStrokeIdRef.current = strokeId;
    imageWorkbenchLastMaskPointRef.current = point;
    setImageWorkbenchBrushCursor(rect ? { x: (point.x / Math.max(rect.width, 1)) * 100, y: (point.y / Math.max(rect.height, 1)) * 100 } : null);
    setImageWorkbenchStatus((current) => (current === "done" ? "idle" : current));
    setImageWorkbenchMaskStrokes((current) => [...current, { id: strokeId, size: imageWorkbenchBrushSize, points: [] }]);
    const synced = syncImageWorkbenchMaskCanvas();
    if (synced) {
      synced.context.beginPath();
      synced.context.arc(point.x, point.y, imageWorkbenchBrushSize / 2, 0, Math.PI * 2);
      synced.context.fill();
    }
  };

  const handleImageWorkbenchMaskPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (!imageWorkbenchImage) return;
    const point = getImageWorkbenchMaskPoint(event);
    const rect = imageWorkbenchMaskCanvasRef.current?.getBoundingClientRect();
    setImageWorkbenchBrushCursor(rect ? { x: (point.x / Math.max(rect.width, 1)) * 100, y: (point.y / Math.max(rect.height, 1)) * 100 } : null);
    if (!imageWorkbenchMaskPaintingRef.current) return;
    appendImageWorkbenchMaskPoint(event);
  };

  const stopImageWorkbenchMaskPainting = () => {
    imageWorkbenchMaskPaintingRef.current = false;
    imageWorkbenchActiveStrokeIdRef.current = null;
    imageWorkbenchLastMaskPointRef.current = null;
  };

  const clearQuickSetSelectTimer = () => {
    if (quickSetSelectTimerRef.current) {
      window.clearTimeout(quickSetSelectTimerRef.current);
      quickSetSelectTimerRef.current = null;
    }
  };

  const resetQuickSetSelectState = () => {
    clearQuickSetSelectTimer();
    openQuickSetSelectRef.current = null;
    visibleQuickSetSelectRef.current = null;
    setOpenQuickSetSelect(null);
    setVisibleQuickSetSelect(null);
    setIsQuickSetSelectClosing(false);
  };

  const closeQuickSetSelect = () => {
    if (!visibleQuickSetSelectRef.current) {
      openQuickSetSelectRef.current = null;
      setOpenQuickSetSelect(null);
      return;
    }
    clearQuickSetSelectTimer();
    openQuickSetSelectRef.current = null;
    setOpenQuickSetSelect(null);
    setIsQuickSetSelectClosing(true);
    quickSetSelectTimerRef.current = window.setTimeout(() => {
      visibleQuickSetSelectRef.current = null;
      setVisibleQuickSetSelect(null);
      setIsQuickSetSelectClosing(false);
      quickSetSelectTimerRef.current = null;
    }, 420);
  };

  const showQuickSetSelect = (key: CloneBasicSelectKey) => {
    clearQuickSetSelectTimer();
    if (visibleQuickSetSelectRef.current && visibleQuickSetSelectRef.current !== key && openQuickSetSelectRef.current) {
      openQuickSetSelectRef.current = null;
      setOpenQuickSetSelect(null);
      setIsQuickSetSelectClosing(true);
      quickSetSelectTimerRef.current = window.setTimeout(() => {
        visibleQuickSetSelectRef.current = key;
        openQuickSetSelectRef.current = key;
        setVisibleQuickSetSelect(key);
        setOpenQuickSetSelect(key);
        setIsQuickSetSelectClosing(false);
        quickSetSelectTimerRef.current = null;
      }, 180);
      return;
    }
    visibleQuickSetSelectRef.current = key;
    openQuickSetSelectRef.current = key;
    setVisibleQuickSetSelect(key);
    setOpenQuickSetSelect(key);
    setIsQuickSetSelectClosing(false);
  };

  const toggleQuickSetSelect = (key: CloneBasicSelectKey) => {
    if (openQuickSetSelectRef.current === key) {
      closeQuickSetSelect();
      return;
    }
    showQuickSetSelect(key);
  };

  const openQuickDetailPage = () => {
    clearSmartCutoutTransition();
    setActiveQuickTool("detail");
    setComposerMenu(null);
    setIsCloneSettingsCollapsed(false);
    setIsQuickPanelCollapsed(false);
    setPreviewZoom(1);
    resetQuickSetSelectState();
    if (!selectedDetailModules.length) setSelectedDetailModules(defaultCloneDetailModuleIds);
  };

  const openHotClonePage = () => {
    clearSmartCutoutTransition();
    setActiveQuickTool("hot");
    setComposerMenu(null);
    setIsCloneSettingsCollapsed(false);
    setIsQuickPanelCollapsed(false);
    setPreviewZoom(1);
    resetQuickSetSelectState();
  };

  const closeSmartCutoutTool = () => {
    runSmartCutoutPageTransition(
      {
        title: "正在返回首页",
        subtitle: "回到电商智能体",
      },
      () => {
        setSmartCutoutBatchImages((current) => {
          revokeSmartCutoutItems(current);
          return [];
        });
        setSmartCutoutImage((current) => {
          revokeSmartCutoutItem(current);
          return null;
        });
        setIsSmartCutoutComparing(false);
        setActiveQuickTool(null);
        setComposerMenu(null);
      },
    );
  };

  const goSmartCutoutPrevious = () => {
    if (!smartCutoutImage) {
      closeSmartCutoutTool();
      return;
    }
    runSmartCutoutPageTransition(
      {
        title: "正在返回上一页",
        subtitle: "回到图片上传页",
      },
      () => {
        setSmartCutoutBatchImages((current) => {
          revokeSmartCutoutItems(current);
          return [];
        });
        setSmartCutoutImage((current) => {
          revokeSmartCutoutItem(current);
          return null;
        });
        setIsSmartCutoutComparing(false);
      },
    );
  };

  const addSmartCutoutImage = (files: File[]) => {
    const imageFiles = files.filter((file) => file.type.startsWith("image/"));
    if (!imageFiles.length) {
      toast.error("请上传图片文件");
      return;
    }
    clearSmartCutoutTransition();
    setSmartCutoutBatchImages((current) => {
      revokeSmartCutoutItems(current);
      return [];
    });
    setSmartCutoutImage((current) => {
      revokeSmartCutoutItem(current);
      return null;
    });
    setIsSmartCutoutComparing(false);
    const nextImages = imageFiles.map((file) => {
      const originalSrc = URL.createObjectURL(file);
      return { src: originalSrc, originalSrc, name: file.name };
    });
    smartCutoutPendingUrlsRef.current = nextImages.map((item) => item.src);
    setActiveQuickTool("cutout");
    setSmartCutoutSizeKey("original");
    setSmartCutoutTransitionMessage({
      title: imageFiles.length > 1 ? "正在批量抠图" : "正在智能抠图",
      subtitle: imageFiles.length > 1 ? `正在处理 ${imageFiles.length} 张图片` : "即将进入图片编辑室",
    });
    setIsSmartCutoutTransitioning(true);
    smartCutoutTransitionTimeoutRef.current = window.setTimeout(() => {
      smartCutoutTransitionTimeoutRef.current = null;
      smartCutoutPendingUrlsRef.current = [];
      setSmartCutoutBatchImages(nextImages);
      setSmartCutoutImage(nextImages[0]);
      setIsSmartCutoutTransitioning(false);
    }, 620);
  };

  const handleSmartCutoutUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files?.length) return;
    addSmartCutoutImage(Array.from(files));
    event.target.value = "";
  };

  const handleSmartCutoutDrop = (event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setIsSmartCutoutDragging(false);
    const files = Array.from(event.dataTransfer.files);
    if (files.length) addSmartCutoutImage(files);
  };

  const smartCutoutBackgroundValue = useMemo(() => {
    const rgb = hexToRgb(smartCutoutBackgroundColor) ?? { r: 255, g: 255, b: 255 };
    if (smartCutoutBackgroundAlpha >= 100) return smartCutoutBackgroundColor;
    return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${Math.round(smartCutoutBackgroundAlpha) / 100})`;
  }, [smartCutoutBackgroundAlpha, smartCutoutBackgroundColor]);

  const smartCutoutColorHsv = useMemo(() => hexToHsv(smartCutoutBackgroundColor), [smartCutoutBackgroundColor]);

  const selectedSmartCutoutSize = useMemo(
    () => smartCutoutSizeOptions.find((option) => option.key === smartCutoutSizeKey) ?? smartCutoutSizeOptions[0],
    [smartCutoutSizeKey],
  );

  const previewSmartCutoutSize = isSmartCutoutComparing ? smartCutoutSizeOptions[0] : selectedSmartCutoutSize;
  const previewSmartCutoutSizeKey = isSmartCutoutComparing ? "original" : smartCutoutSizeKey;
  const previewSmartCutoutImageSrc = isSmartCutoutComparing ? smartCutoutImage?.originalSrc ?? smartCutoutImage?.src : smartCutoutImage?.src;

  const smartCutoutFrameStyle = useMemo<CSSProperties>(
    () => ({
      "--smart-cutout-bg": smartCutoutBackgroundValue,
      "--smart-cutout-frame-width": previewSmartCutoutSize.frameWidth,
      "--smart-cutout-frame-aspect": previewSmartCutoutSize.frameAspect,
      "--smart-cutout-image-max-width": previewSmartCutoutSize.imageMaxWidth,
      "--smart-cutout-image-max-height": previewSmartCutoutSize.imageMaxHeight,
    } as CSSProperties),
    [previewSmartCutoutSize, smartCutoutBackgroundValue],
  );

  const showSmartCutoutOriginalCompare = (event: ReactPointerEvent<HTMLButtonElement>) => {
    event.currentTarget.setPointerCapture(event.pointerId);
    setIsSmartCutoutComparing(true);
  };

  const hideSmartCutoutOriginalCompare = () => {
    setIsSmartCutoutComparing(false);
  };

  const applySmartCutoutHsv = (h: number, s: number, v: number) => {
    const rgb = hsvToRgb(h, s, v);
    setSmartCutoutBackgroundColor(rgbToHex(rgb.r, rgb.g, rgb.b));
  };

  const updateSmartCutoutColorFromPoint = (element: HTMLElement, clientX: number, clientY: number) => {
    const rect = element.getBoundingClientRect();
    const saturation = clampNumber(((clientX - rect.left) / rect.width) * 100, 0, 100);
    const value = clampNumber(100 - ((clientY - rect.top) / rect.height) * 100, 0, 100);
    applySmartCutoutHsv(smartCutoutColorHsv.h, saturation, value);
  };

  const handleSmartCutoutColorPlanePointer = (event: ReactPointerEvent<HTMLButtonElement>) => {
    event.preventDefault();
    event.currentTarget.setPointerCapture(event.pointerId);
    updateSmartCutoutColorFromPoint(event.currentTarget, event.clientX, event.clientY);
  };

  const handleSmartCutoutColorPlaneMove = (event: ReactPointerEvent<HTMLButtonElement>) => {
    if (event.buttons !== 1) return;
    updateSmartCutoutColorFromPoint(event.currentTarget, event.clientX, event.clientY);
  };

  const handleSmartCutoutHexChange = (value: string) => {
    const nextValue = value.startsWith("#") ? value : `#${value}`;
    if (!/^#[0-9a-fA-F]{0,6}$/.test(nextValue)) return;
    setSmartCutoutHexDraft(nextValue);
    const normalized = normalizeHexColor(nextValue);
    if (normalized) setSmartCutoutBackgroundColor(normalized);
  };

  const scrollSmartCutoutTools = (direction: -1 | 1) => {
    smartCutoutToolsRef.current?.scrollBy({
      left: direction * 340,
      behavior: "smooth",
    });
  };

  const handleSmartCutoutDownload = async () => {
    if (!smartCutoutImage) {
      toast.error("请先上传图片");
      return;
    }
    try {
      const image = new Image();
      image.decoding = "async";
      const imageLoaded = new Promise<void>((resolve, reject) => {
        image.onload = () => resolve();
        image.onerror = () => reject(new Error("图片加载失败"));
      });
      image.src = smartCutoutImage.src;
      await imageLoaded;

      const aspect = parseSmartCutoutAspect(selectedSmartCutoutSize.frameAspect);
      const naturalWidth = Math.max(1, image.naturalWidth || image.width || 1200);
      const naturalHeight = Math.max(1, image.naturalHeight || image.height || 900);
      const outputWidth = "outputWidth" in selectedSmartCutoutSize ? selectedSmartCutoutSize.outputWidth : undefined;
      const outputHeight = "outputHeight" in selectedSmartCutoutSize ? selectedSmartCutoutSize.outputHeight : undefined;
      let canvasWidth = naturalWidth;
      let canvasHeight = naturalHeight;
      if (outputWidth && outputHeight) {
        canvasWidth = outputWidth;
        canvasHeight = outputHeight;
      } else if (aspect) {
        const longSide = 1600;
        if (aspect >= 1) {
          canvasWidth = longSide;
          canvasHeight = Math.round(longSide / aspect);
        } else {
          canvasHeight = longSide;
          canvasWidth = Math.round(longSide * aspect);
        }
      } else {
        const maxSide = 1600;
        const scale = Math.min(1, maxSide / Math.max(naturalWidth, naturalHeight));
        canvasWidth = Math.max(1, Math.round(naturalWidth * scale));
        canvasHeight = Math.max(1, Math.round(naturalHeight * scale));
      }

      const canvas = document.createElement("canvas");
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;
      const context = canvas.getContext("2d");
      if (!context) throw new Error("无法生成图片");

      context.clearRect(0, 0, canvasWidth, canvasHeight);
      context.fillStyle = smartCutoutBackgroundValue;
      context.fillRect(0, 0, canvasWidth, canvasHeight);

      const maxWidthRatio = parseSmartCutoutPercent(selectedSmartCutoutSize.imageMaxWidth, 0.82);
      const maxHeightRatio = parseSmartCutoutPercent(selectedSmartCutoutSize.imageMaxHeight, 0.82);
      const fitScale = Math.min((canvasWidth * maxWidthRatio) / naturalWidth, (canvasHeight * maxHeightRatio) / naturalHeight, 1);
      const drawWidth = Math.round(naturalWidth * fitScale);
      const drawHeight = Math.round(naturalHeight * fitScale);
      const drawX = Math.round((canvasWidth - drawWidth) / 2);
      const drawY = Math.round((canvasHeight - drawHeight) / 2);
      context.drawImage(image, drawX, drawY, drawWidth, drawHeight);

      const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, "image/png"));
      if (!blob) throw new Error("图片导出失败");

      const objectUrl = URL.createObjectURL(blob);
      const link = document.createElement("a");
      const safeName = (smartCutoutImage.name || "smart-cutout").replace(/\.[^.]+$/, "").replace(/[\\/:*?"<>|]+/g, "-");
      link.href = objectUrl;
      link.download = `${safeName || "smart-cutout"}-${selectedSmartCutoutSize.label}.png`;
      document.body.appendChild(link);
      link.click();
      link.remove();
      window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
      toast.success("已下载图片");
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "下载图片失败");
    }
  };

  useEffect(() => {
    setSmartCutoutHexDraft(smartCutoutBackgroundColor);
  }, [smartCutoutBackgroundColor]);

  useEffect(() => {
    if (!isSmartCutoutPaletteOpen) return undefined;
    const handlePointerDown = (event: PointerEvent) => {
      if (!smartCutoutPaletteRef.current?.contains(event.target as Node)) {
        setIsSmartCutoutPaletteOpen(false);
      }
    };
    document.addEventListener("pointerdown", handlePointerDown);
    return () => document.removeEventListener("pointerdown", handlePointerDown);
  }, [isSmartCutoutPaletteOpen]);

  const removeSetImage = (imageId: string) => {
    setSetImages((current) => {
      const next = current.filter((item) => item.id !== imageId);
      if (next.length === 0) setProductSetStatus("idle");
      return next;
    });
  };

  const addProductImages = (files: File[]) => {
    const imageFiles = notifyRejectedImages(files);
    if (!imageFiles.length) return;
    const remainingSlots = maxCloneProductImages - productImages.length;
    if (remainingSlots <= 0) return;

    const localItems = createLocalImageItems(imageFiles, remainingSlots, "product");
    setProductImages((current) => [...current, ...localItems].slice(0, maxCloneProductImages));
    setStatus("ready");
    setResults([]);

    Promise.all(localItems.map(async (item) => {
      const uploaded = await uploadImageItem(item);
      if (uploaded.src) {
        URL.revokeObjectURL(item.src);
      }
      return { id: item.id, uploaded };
    })).then((results) => {
      const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
      setProductImages((current) => current.map((item) => {
        const update = updateMap.get(item.id);
        if (!update) return item;
        return { ...item, ...update };
      }));
    }).catch(() => {
      toast.error("商品图后台上传失败，请检查网络后重试");
    });
  };

  const handleProductUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files?.length) return;
    void addProductImages(Array.from(files));
    event.target.value = "";
  };

  const addComposerAssets = (files: File[]) => {
    const imageFiles = files.filter((file) => file.type.startsWith("image/"));
    const unsupportedCount = files.length - imageFiles.length;
    if (imageFiles.length) void addProductImages(imageFiles);
    if (unsupportedCount > 0) toast.info("仅支持上传图片文件");
  };

  const handleComposerAssetUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files?.length) return;
    addComposerAssets(Array.from(files));
    event.target.value = "";
  };

  const handleProductDrop = (event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    setIsProductUploadDragging(false);
    const files = Array.from(event.dataTransfer.files);
    if (files.length) void addProductImages(files);
  };

  const removeProductImage = (imageId: string) => {
    setProductImages((current) => {
      const next = current.filter((item) => item.id !== imageId);
      if (next.length === 0) {
        setStatus("idle");
        setResults([]);
      }
      return next;
    });
  };

  const hydrateCloneReferenceImageMeta = (items: CloneImageItem[]) => {
    items.forEach((item) => {
      readImageDimensions(item.src)
        .then(({ width, height }) => {
          setCloneReferenceImages((current) =>
            current.map((currentItem) => (currentItem.id === item.id ? { ...currentItem, width, height } : currentItem)),
          );
        })
        .catch(() => undefined);
    });
  };

  const addCloneReferenceImages = (files: File[]) => {
    const imageFiles = notifyRejectedImages(files);
    if (!imageFiles.length) return;
    const remainingSlots = maxCloneReferenceImages - cloneReferenceImages.length;
    if (remainingSlots <= 0) return;

    const localItems = createLocalImageItems(imageFiles, remainingSlots, "reference");
    setCloneReferenceImages((current) => [...current, ...localItems].slice(0, maxCloneReferenceImages));
    hydrateCloneReferenceImageMeta(localItems);

    Promise.all(localItems.map(async (item) => {
      const uploaded = await uploadImageItem(item);
      if (uploaded.src) URL.revokeObjectURL(item.src);
      return { id: item.id, uploaded };
    })).then((results) => {
      const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
      setCloneReferenceImages((current) => current.map((item) => {
        const update = updateMap.get(item.id);
        return update ? { ...item, ...update } : item;
      }));
    }).catch(() => {
      toast.error("参考图后台上传失败，请检查网络后重试");
    });
  };

  const removeCloneReferenceImage = (imageId: string) => {
    setCloneReferenceImages((current) => {
      const next = current.filter((item) => item.id !== imageId);
      if (next.length === 0) {
        setHotStatus("idle");
        setHotResultUrl(null);
      }
      return next;
    });
  };

  const handleCloneReferenceUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files?.length) return;
    void addCloneReferenceImages(Array.from(files));
    event.target.value = "";
  };

  const [isCloneReferenceDragging, setIsCloneReferenceDragging] = useState(false);

  const handleCloneReferenceDragOver = (event: DragEvent<HTMLButtonElement>) => {
    event.preventDefault();
    event.stopPropagation();
    if (event.dataTransfer.types.includes("Files")) {
      setIsCloneReferenceDragging(true);
    }
  };

  const handleCloneReferenceDragLeave = (event: DragEvent<HTMLButtonElement>) => {
    event.preventDefault();
    event.stopPropagation();
    if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) {
      setIsCloneReferenceDragging(false);
    }
  };

  const handleCloneReferenceDrop = (event: DragEvent<HTMLButtonElement>) => {
    event.preventDefault();
    event.stopPropagation();
    setIsCloneReferenceDragging(false);
    const files = Array.from(event.dataTransfer.files);
    if (files.length) addCloneReferenceImages(files);
  };

  const updateCloneSetCount = (key: CloneSetCountKey, delta: -1 | 1) => {
    setCloneSetCounts((current) => {
      const total = Object.values(current).reduce((sum, value) => sum + value, 0);
      const nextValue = current[key] + delta;
      if (delta < 0 && (current[key] <= 0 || total <= minCloneSetTotal)) return current;
      if (delta > 0 && total >= maxCloneSetTotal) return current;
      return { ...current, [key]: Math.max(0, Math.min(maxCloneSetTotal, nextValue)) };
    });
  };

  const clearCloneSetCountHold = () => {
    window.removeEventListener("pointerup", clearCloneSetCountHold);
    window.removeEventListener("pointercancel", clearCloneSetCountHold);
    if (countHoldTimeoutRef.current !== null) {
      window.clearTimeout(countHoldTimeoutRef.current);
      countHoldTimeoutRef.current = null;
    }
    if (countHoldIntervalRef.current !== null) {
      window.clearInterval(countHoldIntervalRef.current);
      countHoldIntervalRef.current = null;
    }
  };

  const startCloneSetCountHold = (key: CloneSetCountKey, delta: -1 | 1, disabled: boolean) => {
    if (disabled) return;
    clearCloneSetCountHold();
    updateCloneSetCount(key, delta);
    window.addEventListener("pointerup", clearCloneSetCountHold, { once: true });
    window.addEventListener("pointercancel", clearCloneSetCountHold, { once: true });
    countHoldTimeoutRef.current = window.setTimeout(() => {
      countHoldIntervalRef.current = window.setInterval(() => updateCloneSetCount(key, delta), 110);
    }, 320);
  };

  const toggleCloneDetailModule = (moduleId: string) => {
    setSelectedCloneDetailModules((current) => {
      if (current.includes(moduleId)) return current.filter((item) => item !== moduleId);
      if (current.length >= maxDetailModuleSelection) {
        toast.info(`最多选择 ${maxDetailModuleSelection} 个模块`);
        return current;
      }
      return [...current, moduleId];
    });
  };

  const toggleCloneModelScene = (scene: string) => {
    setSelectedCloneModelScenes((current) => (current[0] === scene ? [] : [scene]));
  };

  const handleProductSetPlatformChange = (nextPlatform: string) => {
    const normalizedPlatform = normalizePlatform(nextPlatform);
    setProductSetPlatform(normalizedPlatform);
    setProductSetRatio((current) => normalizeRatioForPlatform(normalizedPlatform, current, productSetOutput));
    setProductSetLanguage(getPlatformDefaultLanguage(normalizedPlatform, productSetMarket));
  };

  const handleProductSetOutputChange = (nextOutput: ProductSetOutputKey) => {
    setProductSetOutput(nextOutput);
    setProductSetRatio((current) => normalizeRatioForPlatform(productSetPlatform, current, nextOutput));
  };

  const handleProductSetMarketChange = (nextMarket: string) => {
    const normalizedMarket = normalizeMarket(nextMarket);
    setProductSetMarket(normalizedMarket);
    setProductSetLanguage(getPlatformDefaultLanguage(productSetPlatform, normalizedMarket));
  };

  const handleClonePlatformChange = (nextPlatform: string) => {
    const normalizedPlatform = normalizePlatform(nextPlatform);
    setPlatform(normalizedPlatform);
    setRatio((current) =>
      normalizeRatioForPlatform(normalizedPlatform, current, cloneOutput),
    );
    setLanguage(getPlatformDefaultLanguage(normalizedPlatform, market));
  };

  const handleCloneOutputChange = (nextOutput: CloneOutputKey) => {
    setCloneOutput(nextOutput);
    setIsCloneTemplateStripVisible(true);
    if (nextOutput !== "video") setIsVideoWorkspaceVisible(false);
    setRatio((current) =>
      normalizeRatioForPlatform(platform, current, nextOutput),
    );
  };

  const handleCommerceScenarioClick = (nextScenario: CommerceScenarioKey) => {
    if (nextScenario === activeCommerceScenario) {
      setActiveCommerceScenario(null);
      setIsCloneTemplateStripVisible(false);
      setComposerMenu(null);
      return;
    }
    setActiveCommerceScenario(nextScenario);
    setIsCloneTemplateStripVisible(true);
    setComposerMenu(null);
    if (nextScenario === "popular") return;
    const mappedOutput = commerceScenarioOutputMap[nextScenario];
    if (mappedOutput !== cloneOutput) handleCloneOutputChange(mappedOutput);
  };

  const handleCommerceScenarioMoreToggle = () => {
    setIsCommerceScenarioMoreOpen((visible) => !visible);
    setComposerMenu(null);
  };

  const scrollCommerceTemplateStrip = (direction: -1 | 1) => {
    const strip = templateStripRef.current;
    if (!strip) return;
    const firstCard = strip.querySelector<HTMLElement>(".ecom-command-template-card");
    const cardStep = firstCard ? firstCard.offsetWidth + 14 : 0;
    const viewportStep = Math.max(280, strip.clientWidth * 0.78);
    strip.scrollBy({
      left: direction * Math.max(cardStep, viewportStep),
      behavior: "smooth",
    });
  };

  const handleCloneMarketChange = (nextMarket: string) => {
    const normalizedMarket = normalizeMarket(nextMarket);
    setMarket(normalizedMarket);
    setLanguage(getPlatformDefaultLanguage(platform, normalizedMarket));
  };

  const handleDetailPlatformChange = (nextPlatform: string) => {
    const normalizedPlatform = normalizePlatform(nextPlatform);
    setDetailPlatform(normalizedPlatform);
    setDetailLanguage(getPlatformDefaultLanguage(normalizedPlatform, detailMarket));
    setDetailRatio((current) => getQuickSetRatioValue(current));
  };

  const handleDetailMarketChange = (nextMarket: string) => {
    const normalizedMarket = normalizeMarket(nextMarket);
    setDetailMarket(normalizedMarket);
    setDetailLanguage(getPlatformDefaultLanguage(detailPlatform, normalizedMarket));
  };

  const createCloneSettingSnapshot = (name: string, id = `clone-setting-${Date.now()}`): CloneSavedSetting => ({
    id,
    name,
    savedAt: new Date().toISOString(),
    output: cloneOutput,
    platform,
    market,
    language,
    ratio,
    setCounts: { ...cloneSetCounts },
    detailModules: [...selectedCloneDetailModules],
    modelPanelTab: cloneModelPanelTab,
    modelScenes: [...selectedCloneModelScenes],
    modelCustomScene: cloneModelCustomScene,
    modelGender: cloneModelGender,
    modelAge: cloneModelAge,
    modelEthnicity: cloneModelEthnicity,
    modelBody: cloneModelBody,
    modelAppearance: cloneModelAppearance,
    videoQuality: cloneVideoQuality,
    videoDurationSeconds: cloneVideoDuration,
    videoSmart: cloneVideoSmart,
    referenceMode: cloneReferenceMode,
    replicateLevel: cloneReplicateLevel,
    requirement,
  });

  const latestCloneSettingSnapshot = useMemo(
    () => createCloneSettingSnapshot(cloneSettingName, "clone-setting-latest"),
    [
      cloneOutput,
      platform,
      market,
      language,
      ratio,
      cloneSetCounts,
      selectedCloneDetailModules,
      cloneModelPanelTab,
      selectedCloneModelScenes,
      cloneModelCustomScene,
      cloneModelGender,
      cloneModelAge,
      cloneModelEthnicity,
      cloneModelBody,
      cloneModelAppearance,
      cloneVideoQuality,
      cloneVideoDuration,
      cloneVideoSmart,
      cloneReferenceMode,
      cloneReplicateLevel,
      requirement,
      cloneSettingName,
    ],
  );

  const persistLatestCloneSetting = () => {
    const snapshot = createCloneSettingSnapshot(cloneSettingName, "clone-setting-latest");
    latestCloneSettingRef.current = snapshot;
    writeCloneLatestSetting(snapshot);
    return snapshot;
  };

  const applyCloneSavedSetting = (setting: CloneSavedSetting) => {
    const nextCounts = {
      selling: Number.isFinite(setting.setCounts?.selling) ? setting.setCounts.selling : defaultCloneSetCounts.selling,
      white: Number.isFinite(setting.setCounts?.white) ? setting.setCounts.white : defaultCloneSetCounts.white,
      scene: Number.isFinite(setting.setCounts?.scene) ? setting.setCounts.scene : defaultCloneSetCounts.scene,
    };
    const nextPlatform = normalizePlatform(setting.platform);
    const nextMarket = normalizeMarket(setting.market);
    const nextOutput = cloneOutputOptions.some((option) => option.key === setting.output) ? setting.output : defaultCloneOutput;
    setCloneOutput(nextOutput);
    setPlatform(nextPlatform);
    setMarket(nextMarket);
    setLanguage(normalizeLanguageForPlatform(nextPlatform, nextMarket, setting.language));
    setRatio(normalizeRatioForPlatform(nextPlatform, setting.ratio, nextOutput));
    setCloneSetCounts(nextCounts);
    setSelectedCloneDetailModules((setting.detailModules?.length ? setting.detailModules : defaultCloneDetailModuleIds).slice(0, maxDetailModuleSelection));
    setCloneModelPanelTab(setting.modelPanelTab === "model" ? "model" : "scene");
    setSelectedCloneModelScenes(normalizeCloneModelSceneSelection(setting.modelScenes));
    setCloneModelCustomScene(setting.modelCustomScene ?? "");
    setCloneModelGender(tryOnModelOptions.gender.includes(setting.modelGender) ? setting.modelGender : tryOnModelOptions.gender[0]);
    setCloneModelAge(tryOnModelOptions.age.includes(setting.modelAge) ? setting.modelAge : tryOnModelOptions.age[0]);
    setCloneModelEthnicity(
      tryOnModelOptions.ethnicity.includes(setting.modelEthnicity) ? setting.modelEthnicity : tryOnModelOptions.ethnicity[0],
    );
    setCloneModelBody(tryOnModelOptions.body.includes(setting.modelBody) ? setting.modelBody : tryOnModelOptions.body[0]);
    setCloneModelAppearance(setting.modelAppearance ?? "");
    setCloneVideoQuality(
      cloneVideoQualityOptions.some((option) => option.key === setting.videoQuality) ? setting.videoQuality : "high",
    );
    setCloneVideoDuration(clampCloneVideoDuration(setting.videoDurationSeconds));
    setCloneVideoSmart(Boolean(setting.videoSmart));
    setCloneReferenceMode(setting.referenceMode === "link" ? "link" : "upload");
    setCloneReplicateLevel(setting.replicateLevel === "style" ? "style" : "high");
    setRequirement((setting.requirement ?? "").slice(0, 500));
    setCloneSettingName(setting.name);
    latestCloneSettingRef.current = setting;
    writeCloneLatestSetting(setting);
  };

  useEffect(() => {
    latestCloneSettingRef.current = latestCloneSettingSnapshot;
  }, [latestCloneSettingSnapshot]);

  useEffect(() => {
    window.localStorage.removeItem(cloneLatestSettingStorageKey);
  }, []);

  useEffect(() => {
    setProductSetRatio((current) => normalizeRatioForPlatform(productSetPlatform, current, productSetOutput));
  }, [productSetOutput, productSetPlatform]);

  useEffect(() => {
    setRatio((current) => {
      const platformRatios = getPlatformRatioOptions(platform, cloneOutput);
      if (platformRatios.includes(current)) return current;
      const normalizedRatio = normalizeRatioToken(current);
      const matchedRatio = platformRatios.find((option) => normalizeRatioToken(option).includes(normalizedRatio));
      return matchedRatio ?? getPlatformDefaultRatio(platform, cloneOutput);
    });
  }, [cloneOutput, platform]);

  useEffect(() => {
    if (skipInitialCloneAutoSaveRef.current) {
      skipInitialCloneAutoSaveRef.current = false;
      return undefined;
    }
    if (skipNextCloneAutoSaveRef.current) {
      skipNextCloneAutoSaveRef.current = false;
      return undefined;
    }

    const timeoutId = window.setTimeout(() => {
      persistLatestCloneSetting();
    }, 300);

    return () => window.clearTimeout(timeoutId);
  }, [
    activeTool,
    cloneOutput,
    platform,
    market,
    language,
    ratio,
    cloneSetCounts,
    selectedCloneDetailModules,
    cloneModelPanelTab,
    selectedCloneModelScenes,
    cloneModelCustomScene,
    cloneModelGender,
    cloneModelAge,
    cloneModelEthnicity,
    cloneModelBody,
    cloneModelAppearance,
    cloneVideoQuality,
    cloneVideoDuration,
    cloneVideoSmart,
    cloneReferenceMode,
    cloneReplicateLevel,
    requirement,
    cloneSettingName,
  ]);

  useEffect(() => {
    const persistSnapshot = () => {
      if (latestCloneSettingRef.current) writeCloneLatestSetting(latestCloneSettingRef.current);
    };
    const handleVisibilityChange = () => {
      if (document.visibilityState === "hidden") persistSnapshot();
    };

    window.addEventListener("pagehide", persistSnapshot);
    document.addEventListener("visibilitychange", handleVisibilityChange);

    return () => {
      persistSnapshot();
      window.removeEventListener("pagehide", persistSnapshot);
      document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
  }, []);

  useEffect(() => clearCloneSetCountHold, []);

  useEffect(() => {
    if (!openCloneBasicSelect) return undefined;

    const handlePointerDown = (event: PointerEvent) => {
      const target = event.target;
      if (!(target instanceof Element) || target.closest("[data-clone-basic-select]")) return;
      setOpenCloneBasicSelect(null);
    };
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") setOpenCloneBasicSelect(null);
    };

    document.addEventListener("pointerdown", handlePointerDown);
    document.addEventListener("keydown", handleKeyDown);
    return () => {
      document.removeEventListener("pointerdown", handlePointerDown);
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [openCloneBasicSelect]);

  useEffect(() => {
    if (!composerMenu && !(status === "done" && !isCommandComposerCompact)) return undefined;

    const handlePointerDown = (event: PointerEvent) => {
      const target = event.target;
      if (!(target instanceof Node)) return;
      const composer = commandComposerWrapRef.current;
      if (composer?.contains(target)) return;

      if (composerMenu && composerMenu !== "settings") setComposerMenu(null);
      if (status === "done" && !isCommandComposerCompact) setIsCommandComposerCompact(true);
    };

    document.addEventListener("pointerdown", handlePointerDown);
    return () => document.removeEventListener("pointerdown", handlePointerDown);
  }, [composerMenu, isCommandComposerCompact, status]);

  useEffect(() => {
    if (composerMenuCloseTimeoutRef.current !== null) {
      window.clearTimeout(composerMenuCloseTimeoutRef.current);
      composerMenuCloseTimeoutRef.current = null;
    }
    if (composerMenu) {
      setVisibleComposerMenu(composerMenu);
      setIsComposerMenuClosing(false);
      return;
    }
    if (!visibleComposerMenu) return;
    setIsComposerMenuClosing(true);
    composerMenuCloseTimeoutRef.current = window.setTimeout(() => {
      composerMenuCloseTimeoutRef.current = null;
      setVisibleComposerMenu(null);
      setIsComposerMenuClosing(false);
    }, 220);
  }, [composerMenu, visibleComposerMenu]);

  useEffect(
    () => () => {
      if (composerMenuCloseTimeoutRef.current !== null) {
        window.clearTimeout(composerMenuCloseTimeoutRef.current);
      }
    },
    [],
  );

  useEffect(() => {
    if (!openCloneModelSelect) return undefined;

    const handlePointerDown = (event: PointerEvent) => {
      const target = event.target;
      if (!(target instanceof Element) || target.closest("[data-clone-model-select]")) return;
      setOpenCloneModelSelect(null);
      setCloneModelSelectDropUp(false);
    };
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        setOpenCloneModelSelect(null);
        setCloneModelSelectDropUp(false);
      }
    };

    document.addEventListener("pointerdown", handlePointerDown);
    document.addEventListener("keydown", handleKeyDown);
    return () => {
      document.removeEventListener("pointerdown", handlePointerDown);
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [openCloneModelSelect]);

  const addGarmentImages = (files: File[]) => {
    const uploadedFiles = notifyRejectedImages(files);
    if (!uploadedFiles.length) return;
    const remainingSlots = 5 - garmentImages.length;
    if (remainingSlots <= 0) return;

    const localItems = createLocalImageItems(uploadedFiles, remainingSlots, "garment");
    setGarmentImages((current) => [...current, ...localItems].slice(0, 5));
    setTryOnStatus("ready");

    Promise.all(localItems.map(async (item) => {
      const uploaded = await uploadImageItem(item);
      if (uploaded.src) URL.revokeObjectURL(item.src);
      return { id: item.id, uploaded };
    })).then((results) => {
      const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
      setGarmentImages((current) => current.map((item) => {
        const update = updateMap.get(item.id);
        return update ? { ...item, ...update } : item;
      }));
    }).catch(() => {
      toast.error("服饰图后台上传失败，请检查网络后重试");
    });
  };

  const handleGarmentUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files?.length) {
      event.target.value = "";
      return;
    }
    addGarmentImages(Array.from(files));
    event.target.value = "";
  };

  const handleDetailUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (!files?.length) return;
    void addDetailImages(Array.from(files));
    event.target.value = "";
  };

  const handleDetailDrop = (event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    const files = Array.from(event.dataTransfer.files);
    if (files.length) void addDetailImages(files);
  };

  const removeDetailImage = (imageId: string) => {
    setDetailProductImages((current) => {
      const next = current.filter((item) => item.id !== imageId);
      if (next.length === 0) {
        setDetailStatus("idle");
        setDetailResultUrl(null);
      }
      return next;
    });
  };

  const blobToDataUrl = (blob: Blob): Promise<string> =>
    new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(String(reader.result || ""));
      reader.onerror = () => reject(reader.error || new Error("文件读取失败"));
      reader.readAsDataURL(blob);
    });

  const addDetailImages = (files: File[]) => {
    const uploadedFiles = notifyRejectedImages(files);
    if (!uploadedFiles.length) return;
    const remainingSlots = 3 - detailProductImages.length;
    if (remainingSlots <= 0) return;

    const localItems = createLocalImageItems(uploadedFiles, remainingSlots, "detail");
    setDetailProductImages((current) => [...current, ...localItems].slice(0, 3));
    setDetailStatus("ready");
    setDetailResultUrl(null);

    Promise.all(localItems.map(async (item) => {
      const uploaded = await uploadImageItem(item);
      if (uploaded.src) URL.revokeObjectURL(item.src);
      return { id: item.id, uploaded };
    })).then((results) => {
      const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
      setDetailProductImages((current) => current.map((item) => {
        const update = updateMap.get(item.id);
        return update ? { ...item, ...update } : item;
      }));
    }).catch(() => {
      toast.error("详情图后台上传失败，请检查网络后重试");
    });
  };

  const uploadCloneImages = async (images: CloneImageItem[]): Promise<string[]> => {
    const urls: string[] = [];
    for (const item of images) {
      try {
        if (!item.file && item.src.startsWith("blob:")) {
          throw new Error("本地预览图缺少原始文件，无法上传");
        }
        const rawBlob = item.file ?? (item.src.startsWith("data:") ? null : await (await fetch(item.src)).blob());
        const mimeType = normalizeEcommerceImageMime(
          rawBlob?.type || item.src.match(/^data:([^;,]+)/)?.[1] || "image/png",
        );
        const blob = rawBlob ? (rawBlob.type === mimeType ? rawBlob : new Blob([rawBlob], { type: mimeType })) : null;
        const dataUrl = item.src.startsWith("data:") ? item.src : await blobToDataUrl(blob!);
        const { url } = await aiGenerationClient.uploadAsset({ dataUrl, name: item.name, mimeType, scope: ecommerceOssScopes.productSource });
        urls.push(url);
      } catch {
        // skip images that fail to upload
      }
    }
    return urls;
  };

  const setCountLabels: Record<CloneSetCountKey, { label: string; promptDesc: string }> = {
    selling: { label: "卖点图", promptDesc: "selling-point infographic image highlighting core product advantages and detail close-ups" },
    white:   { label: "白底图", promptDesc: "clean white-background product photo showing the item from its best angle, studio lighting, no props" },
    scene:   { label: "场景图", promptDesc: "lifestyle scene image showing the product in a realistic usage environment with natural surroundings" },
  };

  const buildDetailModulePrompt = (moduleIds: string[]): string => {
    if (!moduleIds.length) {
      return "Generate a complete A+ detail layout with hero, selling points, usage scene, product detail, and specification modules.";
    }

    const selectedModules = cloneDetailModules.filter((module) => moduleIds.includes(module.id));
    if (!selectedModules.length) return "";

    const moduleList = selectedModules.map((module) => `${module.title}: ${module.desc}`).join("; ");
    return `Only generate these selected A+ detail modules, no extra modules: ${moduleList}. Keep the output focused even if only one or two modules are selected.`;
  };

  const buildSetSubPrompt = (countKey: CloneSetCountKey, index: number, totalCount: number, pPlatform: string, pRatio: string, pLanguage: string, pMarket: string): string => {
    const info = setCountLabels[countKey];
    const parts: string[] = [];
    parts.push(`Generate an e-commerce ${info.label.toLowerCase()} for a product listing.`);
    parts.push(info.promptDesc);
    if (countKey === "white") {
      parts.push("The output must be a clean white-background product image. Do not use lifestyle backgrounds, props, text overlays, or people.");
    }
    if (countKey === "scene") {
      parts.push("The output must be a realistic usage scene image. Keep the product clearly visible and preserve its shape, color, and key details.");
    }
    if (countKey === "selling") {
      parts.push("The output must be a selling-point graphic with clear hierarchy, concise copy, and product detail callouts.");
    }
    if (totalCount > 1) {
      parts.push(`This is variant ${index + 1} of ${totalCount} —vary the angle, composition, or emphasis to make each distinct.`);
    }
    parts.push(`Platform: ${pPlatform}. Aspect ratio: ${pRatio}. Language/copy: ${pLanguage}. Market: ${pMarket}.`);
    parts.push("Must comply with platform image guidelines —proper margins, no watermark, professional quality.");
    return parts.join(" ");
  };

  const buildEcommerceImagePrompt = (
    outputKey: CloneOutputKey, userText: string,
    pPlatform: string, pRatio: string, pLanguage: string, pMarket: string,
    tryOnOptions?: EcommerceImagePromptOptions,
  ): string => {
    const parts: string[] = [];
    if (outputKey === "detail") {
      parts.push("Generate a professional A+ detail page hero image for an e-commerce product listing.");
      parts.push("Create a high-impact first-screen visual that combines the product photo with key selling points, usage scenes, and detailed specifications in a cohesive layout.");
      parts.push(`Platform: ${pPlatform}. Aspect ratio: ${pRatio}. Language/copy: ${pLanguage}. Market: ${pMarket}.`);
      if (outputKey === "detail" && tryOnOptions?.detailModules) parts.push(buildDetailModulePrompt(tryOnOptions.detailModules));
      parts.push("Follow platform A+ page best practices —clear hierarchy, professional typography, high visual impact.");
    } else if (outputKey === "model") {
      parts.push("Generate model/try-on lifestyle images for an e-commerce product listing.");
      parts.push("Show the product being used or worn by a model in attractive lifestyle settings.");
      parts.push(`Platform: ${pPlatform}. Aspect ratio: ${pRatio}. Language/copy: ${pLanguage}. Market: ${pMarket}.`);
      if (tryOnOptions) {
        if (tryOnOptions.gender) parts.push(`Model gender: ${tryOnOptions.gender}.`);
        if (tryOnOptions.age) parts.push(`Model age: ${tryOnOptions.age}.`);
        if (tryOnOptions.ethnicity) parts.push(`Model ethnicity: ${tryOnOptions.ethnicity}.`);
        if (tryOnOptions.body) parts.push(`Model body type: ${tryOnOptions.body}.`);
        if (tryOnOptions.appearance) parts.push(`Model appearance details: ${tryOnOptions.appearance}.`);
        if (tryOnOptions.scenes?.length) parts.push(`Background scenes: ${tryOnOptions.scenes.join(", ")}.`);
        if (tryOnOptions.customScene) parts.push(`Custom background scene: ${tryOnOptions.customScene}.`);
        if (tryOnOptions.smartScene) parts.push("Use smart scene matching to select the best background context.");
      }
      parts.push("Model should appear natural and appealing. Background should complement the product. Image must meet platform standards.");
    } else if (outputKey === "hot") {
      parts.push("Generate a high-conversion e-commerce product image that closely replicates the style and composition of the reference image while adapting it to the target platform.");
      parts.push(`Replicate the visual style, color palette, and layout feel of the source product image, then adapt it for ${pPlatform} marketplace standards.`);
      parts.push(`Platform: ${pPlatform}. Aspect ratio: ${pRatio}. Language/copy: ${pLanguage}. Market: ${pMarket}.`);
      parts.push("The result must look professional and optimized for high click-through rate and conversion on the specified platform.");
    }
    if (userText.trim()) {
      parts.push(`Additional user requirements: ${userText.trim()}`);
    }
    return parts.join(" ");
  };

  const generateSetImages = async (
    images: CloneImageItem[],
    counts: Record<CloneSetCountKey, number>,
    userText: string,
    pPlatform: string,
    pRatio: string,
    pLanguage: string,
    pMarket: string,
    setStatusFn: (status: "generating" | "done" | "idle" | "failed") => void,
    setResultFn: (urls: string[]) => void,
  ): Promise<void> => {
    setStatusFn("generating");
    try {
      const referenceUrls = await uploadCloneImages(images);
      if (!referenceUrls.length) {
        setStatusFn("idle");
        return;
      }
      if (imageAbortRef.current.current) {
        setStatusFn("idle");
        return;
      }

      const generatedUrls: string[] = [];
      const stamp = Date.now();
      const totalCount = Math.max(1, cloneSetCountKeys.reduce((sum, key) => sum + counts[key], 0));
      let completedCount = 0;
      setGenerationProgress(0);

      for (const countKey of cloneSetCountKeys) {
        if (imageAbortRef.current.current) break;
        const count = counts[countKey];
        for (let i = 0; i < count; i++) {
          if (imageAbortRef.current.current) break;
          const subPrompt = buildSetSubPrompt(countKey, i, count, pPlatform, pRatio, pLanguage, pMarket);
          const fullPrompt = userText.trim() ? `${subPrompt} Additional user requirements: ${userText.trim()}` : subPrompt;

          const { taskId } = await aiGenerationClient.createImageTask({
            prompt: fullPrompt,
            ratio: normalizeRatioForApi(pRatio),
            quality: pRatio.includes("720") ? "720P" : "1080P",
            gridMode: "single",
            referenceUrls,
          });
          trackEcommerceTask(taskId);

          const storeId = imageGen.submitTask({ title: `${setCountLabels[countKey].label} ${i + 1}`, type: "image", status: "running", progress: 5, prompt: fullPrompt, sourceView: "ecommerce", taskId });

          let resultUrl: string | null = null;
          try {
            resultUrl = await waitForTask(taskId, {
              kind: "image",
              abortRef: imageAbortRef.current,
              onProgress: (event) => {
                // 整体进度 = (已完成张数 + 当前张子进度) / 总张数。
                const sub = Math.max(0, Math.min(100, Number(event.progress) || 0));
                const overall = ((completedCount + sub / 100) / totalCount) * 100;
                setGenerationProgress(Math.round(Math.min(99, overall)));
              },
            });
          } finally {
            untrackEcommerceTask(taskId);
          }

          if (imageAbortRef.current.current) break;

          if (resultUrl) {
            const persistedUrl = await persistGeneratedImageUrl(resultUrl, ecommerceOssScopes.cloneResult("set"), `ecommerce-${countKey}-${i + 1}`);
            generatedUrls.push(persistedUrl);
            imageGen.updateTask(storeId, { status: "completed", progress: 100, resultUrl: persistedUrl });
          } else {
            generatedUrls.push("");
            imageGen.updateTask(storeId, { status: "failed", error: "生成未返回结果" });
          }
          completedCount += 1;
          setGenerationProgress(Math.round(Math.min(99, (completedCount / totalCount) * 100)));
        }
      }

      if (imageAbortRef.current.current) {
        setStatusFn("idle");
        return;
      }
      setResultFn(generatedUrls);
      setStatusFn(generatedUrls.some(Boolean) ? "done" : "failed");
    } catch (err) {
      if (imageAbortRef.current.current) {
        setStatusFn("idle");
        return;
      }
      if (err instanceof ServerRequestError && err.status === 402) {
        setResultFn([]);
        toast.error("余额不足，请充值后继续");
      } else {
        const msg = err instanceof Error ? err.message : "生成失败";
        toast.error(msg);
      }
      setStatusFn("failed");
    }
  };

  const generateEcommerceImage = async (
    outputKey: CloneOutputKey,
    images: CloneImageItem[],
    userText: string,
    pPlatform: string,
    pRatio: string,
    pLanguage: string,
    pMarket: string,
    tryOnOptions?: EcommerceImagePromptOptions,
    statusFn?: (status: "generating" | "done" | "idle" | "failed") => void,
    resultFn?: (results: CloneResult[]) => void,
  ): Promise<void> => {
    statusFn?.("generating");
    try {
      const referenceUrls = await uploadCloneImages(images);
      if (!referenceUrls.length) {
        statusFn?.("idle");
        return;
      }
      if (imageAbortRef.current.current) {
        statusFn?.("idle");
        return;
      }

      const prompt = buildEcommerceImagePrompt(outputKey, userText, pPlatform, pRatio, pLanguage, pMarket, tryOnOptions);
      const stamp = Date.now();
      setGenerationProgress(0);

      const { taskId } = await aiGenerationClient.createImageTask({
        prompt,
        ratio: normalizeRatioForApi(pRatio),
        quality: pRatio.includes("720") ? "720P" : "1080P",
        gridMode: "single",
        referenceUrls,
      });
      trackEcommerceTask(taskId);

      const storeId = imageGen.submitTask({ title: `电商${outputKey}图`, type: "image", status: "running", progress: 5, prompt, sourceView: "ecommerce", taskId });

      let resultUrl: string | null = null;
      try {
        resultUrl = await waitForTask(taskId, {
          kind: "image",
          abortRef: imageAbortRef.current,
          onProgress: (event) => {
            const sub = Math.max(0, Math.min(100, Number(event.progress) || 0));
            setGenerationProgress(Math.round(Math.min(99, sub)));
          },
        });
      } finally {
        untrackEcommerceTask(taskId);
      }

      if (imageAbortRef.current.current) {
        statusFn?.("idle");
        return;
      }

      if (resultUrl) {
        const persistedUrl = await persistGeneratedImageUrl(resultUrl, ecommerceOssScopes.cloneResult(outputKey), `ecommerce-${outputKey}`);
        resultFn?.([{ id: `ecommerce-${stamp}`, src: persistedUrl, label: selectedCloneOutput.label }]);
        statusFn?.("done");
        imageGen.updateTask(storeId, { status: "completed", progress: 100, resultUrl: persistedUrl });
      } else {
        statusFn?.("idle");
        imageGen.updateTask(storeId, { status: "failed", error: "生成未返回结果" });
      }
    } catch (err) {
      if (imageAbortRef.current.current) {
        statusFn?.("idle");
        return;
      }
      if (err instanceof ServerRequestError && err.status === 402) {
        resultFn?.([{ id: `ecommerce-error-402`, src: "", label: "余额不足，请充值后继续" }]);
        toast.error("余额不足，请充值后继续");
      } else {
        const msg = err instanceof Error ? err.message : "生成失败";
        toast.error(msg);
      }
      statusFn?.("failed");
    }
  };

  const handleGenerate = () => {
    if (!canGenerate) return;

    if ((appUsage?.balanceCents ?? 0) <= 0) {
      toast.error("积分不足，请充值后继续");
      return;
    }

    if (cloneOutput === "set" && cloneSetTotal > 5) {
      if (!window.confirm("将生成 " + String(cloneSetTotal) + " 张图片，可能消耗较多积分，是否继续？")) return;
    }

    setComposerMenu(null);
    setIsCommandComposerCompact(true);
    imageAbortRef.current = { current: false };
    lastFailedActionRef.current = null;
    setGenerationProgress(0);
    setResults([]);
    setProductSetResultImages([]);
    const pendingGeneration = beginEcommerceHistoryTurn();
    const pendingRecordId = pendingGeneration.record.id;
    const pendingTurnId = pendingGeneration.turn.id;
    setPreviewZoom(1);
    setPreviewOffset({ x: 0, y: 0 });
    previewOffsetRef.current = { x: 0, y: 0 };
    if (cloneOutput === "set") {
      void generateSetImages(
        productImages, cloneSetCounts, requirement,
        platform, ratio, language, market,
        (s) => {
          setStatus(s as ProductCloneStatus);
          if (s === "generating") {
            updateLocalEcommerceHistoryTurn(pendingRecordId, pendingTurnId, (turn) => ({ ...turn, status: "generating", errorMessage: undefined }));
          } else if (s === "failed") {
            updateLocalEcommerceHistoryTurn(pendingRecordId, pendingTurnId, (turn) => ({ ...turn, status: "failed", errorMessage: "生成失败，请检查网络或参数后重试。" }));
          }
        },
        (urls) => {
          setProductSetResultImages(urls);
          const validUrls = urls.filter(Boolean);
          const resultCards = validUrls.map((src, i) => ({ id: `set-${Date.now()}-${i}`, src, label: `套图 ${i + 1}` }));
          updateLocalEcommerceHistoryTurn(pendingRecordId, pendingTurnId, (turn) => ({
            ...turn,
            status: validUrls.length ? "done" : "failed",
            errorMessage: validUrls.length ? undefined : "生成未返回结果",
            setResultImages: validUrls,
            results: resultCards,
          }));
          if (validUrls.length) {
            upsertCanvasNode({
              id: pendingTurnId,
              mode: "set",
              sourceImage: productImages[0]?.src,
              results: resultCards,
              createdAt: Date.now(),
            });
          }
        },
      );
      lastFailedActionRef.current = () => handleGenerate();
    } else {
      const clonePromptOptions: EcommerceImagePromptOptions | undefined =
        cloneOutput === "model"
          ? {
              gender: cloneModelGender,
              age: cloneModelAge,
              ethnicity: cloneModelEthnicity,
              body: cloneModelBody,
              scenes: selectedCloneModelScenes,
              customScene: cloneModelCustomScene,
            }
          : cloneOutput === "detail"
            ? { detailModules: selectedCloneDetailModules }
            : undefined;
      void generateEcommerceImage(
        cloneOutput, productImages, requirement,
        platform, ratio, language, market,
        clonePromptOptions,
        (s: string) => {
          setStatus(s as ProductCloneStatus);
          if (s === "generating") {
            updateLocalEcommerceHistoryTurn(pendingRecordId, pendingTurnId, (turn) => ({ ...turn, status: "generating", errorMessage: undefined }));
          } else if (s === "failed") {
            updateLocalEcommerceHistoryTurn(pendingRecordId, pendingTurnId, (turn) => ({ ...turn, status: "failed", errorMessage: "生成失败，请检查网络或参数后重试。" }));
          }
        },
        (newResults: CloneResult[]) => {
          const validResults = newResults.filter((item) => item.src);
          setResults(validResults);
          updateLocalEcommerceHistoryTurn(pendingRecordId, pendingTurnId, (turn) => ({
            ...turn,
            status: validResults.length ? "done" : "failed",
            errorMessage: validResults.length ? undefined : newResults[0]?.label || "生成未返回结果",
            results: validResults,
            setResultImages: [],
          }));
          if (validResults.length && validResults[0].src) {
            upsertCanvasNode({
              id: pendingTurnId,
              mode: cloneOutput,
              sourceImage: productImages[0]?.src,
              results: validResults,
              createdAt: Date.now(),
            });
          }
        },
      );
      lastFailedActionRef.current = () => handleGenerate();
    }
  };

  const handleGenerateModel = () => {
    imageAbortRef.current = { current: false };
    lastFailedActionRef.current = null;
    setTryOnStatus("modeling");
    void generateEcommerceImage(
      "model", garmentImages, requirement,
      platform, ratio, language, market,
      { gender: modelGender, age: modelAge, ethnicity: modelEthnicity, body: modelBody, appearance, scenes: selectedScenes, smartScene },
      (s) => {
        if (s === "done") setTryOnStatus("ready");
        else setTryOnStatus(s as TryOnStatus);
      },
      () => { setTryOnStatus("ready"); },
    );
    lastFailedActionRef.current = () => handleGenerateModel();
  };

  const handleTryOnGenerate = () => {
    if (!canGenerateTryOn) return;
    imageAbortRef.current = { current: false };
    lastFailedActionRef.current = null;
    void generateEcommerceImage(
      "model", garmentImages, requirement,
      platform, ratio, language, market,
      { gender: modelGender, age: modelAge, ethnicity: modelEthnicity, body: modelBody, appearance, scenes: selectedScenes, smartScene },
      (s) => setTryOnStatus(s as TryOnStatus),
      (res) => {
        const urls: string[] = [];
        for (const item of res) {
          if (item.src) urls.push(item.src);
        }
        setTryOnResultImages(urls);
      },
    );
    lastFailedActionRef.current = () => handleTryOnGenerate();
  };

  const toggleScene = (scene: string) => {
    setSelectedScenes((current) =>
      current.includes(scene) ? current.filter((item) => item !== scene) : [...current, scene],
    );
  };

  const toggleDetailModule = (moduleId: string) => {
    setSelectedDetailModules((current) => {
      if (current.includes(moduleId)) return current.filter((item) => item !== moduleId);
      if (current.length >= maxDetailModuleSelection) {
        toast.info(`最多选择 ${maxDetailModuleSelection} 个模块`);
        return current;
      }
      return [...current, moduleId];
    });
  };

  const handleSetGenerate = () => {
    if (!canGenerateSet) return;
    imageAbortRef.current = { current: false };
    lastFailedActionRef.current = null;
    void generateSetImages(
      setImages, cloneSetCounts, productSetRequirement,
      productSetPlatform, productSetRatio, productSetLanguage, productSetMarket,
      (s) => setProductSetStatus(s as ProductSetStatus),
      (urls) => setProductSetResultImages(urls),
    );
    lastFailedActionRef.current = () => handleSetGenerate();
  };

  const openProductSetPreview = (card: { id?: string; src: string; label: string }, options?: { nodeId?: string; removable?: boolean }) => {
    setSelectedProductSetPreview({
      src: card.src,
      label: card.label,
      cardId: card.id,
      nodeId: options?.nodeId,
      removable: Boolean(options?.removable && options.nodeId && card.id),
    });
  };

  const handleDownloadCanvasResult = async (card: { src: string; label: string }) => {
    try {
      await downloadResultAsset(card.src, card.label || "generated-image", false);
      toast.success("已开始下载图片");
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "下载图片失败");
    }
  };

  const removeCanvasResult = (nodeId: string, cardId: string) => {
    setCanvasNodes((current) =>
      current
        .map((node) => (node.id === nodeId ? { ...node, results: node.results.filter((card) => card.id !== cardId) } : node))
        .filter((node) => node.sourceImage || node.results.length > 0),
    );
    setResults((current) => current.filter((card) => card.id !== cardId));
    toast.success("已从当前视图移除");
  };

  const upsertCanvasNode = (node: Omit<CanvasNode, "x" | "y">) => {
    setCanvasNodes((current) => {
      const existingIndex = current.findIndex((item) => item.id === node.id);
      if (existingIndex >= 0) {
        return current.map((item) => (item.id === node.id ? { ...item, ...node } : item));
      }
      return [
        ...current,
        {
          ...node,
          x: current.length * 420,
          y: current.length % 2 === 0 ? 0 : 160,
        },
      ];
    });
  };

  const removeSelectedProductSetPreview = (preview: ProductSetPreviewSelection) => {
    if (!preview.nodeId || !preview.cardId) return;
    removeCanvasResult(preview.nodeId, preview.cardId);
    setSelectedProductSetPreview(null);
  };

  const handleDetailAiWrite = () => {
    setDetailRequirement(
      "1.产品名称：无线降噪蓝牙耳机\n2.核心卖点：主动降噪、24H续航、低延迟连接、舒适佩戴\n3.适用人群：通勤、办公、运动和旅行用户\n4.期望场景：地铁通勤、居家办公、户外运动\n5.具体参数：蓝牙5.3、IPX4防水、快充10分钟使用2小时",
    );
  };

  const stopDetailProgress = () => {
    if (detailProgressRef.current !== null) {
      window.clearInterval(detailProgressRef.current);
      detailProgressRef.current = null;
    }
  };

  const startDetailProgress = () => {
    stopDetailProgress();
    setDetailProgress(0);
    detailProgressRef.current = window.setInterval(() => {
      setDetailProgress((prev) => {
        if (prev >= 90) {
          stopDetailProgress();
          return 90;
        }
        return prev + (90 - prev) * 0.06;
      });
    }, 500);
  };

  const handleDetailGenerate = () => {
    if (!canGenerateDetail) return;
    imageAbortRef.current = { current: false };
    lastFailedActionRef.current = null;
    startDetailProgress();
    void generateEcommerceImage(
      "detail", detailProductImages, detailRequirement,
      detailPlatform, detailRatio, detailLanguage, detailMarket,
      { detailModules: selectedDetailModules },
      (s: string) => {
        setDetailStatus(s as DetailStatus);
        if (s === "done") {
          stopDetailProgress();
          setDetailProgress(100);
        } else if (s === "failed" || s === "idle") {
          stopDetailProgress();
          setDetailProgress(0);
        }
      },
      (res) => setDetailResultUrl(res[0]?.src ?? null),
    );
  };

  const handleHotPlatformChange = (nextPlatform: string) => {
    const normalizedPlatform = normalizePlatform(nextPlatform);
    setHotPlatform(normalizedPlatform);
    setHotLanguage(getPlatformDefaultLanguage(normalizedPlatform, hotMarket));
    setHotRatio((current) => getQuickSetRatioValue(current));
  };

  const handleHotMarketChange = (nextMarket: string) => {
    const normalizedMarket = normalizeMarket(nextMarket);
    setHotMarket(normalizedMarket);
    setHotLanguage(getPlatformDefaultLanguage(hotPlatform, normalizedMarket));
  };

  const handleHotAiWrite = () => {
    setHotRequirement(
      "1.产品名称：便携式咖啡保温杯\n2.核心卖点：316不锈钢内胆、12小时长效保温、防漏便携、大容量\n3.参考风格：极简日系、暖光氛围、生活场景\n4.期望场景：办公桌面、户外通勤、运动健身\n5.具体参数：容量500ml、口径4.5cm、高度22cm",
    );
  };

  const handleQuickSetAiWrite = () => {
    setQuickSetRequirement(
      "1.产品名称：无线降噪蓝牙耳机\n2.核心卖点：主动降噪、24H续航、低延迟连接、舒适佩戴\n3.适用人群：通勤、办公、运动和旅行用户\n4.期望场景：地铁通勤、居家办公、户外运动\n5.具体参数：蓝牙5.3、IPX4防水、快充10分钟使用2小时",
    );
  };

  const stopHotProgress = () => {
    if (hotProgressRef.current !== null) {
      window.clearInterval(hotProgressRef.current);
      hotProgressRef.current = null;
    }
  };

  const stopQuickSetProgress = () => {
    if (quickSetProgressRef.current !== null) {
      window.clearInterval(quickSetProgressRef.current);
      quickSetProgressRef.current = null;
    }
  };

  const startQuickSetProgress = () => {
    stopQuickSetProgress();
    setQuickSetProgress(0);
    quickSetProgressRef.current = window.setInterval(() => {
      setQuickSetProgress((prev) => {
        if (prev >= 90) {
          stopQuickSetProgress();
          return 90;
        }
        return prev + (90 - prev) * 0.06;
      });
    }, 500);
  };

  const startHotProgress = () => {
    stopHotProgress();
    setHotProgress(0);
    hotProgressRef.current = window.setInterval(() => {
      setHotProgress((prev) => {
        if (prev >= 90) {
          stopHotProgress();
          return 90;
        }
        return prev + (90 - prev) * 0.06;
      });
    }, 500);
  };

  const handleHotGenerate = () => {
    if (!canGenerateHot) return;
    imageAbortRef.current = { current: false };
    lastFailedActionRef.current = null;
    startHotProgress();
    void generateEcommerceImage(
      "hot", cloneReferenceImages, hotRequirement,
      hotPlatform, hotRatio, hotLanguage, hotMarket,
      undefined,
      (s: string) => {
        setHotStatus(s as DetailStatus);
        if (s === "done") {
          stopHotProgress();
          setHotProgress(100);
        } else if (s === "failed" || s === "idle") {
          stopHotProgress();
          setHotProgress(0);
        }
      },
      (res) => setHotResultUrl(res[0]?.src ?? null),
    );
  };

  const handleQuickSetGenerate = () => {
    if (!canGenerateQuickSet) return;
    imageAbortRef.current = { current: false };
    lastFailedActionRef.current = null;
    startQuickSetProgress();
    setQuickSetStatus("generating");
    void generateSetImages(
      productImages, cloneSetCounts, quickSetRequirement,
      platform, ratio, language, market,
      (s) => {
        setQuickSetStatus(s as ProductCloneStatus);
        if (s === "done") {
          stopQuickSetProgress();
          setQuickSetProgress(100);
        } else if (s === "failed") {
          stopQuickSetProgress();
          setQuickSetProgress(0);
        }
      },
      (urls) => {
        setQuickSetResultUrls(urls);
        const validUrls = urls.filter(Boolean);
        if (validUrls.length) {
          setQuickSetStatus("done");
          stopQuickSetProgress();
          setQuickSetProgress(100);
        } else {
          setQuickSetStatus("failed");
          stopQuickSetProgress();
          setQuickSetProgress(0);
        }
      },
    );
    lastFailedActionRef.current = () => handleQuickSetGenerate();
  };

  const handleHotMaterialMouseEnter = (src: string, event: ReactMouseEvent<HTMLElement>) => {
    const rect = event.currentTarget.getBoundingClientRect();
    const previewHalfWidth = 150;
    const previewHeight = 360;
    const gap = 12;
    const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
    const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
    const x = Math.min(
      Math.max(rect.left + rect.width / 2, previewHalfWidth + gap),
      Math.max(previewHalfWidth + gap, viewportWidth - previewHalfWidth - gap),
    );
    const showAbove = rect.top > previewHeight + gap;
    const y = showAbove
      ? rect.top - gap
      : Math.min(rect.bottom + gap, viewportHeight - gap);
    setHotMaterialHoverZoom({ src, x, y, placement: showAbove ? "above" : "below" });
  };
  const handleHotMaterialMouseLeave = () => setHotMaterialHoverZoom(null);

  const renderHotMaterialThumbs = (items: CloneImageItem[], onRemove: (imageId: string) => void) => (
    <div className="ecom-quick-upload-thumbs" aria-label="已上传商品素材">
      {items.map((item) => (
        <figure
          key={item.id}
          className="ecom-command-asset-thumb ecom-quick-upload-thumb"
          onMouseEnter={(e) => handleHotMaterialMouseEnter(item.src, e)}
          onMouseLeave={handleHotMaterialMouseLeave}
        >
          <img src={item.src} alt={item.name} />
          <button
            type="button"
            className="ecom-hot-material-delete"
            aria-label={`删除${item.name || "图片"}`}
            onClick={(event) => {
              event.stopPropagation();
              setHotMaterialHoverZoom(null);
              onRemove(item.id);
            }}
          >
            <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
              <path d="M9 6V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v1" />
              <path d="M5 6h14" />
              <path d="M8 6l1 14h6l1-14" />
              <path d="M10.5 10v6" />
              <path d="M13.5 10v6" />
            </svg>
          </button>
        </figure>
      ))}
    </div>
  );

  const closeHotClonePage = () => {
    stopHotProgress();
    setActiveQuickTool(null);
    setHotStatus("idle");
    setHotResultUrl(null);
    setHotProgress(0);
    setHotRequirement("");
    setIsHotMaterialDragging(false);
    setHotMaterialHoverZoom(null);
    setComposerMenu(null);
  };

  const openQuickSetPage = () => {
    clearSmartCutoutTransition();
    setActiveQuickTool("quick-set");
    setComposerMenu(null);
    setIsCloneSettingsCollapsed(false);
    setIsQuickPanelCollapsed(false);
  };

  const closeQuickSetPage = () => {
    stopQuickSetProgress();
    setActiveQuickTool(null);
    setQuickSetStatus("idle");
    setQuickSetResultUrls([]);
    setQuickSetProgress(0);
    setQuickSetRequirement("");
    setComposerMenu(null);
  };

  const openCopywritingPage = () => {
    clearSmartCutoutTransition();
    setActiveQuickTool("copywriting");
    setComposerMenu(null);
    setIsCloneSettingsCollapsed(false);
    setIsQuickPanelCollapsed(false);
  };

  const closeCopywritingPage = () => {
    setActiveQuickTool(null);
    setComposerMenu(null);
  };

  const resetTask = () => {
    setSetImages([]);
    setProductSetRequirement("");
    setProductSetPlatform(defaultEcommercePlatform);
    setProductSetLanguage(getPlatformDefaultLanguage(defaultEcommercePlatform, productSetMarket));
    setProductSetOutput(defaultProductSetOutput);
    setProductSetRatio(getPlatformDefaultRatio(defaultEcommercePlatform, defaultProductSetOutput));
    setProductSetStatus("idle");
    setProductSetResultImages([]);
    setIsSetUploadDragging(false);
    setSelectedProductSetPreview(null);
    setShowHostingModal(false);
    setProductImages([]);
    setIsProductUploadDragging(false);
    setCloneOutput(defaultCloneOutput);
    setPlatform(defaultEcommercePlatform);
    setLanguage(getPlatformDefaultLanguage(defaultEcommercePlatform, market));
    setRatio(getPlatformDefaultRatio(defaultEcommercePlatform, defaultCloneOutput));
    setCloneSetCounts(defaultCloneSetCounts);
    setSelectedCloneDetailModules(defaultCloneDetailModuleIds);
    setCloneModelPanelTab("scene");
    setSelectedCloneModelScenes([]);
    setCloneModelCustomScene("");
    setCloneModelGender(tryOnModelOptions.gender[0]);
    setCloneModelAge(tryOnModelOptions.age[0]);
    setCloneModelEthnicity(tryOnModelOptions.ethnicity[0]);
    setCloneModelBody(tryOnModelOptions.body[0]);
    setCloneModelAppearance("");
    setCloneVideoQuality("high");
    setCloneVideoDuration(10);
    setCloneVideoSmart(true);
    setCloneReferenceMode("upload");
    setCloneReferenceImages([]);
    setCloneReplicateLevel("high");
    setRequirement("");
    setCloneSettingName("新建创作");
    setResults([]);
    setStatus("idle");
    setGarmentImages([]);
    setAppearance("");
    setSelectedScenes([]);
    setCustomScene("");
    setSmartScene(false);
    setTryOnRatio(tryOnRatioOptions[0]);
    setTryOnStatus("idle");
    setTryOnResultImages([]);
    setDetailProductImages([]);
    setDetailRequirement("");
    setSelectedDetailModules(defaultDetailModuleIds);
    setDetailStatus("idle");
  };

  const activeToolMeta = sideTools.find((tool) => tool.key === activeTool);
  const isSetTool = activeTool === "set";
  const isDetail = activeTool === "detail";
  const isTryOn = activeTool === "wear";
  const isCloneTool = activeTool === "clone";
  const isSmartCutoutTool = isCloneTool && activeQuickTool === "cutout";
  const isQuickDetailTool = isCloneTool && activeQuickTool === "detail";
  const isWatermarkTool = isCloneTool && activeQuickTool === "watermark";
  const isImageEditTool = isCloneTool && activeQuickTool === "image-edit";
  const isTranslateTool = isCloneTool && activeQuickTool === "translate";
  const isHotCloneTool = isCloneTool && activeQuickTool === "hot";
  const isQuickSetTool = isCloneTool && activeQuickTool === "quick-set";
  const isCopywritingTool = isCloneTool && activeQuickTool === "copywriting";
  const pageLabel = isSetTool ? "商品套图" : isDetail ? "A+/详情页" : isTryOn ? "AI服饰穿戴" : activeToolMeta?.label || "商品工具";
  const setPrimaryLabel =
    setImages.length === 0
      ? "请先上传商品原图"
    : productSetStatus === "generating"
      ? "生成中..."
      : "生成" + selectedProductSetOutput.label;
  const tryOnPrimaryLabel =
    garmentImages.length === 0 ? "请先上传服装图片" : tryOnStatus === "generating" ? "生成中..." : "生成服饰穿戴图";
  const detailPrimaryLabel =
    detailProductImages.length === 0 ? "请上传产品图" : detailStatus === "generating" ? "生成中..." : "生成A+详情页";
  const clonePrimaryLabel =
    cloneOutput === "video"
      ? !productImages.length && !requirement.trim()
        ? "填写需求或上传商品图"
        : !isAuthenticated
          ? "登录后生成短视频"
          : "生成短视频"
      : productImages.length === 0 ? "请先上传商品原图" : status === "generating" ? "生成中..." : "生成" + selectedCloneOutput.label;
  const setPreviewCards: CloneResult[] = [];
  let setIndex = 0;
  for (const countKey of cloneSetCountKeys) {
    const count = cloneSetCounts[countKey];
    const info = setCountLabels[countKey];
    for (let i = 0; i < count; i++) {
      setPreviewCards.push({
        id: String(countKey) + "-" + String(i),
        src: productSetResultImages[setIndex] || productSetPreviewCards[setIndex % productSetPreviewCards.length]?.src || "",
        label: info.label + (count > 1 ? " " + String(i + 1) : ""),
      });
      setIndex++;
    }
  }

  const clonePreviewCards: CloneResult[] = [];
  let cloneIndex = 0;
  for (const countKey of cloneSetCountKeys) {
    const count = cloneSetCounts[countKey];
    const info = setCountLabels[countKey];
    for (let i = 0; i < count; i++) {
      clonePreviewCards.push({
        id: String(countKey) + "-" + String(i),
        src: productSetResultImages[cloneIndex] || productSetPreviewCards[cloneIndex % productSetPreviewCards.length]?.src || "",
        label: info.label + (count > 1 ? " " + String(i + 1) : ""),
      });
      cloneIndex++;
    }
  }

  const getCurrentHistoryResults = () =>
    cloneOutput === "set"
      ? productSetResultImages
          .filter(Boolean)
            .map((src, index) => ({ id: "history-set-" + String(index), src, label: clonePreviewCards[index]?.label || "套图 " + String(index + 1) }))
      : results.filter((item) => item.src);

  const buildHistorySignature = (output: CloneOutputKey, prompt: string, historyResults: CloneResult[], sourceImages: CloneImageItem[]) =>
    [
      output,
      prompt.trim(),
      historyResults.map((item) => item.src).join("|"),
      sourceImages.map((item) => item.src).join("|"),
    ].join("::");

  const getHistoryRecordResults = (record: EcommerceHistoryRecord) => {
    const turns = record.turns?.length ? record.turns : [buildHistoryTurnFromRecord(record)];
    return turns.flatMap(getTurnResults);
  };

  const persistEcommerceHistoryRecord = (record: EcommerceHistoryRecord, historyResults: CloneResult[]) => {
    void saveUnifiedEcommerceGenerationRecord({
      clientRecordId: record.id,
      title: record.title,
      mode: record.output,
      prompt: record.requirement,
      sourceImages: record.productImages.map((image, index) => ({
        url: image.src,
        ossKey: image.ossKey,
        label: image.name || `source-${index + 1}`,
      })),
      results: historyResults.map((item) => ({
        url: item.src,
        label: item.label,
        mediaType: "image",
      })),
      config: {
        platform: record.platform,
        market: record.market,
        language: record.language,
        ratio: record.ratio,
        setCounts: record.setCounts,
        detailModules: record.detailModules,
        modelScenes: record.modelScenes,
        replicateLevel: record.replicateLevel,
      },
      metadata: {
        localHistoryStorageKey: ecommerceHistoryStorageKey,
        referenceImageCount: record.referenceImages.length,
        turnCount: record.turns?.length ?? 1,
        latestTurnId: record.turns?.[record.turns.length - 1]?.id,
      },
      createdAt: new Date(record.createdAt).toISOString(),
    });
  };

  const formatHistoryTime = (timestamp: number) => {
    const diff = Math.max(0, Date.now() - timestamp);
    const minute = 60 * 1000;
    const hour = 60 * minute;
    const day = 24 * hour;
    if (diff < minute) return "刚刚";
    if (diff < hour) return String(Math.floor(diff / minute)) + " 分钟前";
    if (diff < day) return String(Math.floor(diff / hour)) + " 小时前";
    return String(Math.floor(diff / day)) + " 天前";
  };

  const buildEcommerceHistoryTitle = (output: CloneOutputKey, prompt: string, createdAt: number) => {
    const outputLabel = cloneOutputOptions.find((option) => option.key === output)?.label || "生成记录";
    return prompt.trim() || outputLabel + " " + new Date(createdAt).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
  };

  const updateLocalEcommerceHistoryRecord = (recordId: string, updater: (record: EcommerceHistoryRecord) => EcommerceHistoryRecord) => {
    setEcommerceHistoryRecords((current) => {
      const nextRecords = current.map((record) => (record.id === recordId ? normalizeEcommerceHistoryRecord(updater(record)) : record));
      writeEcommerceHistoryRecords(nextRecords);
      return nextRecords;
    });
  };

  const buildCurrentEcommerceHistoryTurn = (turnId: string, createdAt: number, turnStatus: EcommerceHistoryStatus = "generating"): EcommerceHistoryTurn => ({
    id: turnId,
    createdAt,
    status: turnStatus,
    output: cloneOutput,
    platform,
    market,
    language,
    ratio,
    requirement,
    productImages,
    results: [],
    setResultImages: [],
    setCounts: cloneSetCounts,
    detailModules: selectedCloneDetailModules,
    modelScenes: selectedCloneModelScenes,
    referenceImages: cloneReferenceImages,
    replicateLevel: cloneReplicateLevel,
  });

  const syncRecordSummaryWithTurn = (record: EcommerceHistoryRecord, turn: EcommerceHistoryTurn): EcommerceHistoryRecord => ({
    ...record,
    status: turn.status,
    errorMessage: turn.status === "failed" ? turn.errorMessage : undefined,
    output: turn.output,
    platform: turn.platform,
    market: turn.market,
    language: turn.language,
    ratio: turn.ratio,
    requirement: turn.requirement,
    productImages: turn.productImages,
    results: turn.results,
    setResultImages: turn.setResultImages,
    setCounts: turn.setCounts,
    detailModules: turn.detailModules,
    modelScenes: turn.modelScenes,
    referenceImages: turn.referenceImages,
    replicateLevel: turn.replicateLevel,
  });

  const updateLocalEcommerceHistoryTurn = (
    recordId: string,
    turnId: string,
    updater: (turn: EcommerceHistoryTurn) => EcommerceHistoryTurn,
  ) => {
    updateLocalEcommerceHistoryRecord(recordId, (record) => {
      const turns = record.turns?.length ? record.turns : [buildHistoryTurnFromRecord(record)];
      let updatedTurn: EcommerceHistoryTurn | null = null;
      const nextTurns = turns.map((turn) => {
        if (turn.id !== turnId) return turn;
        updatedTurn = normalizeEcommerceHistoryTurn(updater(turn), record, turns.indexOf(turn));
        return updatedTurn;
      });
      return updatedTurn ? syncRecordSummaryWithTurn({ ...record, turns: nextTurns }, updatedTurn) : record;
    });
  };

  const beginEcommerceHistoryTurn = () => {
    const createdAt = Date.now();
    const turn = buildCurrentEcommerceHistoryTurn(crypto.randomUUID(), createdAt);
    const existingRecord = activeHistoryRecordId
      ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId)
      : null;
    const recordId = existingRecord?.id ?? crypto.randomUUID();
    const baseRecord: EcommerceHistoryRecord = existingRecord ?? {
      id: recordId,
      title: buildEcommerceHistoryTitle(cloneOutput, requirement, createdAt),
      createdAt,
      status: turn.status,
      output: turn.output,
      platform: turn.platform,
      market: turn.market,
      language: turn.language,
      ratio: turn.ratio,
      requirement: turn.requirement,
      productImages: turn.productImages,
      results: turn.results,
      setResultImages: turn.setResultImages,
      setCounts: turn.setCounts,
      detailModules: turn.detailModules,
      modelScenes: turn.modelScenes,
      referenceImages: turn.referenceImages,
      replicateLevel: turn.replicateLevel,
      turns: [],
    };
    const previousTurns = baseRecord.turns?.length ? baseRecord.turns : existingRecord ? [buildHistoryTurnFromRecord(baseRecord)] : [];
    const record = normalizeEcommerceHistoryRecord(syncRecordSummaryWithTurn({
      ...baseRecord,
      turns: [...previousTurns, turn],
    }, turn));
    setEcommerceHistoryRecords((current) => {
      const nextRecords = [record, ...current.filter((item) => item.id !== record.id)].slice(0, 30);
      writeEcommerceHistoryRecords(nextRecords);
      return nextRecords;
    });
    setActiveHistoryRecordId(record.id);
    activeHistoryTurnIdRef.current = turn.id;
    return { record, turn };
  };

  const saveCurrentEcommerceHistory = () => {
    const activeRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null;
    const historyResults = activeRecord?.turns?.length ? getHistoryRecordResults(activeRecord) : getCurrentHistoryResults();
    if (!historyResults.length) return null;
    const signature = activeRecord?.turns?.length
      ? buildHistorySignature(activeRecord.output, activeRecord.requirement, historyResults, activeRecord.productImages)
      : buildHistorySignature(cloneOutput, requirement, historyResults, productImages);
    if (lastSavedHistorySignatureRef.current === signature && activeHistoryRecordId) return activeHistoryRecordId;

    const createdAt = Date.now();
    const record: EcommerceHistoryRecord = activeRecord?.turns?.length
      ? normalizeEcommerceHistoryRecord(activeRecord)
      : normalizeEcommerceHistoryRecord({
          id: activeRecord?.id ?? crypto.randomUUID(),
          title: activeRecord?.title ?? buildEcommerceHistoryTitle(cloneOutput, requirement, createdAt),
          createdAt: activeRecord?.createdAt ?? createdAt,
          status: "done",
          errorMessage: undefined,
          output: cloneOutput,
          platform,
          market,
          language,
          ratio,
          requirement,
          productImages,
          results: historyResults,
          setResultImages: cloneOutput === "set" ? historyResults.map((item) => item.src) : [],
          setCounts: cloneSetCounts,
          detailModules: selectedCloneDetailModules,
          modelScenes: selectedCloneModelScenes,
          referenceImages: cloneReferenceImages,
          replicateLevel: cloneReplicateLevel,
        });
    lastSavedHistorySignatureRef.current = signature;
    persistEcommerceHistoryRecord(record, historyResults);
    setEcommerceHistoryRecords((current) => {
      const nextRecords = [record, ...current.filter((item) => item.id !== record.id)].slice(0, 30);
      writeEcommerceHistoryRecords(nextRecords);
      return nextRecords;
    });
    setActiveHistoryRecordId(record.id);
    return record.id;
  };

  const openEcommerceHistoryRecord = (record: EcommerceHistoryRecord) => {
    setActiveTool("clone");
    setCloneOutput(record.output);
    setPlatform(record.platform);
    setMarket(record.market);
    setLanguage(record.language);
    setRatio(record.ratio);
    setRequirement(record.requirement);
    setProductImages(record.productImages);
    setCloneSetCounts(record.setCounts);
    setSelectedCloneDetailModules(record.detailModules.slice(0, maxDetailModuleSelection));
    setSelectedCloneModelScenes(record.modelScenes);
    setCloneReferenceImages(record.referenceImages);
    setCloneReplicateLevel(record.replicateLevel);
    setProductSetResultImages(record.setResultImages);
    setResults(record.output === "set" ? [] : record.results);
    setStatus((record.status ?? "done") as ProductCloneStatus);
    setPreviewZoom(1);
    setComposerMenu(null);
    setActiveHistoryRecordId(record.id);
    activeHistoryTurnIdRef.current = record.status === "generating"
      ? record.turns?.find((turn) => turn.status === "generating")?.id ?? null
      : null;
    const recordResults = getHistoryRecordResults(record);
    lastSavedHistorySignatureRef.current = buildHistorySignature(record.output, record.requirement, recordResults, record.productImages);
    setIsCommandComposerCompact(true);

    const turns = record.turns?.length ? record.turns : [buildHistoryTurnFromRecord(record)];
    const nodes = turns.reduce<CanvasNode[]>((items, turn) => {
      const turnResults = getTurnResults(turn);
      if (!turnResults.length) return items;
      const index = items.length;
      items.push({
        id: turn.id,
        mode: turn.output,
        sourceImage: turn.productImages[0]?.src,
        results: turnResults,
        createdAt: turn.createdAt,
        x: index * 420,
        y: index % 2 === 0 ? 0 : 160,
      });
      return items;
    }, []);
    setCanvasNodes(nodes);

    setPreviewOffset({ x: 0, y: 0 });
    previewOffsetRef.current = { x: 0, y: 0 };
  };

  const handleNewEcommerceConversation = () => {
    saveCurrentEcommerceHistory();
    resetTask();
    setCanvasNodes([]);
    setPreviewZoom(1);
    setPreviewOffset({ x: 0, y: 0 });
    setComposerMenu(null);
    setIsCommandComposerCompact(false);
    setActiveHistoryRecordId(null);
    activeHistoryTurnIdRef.current = null;
    lastSavedHistorySignatureRef.current = "";
  };

  const refreshEcommerceHistory = () => {
    if (historyRefreshLockRef.current) return;
    historyRefreshLockRef.current = true;
    setIsHistoryRefreshing(true);
    setHistoryRefreshMessage("刷新中...");
    setHistoryRefreshStamp(Date.now());

    window.setTimeout(() => {
      const storedRecords = readEcommerceHistoryRecords();
      const mergedRecords = [...ecommerceHistoryRecords, ...storedRecords]
        .reduce<EcommerceHistoryRecord[]>((records, record) => {
          if (!records.some((item) => item.id === record.id)) records.push(record);
          return records;
        }, [])
        .sort((a, b) => b.createdAt - a.createdAt)
        .slice(0, 30);
      writeEcommerceHistoryRecords(mergedRecords);
      setHistoryRefreshTick((tick) => tick + 1);
      setEcommerceHistoryRecords(mergedRecords);
        setHistoryRefreshMessage(mergedRecords.length ? "已刷新 " + String(mergedRecords.length) + " 条记录" : "暂无可刷新记录");
      setHistoryRefreshStamp(Date.now());
      setIsHistoryRefreshing(false);
      historyRefreshLockRef.current = false;
    }, 180);

    window.setTimeout(() => setHistoryRefreshMessage(""), 3000);
  };

  const deleteHistoryRecord = (recordId: string, event: ReactMouseEvent) => {
    event.stopPropagation();
    const record = ecommerceHistoryRecords.find((r) => r.id === recordId);
    if (!record) return;
    const next = ecommerceHistoryRecords.filter((r) => r.id !== recordId);
    setEcommerceHistoryRecords(next);
    writeEcommerceHistoryRecords(next);
    if (activeHistoryRecordId === recordId) {
      setActiveHistoryRecordId(null);
      activeHistoryTurnIdRef.current = null;
    }
    deleteEcommerceGenerationRecord(recordId).catch(() => {});
  };

  useEffect(() => {
    if (status === "done") saveCurrentEcommerceHistory();
  }, [status, results, productSetResultImages]);

  const detailSourcePreviewImages = detailProductImages.length
    ? detailProductImages.reduce<string[]>((urls, item) => {
        urls.push(item.src);
        return urls;
      }, [])
    : detailProductSamples;
  const cloneBasicSelects: Array<{
    key: CloneBasicSelectKey;
    label: string;
    value: string;
    options: string[];
    onChange: (value: string) => void;
  }> = [
    { key: "platform", label: "平台", value: platform, options: platformOptions, onChange: handleClonePlatformChange },
    { key: "market", label: "国家", value: market, options: marketOptions, onChange: handleCloneMarketChange },
    { key: "language", label: "语种", value: language, options: cloneLanguageOptions, onChange: setLanguage },
    { key: "ratio", label: "尺寸/比例", value: ratio, options: cloneRatioOptions, onChange: setRatio },
  ];
  const quickDetailBasicSelects: Array<{
    key: CloneBasicSelectKey;
    label: string;
    value: string;
    options: string[];
    onChange: (value: string) => void;
  }> = [
    { key: "platform", label: "平台", value: detailPlatform, options: platformOptions, onChange: handleDetailPlatformChange },
    { key: "market", label: "国家", value: detailMarket, options: marketOptions, onChange: handleDetailMarketChange },
    { key: "language", label: "语种", value: detailLanguage, options: detailLanguageOptions, onChange: setDetailLanguage },
    { key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(detailRatio), options: quickSetRatioOptions, onChange: setDetailRatio },
  ];

  const quickHotBasicSelects: Array<{
    key: CloneBasicSelectKey;
    label: string;
    value: string;
    options: string[];
    onChange: (value: string) => void;
  }> = [
    { key: "platform", label: "平台", value: hotPlatform, options: platformOptions, onChange: handleHotPlatformChange },
    { key: "market", label: "国家", value: hotMarket, options: marketOptions, onChange: handleHotMarketChange },
    { key: "language", label: "语种", value: hotLanguage, options: hotLanguageOptions, onChange: setHotLanguage },
    { key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(hotRatio), options: quickSetRatioOptions, onChange: setHotRatio },
  ];

  const quickSetBasicSelects: Array<{
    key: CloneBasicSelectKey;
    label: string;
    value: string;
    options: string[];
    onChange: (value: string) => void;
  }> = [
    { key: "platform", label: "平台", value: platform, options: platformOptions, onChange: setPlatform },
    { key: "market", label: "国家", value: market, options: marketOptions, onChange: setMarket },
    { key: "language", label: "语种", value: language, options: cloneLanguageOptions, onChange: setLanguage },
    { key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(ratio), options: quickSetRatioOptions, onChange: setRatio },
  ];

  const cloneModelSelects: Array<{
    key: CloneModelSelectKey;
    label: string;
    value: string;
    options: string[];
    onChange: (value: string) => void;
  }> = [
    { key: "gender", label: "性别", value: cloneModelGender, options: tryOnModelOptions.gender, onChange: setCloneModelGender },
    { key: "age", label: "年龄", value: cloneModelAge, options: tryOnModelOptions.age, onChange: setCloneModelAge },
    {
      key: "ethnicity",
      label: "人种",
      value: cloneModelEthnicity,
      options: tryOnModelOptions.ethnicity,
      onChange: setCloneModelEthnicity,
    },
    { key: "body", label: "体型", value: cloneModelBody, options: tryOnModelOptions.body, onChange: setCloneModelBody },
  ];

  const setPanel = (
    <EcommerceSetPanel
      setInputRef={setInputRef}
      setImages={setImages}
      isSetUploadDragging={isSetUploadDragging}
      productSetOutputOptions={productSetOutputOptions}
      productSetOutput={productSetOutput}
      platformOptions={platformOptions}
      marketOptions={marketOptions}
      productSetLanguageOptions={productSetLanguageOptions}
      productSetRatioOptions={productSetRatioOptions}
      productSetPlatform={productSetPlatform}
      productSetMarket={productSetMarket}
      productSetLanguage={productSetLanguage}
      productSetRatio={productSetRatio}
      setIsSetUploadDragging={setIsSetUploadDragging}
      handleSetDrop={handleSetDrop}
      handleSetUpload={handleSetUpload}
      removeSetImage={removeSetImage}
      handleProductSetOutputChange={handleProductSetOutputChange}
      handleProductSetPlatformChange={handleProductSetPlatformChange}
      handleProductSetMarketChange={handleProductSetMarketChange}
      setProductSetLanguage={setProductSetLanguage}
      setProductSetRatio={setProductSetRatio}
      formatRatioDisplayValue={formatRatioDisplayValue}
    />
  );

  const clonePanel = (
    <EcommerceClonePanel
      productInputRef={productInputRef}
      cloneReferenceInputRef={cloneReferenceInputRef}
      productImages={productImages}
      isProductUploadDragging={isProductUploadDragging}
      cloneOutput={cloneOutput}
      cloneOutputOptions={cloneOutputOptions}
      cloneBasicSelects={cloneBasicSelects}
      openCloneBasicSelect={openCloneBasicSelect}
      cloneReferenceMode={cloneReferenceMode}
      cloneReferenceImages={cloneReferenceImages}
      maxCloneReferenceImages={maxCloneReferenceImages}
      cloneReplicateLevel={cloneReplicateLevel}
      cloneReplicateLevelOptions={cloneReplicateLevelOptions}
      cloneSetCounts={cloneSetCounts}
      cloneSetCountOptions={cloneSetCountOptions}
      cloneSetTotal={cloneSetTotal}
      minCloneSetTotal={minCloneSetTotal}
      maxCloneSetTotal={maxCloneSetTotal}
      selectedCloneDetailModules={selectedCloneDetailModules}
      cloneDetailModules={cloneDetailModules}
      cloneModelPanelTab={cloneModelPanelTab}
      tryOnScenes={tryOnScenes}
      selectedCloneModelScenes={selectedCloneModelScenes}
      cloneModelCustomScene={cloneModelCustomScene}
      cloneModelSelects={cloneModelSelects}
      openCloneModelSelect={openCloneModelSelect}
      cloneModelSelectDropUp={cloneModelSelectDropUp}
      cloneVideoQuality={cloneVideoQuality}
      cloneVideoQualityOptions={cloneVideoQualityOptions}
      cloneVideoDuration={cloneVideoDuration}
      cloneVideoDurationMin={cloneVideoDurationMin}
      cloneVideoDurationMax={cloneVideoDurationMax}
      cloneVideoDurationStyle={cloneVideoDurationStyle}
      cloneVideoSmart={cloneVideoSmart}
      canGenerate={canGenerate}
      status={status}
      lastFailedActionRef={lastFailedActionRef}
      setIsProductUploadDragging={setIsProductUploadDragging}
      handleProductDrop={handleProductDrop}
      removeProductImage={removeProductImage}
      handleProductUpload={handleProductUpload}
      handleCloneOutputChange={handleCloneOutputChange}
      setOpenCloneBasicSelect={setOpenCloneBasicSelect}
      setCloneReferenceMode={setCloneReferenceMode}
      handleCloneReferenceUpload={handleCloneReferenceUpload}
      isCloneReferenceDragging={isCloneReferenceDragging}
      handleCloneReferenceDragOver={handleCloneReferenceDragOver}
      handleCloneReferenceDragLeave={handleCloneReferenceDragLeave}
      handleCloneReferenceDrop={handleCloneReferenceDrop}
      setCloneReplicateLevel={setCloneReplicateLevel}
      startCloneSetCountHold={startCloneSetCountHold}
      clearCloneSetCountHold={clearCloneSetCountHold}
      toggleCloneDetailModule={toggleCloneDetailModule}
      setCloneModelPanelTab={setCloneModelPanelTab}
      toggleCloneModelScene={toggleCloneModelScene}
      setCloneModelCustomScene={setCloneModelCustomScene}
      setOpenCloneModelSelect={setOpenCloneModelSelect}
      setCloneModelSelectDropUp={setCloneModelSelectDropUp}
      setCloneVideoQuality={setCloneVideoQuality}
      setCloneVideoDuration={setCloneVideoDuration}
      clampCloneVideoDuration={clampCloneVideoDuration}
      setCloneVideoSmart={setCloneVideoSmart}
      handleGenerate={handleGenerate}
      onCancelGenerate={handleCancelGenerate}
      formatRatioDisplayValue={formatRatioDisplayValue}
      onStartVideoPlan={handleStartVideoPlan}
    />
  );

  const detailPanel = (
    <EcommerceDetailPanel
      detailInputRef={detailInputRef}
      detailProductImages={detailProductImages}
      detailPlatform={detailPlatform}
      detailMarket={detailMarket}
      detailLanguage={detailLanguage}
      detailType={detailType}
      detailRequirement={detailRequirement}
      selectedDetailModules={selectedDetailModules}
      detailStatus={detailStatus}
      canGenerateDetail={canGenerateDetail}
      detailPrimaryLabel={detailPrimaryLabel}
      platformOptions={platformOptions}
      marketOptions={marketOptions}
      detailLanguageOptions={detailLanguageOptions}
      detailTypeOptions={detailTypeOptions}
      detailModules={detailModules}
      handleDetailUpload={handleDetailUpload}
      handleDetailPlatformChange={handleDetailPlatformChange}
      handleDetailMarketChange={handleDetailMarketChange}
      setDetailLanguage={setDetailLanguage}
      setDetailType={setDetailType}
      setDetailRequirement={setDetailRequirement}
      handleDetailAiWrite={handleDetailAiWrite}
      toggleDetailModule={toggleDetailModule}
      handleDetailGenerate={handleDetailGenerate}
      onCancelGenerate={handleCancelGenerate}
    />
  );

  const tryOnPanel = (
    <EcommerceTryOnPanel
      garmentInputRef={garmentInputRef}
      garmentImages={garmentImages}
      modelSource={modelSource}
      modelGender={modelGender}
      modelAge={modelAge}
      modelEthnicity={modelEthnicity}
      modelBody={modelBody}
      appearance={appearance}
      selectedScenes={selectedScenes}
      customScene={customScene}
      smartScene={smartScene}
      tryOnRatio={tryOnRatio}
      tryOnStatus={tryOnStatus}
      canGenerateTryOn={canGenerateTryOn}
      tryOnPrimaryLabel={tryOnPrimaryLabel}
      tryOnModelOptions={tryOnModelOptions}
      tryOnAssets={tryOnAssets}
      tryOnScenes={tryOnScenes}
      tryOnRatioOptions={tryOnRatioOptions}
      handleGarmentUpload={handleGarmentUpload}
      setModelSource={setModelSource}
      setModelGender={setModelGender}
      setModelAge={setModelAge}
      setModelEthnicity={setModelEthnicity}
      setModelBody={setModelBody}
      setAppearance={setAppearance}
      handleGenerateModel={handleGenerateModel}
      toggleScene={toggleScene}
      setCustomScene={setCustomScene}
      setSmartScene={setSmartScene}
      setTryOnRatio={setTryOnRatio}
      handleTryOnGenerate={handleTryOnGenerate}
      onCancelGenerate={handleCancelGenerate}
    />
  );

  const placeholderPanel = (
    <>
      <div className="product-clone-panel__scroll">
        <section className="product-clone-empty-panel">
          <span>{activeToolMeta?.icon}</span>
          <h2>{activeToolMeta?.label}</h2>
          <p>该工具页面正在接入，当前可使用电商 AI 作图、商品套图、A+ 详情与服饰穿搭。</p>
        </section>
      </div>
      <footer className="product-clone-panel__footer">
        <button type="button" className="product-clone-primary" disabled>
          暂未开放
        </button>
      </footer>
    </>
  );

  const setPreview = (
    <main className="product-clone-preview product-clone-preview--set" aria-label="AI商品套图预览" onWheel={handlePreviewWheel}>
      <div className="product-clone-preview__headline">
        <h1>预览</h1>
        <p>
          上传商品图，AI 即刻生成 <span>符合多电商平台规范</span> 的高转化率商品套图。
        </p>
      </div>

      {productSetPreviewReady ? (
        <section className="product-set-demo-board">
          <button
            type="button"
            className="product-set-main-card"
            onClick={() => openProductSetPreview(setPreviewCards[0] ?? productSetPreviewCards[0])}
          >
            <img src={setImages[0]?.src ?? (setPreviewCards[0]?.src ?? productSetPreviewCards[0].src)} alt="商品原图" />
          </button>
          <div className="product-set-flow-arrow" aria-hidden="true" />
          <div className="product-set-card-grid result-reveal">
            {setPreviewCards.map((card) => (
              <button key={card.id} type="button" onClick={() => openProductSetPreview(card)}>
                <img src={card.src} alt={card.label} />
              </button>
            ))}
          </div>
        </section>
      ) : (
        <section className="product-set-empty-preview" aria-live="polite">
          {productSetStatus === "generating" ? <LoadingOutlined /> : <FileImageOutlined />}
          <strong>{productSetStatus === "generating" ? "正在生成" : "等待生成"}</strong>
          {productSetStatus === "generating" ? <EcommerceProgressBar status="generating" progress={generationProgress} onCancel={handleCancelGenerate} label="商品套图" /> : null}
          <span>{productSetStatus === "generating" ? "AI 正在整理主图、场景、细节与卖点图。" : "上传商品原图并填写信息后，AI 将为您生成专业的电商商品图。"}</span>
        </section>
      )}

      {productSetStatus === "done" ? <p className="product-set-generated-note">已生成{selectedProductSetOutput.label}预览</p> : null}

      <section className="product-set-floating-detail" aria-label="信息详情">
        <div className="product-set-floating-detail__head">
          <strong>信息详情</strong>
          <span>{productSetRequirement.length}/500</span>
        </div>
        <textarea
          value={productSetRequirement}
          onChange={(event) => setProductSetRequirement(event.target.value)}
          maxLength={500}
          placeholder="建议包含以下信息：产品名称、核心卖点、期望场景、具体参数"
        />
        <button type="button" className="product-set-floating-submit" disabled={!canGenerateSet} onClick={handleSetGenerate}>
          {productSetStatus === "generating" ? <LoadingOutlined /> : null}
          {setPrimaryLabel}
        </button>
        {productSetStatus === "generating" ? (
          <button type="button" className="product-set-floating-submit product-set-floating-submit--cancel" onClick={handleCancelGenerate}>
            {"\u53d6\u6d88\u751f\u6210"}
          </button>
        ) : null}
      </section>

      <button type="button" className="product-clone-help" aria-label="帮助">
        <QuestionCircleOutlined />
      </button>
    </main>
  );

  const composerSettingLabel =
    cloneOutput === "set"
      ? "套图 " + String(cloneSetTotal) + "张"
      : cloneOutput === "detail"
        ? "详情 " + String(selectedCloneDetailModules.length) + "项"
      : cloneOutput === "model"
        ? cloneModelPanelTab === "scene" ? "场景设置" : "模特设置"
      : cloneOutput === "video"
        ? String(cloneVideoDuration) + "秒 " + (cloneVideoQuality === "standard" ? "720P" : "1080P")
        : "换装素材";

  const composerAssetTabs: Array<{ key: ComposerAssetTabKey; label: string }> = [
    { key: "recent", label: "最近保存" },
    { key: "recipe", label: "套图配方" },
    { key: "model", label: "模特库" },
  ];
  const composerWorkModeOptions: Array<{ key: ComposerWorkModeKey; label: string; desc: string }> = [
    { key: "quick", label: "快捷", desc: "快速整理提示词，适合常规商品图生成。" },
    { key: "think", label: "思考", desc: "更强调卖点拆解、场景规划和图文一致性。" },
  ];

  const applyAiWriteSuggestion = () => {
    const keyword = aiWriteDraft.trim();
    if (!keyword) {
      toast.info("请输入产品关键词或卖点");
      return;
    }
    const modeHint = composerWorkMode === "think" ? "先拆解目标人群、核心卖点和使用场景，" : "";
    const nextValue = `${keyword}。${modeHint}请生成适合${platform}的高转化电商素材，画面干净高级，突出产品主体、核心卖点、使用场景和购买理由。`.slice(0, 500);
    setRequirement(nextValue);
    setComposerMenu(null);
  };

  const renderComposerAssetPanel = () => {
    const renderEmpty = (label: string) => (
      <div className="ecom-command-library-empty">
        <FolderOpenOutlined />
        <strong>暂无数据</strong>
        <span>{label}</span>
      </div>
    );

    let content: ReactNode;
    if (composerAssetTab === "recent") {
      content = ecommerceHistoryRecords.length ? (
        <div className="ecom-command-library-list">
          {ecommerceHistoryRecords.slice(0, 4).map((record) => {
            const outputLabel = cloneOutputOptions.find((option) => option.key === record.output)?.label || "生成记录";
            return (
              <button key={record.id} type="button" onClick={() => { openEcommerceHistoryRecord(record); setComposerMenu(null); }}>
                <strong>{record.title}</strong>
                <span>{outputLabel} · {formatHistoryTime(record.createdAt)}</span>
              </button>
            );
          })}
        </div>
      ) : renderEmpty("生成后保存的素材会沉淀在这里");
    } else if (composerAssetTab === "recipe") {
      content = (
        <div className="ecom-command-library-list">
          {commerceScenarioTemplates.slice(0, 4).map((template) => (
            <button key={template.id} type="button" onClick={() => { handleCloneTemplateCardClick(template); setComposerMenu(null); }}>
              <strong>{template.title}</strong>
              <span>{template.badge} · {template.desc}</span>
            </button>
          ))}
        </div>
      );
    } else {
      content = (
        <div className="ecom-command-library-list ecom-command-library-list--model">
          {tryOnScenes.slice(0, 4).map((scene) => (
            <button key={scene} type="button" className={selectedCloneModelScenes.includes(scene) ? "is-active" : ""} onClick={() => toggleCloneModelScene(scene)}>
              <strong>{scene}</strong>
              <span>模特场景</span>
            </button>
          ))}
        </div>
      );
    }

    return (
      <>
        <header className="ecom-command-library-head">
          <strong>资产库</strong>
          <button type="button" className="ecom-command-library-help" onClick={(event) => event.preventDefault()}>
            <QuestionCircleOutlined /> 使用教程
          </button>
        </header>
        <div className="ecom-command-library-tabs" role="tablist" aria-label="资产库分类">
          {composerAssetTabs.map((tab) => (
            <button key={tab.key} type="button" className={composerAssetTab === tab.key ? "is-active" : ""} onClick={() => setComposerAssetTab(tab.key)}>
              {tab.label}
            </button>
          ))}
        </div>
        {content}
      </>
    );
  };

  const renderComposerMenu = () => {
    const composerLanguageOptions = Array.from(new Set(marketLanguageOptions.flatMap((option) => option.languages))).map((item) => ({
      language: item,
      countries: marketLanguageOptions.filter((option) => option.languages.includes(item)).map((option) => option.country),
    }));
    const composerPopoverStyle = {
      "--composer-popover-left": `${composerPopoverLeft}px`,
      "--composer-popover-top": `${composerPopoverTop}px`,
    } as CSSProperties;
    const menuToRender = composerMenu ?? visibleComposerMenu;
    if (!menuToRender) return null;
    const popoverClosingClass = !composerMenu && isComposerMenuClosing ? " is-closing" : "";
    const composerPopoverKey = `${menuToRender}-${cloneOutput}-${popoverClosingClass ? "closing" : "open"}`;
    if (menuToRender === "assetLibrary") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--library${popoverClosingClass}`} style={composerPopoverStyle}>
          {renderComposerAssetPanel()}
        </div>
      );
    }
    if (menuToRender === "workMode") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--work-mode${popoverClosingClass}`} style={composerPopoverStyle}>
          <header><strong>模式</strong><span>仅调整创作体验，不改变接口</span></header>
          {composerWorkModeOptions.map((option) => (
            <button key={option.key} type="button" className={composerWorkMode === option.key ? "is-active" : ""} onClick={() => { setComposerWorkMode(option.key); setComposerMenu(null); }}>
              <strong>{option.label}</strong>
              <span>{option.desc}</span>
            </button>
          ))}
        </div>
      );
    }
    if (menuToRender === "aiWrite") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--ai-write${popoverClosingClass}`} style={composerPopoverStyle}>
          <header><strong>AI 帮写</strong><span>把关键词扩写成商业提示词</span></header>
          <textarea value={aiWriteDraft} onChange={(event) => setAiWriteDraft(event.target.value.slice(0, 120))} placeholder="输入产品名称、卖点或期望风格" />
          <button type="button" className="ecom-command-ai-submit" onClick={applyAiWriteSuggestion}>
            开始智能优化
          </button>
        </div>
      );
    }
    if (menuToRender === "mode") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--grid ecom-command-popover--mode${popoverClosingClass}`} style={composerPopoverStyle}>
          {cloneOutputOptions.map((option) => (
            <button key={option.key} type="button" className={cloneOutput === option.key ? "is-active" : ""} onClick={() => { handleCloneOutputChange(option.key); setComposerMenu(null); }}>
              <span className={`ecom-command-mode-icon ecom-command-mode-icon--${option.key}`} aria-hidden="true">{option.icon}</span>
              <strong>{option.label}</strong>
              <em>{option.desc}</em>
            </button>
          ))}
        </div>
      );
    }
    if (menuToRender === "platform") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--list ecom-command-popover--platforms${popoverClosingClass}`} style={composerPopoverStyle}>
          {platformOptions.map((option) => (
            <button key={option} type="button" className={platform === option ? "is-active" : ""} onClick={() => { handleClonePlatformChange(option); setComposerMenu(null); }}>
              {option}
            </button>
          ))}
        </div>
      );
    }
    if (menuToRender === "language") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--languages${popoverClosingClass}`} style={composerPopoverStyle}>
          {composerLanguageOptions.map((option) => (
            <button key={option.language} type="button" className={language === option.language ? "is-active" : ""} onClick={() => { setLanguage(option.language); setComposerMenu(null); }}>
              {option.language}
            </button>
          ))}
        </div>
      );
    }
    if (menuToRender === "ratio") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--list ecom-command-popover--ratio-picker${popoverClosingClass}`} style={composerPopoverStyle}>
          {composerRatioOptions.map((option) => {
            const ratioParts = getRatioDisplayParts(option);
            return (
              <button key={option} type="button" className={ratio === option ? "is-active" : ""} onClick={() => { setRatio(option); setComposerMenu(null); }}>
                {ratioParts.aspect}
              </button>
            );
          })}
        </div>
      );
    }
    if (menuToRender === "settings" && activeCommerceScenario === "salesVideo") {
      return (
        <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--list ecom-command-popover--duration${popoverClosingClass}`} style={composerPopoverStyle}>
          {composerDurationOptions.map((option) => (
            <button key={option} type="button" className={cloneVideoDuration === option ? "is-active" : ""} onClick={() => { setCloneVideoDuration(option); setComposerMenu(null); }}>
              {option}秒
            </button>
          ))}
        </div>
      );
    }
    return (
      <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--settings ecom-command-popover--settings-${cloneOutput}${popoverClosingClass}`} style={composerPopoverStyle}>
        {cloneOutput === "set" ? (
          <>
            <header><strong>套图分类设置</strong><span>总数 1-16 张</span></header>
            {cloneSetCountOptions.map((item) => (
              <div key={item.key} className="ecom-command-count-row">
                <span><strong>{item.title}</strong><em>{item.desc}</em></span>
                <div>
                  <button type="button" onClick={() => updateCloneSetCount(item.key, -1)} disabled={cloneSetCounts[item.key] <= 0 || cloneSetTotal <= minCloneSetTotal}>-</button>
                  <b>{cloneSetCounts[item.key]}</b>
                  <button type="button" onClick={() => updateCloneSetCount(item.key, 1)} disabled={cloneSetTotal >= maxCloneSetTotal}>+</button>
                </div>
              </div>
            ))}
          </>
        ) : cloneOutput === "detail" ? (
          <>
            <header><strong>详情模块设置</strong><span>多选</span></header>
            <div className="ecom-command-module-grid ecom-command-module-grid--detail">
              {cloneDetailModules.map((module) => (
                <button key={module.id} type="button" className={selectedCloneDetailModules.includes(module.id) ? "is-active" : ""} onClick={() => toggleCloneDetailModule(module.id)}>
                  <strong>{module.title}</strong><span>{module.desc}</span>
                </button>
              ))}
            </div>
          </>
        ) : cloneOutput === "model" ? (
          <>
            <header><strong>场景设置</strong><span>场景 / 形象</span></header>
            <div className="ecom-command-module-grid ecom-command-module-grid--model">
              {tryOnScenes.map((scene) => (
                <button key={scene} type="button" className={selectedCloneModelScenes.includes(scene) ? "is-active" : ""} onClick={() => toggleCloneModelScene(scene)}>{scene}</button>
              ))}
            </div>
            <div className="ecom-command-model-profile">
              <strong className="ecom-command-model-profile__title">模特形象</strong>
              {cloneModelSelects.map((item) => (
                <section key={item.key}>
                  <strong>{item.label}</strong>
                  <div>
                    {item.options.map((option) => (
                      <button key={option} type="button" className={item.value === option ? "is-active" : ""} onClick={() => item.onChange(option)}>{option}</button>
                    ))}
                  </div>
                </section>
              ))}
            </div>
          </>
        ) : cloneOutput === "video" ? (
          <>
            <header><strong>短视频设置</strong><span>{cloneVideoDuration}秒</span></header>
            <div className="ecom-command-module-grid ecom-command-module-grid--video">
              {cloneVideoQualityOptions.map((option) => (
                <button key={option.key} type="button" className={cloneVideoQuality === option.key ? "is-active" : ""} onClick={() => setCloneVideoQuality(option.key)}>
                  <strong>{option.label}</strong><span>{option.desc}</span>
                </button>
              ))}
            </div>
            <label className="ecom-command-range">
              <span>视频时长 {cloneVideoDuration}秒</span>
              <input type="range" min={cloneVideoDurationMin} max={cloneVideoDurationMax} step={5} value={cloneVideoDuration} onChange={(event) => setCloneVideoDuration(clampCloneVideoDuration(Number(event.target.value)))} />
            </label>
          </>
        ) : (
          <>
            <header><strong>视频换装设置</strong><span>上传视频和服装参考</span></header>
            <p className="ecom-command-popover-note">请在提示词中描述换装目标，并上传参考商品图。</p>
          </>
        )}
      </div>
    );
  };

  const toggleComposerMenu = (menuKey: ComposerMenuKey, event: ReactMouseEvent<HTMLButtonElement>) => {
    const composerRect = event.currentTarget.closest(".clone-ai-input-wrapper.ecom-command-composer")?.getBoundingClientRect();
    const buttonRect = event.currentTarget.getBoundingClientRect();
    const composerLeft = composerRect?.left ?? buttonRect.left;
    const composerTop = composerRect?.top ?? buttonRect.top;
    setComposerPopoverLeft(Math.max(0, buttonRect.left - composerLeft));
    setComposerPopoverTop(Math.max(0, buttonRect.bottom - composerTop + 8));
    setComposerMenu((menu) => (menu === menuKey ? null : menuKey));
  };

  const canPlanVideo = productImages.length > 0 || requirement.trim().length > 0;
  const commandGenerateDisabled = cloneOutput === "video" ? false : !canGenerate;

  function handleStartVideoPlan() {
    if (!canPlanVideo) {
      toast.info("请先上传商品图或填写短视频需求");
      return;
    }
    if (!isAuthenticated) {
      requestLogin();
      return;
    }
    setIsVideoWorkspaceVisible(true);
    setVideoPlanTrigger((value) => value + 1);
  }

  const handleCommandGenerate = () => {
    if (cloneOutput === "video") {
      handleStartVideoPlan();
      return;
    }
    handleGenerate();
  };

  const showMainVideoWorkspace = cloneOutput === "video" && isVideoWorkspaceVisible;

  const scrollInspirationRow = (event: ReactMouseEvent<HTMLButtonElement>, direction: -1 | 1) => {
    const row = event.currentTarget.closest(".ecom-inspiration-row");
    const strip = row?.querySelector<HTMLElement>(".ecom-inspiration-strip");
    if (!strip) return;
    strip.scrollBy({ left: direction * Math.max(280, strip.clientWidth * 0.78), behavior: "smooth" });
  };

  const applyInspirationPrompt = (prompt: string) => {
    const nextValue = prompt.slice(0, 500);
    // 回到主指令栏（关闭可能打开的快捷工具页），把提示词填入并聚焦。
    setActiveQuickTool(null);
    setRequirement(nextValue);
    syncRequirementMentionQuery(nextValue, nextValue.length);
    setInspirationPreview(null);
    requestAnimationFrame(() => {
      const textarea = requirementTextareaRef.current;
      if (textarea) {
        textarea.focus();
        textarea.setSelectionRange(nextValue.length, nextValue.length);
        textarea.scrollIntoView({ behavior: "smooth", block: "center" });
      }
    });
    toast.success("提示词已填入指令栏");
  };

  const applyComposerPrompt = (prompt: string) => {
    const nextValue = prompt.slice(0, 500);
    setActiveQuickTool(null);
    setComposerMenu(null);
    setRequirement(nextValue);
    syncRequirementMentionQuery(nextValue, nextValue.length);
    setInspirationPreview(null);
    requestAnimationFrame(() => {
      const textarea = requirementTextareaRef.current;
      if (textarea) {
        textarea.focus();
        textarea.setSelectionRange(nextValue.length, nextValue.length);
        textarea.scrollIntoView({ behavior: "smooth", block: "center" });
      }
      window.setTimeout(() => {
        const latestTextarea = requirementTextareaRef.current;
        if (!latestTextarea) return;
        latestTextarea.focus();
        latestTextarea.setSelectionRange(nextValue.length, nextValue.length);
      }, 80);
    });
  };

  const addTemplateImageToComposer = async (card: CloneTemplateAsset) => {
    if (productImages.length >= maxCloneProductImages) {
      toast.info("模板图片已达上限");
      return;
    }

    try {
      const stamp = Date.now();
      const uploaded = await aiGenerationClient.uploadAssetByUrl({
        sourceUrl: card.mediaUrl,
        name: `${card.id}-${stamp}`,
        scope: ecommerceOssScopes.productSource,
      });
      const nextImage: CloneImageItem = {
        id: `template-${card.id}-${stamp}`,
        src: uploaded.url || card.mediaUrl,
        name: card.title,
        ossKey: uploaded.ossKey,
      };
      setProductImages((current) => [...current, nextImage].slice(0, maxCloneProductImages));
      void readImageDimensions(nextImage.src)
        .then(({ width, height }) => {
          setProductImages((current) =>
            current.map((item) => (item.id === nextImage.id ? { ...item, width, height } : item)),
          );
        })
        .catch(() => undefined);
    } catch {
      toast.error("模板图片导入失败");
    }
  };

  const handleCloneTemplateCardClick = (card: CommerceScenarioTemplate) => {
    setActiveCommerceScenario(card.scenario);
    if (card.output !== cloneOutput) handleCloneOutputChange(card.output);
    setIsCloneTemplateStripVisible(true);
    setComposerMenu(null);
    void addTemplateImageToComposer(card);
    applyComposerPrompt(card.prompt);
  };

  const inspirationPreviewOverlay =
    inspirationPreview && typeof document !== "undefined"
      ? createPortal(
          <div
            className="ecom-inspiration-preview"
            onClick={() => setInspirationPreview(null)}
            onWheel={(event) => {
              event.preventDefault();
              event.stopPropagation();
            }}
          >
            <div className="ecom-inspiration-preview__backdrop" />
            <div className="ecom-inspiration-preview__content" onClick={(event) => event.stopPropagation()}>
              {inspirationPreview.mediaType === "video" ? (
                <video src={inspirationPreview.mediaUrl} controls autoPlay loop playsInline className="ecom-inspiration-preview__media" />
              ) : (
                <img src={inspirationPreview.mediaUrl} alt="" className="ecom-inspiration-preview__media" />
              )}
              {inspirationPreview.prompt ? (
                <div className="ecom-inspiration-preview__actions">
                  <button
                    type="button"
                    className="ecom-inspiration-preview__use-prompt"
                    onClick={() => applyInspirationPrompt(inspirationPreview.prompt)}
                  >
                    <EditOutlined />
                    <span>使用此提示词</span>
                  </button>
                </div>
              ) : null}
            </div>
          </div>,
          document.body,
        )
      : null;

  const clonePreview = (
    <main
      className={`product-clone-preview clone-ai-preview${showMainVideoWorkspace ? " has-main-video-workspace" : ""}`}
      data-status={status}
      aria-label="电商 AI 作图预览"
      {...getPreviewSurfaceProps()}
    >
      <header className="clone-ai-preview-header">
        <strong>预览</strong>
        <span>
          上传商品图，AI 即刻生成 <b>符合多电商平台规范</b> 的高转化率商品素材。
        </span>
        <div className="clone-ai-preview-zoom">
          <button type="button" onClick={() => setPreviewZoom((z) => Math.max(0.25, z - 0.1))} disabled={previewZoom <= 0.25} aria-label="缩小">-</button>
          <span>{Math.round(previewZoom * 100)}%</span>
          <button type="button" onClick={() => setPreviewZoom((z) => Math.min(2, z + 0.1))} disabled={previewZoom >= 2} aria-label="放大">+</button>
          {activeHistoryRecordId ? (
            <button type="button" onClick={() => { setPreviewZoom(1); setPreviewOffset({ x: 0, y: 0 }); }} aria-label="重置画布">重置</button>
          ) : null}
        </div>
      </header>

      {cloneOutput === "video" ? (
        showMainVideoWorkspace ? (
          <section className="clone-ai-main-video-workspace" style={previewTransformStyle} aria-label="短视频生成画布">
            <EcommerceVideoWorkspace
              isAuthenticated={isAuthenticated}
              productImageDataUrls={ecommerceVideoImageDataUrls}
              productImageFiles={ecommerceVideoImageFiles}
              requirement={requirement}
              platform={platform}
              aspectRatio={ratio.includes("9:16") ? "9:16" : ratio.includes("16:9") ? "16:9" : ratio.includes("3:4") ? "3:4" : "9:16"}
              durationSeconds={cloneVideoDuration}
              resolution={cloneVideoQuality === "standard" ? "720P" : "1080P"}
              onRequestLogin={() => (isAuthenticated ? undefined : requestLogin())}
              onOpenHistory={() => setVideoHistoryVisible(true)}
              triggerPlan={videoPlanTrigger}
            />
          </section>
        ) : status === "idle" || status === "ready" ? null : (
        <>
          <section className="clone-ai-flow-pipeline" aria-label="生成流程">
            {/* Source Node —原图素材 */}
            <div className="clone-ai-flow-source">
              <div className="clone-ai-flow-node clone-ai-flow-node--source">
                {productImages[0]?.src ? (
                  <img src={productImages[0].src} alt="商品原图" />
                ) : (
                  <div className="clone-ai-flow-node__placeholder">
                    <FileImageOutlined />
                  </div>
                )}
              </div>
              <span className="clone-ai-flow-node__label">附件原图</span>
            </div>

            {/* Connector - branch lines */}
            <div className="clone-ai-flow-connector" aria-hidden="true">
              <div className="clone-ai-flow-connector__trunk" />
              <div className="clone-ai-flow-connector__branches">
                <div className="clone-ai-flow-connector__branch" />
                <div className="clone-ai-flow-connector__branch" />
                <div className="clone-ai-flow-connector__branch" />
              </div>
            </div>

            {/* Branches —生成路径分支 */}
            {status === "done" ? (
              <div className="clone-ai-flow-branches">
                {results[0]?.src ? (
                  <div className="clone-ai-flow-branch">
                    <div className="clone-ai-flow-node clone-ai-flow-node--text">
                      <div className="clone-ai-flow-node__text-content">
                        <span className="clone-ai-flow-node__text-title">{selectedCloneOutput.label}</span>
                        <span className="clone-ai-flow-node__text-desc">{requirement || "AI 智能生成"}</span>
                      </div>
                    </div>
                    <div className="clone-ai-flow-arrow" aria-hidden="true" />
                    <button
                      type="button"
                      className="clone-ai-flow-node clone-ai-flow-node--result"
                      onClick={() => openProductSetPreview(results[0])}
                    >
                      <img src={results[0].src} alt={selectedCloneOutput.label} />
                      <span className="clone-ai-flow-node__tag">{selectedCloneOutput.label}</span>
                    </button>
                    <div className="clone-ai-flow-arrow" aria-hidden="true" />
                    <div className="clone-ai-flow-node clone-ai-flow-node--video">
                      <img src={results[0].src} alt="分镜视频" />
                      <span className="clone-ai-flow-node__tag clone-ai-flow-node__tag--accent">分镜视频</span>
                    </div>
                  </div>
                ) : null}
              </div>
            ) : (
              <div className="clone-ai-flow-branches clone-ai-flow-branches--empty">
                {[1, 2, 3].map((branchIndex) => (
                  <div
                    key={branchIndex}
                    className={`clone-ai-flow-branch${status === "generating" ? " is-generating" : ""}${status === "failed" ? " is-failed" : ""}`}
                  >
                    <div className="clone-ai-flow-node clone-ai-flow-node--text">
                      <div className="clone-ai-flow-node__text-content">
                        <span className="clone-ai-flow-node__text-title">分镜文本{branchIndex}</span>
                        <span className="clone-ai-flow-node__text-desc">
                          {status === "generating" ? "AI 解析中..." : "等待生成"}
                        </span>
                      </div>
                    </div>
                    <div className="clone-ai-flow-arrow" aria-hidden="true" />
                    <div className="clone-ai-flow-node clone-ai-flow-node--result">
                      <div className="clone-ai-flow-node__placeholder">
                        {status === "generating" ? <LoadingOutlined /> : <FileImageOutlined />}
                      </div>
                      <span className="clone-ai-flow-node__tag">分镜图{branchIndex}</span>
                    </div>
                    <div className="clone-ai-flow-arrow" aria-hidden="true" />
                    <div className="clone-ai-flow-node clone-ai-flow-node--video">
                      <div className="clone-ai-flow-node__placeholder">
                        {status === "generating" ? <LoadingOutlined /> : <FileImageOutlined />}
                      </div>
                      <span className="clone-ai-flow-node__tag">分镜视频{branchIndex}</span>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </section>

          {/* Status Overlay —生成状态覆盖层 */}
          {status === "generating" || status === "failed" ? (
            <section className="clone-ai-flow-status" aria-live="polite">
              {status === "generating" ? (
                <>
                  <LoadingOutlined style={{ fontSize: 28 }} />
                  <strong>正在生成</strong>
                  <EcommerceProgressBar status="generating" progress={generationProgress} onCancel={handleCancelGenerate} label={`${selectedCloneOutput.label}生成`} />
                  <span>AI 正在为 {platform} / {market} 整理{selectedCloneOutput.label}。</span>
                </>
              ) : status === "failed" ? (
                <>
                  <FrownOutlined style={{ fontSize: 28 }} />
                  <strong>生成失败</strong>
                  <span>请检查网络后点击下方重试</span>
                  {lastFailedActionRef.current ? (
                    <button type="button" className="clone-ai-retry-btn" onClick={lastFailedActionRef.current}>
                      <ReloadOutlined /> 重试
                    </button>
                  ) : null}
                </>
              ) : null}
            </section>
          ) : null}
        </>
        )
      ) : (
        <>
          {status === "done" || canvasNodes.length > 0 ? (
            <div className="clone-ai-preview-zoom-wrap" style={previewTransformStyle}>
            <section className="clone-ai-canvas-nodes" aria-label="生成结果">
              {canvasNodes.map((node) => (
                <article
                  key={node.id}
                  className="clone-ai-canvas-node"
                  data-mode={node.mode}
                  data-node-id={node.id}
                  style={{ transform: `translate(${node.x}px, ${node.y}px)` }}
                  onPointerDown={(event) => startCanvasNodeDrag(event, node)}
                  onPointerMove={(event) => moveCanvasNodeDrag(event, node.id)}
                  onPointerUp={(event) => stopCanvasNodeDrag(event, node.id)}
                  onPointerCancel={(event) => stopCanvasNodeDrag(event, node.id)}
                >
                  <div
                    className="clone-ai-node-drag-handle"
                    onPointerDown={(e) => {
                      if (e.button !== 0) return;
                      e.stopPropagation();
                      e.currentTarget.setPointerCapture(e.pointerId);
                      nodeDragRef.current = { active: true, nodeId: node.id, startX: e.clientX, startY: e.clientY, originX: node.x, originY: node.y };
                    }}
                    onPointerMove={(e) => {
                      const drag = nodeDragRef.current;
                      if (!drag.active || drag.nodeId !== node.id) return;
                      const zoom = previewZoomRef.current;
                      const dx = (e.clientX - drag.startX) / zoom;
                      const dy = (e.clientY - drag.startY) / zoom;
                      setCanvasNodes((prev) => prev.map((n) => n.id === node.id ? { ...n, x: drag.originX + dx, y: drag.originY + dy } : n));
                    }}
                    onPointerUp={(e) => {
                      if (nodeDragRef.current.nodeId === node.id) {
                        nodeDragRef.current = { ...nodeDragRef.current, active: false };
                        e.currentTarget.releasePointerCapture(e.pointerId);
                      }
                    }}
                  />
                  {node.sourceImage ? (
                    <div className="clone-ai-source-stack">
                      <button
                        type="button"
                        className="clone-ai-source-corner-action"
                        onClick={() => openProductSetPreview({ src: node.sourceImage!, label: "原图素材" })}
                      >
                        原图素材
                      </button>
                      <button
                        type="button"
                        className="clone-ai-main-result"
                        aria-label="预览原图素材"
                        onClick={() => openProductSetPreview({ src: node.sourceImage!, label: "原图素材" })}
                      >
                        <img src={node.sourceImage} alt="原图素材" />
                      </button>
                    </div>
                  ) : null}
                  <div className="clone-ai-flow-arrow" aria-hidden="true" />
                  <div className="clone-ai-result-stack">
                    <span className="clone-ai-node-label">{node.mode === "set" ? "套图" : node.mode === "detail" ? "详情图" : node.mode === "model" ? "模特图" : node.mode === "hot" ? "爆款图" : node.mode}</span>
                    <div className="clone-ai-result-grid result-reveal">
                      {node.results.map((card) => (
                        <button key={card.id} type="button" style={{ aspectRatio: parseRatioToAspectCss(ratio) }} onClick={() => openProductSetPreview(card, { nodeId: node.id, removable: true })}>
                          <img src={card.src} alt={card.label} />
                        </button>
                      ))}
                    </div>
                  </div>
                </article>
              ))}
              {status === "generating" ? (
                <article className="clone-ai-canvas-node is-generating" style={{ transform: `translate(${canvasNodes.length * 420}px, 0px)` }}>
                  <LoadingOutlined style={{ fontSize: 24 }} />
                  <span>正在生成{selectedCloneOutput.label}…</span>
                </article>
              ) : null}
            </section>
            </div>
          ) : status === "idle" || status === "ready" ? null : (
            <section className="clone-ai-empty-state" aria-live="polite">
              {status === "generating" ? <LoadingOutlined /> : status === "failed" ? <FrownOutlined /> : <FileImageOutlined />}
              <strong>{status === "generating" ? "正在生成" : status === "failed" ? "生成失败" : "等待生成"}</strong>
              {status === "generating" ? <EcommerceProgressBar status="generating" progress={generationProgress} onCancel={handleCancelGenerate} label={`${selectedCloneOutput.label}生成`} /> : null}
              <span>
                {status === "generating"
                  ? "AI 正在为 " + platform + " / " + market + " 整理" + selectedCloneOutput.label + "。"
                  : status === "failed"
                  ? "请检查网络后点击下方重试"
                  : "上传商品原图并填写信息后，AI 将在这里展示生成结果。"}
              </span>
              {status === "failed" && lastFailedActionRef.current ? (
                <button type="button" className="clone-ai-retry-btn" onClick={lastFailedActionRef.current}>
                  <ReloadOutlined /> 重试
                </button>
              ) : null}
            </section>
          )}
        </>
      )}

      <section
        ref={commandComposerWrapRef}
        className={`clone-ai-bottom-input ecom-command-composer-wrap${status === "done" || canvasNodes.length > 0 ? " has-generated" : " is-before-generate"}${isCommandComposerCompact && (status === "done" || canvasNodes.length > 0) ? " is-compact" : ""}`}
        aria-label="生成指令"
        onClick={() => {
          if (isCommandComposerCompact) setIsCommandComposerCompact(false);
        }}
      >
        <h1 className={`ecom-command-title${status === "done" || canvasNodes.length > 0 ? " is-after-generate" : ""}`}>
          {typewriterText}
          <span className="typewriter-cursor" aria-hidden="true">|</span>
        </h1>
        <input
          ref={cloneReferenceInputRef}
          type="file"
          accept="image/*"
          multiple
          className="ecom-command-hidden-file"
          onChange={handleCloneReferenceUpload}
          aria-label="上传参考图片"
        />
        <input
          ref={productInputRef}
          type="file"
          accept="image/*"
          multiple
          className="ecom-command-hidden-file"
          onChange={handleComposerAssetUpload}
          aria-label="上传商品素材"
        />
        <input
          ref={smartCutoutInputRef}
          type="file"
          accept="image/*"
          multiple
          className="ecom-command-hidden-file"
          onChange={handleSmartCutoutUpload}
          aria-label="上传智能抠图素材"
        />
        <div className="ecom-command-scenario-shell" data-expanded={isCommerceScenarioMoreOpen ? "true" : "false"}>
          <div className="ecom-command-mode-tabs ecom-command-scenario-tabs" aria-label="电商场景">
            {visibleCommerceScenarioOptions.map((option) => (
              <button
                key={option.key}
                type="button"
                className={`${activeCommerceScenario === option.key ? "is-active" : ""}${activeCommerceScenario === option.key && isCloneTemplateStripVisible ? " is-open" : ""}`}
                onClick={() => handleCommerceScenarioClick(option.key)}
              >
                <span className={`ecom-command-mode-icon ecom-command-mode-icon--${option.key}`} aria-hidden="true">{option.icon}</span>
                <strong>{option.label}</strong>
                {activeCommerceScenario === option.key && isCloneTemplateStripVisible ? (
                  <span className="ecom-command-scenario-close" aria-hidden="true"><CloseOutlined /></span>
                ) : null}
              </button>
            ))}
            <button
              type="button"
              className={`ecom-command-scenario-more${isCommerceScenarioMoreOpen ? " is-open" : ""}`}
              onClick={handleCommerceScenarioMoreToggle}
              aria-expanded={isCommerceScenarioMoreOpen}
            >
              <span className="ecom-command-mode-icon ecom-command-mode-icon--more" aria-hidden="true">
                {isCommerceScenarioMoreOpen ? <CloseOutlined /> : "···"}
              </span>
              <strong>{isCommerceScenarioMoreOpen ? "收起" : "更多"}</strong>
            </button>
          </div>
        </div>
        <span className="ecom-command-scenario-scroll-hint" aria-hidden="true">
          {isCommerceScenarioMoreOpen ? "左右滑动查看全部场景" : "点击更多查看全部场景"}
        </span>
        <div className="clone-ai-input-wrapper ecom-command-composer">
          {productImages.length ? (
            <div className="ecom-command-asset-popover" aria-label={`已上传素材，${productImages.length}/${maxCloneProductImages}张`}>
              <button
                type="button"
                className="ecom-command-asset-add"
                onClick={() => productImages.length < maxCloneProductImages && productInputRef.current?.click()}
                disabled={productImages.length >= maxCloneProductImages}
                aria-label={productImages.length >= maxCloneProductImages ? `最多上传${maxCloneProductImages}张素材` : "继续上传素材"}
                title={productImages.length >= maxCloneProductImages ? `最多上传 ${maxCloneProductImages} 张素材` : `继续上传素材 ${productImages.length}/${maxCloneProductImages}`}
              >
                <span aria-hidden="true">+</span>
                <small>{productImages.length >= maxCloneProductImages ? "已满" : "上传"}</small>
              </button>
              {productImages.map((image) => (
                <figure key={image.id} className="ecom-command-asset-thumb">
                  <img src={image.src} alt={image.name || "上传图片"} />
                  <button type="button" onClick={() => removeProductImage(image.id)} aria-label="删除图片">
                    <DeleteOutlined />
                  </button>
                </figure>
              ))}
            </div>
          ) : null}
          {shouldShowScenarioSettings ? (
            <div className="ecom-command-option-row ecom-command-option-row--settings">
              {activeCommerceScenario !== "salesVideo" && activeCommerceScenario !== "poster" ? (
                <button type="button" className={composerMenu === "platform" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("platform", event)}>
                  <span className="ecom-command-option-icon" aria-hidden="true"><GlobalOutlined /></span>
                  <span>平台</span>{platform}
                </button>
              ) : null}
              {activeCommerceScenario !== "salesVideo" ? (
                <button type="button" className={composerMenu === "language" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("language", event)}>
                  <span className="ecom-command-option-icon" aria-hidden="true"><FileImageOutlined /></span>
                  <span>语种</span>{language}
                </button>
              ) : null}
              <button type="button" className={composerMenu === "ratio" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("ratio", event)}>
                <span className="ecom-command-option-icon" aria-hidden="true"><TableOutlined /></span>
                <span>比例</span>{getRatioDisplayParts(ratio).aspect}
              </button>
              {scenarioAdvancedSettingsKeys.includes(activeCommerceScenario) ? (
                <button type="button" className={composerMenu === "settings" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("settings", event)}>
                  <span className="ecom-command-option-icon" aria-hidden="true"><SettingOutlined /></span>
                  <span>{activeCommerceScenario === "salesVideo" ? "时长" : "设置"}</span>
                  {activeCommerceScenario === "salesVideo" ? `${cloneVideoDuration}秒` : composerSettingLabel}
                </button>
              ) : null}
            </div>
          ) : null}
          <textarea
            ref={requirementTextareaRef}
            value={requirement}
            onChange={(event) => {
              const nextValue = event.target.value.slice(0, 500);
              setRequirement(nextValue);
              syncRequirementMentionQuery(nextValue, event.target.selectionStart);
            }}
            onClick={(event) => syncRequirementMentionQuery(requirement, event.currentTarget.selectionStart)}
            onKeyUp={(event) => syncRequirementMentionQuery(requirement, event.currentTarget.selectionStart)}
            onKeyDown={(event) => {
              if (event.key === "Escape") setRequirementImageMentionQuery(null);
            }}
            maxLength={500}
            placeholder={cloneRequirementPlaceholder}
          />
          {requirementImageMentionQuery !== null && ecommerceMentionImages.length ? (
            <ImageMentionMenu images={ecommerceMentionImages} query={requirementImageMentionQuery} onSelect={insertRequirementImageMention} />
          ) : null}
          <div className="ecom-command-toolbar" aria-label="生成设置">
            <div className="ecom-command-composer-actions">
              <button
                type="button"
                className={`ecom-command-reference ecom-command-reference--bottom ecom-command-tool ecom-command-tool--upload${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`}
                onClick={() => productInputRef.current?.click()}
                onDragEnter={(event) => {
                  event.preventDefault();
                  setIsProductUploadDragging(true);
                }}
                onDragOver={(event) => event.preventDefault()}
                onDragLeave={() => setIsProductUploadDragging(false)}
                onDrop={(event) => {
                  event.preventDefault();
                  setIsProductUploadDragging(false);
                  const files = Array.from(event.dataTransfer.files);
                  if (files.length) addComposerAssets(files);
                }}
                aria-label="上传素材"
                title="上传素材"
              >
                <span aria-hidden="true"><PaperClipOutlined /></span>
                <strong>上传素材</strong>
              </button>
              <button
                type="button"
                className={`ecom-command-tool ecom-command-tool--icon${composerMenu === "assetLibrary" ? " is-active" : ""}`}
                onClick={(event) => toggleComposerMenu("assetLibrary", event)}
                onMouseEnter={(event) => {
                  const rect = event.currentTarget.getBoundingClientRect();
                  setComposerTooltip({ text: "资产库", left: rect.left + rect.width / 2, top: rect.top - 8 });
                }}
                onMouseLeave={() => setComposerTooltip(null)}
                aria-label="资产库"
                title="资产库"
                data-tooltip="资产库"
              >
                <FolderOpenOutlined />
              </button>
              <button
                type="button"
                className={`ecom-command-tool ecom-command-tool--icon${composerMenu === "workMode" ? " is-active" : ""}`}
                onClick={(event) => toggleComposerMenu("workMode", event)}
                onMouseEnter={(event) => {
                  const rect = event.currentTarget.getBoundingClientRect();
                  setComposerTooltip({ text: `模式：${composerWorkMode === "quick" ? "快捷" : "思考"}`, left: rect.left + rect.width / 2, top: rect.top - 8 });
                }}
                onMouseLeave={() => setComposerTooltip(null)}
                aria-label={`模式：${composerWorkMode === "quick" ? "快捷" : "思考"}`}
                title={`模式：${composerWorkMode === "quick" ? "快捷" : "思考"}`}
                data-tooltip={`模式：${composerWorkMode === "quick" ? "快捷" : "思考"}`}
              >
                <ArrowsCounterClockwise size={17} weight="bold" />
              </button>
              <button
                type="button"
                className={`ecom-command-tool ecom-command-tool--icon${composerMenu === "aiWrite" ? " is-active" : ""}`}
                onClick={(event) => toggleComposerMenu("aiWrite", event)}
                onMouseEnter={(event) => {
                  const rect = event.currentTarget.getBoundingClientRect();
                  setComposerTooltip({ text: "AI帮写", left: rect.left + rect.width / 2, top: rect.top - 8 });
                }}
                onMouseLeave={() => setComposerTooltip(null)}
                aria-label="AI帮写"
                title="AI帮写"
                data-tooltip="AI帮写"
              >
                <MagicWand size={17} weight="bold" />
              </button>
            </div>
            <div className="ecom-command-submit-row">
              <button type="button" className="clone-ai-send-button ecom-command-send" disabled={commandGenerateDisabled} onClick={handleCommandGenerate} aria-label={clonePrimaryLabel}>
                {status === "generating" ? <LoadingOutlined /> : <PaperPlaneRight size={18} weight="fill" />}
              </button>
            </div>
          </div>
          {renderComposerMenu()}
          {composerTooltip ? createPortal(
            <span className="ecom-command-tooltip-floating" style={{ left: composerTooltip.left, top: composerTooltip.top }}>
              {composerTooltip.text}
            </span>,
            document.body
          ) : null}
        </div>
        {(status === "idle" || status === "ready") && !showMainVideoWorkspace && activeCommerceScenario !== null && isCloneTemplateStripVisible ? (
          <div className={`ecom-command-template-carousel ecom-command-template-carousel--${activeCommerceScenario}`}>
            <button
              type="button"
              className="ecom-command-template-nav ecom-command-template-nav--prev"
              onClick={() => scrollCommerceTemplateStrip(-1)}
              aria-label="查看上一组模板"
            >
              ‹
            </button>
            <section
              ref={templateStripRef}
              className={`ecom-command-template-strip ecom-command-template-strip--${activeCommerceScenario}`}
              aria-label="模板卡片"
            >
              {activeCommerceScenarioTemplates.map((card) => (
                <button
                  key={card.id}
                  type="button"
                  className="ecom-command-template-card"
                  aria-label={card.title}
                  onClick={(event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    handleCloneTemplateCardClick(card);
                  }}
                >
                  <span className="ecom-command-template-card__media" aria-hidden="true">
                    <img src={card.mediaUrl} alt="" loading="lazy" />
                  </span>
                  <span className="ecom-command-template-card__body">
                    <span className="ecom-command-template-card__badge">{card.badge}</span>
                    <strong>{card.title}</strong>
                    <em>{card.desc}</em>
                  </span>
                </button>
              ))}
            </section>
            <button
              type="button"
              className="ecom-command-template-nav ecom-command-template-nav--next"
              onClick={() => scrollCommerceTemplateStrip(1)}
              aria-label="查看下一组模板"
            >
              ›
            </button>
          </div>
        ) : null}
        {(status === "idle" || status === "ready") && !showMainVideoWorkspace ? (
          <section className="ecom-command-quick-board" aria-label="快捷功能">
            {[
              { label: "A+/详情页", tone: "detail", icon: <LayoutOutlined />, onClick: openQuickDetailPage },
              { label: "爆款复刻", tone: "hot", icon: <FireOutlined />, onClick: openHotClonePage },
              { label: "图片修改", tone: "edit", icon: <EditOutlined />, onClick: openImageWorkbenchPage },
              { label: "智能抠图", tone: "cutout", icon: <ScissorOutlined />, onClick: openSmartCutoutUpload },
              { label: "去除水印", tone: "watermark", icon: <ClearOutlined />, onClick: openWatermarkRemovalPage },
              { label: "图片翻译", tone: "translate", icon: <GlobalOutlined />, onClick: openImageTranslatePage },
              { label: "商品套图", tone: "product", icon: <AppstoreOutlined />, onClick: openQuickSetPage },
              { label: "一键文案", tone: "copywriting", icon: <EditOutlined />, onClick: openCopywritingPage },
              { label: "更多功能", tone: "more", icon: <SettingOutlined />, disabled: true },
            ].map((item) => (
              <button
                key={item.label}
                type="button"
                className={`ecom-command-quick-card ecom-command-quick-card--${item.tone}`}
                disabled={item.disabled}
                onClick={(event) => {
                  event.preventDefault();
                  event.stopPropagation();
                  if (item.disabled) {
                    toast.info("更多功能即将上线，敬请期待！");
                  } else {
                    item.onClick?.();
                  }
                }}
              >
                <span aria-hidden="true">{item.icon}</span>
                <strong>{item.label}</strong>
              </button>
            ))}
          </section>
        ) : null}
        {(status === "idle" || status === "ready") && !showMainVideoWorkspace ? (
          <section className="ecom-inspiration-lab" aria-label="电商灵感案例">
            <div className="ecom-inspiration-rows">
              {ecommerceInspirationRows.map((row) => (
                <section key={row.title} className={`ecom-inspiration-row ecom-inspiration-row--${row.variant}`} aria-label={row.title}>
                  <div className="ecom-inspiration-row__meta">
                    <strong>{row.title}</strong>
                    <span>{row.desc}</span>
                    <div className="ecom-inspiration-row__controls">
                      <button type="button" aria-label={`向左浏览${row.title}`} onClick={(event) => scrollInspirationRow(event, -1)}>‹</button>
                      <button type="button" aria-label={`向右浏览${row.title}`} onClick={(event) => scrollInspirationRow(event, 1)}>›</button>
                    </div>
                  </div>
                  <div className="ecom-inspiration-strip" tabIndex={0}>
                    {row.cards.map((card, index) => (
                      <article key={card.title} className="ecom-inspiration-card" onClick={() => setInspirationPreview({ mediaUrl: card.mediaUrl, mediaType: card.mediaType, prompt: buildInspirationPrompt(card.title, card.meta) })}>
                        <div className="ecom-inspiration-card__visual" aria-hidden="true">
                          {card.mediaType === "video" ? (
                            <video src={card.mediaUrl} muted playsInline loop autoPlay preload="metadata" />
                          ) : (
                            <img src={card.mediaUrl} alt="" loading="lazy" />
                          )}
                        </div>
                        <strong>{card.title}</strong>
                        <p>{card.meta}</p>
                        <em>{String(index + 1).padStart(2, "0")}</em>
                      </article>
                    ))}
                  </div>
                </section>
              ))}
            </div>
          </section>
        ) : null}
      </section>
      {inspirationPreviewOverlay}
    </main>
  );

  const smartCutoutPreview = (
    <main key={`smart-cutout-${smartCutoutImage ? "editor" : "upload"}`} className={`ecom-smart-cutout-page ecom-tool-page-enter${smartCutoutImage ? " is-editor" : " is-upload"}${isSmartCutoutTransitioning ? " is-transitioning" : ""}`} aria-label="智能抠图">
      <input
        ref={smartCutoutInputRef}
        type="file"
        accept="image/*"
        multiple
        className="ecom-command-hidden-file"
        onChange={handleSmartCutoutUpload}
        aria-label="上传智能抠图素材"
      />
      <nav className="ecom-smart-cutout-nav" aria-label="智能抠图导航">
        <button type="button" onClick={closeSmartCutoutTool}>
          首页
        </button>
        <button type="button" onClick={goSmartCutoutPrevious}>
          上一页
        </button>
      </nav>
      {isSmartCutoutTransitioning ? (
        <div className="ecom-smart-cutout-transition" role="status" aria-live="polite">
          <span aria-hidden="true" />
          <strong>{smartCutoutTransitionMessage.title}</strong>
          <em>{smartCutoutTransitionMessage.subtitle}</em>
        </div>
      ) : null}
      {!smartCutoutImage ? (
        <section className="ecom-smart-cutout-upload">
          <div className="ecom-smart-cutout-head">
            <strong>智能抠图</strong>
            <span>3s 一键抠图，快速去除背景</span>
          </div>
          <div className="ecom-smart-cutout-upload__body">
            <div className="ecom-smart-cutout-demo" aria-hidden="true">
              <div className="ecom-smart-cutout-demo__tile ecom-smart-cutout-demo__tile--flower" />
              <div className="ecom-smart-cutout-demo__tile ecom-smart-cutout-demo__tile--product" />
              <div className="ecom-smart-cutout-demo__tile ecom-smart-cutout-demo__tile--poster" />
              <div className="ecom-smart-cutout-demo__tile ecom-smart-cutout-demo__tile--object" />
            </div>
            <div
              className={`ecom-smart-cutout-upload-box${isSmartCutoutDragging ? " is-dragging" : ""}`}
              role="button"
              tabIndex={0}
              onClick={() => smartCutoutInputRef.current?.click()}
              onKeyDown={(event) => {
                if (event.key === "Enter" || event.key === " ") {
                  event.preventDefault();
                  smartCutoutInputRef.current?.click();
                }
              }}
              onDragEnter={(event) => {
                event.preventDefault();
                setIsSmartCutoutDragging(true);
              }}
              onDragOver={(event) => event.preventDefault()}
              onDragLeave={() => setIsSmartCutoutDragging(false)}
              onDrop={handleSmartCutoutDrop}
            >
              <button type="button" className="ecom-smart-cutout-upload__primary">
                <CloudUploadOutlined />
                上传图片
              </button>
              <button type="button" className="ecom-smart-cutout-upload__secondary">
                <FolderOpenOutlined />
                批量上传
              </button>
              <span>上传文件夹 &gt;</span>
            </div>
          </div>
        </section>
      ) : (
        <section className="ecom-smart-editor">
          <div className="ecom-smart-editor__workspace">
            <div className="ecom-smart-editor__canvas">
              <div className={`ecom-smart-editor__checker is-size-${previewSmartCutoutSizeKey}`} style={smartCutoutFrameStyle}>
                <div className="ecom-smart-editor__background-layer" aria-hidden="true" />
                <img src={previewSmartCutoutImageSrc ?? smartCutoutImage.src} alt={smartCutoutImage.name} />
              </div>
              <div className="ecom-smart-editor__canvas-actions">
                <button
                  type="button"
                  onPointerDown={showSmartCutoutOriginalCompare}
                  onPointerUp={hideSmartCutoutOriginalCompare}
                  onPointerCancel={hideSmartCutoutOriginalCompare}
                  onBlur={hideSmartCutoutOriginalCompare}
                  onKeyDown={(event) => {
                    if (event.key === " " || event.key === "Enter") setIsSmartCutoutComparing(true);
                  }}
                  onKeyUp={hideSmartCutoutOriginalCompare}
                >
                  对比
                </button>
              </div>
            </div>
            <div className="ecom-smart-editor__tools-shell">
              <strong className="ecom-smart-editor__tools-title">功能</strong>
              <button type="button" className="ecom-smart-editor__tools-nav" onClick={() => scrollSmartCutoutTools(-1)} aria-label="查看上一组尺寸">
                ‹
              </button>
              <div className="ecom-smart-editor__tools" ref={smartCutoutToolsRef}>
                {smartCutoutSizeOptions.map((item) => (
                  <div className="ecom-smart-editor__tool-item" key={item.key}>
                    <button
                      type="button"
                      className={smartCutoutSizeKey === item.key ? "is-active" : ""}
                      onClick={() => setSmartCutoutSizeKey(item.key)}
                      aria-label={`${item.label}${"sizeLabel" in item ? ` ${item.sizeLabel}` : ""}`}
                      aria-pressed={smartCutoutSizeKey === item.key}
                    >
                      <span className={`ecom-smart-editor__tool-icon ecom-smart-editor__tool-icon--${item.icon}`} aria-hidden="true" />
                    </button>
                    <span className="ecom-smart-editor__tool-text">
                      <span>{item.label}</span>
                      {"sizeLabel" in item ? <span>{item.sizeLabel}</span> : null}
                    </span>
                  </div>
                ))}
              </div>
              <button type="button" className="ecom-smart-editor__tools-nav" onClick={() => scrollSmartCutoutTools(1)} aria-label="查看更多尺寸">
                ›
              </button>
            </div>
            {smartCutoutBatchImages.length > 1 ? (
              <section className="ecom-smart-editor__batch" aria-label="批量图片">
                <header>
                  <strong>批量图片</strong>
                  <span>{smartCutoutBatchImages.length} 张</span>
                </header>
                <div>
                  {smartCutoutBatchImages.map((image, index) => (
                    <button
                      key={`${image.src}-${index}`}
                      type="button"
                      className={smartCutoutImage.src === image.src ? "is-active" : ""}
                      onClick={() => setSmartCutoutImage(image)}
                    >
                      <img src={image.src} alt={image.name || `上传图片 ${index + 1}`} />
                      <span>{index + 1}</span>
                    </button>
                  ))}
                </div>
              </section>
            ) : null}
            <section className="ecom-smart-editor__gallery">
              <header><strong>纯色</strong><button type="button">查看全部 &gt;</button></header>
              <div className="ecom-smart-editor__swatches">
                {["#ffffff", "#eeeae3", "#f2e3cf", "#000000", "#a89682", "#c9c9c9"].map((color) => (
                  <button
                    key={color}
                    type="button"
                    className={smartCutoutBackgroundColor.toLowerCase() === color ? "is-active" : ""}
                    style={{ "--smart-cutout-swatch-bg": color } as CSSProperties}
                    onClick={() => {
                      setSmartCutoutBackgroundColor(color);
                      setSmartCutoutBackgroundAlpha(100);
                    }}
                  >
                    <span className="ecom-smart-editor__swatch-bg" aria-hidden="true" />
                    <img src={smartCutoutImage.src} alt="" />
                  </button>
                ))}
              </div>
              <header><strong>AI换背景</strong><button type="button">查看全部 &gt;</button></header>
              <div className="ecom-smart-editor__scenes">
                <button type="button" className="ecom-smart-editor__generate"><SettingOutlined />立即生成</button>
                {["客厅陈列", "桌面日光", "香氛产品", "绿植窗边", "居家空间"].map((item) => (
                  <button key={item} type="button"><span>{item}</span></button>
                ))}
              </div>
            </section>
          </div>
          <aside className="ecom-smart-editor__side">
            <strong>背景颜色</strong>
            <div className="ecom-smart-editor__color-row">
              <div className="ecom-smart-editor__color-wrap" ref={smartCutoutPaletteRef} style={{ "--smart-cutout-bg": smartCutoutBackgroundValue } as CSSProperties}>
                <button
                  type="button"
                  className={`ecom-smart-editor__custom-color${isSmartCutoutPaletteOpen ? " is-active" : ""}`}
                  style={{ background: smartCutoutBackgroundColor }}
                  onClick={() => setIsSmartCutoutPaletteOpen((open) => !open)}
                  aria-label="打开背景调色盘"
                >
                  <span>色</span>
                </button>
                {isSmartCutoutPaletteOpen ? (
                  <div className="ecom-smart-color-picker" role="dialog" aria-label="背景调色盘">
                    <button
                      type="button"
                      className="ecom-smart-color-picker__plane"
                      style={{ background: `linear-gradient(to top, #000000, transparent), linear-gradient(to right, #ffffff, hsl(${smartCutoutColorHsv.h} 100% 50%))` }}
                      onPointerDown={handleSmartCutoutColorPlanePointer}
                      onPointerMove={handleSmartCutoutColorPlaneMove}
                      aria-label="选择颜色明度和饱和度"
                    >
                      <span style={{ left: `${smartCutoutColorHsv.s}%`, top: `${100 - smartCutoutColorHsv.v}%` }} />
                    </button>
                    <div className="ecom-smart-color-picker__slider ecom-smart-color-picker__slider--hue">
                      <span style={{ background: `hsl(${smartCutoutColorHsv.h} 100% 50%)` }} />
                      <input
                        type="range"
                        min={0}
                        max={360}
                        value={smartCutoutColorHsv.h}
                        onChange={(event) => applySmartCutoutHsv(Number(event.target.value), smartCutoutColorHsv.s, smartCutoutColorHsv.v)}
                        aria-label="色相"
                      />
                    </div>
                    <div className="ecom-smart-color-picker__slider ecom-smart-color-picker__slider--alpha">
                      <span style={{ background: smartCutoutBackgroundValue }} />
                      <input
                        type="range"
                        min={0}
                        max={100}
                        value={smartCutoutBackgroundAlpha}
                        onChange={(event) => setSmartCutoutBackgroundAlpha(Number(event.target.value))}
                        aria-label="透明度"
                      />
                    </div>
                    <div className="ecom-smart-color-picker__fields">
                      <span aria-hidden="true">↙</span>
                      <input
                        value={smartCutoutHexDraft}
                        onChange={(event) => handleSmartCutoutHexChange(event.target.value)}
                        onBlur={() => setSmartCutoutHexDraft(smartCutoutBackgroundColor)}
                        aria-label="HEX 色值"
                      />
                      <input
                        value={Math.round(smartCutoutBackgroundAlpha)}
                        onChange={(event) => setSmartCutoutBackgroundAlpha(clampNumber(Number(event.target.value) || 0, 0, 100))}
                        aria-label="透明度百分比"
                      />
                      <strong>%</strong>
                    </div>
                    <p>纯色预设</p>
                    <div className="ecom-smart-color-picker__presets">
                      {smartCutoutColorPresets.map((color) => (
                        <button
                          key={color}
                          type="button"
                          className={smartCutoutBackgroundColor.toLowerCase() === color ? "is-active" : ""}
                          style={{ background: color }}
                          onClick={() => {
                            setSmartCutoutBackgroundColor(color);
                            setSmartCutoutBackgroundAlpha(100);
                          }}
                          aria-label={`选择颜色 ${color}`}
                        />
                      ))}
                    </div>
                  </div>
                ) : null}
              </div>
              {["#ffffff", "#f8f9fa", "#000000", "#bdbdbd"].map((color) => (
                <button
                  key={color}
                  type="button"
                  className={smartCutoutBackgroundColor.toLowerCase() === color ? "is-active" : ""}
                  style={{ background: color }}
                  onClick={() => {
                    setSmartCutoutBackgroundColor(color);
                    setSmartCutoutBackgroundAlpha(100);
                  }}
                  aria-label={`背景颜色 ${color}`}
                />
              ))}
            </div>
            <div className="ecom-smart-editor__side-actions">
              <button type="button" className="ecom-smart-editor__download" onClick={handleSmartCutoutDownload}>下载图片</button>
              <button type="button" onClick={() => smartCutoutInputRef.current?.click()}>再次上传</button>
            </div>
          </aside>
        </section>
      )}
    </main>
  );

  const imageWorkbenchPreview = (
    <main key="image-workbench" className="ecom-image-workbench-page ecom-tool-page-enter" aria-label="图片修改局部重绘">
      <input
        ref={imageWorkbenchInputRef}
        type="file"
        accept="image/*"
        className="ecom-command-hidden-file"
        onChange={handleImageWorkbenchUpload}
        aria-label="上传图片"
      />
      <aside className="ecom-image-workbench-side">
        <header className="ecom-quick-set-panel-head ecom-image-workbench-panel-head">
          <strong className="ecom-quick-set-page-title">图片修改</strong>
          <button type="button" className="ecom-quick-set-back" onClick={closeImageWorkbenchPage}>首页</button>
          <button type="button" className="ecom-quick-set-back" onClick={closeImageWorkbenchPage}>上一页</button>
        </header>
        <p className="ecom-image-workbench-intro">涂抹需要修改的区域，输入提示词后让 AI 替换局部内容。</p>

        <section className="ecom-image-workbench-panel">
          <header>
            <strong>素材</strong>
            <span>{imageWorkbenchImage ? "已上传" : "待上传"}</span>
          </header>
          <div
            className={`ecom-image-workbench-upload${isImageWorkbenchDragging ? " is-dragging" : ""}${imageWorkbenchImage ? " has-image" : ""}`}
            role="button"
            tabIndex={0}
            onClick={() => imageWorkbenchInputRef.current?.click()}
            onKeyDown={(event) => {
              if (event.key === "Enter" || event.key === " ") {
                event.preventDefault();
                imageWorkbenchInputRef.current?.click();
              }
            }}
            onDragEnter={(event) => {
              event.preventDefault();
              setIsImageWorkbenchDragging(true);
            }}
            onDragOver={(event) => event.preventDefault()}
            onDragLeave={() => setIsImageWorkbenchDragging(false)}
            onDrop={handleImageWorkbenchDrop}
          >
            {imageWorkbenchImage ? (
              <>
                <button
                  type="button"
                  className="ecom-image-workbench-remove"
                  onClick={(event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    removeImageWorkbenchImage();
                  }}
                  aria-label="删除素材"
                >
                  ×
                </button>
                <figure>
                  <img src={imageWorkbenchImage.src} alt={imageWorkbenchImage.name} />
                </figure>
                <div>
                  <strong>{imageWorkbenchImage.name}</strong>
                  <span>{imageWorkbenchImage.format || "PNG / JPG / WebP"}</span>
                </div>
              </>
            ) : (
              <>
                <FileImageOutlined />
                <strong>选择图片</strong>
                <span>PNG / JPG / WebP</span>
              </>
            )}
          </div>
          <div className="ecom-image-workbench-url-row">
            <input
              ref={imageWorkbenchUrlInputRef}
              placeholder="粘贴图片 URL"
              aria-label="粘贴图片 URL"
              onKeyDown={(event) => {
                if (event.key === "Enter") void handleImageWorkbenchUrlImport();
              }}
            />
            <button type="button" onClick={() => void handleImageWorkbenchUrlImport()}>添加</button>
          </div>
        </section>

        <section className="ecom-image-workbench-panel">
          <header>
            <strong>笔刷</strong>
            <span>{imageWorkbenchMaskStrokes.length ? `已遮罩 ${imageWorkbenchMaskStrokes.length}` : "未遮罩"}</span>
          </header>
          <label className="ecom-image-workbench-slider">
            <span>大小</span>
            <input
              type="range"
              min={10}
              max={120}
              value={imageWorkbenchBrushSize}
              onChange={(event) => setImageWorkbenchBrushSize(Number(event.target.value))}
            />
            <em>{imageWorkbenchBrushSize}px</em>
          </label>
          <button
            type="button"
            className="ecom-image-workbench-clear"
            onClick={() => {
              setImageWorkbenchMaskStrokes([]);
              setImageWorkbenchBrushCursor(null);
              clearImageWorkbenchMaskCanvas();
              setImageWorkbenchStatus("idle");
              setImageWorkbenchResultUrl(null);
            }}
            disabled={!imageWorkbenchMaskStrokes.length}
          >
            清空遮罩
          </button>
        </section>

        <section className="ecom-image-workbench-panel">
          <strong>参数</strong>
          <div className="ecom-image-workbench-ratios" aria-label="输出分辨率">
            {["9:16", "16:9", "4:3", "3:4", "1:1"].map((item) => (
              <button
                key={item}
                type="button"
                className={imageWorkbenchRatio === item ? "is-active" : ""}
                onClick={() => setImageWorkbenchRatio(item)}
              >
                {item}
              </button>
            ))}
          </div>
        </section>

        <section className="ecom-image-workbench-panel">
          <strong>提示词</strong>
          <textarea
            value={imageWorkbenchPrompt}
            onChange={(event) => setImageWorkbenchPrompt(event.target.value.slice(0, 300))}
            placeholder="描述需要重绘的内容，例如：将背景替换为森林"
          />
        </section>

        <button
          type="button"
          className="ecom-image-workbench-primary"
          onClick={handleImageWorkbenchGenerate}
          disabled={!imageWorkbenchImage || imageWorkbenchStatus === "processing"}
        >
          {imageWorkbenchStatus === "processing" ? <LoadingOutlined /> : <SettingOutlined />}
          {imageWorkbenchStatus === "processing" ? "重绘中" : "开始重绘"}
        </button>
      </aside>

      <section className="ecom-image-workbench-stage">
        {!imageWorkbenchImage ? (
          <div
            className={`ecom-watermark-dropzone${isImageWorkbenchDragging ? " is-dragging" : ""}`}
            role="button"
            tabIndex={0}
            onClick={() => imageWorkbenchInputRef.current?.click()}
            onKeyDown={(event) => {
              if (event.key === "Enter" || event.key === " ") {
                event.preventDefault();
                imageWorkbenchInputRef.current?.click();
              }
            }}
            onDragEnter={(event) => {
              event.preventDefault();
              setIsImageWorkbenchDragging(true);
            }}
            onDragOver={(event) => event.preventDefault()}
            onDragLeave={() => setIsImageWorkbenchDragging(false)}
            onDrop={handleImageWorkbenchDrop}
          >
            <CloudUploadOutlined />
            <strong>点击或拖拽上传图片</strong>
            <span>支持 PNG / JPG / WebP，上传后使用画笔标记需要重绘的区域</span>
          </div>
        ) : (
          <div className="ecom-watermark-grid">
            <article className="ecom-watermark-preview-card">
              <span>原图 / 涂抹区域</span>
              <div
                className="ecom-image-workbench-image-frame"
                onPointerDown={handleImageWorkbenchMaskPointerDown}
                onPointerMove={handleImageWorkbenchMaskPointerMove}
                onPointerUp={stopImageWorkbenchMaskPainting}
                onPointerCancel={stopImageWorkbenchMaskPainting}
                onPointerLeave={() => {
                  stopImageWorkbenchMaskPainting();
                  setImageWorkbenchBrushCursor(null);
                }}
              >
                <img src={imageWorkbenchImage.src} alt="局部重绘素材" draggable={false} />
                <canvas ref={imageWorkbenchMaskCanvasRef} className="ecom-image-workbench-mask-layer" aria-label="遮罩区域" />
                <span
                  className="ecom-image-workbench-brush"
                  style={{
                    left: `${imageWorkbenchBrushCursor?.x ?? 50}%`,
                    top: `${imageWorkbenchBrushCursor?.y ?? 50}%`,
                    width: imageWorkbenchBrushSize,
                    height: imageWorkbenchBrushSize,
                  }}
                />
              </div>
            </article>

            <article className="ecom-watermark-preview-card">
              <span>重绘结果</span>
              {imageWorkbenchStatus === "processing" ? (
                <div className="ecom-watermark-processing" role="status" aria-live="polite">
                  <LoadingOutlined />
                  <strong>正在重绘</strong>
                  <em>AI 正在根据遮罩和提示词生成局部重绘结果</em>
                  <div className="ecom-quick-set-progress">
                    <div className="ecom-quick-set-progress-bar" style={{ width: `${Math.round(imageWorkbenchProgress)}%` }} />
                  </div>
                  <em className="ecom-quick-set-progress-text">{Math.round(imageWorkbenchProgress)}%</em>
                </div>
              ) : imageWorkbenchStatus === "done" && imageWorkbenchResultUrl ? (
                <>
                  <img src={imageWorkbenchResultUrl} alt="重绘结果" />
                </>
              ) : imageWorkbenchStatus === "failed" ? (
                <div className="ecom-watermark-empty">
                  <FrownOutlined />
                  <strong>重绘失败</strong>
                  <em>请检查网络或重试，如余额不足请先充值</em>
                </div>
              ) : (
                <div className="ecom-watermark-empty">
                  <FileImageOutlined />
                  <strong>等待处理</strong>
                  <em>涂抹需要修改的区域后点击开始重绘</em>
                </div>
              )}
              <div className="ecom-watermark-actions">
                <button type="button" onClick={() => toast.success("已加入资产库")} disabled={imageWorkbenchStatus !== "done"}>
                  <FolderOpenOutlined />
                  加入资产库
                </button>
                <button type="button" onClick={() => {
                  if (!imageWorkbenchResultUrl) return;
                  const link = document.createElement("a");
                  link.href = imageWorkbenchResultUrl;
                  link.download = `inpaint-result-${Date.now()}.png`;
                  document.body.appendChild(link);
                  link.click();
                  link.remove();
                }} disabled={imageWorkbenchStatus !== "done"}>
                  <CloudUploadOutlined />
                  下载图片
                </button>
              </div>
            </article>
          </div>
        )}
      </section>
    </main>
  );

  const watermarkPreview = (
    <main key="watermark" className="ecom-watermark-page ecom-tool-page-enter" aria-label="去水印">
      <input
        ref={watermarkInputRef}
        type="file"
        accept="image/*"
        className="ecom-command-hidden-file"
        onChange={handleWatermarkUpload}
        aria-label="上传去水印图片"
      />
      <aside className="ecom-watermark-side">
        <header className="ecom-quick-set-panel-head ecom-watermark-panel-head">
          <strong className="ecom-quick-set-page-title">去水印</strong>
          <button type="button" className="ecom-quick-set-back" onClick={closeWatermarkRemovalPage}>首页</button>
          <button type="button" className="ecom-quick-set-back" onClick={closeWatermarkRemovalPage}>上一页</button>
        </header>
        <p className="ecom-watermark-intro">上传商品素材，快速清理画面中的水印、文字和瑕疵。</p>
        <section className="ecom-watermark-panel">
          <header>
            <strong>上传素材</strong>
            <span>{watermarkImage ? "已上传" : "待上传"}</span>
          </header>
          <div
            className={`ecom-watermark-upload-card${isWatermarkDragging ? " is-dragging" : ""}${watermarkImage ? " has-image" : ""}`}
            role="button"
            tabIndex={0}
            onClick={() => watermarkInputRef.current?.click()}
            onKeyDown={(event) => {
              if (event.key === "Enter" || event.key === " ") {
                event.preventDefault();
                watermarkInputRef.current?.click();
              }
            }}
            onDragEnter={(event) => {
              event.preventDefault();
              setIsWatermarkDragging(true);
            }}
            onDragOver={(event) => event.preventDefault()}
            onDragLeave={() => setIsWatermarkDragging(false)}
            onDrop={handleWatermarkDrop}
          >
            {watermarkImage ? (
              <>
                <button
                  type="button"
                  className="ecom-watermark-remove"
                  onClick={(event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    removeWatermarkImage();
                  }}
                  aria-label="删除素材"
                >
                  ×
                </button>
                <figure>
                  <img src={watermarkImage.src} alt={watermarkImage.name} />
                </figure>
                <div>
                  <strong>{watermarkImage.name}</strong>
                  <span>{watermarkImage.format || "PNG / JPG / WebP"}</span>
                </div>
              </>
            ) : (
              <>
                <CloudUploadOutlined />
                <strong>上传含水印图片</strong>
                <span>支持 PNG / JPG / WebP，拖拽或点击上传</span>
              </>
            )}
          </div>
          <div className="ecom-watermark-url-row">
            <input
              ref={watermarkUrlInputRef}
              placeholder="粘贴图片 URL"
              aria-label="粘贴图片 URL"
              onKeyDown={(event) => {
                if (event.key === "Enter") void handleWatermarkUrlImport();
              }}
            />
            <button type="button" onClick={() => void handleWatermarkUrlImport()}>导入</button>
          </div>
        </section>

        <section className="ecom-watermark-panel">
          <strong>处理说明</strong>
          <p>优先保留商品主体、材质和边缘细节，适合电商主图、详情图和社媒素材清理。</p>
        </section>

        <button
          type="button"
          className="ecom-watermark-primary"
          onClick={handleWatermarkGenerate}
          disabled={!watermarkImage || watermarkStatus === "processing"}
        >
          {watermarkStatus === "processing" ? <LoadingOutlined /> : <FileImageOutlined />}
          {watermarkStatus === "processing" ? "处理中" : "开始去水印"}
        </button>
      </aside>

      <section className="ecom-watermark-workspace">
        {!watermarkImage ? (
          <div
            className={`ecom-watermark-dropzone${isWatermarkDragging ? " is-dragging" : ""}`}
            role="button"
            tabIndex={0}
            onClick={() => watermarkInputRef.current?.click()}
            onKeyDown={(event) => {
              if (event.key === "Enter" || event.key === " ") {
                event.preventDefault();
                watermarkInputRef.current?.click();
              }
            }}
            onDragEnter={(event) => {
              event.preventDefault();
              setIsWatermarkDragging(true);
            }}
            onDragOver={(event) => event.preventDefault()}
            onDragLeave={() => setIsWatermarkDragging(false)}
            onDrop={handleWatermarkDrop}
          >
            <CloudUploadOutlined />
            <strong>点击或拖拽上传图片</strong>
            <span>支持 PNG / JPG / WebP，上传含水印图片后点击开始去水印</span>
          </div>
        ) : (
          <div className="ecom-watermark-grid">
            <article className="ecom-watermark-preview-card">
              <span>原图</span>
              <img src={watermarkImage.src} alt="原图" />
            </article>

            <article className="ecom-watermark-preview-card">
              <span>去水印结果</span>
              {watermarkStatus === "processing" ? (
                <div className="ecom-watermark-processing" role="status" aria-live="polite">
                  <LoadingOutlined />
                  <strong>正在去水印</strong>
                  <em>AI 正在清理图片中的水印和文字</em>
                  <div className="ecom-quick-set-progress">
                    <div className="ecom-quick-set-progress-bar" style={{ width: `${Math.round(watermarkProgress)}%` }} />
                  </div>
                  <em className="ecom-quick-set-progress-text">{Math.round(watermarkProgress)}%</em>
                </div>
              ) : watermarkStatus === "done" && watermarkResultUrl ? (
                <>
                  <img src={watermarkResultUrl} alt="去水印结果" />
                  <button type="button" className="ecom-watermark-zoom" aria-label="查看大图">
                    <QuestionCircleOutlined />
                  </button>
                </>
              ) : watermarkStatus === "failed" ? (
                <div className="ecom-watermark-empty">
                  <FrownOutlined />
                  <strong>去水印失败</strong>
                  <em>请检查网络或重试，如余额不足请先充值</em>
                </div>
              ) : (
                <div className="ecom-watermark-empty">
                  <FileImageOutlined />
                  <strong>等待处理</strong>
                  <em>点击开始去水印后显示结果</em>
                </div>
              )}
              <div className="ecom-watermark-actions">
                <button type="button" onClick={() => toast.success("已加入资产库")} disabled={watermarkStatus !== "done"}>
                  <FolderOpenOutlined />
                  加入资产库
                </button>
                <button type="button" onClick={handleWatermarkDownload} disabled={watermarkStatus !== "done"}>
                  <CloudUploadOutlined />
                  下载图片
                </button>
              </div>
            </article>
          </div>
        )}
      </section>
    </main>
  );

  const translateLanguageOptions = [
    { value: "zh", label: "中文" },
    { value: "en", label: "English" },
    { value: "ja", label: "日本語" },
    { value: "ko", label: "한국어" },
    { value: "fr", label: "Français" },
    { value: "de", label: "Deutsch" },
    { value: "es", label: "Español" },
    { value: "pt", label: "Português" },
    { value: "ru", label: "Русский" },
    { value: "ar", label: "العربية" },
  ];

  const translatePreview = (
    <main key="translate" className="ecom-watermark-page ecom-translate-page ecom-tool-page-enter" aria-label="图片翻译">
      <input
        ref={translateInputRef}
        type="file"
        accept="image/*"
        className="ecom-command-hidden-file"
        onChange={handleTranslateUpload}
        aria-label="上传翻译图片"
      />
      <aside className="ecom-watermark-side">
        <header className="ecom-quick-set-panel-head ecom-watermark-panel-head">
          <strong className="ecom-quick-set-page-title">图片翻译</strong>
          <button type="button" className="ecom-quick-set-back" onClick={closeImageTranslatePage}>首页</button>
          <button type="button" className="ecom-quick-set-back" onClick={closeImageTranslatePage}>上一页</button>
        </header>
        <p className="ecom-watermark-intro">上传含文字的图片，AI 自动识别并翻译为目标语言。</p>

        <section className="ecom-watermark-panel ecom-translate-lang-panel">
          <header>
            <strong>目标语言</strong>
          </header>
          <select
            className="ecom-translate-lang-select"
            value={translateLanguage}
            onChange={(event) => setTranslateLanguage(event.target.value)}
            aria-label="选择目标语言"
          >
            {translateLanguageOptions.map((option) => (
              <option key={option.value} value={option.value}>{option.label}</option>
            ))}
          </select>
        </section>

        <section className="ecom-watermark-panel">
          <header>
            <strong>上传素材</strong>
            <span>{translateImage ? "已上传" : "待上传"}</span>
          </header>
          <div
            className={`ecom-watermark-upload-card${isTranslateDragging ? " is-dragging" : ""}${translateImage ? " has-image" : ""}`}
            role="button"
            tabIndex={0}
            onClick={() => translateInputRef.current?.click()}
            onKeyDown={(event) => {
              if (event.key === "Enter" || event.key === " ") {
                event.preventDefault();
                translateInputRef.current?.click();
              }
            }}
            onDragEnter={(event) => {
              event.preventDefault();
              setIsTranslateDragging(true);
            }}
            onDragOver={(event) => event.preventDefault()}
            onDragLeave={() => setIsTranslateDragging(false)}
            onDrop={handleTranslateDrop}
          >
            {translateImage ? (
              <>
                <button
                  type="button"
                  className="ecom-watermark-remove"
                  onClick={(event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    removeTranslateImage();
                  }}
                  aria-label="删除素材"
                >
                  ×
                </button>
                <figure>
                  <img src={translateImage.src} alt={translateImage.name} />
                </figure>
                <div>
                  <strong>{translateImage.name}</strong>
                  <span>{translateImage.format || "PNG / JPG / WebP"}</span>
                </div>
              </>
            ) : (
              <>
                <CloudUploadOutlined />
                <strong>上传含文字图片</strong>
                <span>支持 PNG / JPG / WebP，拖拽或点击上传</span>
              </>
            )}
          </div>
          <div className="ecom-watermark-url-row">
            <input
              ref={translateUrlInputRef}
              placeholder="粘贴图片 URL"
              aria-label="粘贴图片 URL"
              onKeyDown={(event) => {
                if (event.key === "Enter") void handleTranslateUrlImport();
              }}
            />
            <button type="button" onClick={() => void handleTranslateUrlImport()}>导入</button>
          </div>
        </section>

        <section className="ecom-watermark-panel">
          <strong>处理说明</strong>
          <p>自动识别图片中的文字内容，翻译为目标语言并保持原图排版，适合商品包装、说明书、宣传图翻译。</p>
        </section>

        <button
          type="button"
          className="ecom-watermark-primary"
          onClick={handleTranslateGenerate}
          disabled={!translateImage || translateStatus === "processing"}
        >
          {translateStatus === "processing" ? <LoadingOutlined /> : <GlobalOutlined />}
          {translateStatus === "processing" ? "翻译中" : "开始翻译"}
        </button>
      </aside>

      <section className="ecom-watermark-workspace">
        {!translateImage ? (
          <div
            className={`ecom-watermark-dropzone${isTranslateDragging ? " is-dragging" : ""}`}
            role="button"
            tabIndex={0}
            onClick={() => translateInputRef.current?.click()}
            onKeyDown={(event) => {
              if (event.key === "Enter" || event.key === " ") {
                event.preventDefault();
                translateInputRef.current?.click();
              }
            }}
            onDragEnter={(event) => {
              event.preventDefault();
              setIsTranslateDragging(true);
            }}
            onDragOver={(event) => event.preventDefault()}
            onDragLeave={() => setIsTranslateDragging(false)}
            onDrop={handleTranslateDrop}
          >
            <CloudUploadOutlined />
            <strong>点击或拖拽上传图片</strong>
            <span>支持 PNG / JPG / WebP，上传含文字图片后选择目标语言并点击开始翻译</span>
          </div>
        ) : (
          <div className="ecom-watermark-grid">
            <article className="ecom-watermark-preview-card">
              <span>原图</span>
              <img src={translateImage.src} alt="原图" />
            </article>

            <article className="ecom-watermark-preview-card">
              <span>翻译结果</span>
              {translateStatus === "processing" ? (
                <div className="ecom-watermark-processing" role="status" aria-live="polite">
                  <LoadingOutlined />
                  <strong>正在翻译</strong>
                  <em>AI 正在识别并翻译图片中的文字</em>
                </div>
              ) : translateStatus === "done" && translateResultUrl ? (
                <>
                  <img src={translateResultUrl} alt="翻译结果" />
                  <button type="button" className="ecom-watermark-zoom" aria-label="查看大图">
                    <QuestionCircleOutlined />
                  </button>
                </>
              ) : translateStatus === "failed" ? (
                <div className="ecom-watermark-empty">
                  <GlobalOutlined />
                  <strong>翻译失败</strong>
                  <em>请重试或更换图片</em>
                </div>
              ) : (
                <div className="ecom-watermark-empty">
                  <GlobalOutlined />
                  <strong>等待处理</strong>
                  <em>点击开始翻译后显示结果</em>
                </div>
              )}
              <div className="ecom-watermark-actions">
                <button type="button" onClick={() => toast.success("已加入资产库")} disabled={translateStatus !== "done"}>
                  <FolderOpenOutlined />
                  加入资产库
                </button>
                <button type="button" onClick={handleTranslateDownload} disabled={translateStatus !== "done"}>
                  <CloudUploadOutlined />
                  下载图片
                </button>
              </div>
            </article>
          </div>
        )}
      </section>
    </main>
  );

  const openQuickUploadWithKeyboard = (
    event: ReactKeyboardEvent<HTMLDivElement>,
    inputRef: { current: HTMLInputElement | null },
  ) => {
    if (event.key !== "Enter" && event.key !== " ") return;
    event.preventDefault();
    inputRef.current?.click();
  };
  const renderQuickUploadThumbs = (items: CloneImageItem[], onRemove: (imageId: string) => void) => (
    <div className="ecom-quick-upload-thumbs" aria-label="已上传商品原图">
      {items.map((item) => (
        <figure key={item.id} className="ecom-command-asset-thumb ecom-quick-upload-thumb">
          <img src={item.src} alt={item.name} />
          <span className="ecom-command-asset-zoom" aria-hidden="true">
            <img src={item.src} alt="" />
          </span>
          <button
            type="button"
            aria-label="删除图片"
            onClick={(event) => {
              event.stopPropagation();
              onRemove(item.id);
            }}
          >
            ×
          </button>
        </figure>
      ))}
    </div>
  );

  const quickDetailVisibleSelect = quickDetailBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;
  const quickHotVisibleSelect = quickHotBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;
  const quickSetVisibleSelect = quickSetBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;

  const quickDetailPreview = (
    <main key="quick-detail" className={`ecom-quick-set-page ecom-quick-detail-page ecom-tool-page-enter${isQuickPanelCollapsed ? " is-panel-collapsed" : ""}`} aria-label="A+详情页生成">
      <div className="ecom-quick-set-body">
        <aside className="ecom-quick-set-panel" aria-label="A+详情页设置" onWheel={handleQuickPanelWheel}>
          <header className="ecom-quick-set-panel-head">
            <strong className="ecom-quick-set-page-title">A+/详情</strong>
            <button type="button" className="ecom-quick-set-back" onClick={() => setActiveQuickTool(null)}>
              首页
            </button>
            <button type="button" className="ecom-quick-set-back" onClick={() => setActiveQuickTool(null)}>
              上一页
            </button>
          </header>
          <section>
            <strong><CloudUploadOutlined /> 上传商品原图</strong>
            <div
              role="button"
              tabIndex={0}
              className={`ecom-quick-set-upload${detailProductImages.length ? " has-images" : ""}`}
              onClick={() => detailInputRef.current?.click()}
              onKeyDown={(event) => openQuickUploadWithKeyboard(event, detailInputRef)}
              onDragOver={(event) => event.preventDefault()}
              onDrop={handleDetailDrop}
            >
              <FileImageOutlined />
              <span>拖拽或点击上传</span>
              <em>同一产品，最多 3 张</em>
              <b>+ 上传图片</b>
              {detailProductImages.length ? renderQuickUploadThumbs(detailProductImages, removeDetailImage) : null}
            </div>
            <input
              ref={detailInputRef}
              type="file"
              accept="image/*"
              multiple
              className="ecom-command-hidden-file"
              onChange={handleDetailUpload}
              aria-label="上传详情页图片"
            />
          </section>
          <section className="ecom-quick-set-basic-section">
            <span className="ecom-quick-set-label">生成内容</span>
            <div className="ecom-quick-detail-types">
              {detailTypeOptions.map((item) => (
                <button
                  key={item}
                  type="button"
                  className={detailType === item ? "is-active" : ""}
                  onClick={() => setDetailType(item)}
                >
                  {item}
                </button>
              ))}
            </div>
            <span className="ecom-quick-set-label">基础设置</span>
            <div className="ecom-quick-set-select-anchor">
              <div className="ecom-quick-set-selects">
                {quickDetailBasicSelects.map((item) => (
                  <button
                    key={item.key}
                    type="button"
                    className={openQuickSetSelect === item.key ? "is-active" : ""}
                    onClick={() => toggleQuickSetSelect(item.key)}
                  >
                    <span>{item.label}</span><strong>{formatRatioDisplayValue(item.value)}</strong><em>⌄</em>
                  </button>
                ))}
              </div>
              {quickDetailVisibleSelect ? (
                <div
                  className={`ecom-quick-set-dropdown ecom-quick-set-dropdown--${quickDetailVisibleSelect.key}${isQuickSetSelectClosing ? " is-closing" : ""}`}
                  role="listbox"
                  aria-label={quickDetailVisibleSelect.label}
                >
                  {quickDetailVisibleSelect.options.map((option) => (
                    <button
                      key={option}
                      type="button"
                      className={quickDetailVisibleSelect.value === option ? "is-active" : ""}
                      onClick={() => {
                        quickDetailVisibleSelect.onChange(option);
                        closeQuickSetSelect();
                      }}
                    >
                      {formatRatioDisplayValue(option)}
                    </button>
                  ))}
                </div>
              ) : null}
            </div>
          </section>
          <section>
            <strong>包含模块（多选）</strong>
            <div className="ecom-quick-detail-modules">
              {detailModules.map((module) => (
                <button
                  key={module.id}
                  type="button"
                  className={selectedDetailModules.includes(module.id) ? "is-active" : ""}
                  onClick={() => toggleDetailModule(module.id)}
                >
                  <strong>{module.title}</strong>
                  <span>{module.desc}</span>
                </button>
              ))}
            </div>
          </section>
          <button type="button" className="ecom-quick-set-primary" onClick={handleDetailGenerate} disabled={!canGenerateDetail}>
            {detailStatus === "generating" ? <LoadingOutlined /> : "✦"} 开始生成
          </button>
          {detailStatus === "generating" ? (
            <button type="button" className="ecom-quick-set-primary ecom-quick-set-primary--cancel" onClick={handleCancelGenerate}>取消生成</button>
          ) : null}
        </aside>
        <section className="ecom-quick-set-stage">
          <header className="ecom-quick-set-preview-head">
            <h1>预览</h1>
            <p>上传商品图，AI 即刻生成 <span>符合多电商平台规范</span> 的高转化率详情页素材。</p>
            <div>
              <button type="button" onClick={() => setPreviewZoom((value) => Math.max(0.25, value - 0.1))}>-</button>
              <strong>{Math.round(previewZoom * 100)}%</strong>
              <button type="button" onClick={() => setPreviewZoom((value) => Math.min(2, value + 0.1))}>+</button>
            </div>
          </header>
          <div className="ecom-quick-set-canvas" onWheel={handleQuickPreviewWheel}>
            {detailStatus === "done" && detailResultUrl ? (
              <section className="ecom-quick-detail-result" style={{ transform: `scale(${previewZoom})` }}>
                <img src={detailResultUrl} alt="A+详情页生成结果" />
                <button
                  type="button"
                  className="ecom-quick-detail-download"
                  onClick={() => {
                    const link = document.createElement("a");
                    link.href = detailResultUrl;
                    link.download = `A+详情页-${Date.now()}.png`;
                    document.body.appendChild(link);
                    link.click();
                    link.remove();
                  }}
                >
                  <CloudUploadOutlined />
                  保存本地
                </button>
              </section>
            ) : detailStatus === "generating" ? (
              <section className="ecom-quick-set-generating">
                <LoadingOutlined />
                <strong>正在生成 A+ 详情页</strong>
                <span>AI 正在根据您的商品图和设置生成详情页素材，请稍候...</span>
                <div className="ecom-quick-set-progress">
                  <div className="ecom-quick-set-progress-bar" style={{ width: `${Math.round(detailProgress)}%` }} />
                </div>
                <em className="ecom-quick-set-progress-text">{Math.round(detailProgress)}%</em>
              </section>
            ) : detailStatus === "failed" ? (
              <section className="ecom-quick-set-failed">
                <FrownOutlined />
                <strong>生成失败</strong>
                <span>请检查网络或重试，如余额不足请先充值。</span>
                <button type="button" onClick={handleDetailGenerate} disabled={!canGenerateDetail}>重新生成</button>
              </section>
            ) : detailProductImages.length ? (
              <section className="ecom-quick-detail-preview-card" style={{ transform: `scale(${previewZoom})` }}>
                {detailGridSamples.slice(0, 6).map((src, index) => (
                  <figure key={src}>
                    <img src={src} alt={`详情页模块预览 ${index + 1}`} />
                    <span>{detailModules[index]?.title ?? "详情模块"}</span>
                  </figure>
                ))}
              </section>
            ) : (
              <section className="ecom-quick-set-empty">
                <FileImageOutlined />
                <strong>等待生成</strong>
                <span>上传商品原图并填写信息后，AI 将在这里展示生成结果。</span>
              </section>
            )}
          </div>
          <section className="ecom-quick-set-prompt">
            <textarea
              value={detailRequirement}
              onChange={(event) => setDetailRequirement(event.target.value.slice(0, 500))}
              placeholder="建议包含以下信息，产品名称，核心卖点，期望场景，具体参数"
            />
            <div className="ecom-quick-detail-prompt-actions">
              <button type="button" onClick={handleDetailAiWrite} aria-label="AI帮写">AI</button>
              <button type="button" onClick={handleDetailGenerate} disabled={!canGenerateDetail}>↑</button>
            </div>
            <span>{detailRequirement.length}/500</span>
          </section>
        </section>
      </div>
      <button type="button" className="ecom-quick-set-help" aria-label="帮助" onClick={() => toast.info("上传商品图后，选择平台和详情模块即可生成 A+ 详情页。")}>?</button>
    </main>
  );

  const hotClonePreview = (
    <main key="quick-hot" className={`ecom-quick-set-page ecom-quick-hot-page ecom-tool-page-enter${isQuickPanelCollapsed ? " is-panel-collapsed" : ""}`} aria-label="爆款复刻生成">
      <div className="ecom-quick-set-body">
        <aside className="ecom-quick-set-panel" aria-label="爆款复刻设置" onWheel={handleQuickPanelWheel}>
          <header className="ecom-quick-set-panel-head">
            <strong className="ecom-quick-set-page-title">爆款复刻</strong>
            <button type="button" className="ecom-quick-set-back" onClick={closeHotClonePage}>
              首页
            </button>
            <button type="button" className="ecom-quick-set-back" onClick={closeHotClonePage}>
              上一页
            </button>
          </header>
          <section>
            <strong><FileImageOutlined /> 上传素材</strong>
            {productImages.length ? (
              <div
                role="button"
                tabIndex={0}
                className={`ecom-quick-set-upload ecom-quick-hot-material has-images${isHotMaterialDragging ? " is-dragging" : ""}`}
                onClick={() => hotMaterialInputRef.current?.click()}
                onKeyDown={(event) => openQuickUploadWithKeyboard(event, hotMaterialInputRef)}
                onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsHotMaterialDragging(true); }}
                onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsHotMaterialDragging(false); }}
                onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsHotMaterialDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }}
              >
                {renderHotMaterialThumbs(productImages, removeProductImage)}
                <button
                  type="button"
                  className="ecom-quick-hot-add-btn"
                  aria-label="添加更多素材"
                  onClick={(event) => {
                    event.stopPropagation();
                    hotMaterialInputRef.current?.click();
                  }}
                >
                  <PlusOutlined />
                </button>
              </div>
            ) : (
              <div
                role="button"
                tabIndex={0}
                className={`ecom-quick-set-upload ecom-quick-hot-material${isHotMaterialDragging ? " is-dragging" : ""}`}
                onClick={() => hotMaterialInputRef.current?.click()}
                onKeyDown={(event) => openQuickUploadWithKeyboard(event, hotMaterialInputRef)}
                onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsHotMaterialDragging(true); }}
                onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsHotMaterialDragging(false); }}
                onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsHotMaterialDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }}
              >
                <FileImageOutlined />
                <span>拖拽或点击上传</span>
                <em>上传商品素材图，最多 {maxCloneProductImages} 张</em>
                <b>+ 上传图片</b>
              </div>
            )}
            <input
              ref={hotMaterialInputRef}
              type="file"
              accept="image/*"
              multiple
              className="ecom-command-hidden-file"
              onChange={handleProductUpload}
              aria-label="上传爆款复刻素材"
            />
          </section>
          <section>
            <strong><FireOutlined /> 上传参考图片</strong>
            <div
              role="button"
              tabIndex={0}
              className={`ecom-quick-set-upload ecom-quick-hot-material ecom-quick-hot-reference${cloneReferenceImages.length ? " has-images" : ""}${isCloneReferenceDragging ? " is-dragging" : ""}`}
              onClick={() => cloneReferenceInputRef.current?.click()}
              onKeyDown={(event) => openQuickUploadWithKeyboard(event, cloneReferenceInputRef)}
              onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsCloneReferenceDragging(true); }}
              onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsCloneReferenceDragging(false); }}
              onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsCloneReferenceDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addCloneReferenceImages(files); }}
            >
              <FileImageOutlined />
              <span>拖拽或点击上传</span>
              <em>参考图用于风格迁移，最多 {maxCloneReferenceImages} 张</em>
              <b>+ 上传图片</b>
              {cloneReferenceImages.length ? (
                <>
                  {renderHotMaterialThumbs(cloneReferenceImages, removeCloneReferenceImage)}
                  <button
                    type="button"
                    className="ecom-quick-hot-add-btn"
                    aria-label="添加更多参考图"
                    onClick={(event) => {
                      event.stopPropagation();
                      cloneReferenceInputRef.current?.click();
                    }}
                  >
                    <PlusOutlined />
                  </button>
                </>
              ) : null}
            </div>
            <input
              ref={cloneReferenceInputRef}
              type="file"
              accept="image/*"
              multiple
              className="ecom-command-hidden-file"
              onChange={handleCloneReferenceUpload}
              aria-label="上传爆款复刻参考图"
            />
          </section>
          <section>
            <strong>复刻强度</strong>
            <div className="ecom-quick-detail-modules">
              {cloneReplicateLevelOptions.map((option) => (
                <button
                  key={option.key}
                  type="button"
                  className={cloneReplicateLevel === option.key ? "is-active" : ""}
                  onClick={() => setCloneReplicateLevel(option.key)}
                >
                  <strong>{option.title}</strong>
                  <span>{option.desc}</span>
                </button>
              ))}
            </div>
          </section>
          <section className="ecom-quick-set-basic-section">
            <span className="ecom-quick-set-label">基础设置</span>
            <div className="ecom-quick-set-select-anchor">
              <div className="ecom-quick-set-selects">
                {quickHotBasicSelects.map((item) => (
                  <button
                    key={item.key}
                    type="button"
                    className={openQuickSetSelect === item.key ? "is-active" : ""}
                    onClick={() => toggleQuickSetSelect(item.key)}
                  >
                    <span>{item.label}</span><strong>{formatRatioDisplayValue(item.value)}</strong><em>⌄</em>
                  </button>
                ))}
              </div>
              {quickHotVisibleSelect ? (
                <div
                  className={`ecom-quick-set-dropdown ecom-quick-set-dropdown--${quickHotVisibleSelect.key}${isQuickSetSelectClosing ? " is-closing" : ""}`}
                  role="listbox"
                  aria-label={quickHotVisibleSelect.label}
                >
                  {quickHotVisibleSelect.options.map((option) => (
                    <button
                      key={option}
                      type="button"
                      className={quickHotVisibleSelect.value === option ? "is-active" : ""}
                      onClick={() => {
                        quickHotVisibleSelect.onChange(option);
                        closeQuickSetSelect();
                      }}
                    >
                      {formatRatioDisplayValue(option)}
                    </button>
                  ))}
                </div>
              ) : null}
            </div>
          </section>
          <section className="ecom-quick-hot-requirement">
            <div className="ecom-quick-hot-requirement__head">
              <strong>商品卖点 &amp; 需求</strong>
              <button type="button" className="ecom-quick-hot-requirement__ai" onClick={handleHotAiWrite}>AI 帮写</button>
            </div>
            <div className="ecom-quick-hot-requirement__input">
              <textarea
                value={hotRequirement}
                onChange={(event) => setHotRequirement(event.target.value.slice(0, 500))}
                placeholder="建议包含以下信息：产品名称、核心卖点、参考风格、期望场景、具体参数"
                maxLength={500}
              />
              <span>{hotRequirement.length}/500</span>
            </div>
          </section>
          <div className="ecom-quick-hot-actions">
            <button type="button" className="ecom-quick-set-primary ecom-quick-hot-generate" onClick={handleHotGenerate} disabled={!canGenerateHot}>
              {hotStatus === "generating" ? <LoadingOutlined /> : "✦"} 开始复刻
            </button>
            <button
              type="button"
              className={`ecom-quick-set-primary ecom-quick-set-primary--cancel${hotStatus !== "generating" ? " is-disabled" : ""}`}
              onClick={hotStatus === "generating" ? handleCancelGenerate : undefined}
              disabled={hotStatus !== "generating"}
            >
              取消复刻
            </button>
          </div>
        </aside>
        {hotMaterialHoverZoom && typeof document !== "undefined"
          ? createPortal(
              <div
                className={`ecom-hot-material-zoom-portal is-${hotMaterialHoverZoom.placement}`}
                style={{ left: hotMaterialHoverZoom.x, top: hotMaterialHoverZoom.y }}
              >
                <img src={hotMaterialHoverZoom.src} alt="" />
              </div>,
              document.body,
            )
          : null}
        <section className="ecom-quick-set-stage">
          <header className="ecom-quick-set-preview-head">
            <h1>预览</h1>
            <p>上传参考图，AI 按选定风格强度 <span>复刻同款视觉表现</span>，快速产出高转化素材。</p>
            <div>
              <button type="button" onClick={() => setPreviewZoom((value) => Math.max(0.25, value - 0.1))}>-</button>
              <strong>{Math.round(previewZoom * 100)}%</strong>
              <button type="button" onClick={() => setPreviewZoom((value) => Math.min(2, value + 0.1))}>+</button>
            </div>
          </header>
          <div className="ecom-quick-set-canvas" onWheel={handleQuickPreviewWheel}>
            {hotStatus === "done" && hotResultUrl ? (
              <section className="ecom-quick-detail-result" style={{ transform: `scale(${previewZoom})` }}>
                <img src={hotResultUrl} alt="爆款复刻结果" />
                <button
                  type="button"
                  className="ecom-quick-detail-download"
                  onClick={() => {
                    const link = document.createElement("a");
                    link.href = hotResultUrl;
                    link.download = `爆款复刻-${Date.now()}.png`;
                    document.body.appendChild(link);
                    link.click();
                    link.remove();
                  }}
                >
                  <CloudUploadOutlined />
                  保存本地
                </button>
              </section>
            ) : hotStatus === "generating" ? (
              <section className="ecom-quick-set-generating">
                <LoadingOutlined />
                <strong>正在生成爆款复刻</strong>
                <span>AI 正在根据参考图和复刻强度生成同款素材，请稍候...</span>
                <div className="ecom-quick-set-progress">
                  <div className="ecom-quick-set-progress-bar" style={{ width: `${Math.round(hotProgress)}%` }} />
                </div>
                <em className="ecom-quick-set-progress-text">{Math.round(hotProgress)}%</em>
              </section>
            ) : hotStatus === "failed" ? (
              <section className="ecom-quick-set-failed">
                <FrownOutlined />
                <strong>生成失败</strong>
                <span>请检查网络或重试，如余额不足请先充值。</span>
                <button type="button" onClick={handleHotGenerate} disabled={!canGenerateHot}>重新生成</button>
              </section>
            ) : (
              <section className="ecom-quick-set-empty">
                <FileImageOutlined />
                <strong>等待生成</strong>
                <span>上传参考图片并选择复刻强度后，AI 将在这里展示生成结果。</span>
              </section>
            )}
          </div>
        </section>
      </div>
      <button type="button" className="ecom-quick-set-help" aria-label="帮助" onClick={() => toast.info("上传参考图后，选择复刻强度和平台即可生成爆款同款。")}>?</button>
    </main>
  );

  const quickSetGenPreview = (
    <main key="quick-set" className={`ecom-quick-set-page ecom-quick-hot-page ecom-tool-page-enter${isQuickPanelCollapsed ? " is-panel-collapsed" : ""}`} aria-label="电商套图生成">
      <div className="ecom-quick-set-body">
        <aside className="ecom-quick-set-panel" aria-label="电商套图设置" onWheel={handleQuickPanelWheel}>
          <header className="ecom-quick-set-panel-head">
            <strong className="ecom-quick-set-page-title">电商套图</strong>
            <button type="button" className="ecom-quick-set-back" onClick={closeQuickSetPage}>首页</button>
            <button type="button" className="ecom-quick-set-back" onClick={closeQuickSetPage}>上一页</button>
          </header>
          <section>
            <strong><FileImageOutlined /> 上传商品原图</strong>
            {productImages.length ? (
              <div
                role="button"
                tabIndex={0}
                className={`ecom-quick-set-upload ecom-quick-hot-material has-images${isProductUploadDragging ? " is-dragging" : ""}`}
                onClick={() => productInputRef.current?.click()}
                onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)}
                onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true); }}
                onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsProductUploadDragging(false); }}
                onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsProductUploadDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }}
              >
                {renderQuickUploadThumbs(productImages, removeProductImage)}
                <button
                  type="button"
                  className="ecom-quick-hot-add-btn"
                  aria-label="添加更多素材"
                  onClick={(event) => {
                    event.stopPropagation();
                    productInputRef.current?.click();
                  }}
                >
                  <PlusOutlined />
                </button>
              </div>
            ) : (
              <div
                role="button"
                tabIndex={0}
                className={`ecom-quick-set-upload ecom-quick-hot-material${isProductUploadDragging ? " is-dragging" : ""}`}
                onClick={() => productInputRef.current?.click()}
                onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)}
                onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true); }}
                onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsProductUploadDragging(false); }}
                onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsProductUploadDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }}
              >
                <FileImageOutlined />
                <span>拖拽或点击上传</span>
                <em>同一产品，最多 7 张</em>
                <b>+ 上传图片</b>
              </div>
            )}
            <input
              ref={productInputRef}
              type="file"
              accept="image/*"
              multiple
              className="ecom-command-hidden-file"
              onChange={handleProductUpload}
              aria-label="上传商品图片"
            />
          </section>
          <section className="ecom-quick-set-basic-section">
            <span className="ecom-quick-set-label">基础设置</span>
            <div className="ecom-quick-set-select-anchor">
              <div className="ecom-quick-set-selects">
                {quickSetBasicSelects.map((item) => (
                  <button
                    key={item.key}
                    type="button"
                    className={openQuickSetSelect === item.key ? "is-active" : ""}
                    onClick={() => toggleQuickSetSelect(item.key)}
                  >
                    <span>{item.label}</span><strong>{formatRatioDisplayValue(item.value)}</strong><em>⌄</em>
                  </button>
                ))}
              </div>
              {quickSetVisibleSelect ? (
                <div
                  className={`ecom-quick-set-dropdown ecom-quick-set-dropdown--${quickSetVisibleSelect.key}${isQuickSetSelectClosing ? " is-closing" : ""}`}
                  role="listbox"
                  aria-label={quickSetVisibleSelect.label}
                >
                  {quickSetVisibleSelect.options.map((option) => (
                    <button
                      key={option}
                      type="button"
                      className={quickSetVisibleSelect.value === option ? "is-active" : ""}
                      onClick={() => {
                        quickSetVisibleSelect.onChange(option);
                        closeQuickSetSelect();
                      }}
                    >
                      {formatRatioDisplayValue(option)}
                    </button>
                  ))}
                </div>
              ) : null}
            </div>
          </section>
          <section className="ecom-quick-set-count-section">
            <span className="ecom-quick-set-label">图片数量</span>
            <p className="ecom-quick-set-hint">可自由调整各类型图片数量，总数 1-16 张</p>
            <div className="ecom-quick-set-counts">
              {cloneSetCountOptions.map((item) => {
                const count = cloneSetCounts[item.key];
                const decrementDisabled = count <= 0 || cloneSetTotal <= minCloneSetTotal;
                const incrementDisabled = cloneSetTotal >= maxCloneSetTotal;
                return (
                  <div key={item.key} className="ecom-quick-set-count-row">
                    <div className="ecom-quick-set-count-info">
                      <strong>{item.title}</strong>
                      <span>{item.desc}</span>
                    </div>
                    <div className="clone-ai-count-stepper" aria-label={`${item.title}数量`}>
                      <button
                        type="button"
                        disabled={decrementDisabled}
                        onPointerDown={(event) => {
                          event.preventDefault();
                          startCloneSetCountHold(item.key, -1, decrementDisabled);
                        }}
                        onPointerUp={clearCloneSetCountHold}
                        onPointerLeave={clearCloneSetCountHold}
                        onPointerCancel={clearCloneSetCountHold}
                        onBlur={clearCloneSetCountHold}
                        aria-label={`减少${item.title}`}
                      >
                        -
                      </button>
                      <b>{count}</b>
                      <button
                        type="button"
                        disabled={incrementDisabled}
                        onPointerDown={(event) => {
                          event.preventDefault();
                          startCloneSetCountHold(item.key, 1, incrementDisabled);
                        }}
                        onPointerUp={clearCloneSetCountHold}
                        onPointerLeave={clearCloneSetCountHold}
                        onPointerCancel={clearCloneSetCountHold}
                        onBlur={clearCloneSetCountHold}
                        aria-label={`增加${item.title}`}
                      >
                        +
                      </button>
                    </div>
                  </div>
                );
              })}
            </div>
          </section>
          <section className="ecom-quick-hot-requirement">
            <div className="ecom-quick-hot-requirement__head">
              <strong>商品卖点 &amp; 需求</strong>
              <button type="button" className="ecom-quick-hot-requirement__ai" onClick={handleQuickSetAiWrite}>AI 帮写</button>
            </div>
            <div className="ecom-quick-hot-requirement__input">
              <textarea
                value={quickSetRequirement}
                onChange={(event) => setQuickSetRequirement(event.target.value.slice(0, 500))}
                placeholder="建议包含以下信息：产品名称、核心卖点、期望场景、具体参数"
                maxLength={500}
              />
              <span>{quickSetRequirement.length}/500</span>
            </div>
          </section>
          <div className="ecom-quick-hot-actions">
            <button type="button" className="ecom-quick-set-primary ecom-quick-hot-generate" onClick={handleQuickSetGenerate} disabled={!canGenerateQuickSet}>
              {quickSetStatus === "generating" ? <LoadingOutlined /> : "✦"} 开始生成
            </button>
            <button
              type="button"
              className={`ecom-quick-set-primary ecom-quick-set-primary--cancel${quickSetStatus !== "generating" ? " is-disabled" : ""}`}
              onClick={quickSetStatus === "generating" ? handleCancelGenerate : undefined}
              disabled={quickSetStatus !== "generating"}
            >
              取消生成
            </button>
          </div>
        </aside>
        <section className="ecom-quick-set-stage">
          <header className="ecom-quick-set-preview-head">
            <h1>预览</h1>
            <p>上传商品图，AI 即刻生成 <span>符合多电商平台规范</span> 的高转化率商品套图。</p>
            <div>
              <button type="button" onClick={() => setPreviewZoom((value) => Math.max(0.25, value - 0.1))}>-</button>
              <strong>{Math.round(previewZoom * 100)}%</strong>
              <button type="button" onClick={() => setPreviewZoom((value) => Math.min(2, value + 0.1))}>+</button>
            </div>
          </header>
          <div className="ecom-quick-set-canvas" onWheel={handleQuickPreviewWheel}>
            {quickSetStatus === "done" && quickSetResultUrls.length > 0 ? (
              <section className="ecom-quick-detail-result" style={{ transform: `scale(${previewZoom})` }}>
                <div className="ecom-quick-set-result-grid">
                  {quickSetResultUrls.map((url, index) => (
                    <figure key={`quick-set-${index}`}>
                      <img src={url} alt={`套图 ${index + 1}`} />
                      <span>套图 {index + 1}</span>
                    </figure>
                  ))}
                </div>
                <button
                  type="button"
                  className="ecom-quick-detail-download"
                  onClick={() => {
                    quickSetResultUrls.forEach((url, index) => {
                      const link = document.createElement("a");
                      link.href = url;
                      link.download = `电商套图-${index + 1}-${Date.now()}.png`;
                      document.body.appendChild(link);
                      link.click();
                      link.remove();
                    });
                  }}
                >
                  <CloudUploadOutlined />
                  保存本地
                </button>
              </section>
            ) : quickSetStatus === "generating" ? (
              <section className="ecom-quick-set-generating">
                <LoadingOutlined />
                <strong>正在生成电商套图</strong>
                <span>AI 正在根据您的商品图和设置生成套图素材，请稍候...</span>
                <div className="ecom-quick-set-progress">
                  <div className="ecom-quick-set-progress-bar" style={{ width: `${Math.round(quickSetProgress)}%` }} />
                </div>
                <em className="ecom-quick-set-progress-text">{Math.round(quickSetProgress)}%</em>
              </section>
            ) : quickSetStatus === "failed" ? (
              <section className="ecom-quick-set-failed">
                <FrownOutlined />
                <strong>生成失败</strong>
                <span>请检查网络或重试，如余额不足请先充值。</span>
                <button type="button" onClick={handleQuickSetGenerate} disabled={!canGenerateQuickSet}>重新生成</button>
              </section>
            ) : productImages.length ? (
              <section className="ecom-quick-detail-preview-card" style={{ transform: `scale(${previewZoom})` }}>
                {detailGridSamples.slice(0, 6).map((src, index) => (
                  <figure key={src}>
                    <img src={src} alt={`套图预览 ${index + 1}`} />
                    <span>{detailModules[index]?.title ?? "套图模块"}</span>
                  </figure>
                ))}
              </section>
            ) : (
              <section className="ecom-quick-set-empty">
                <FileImageOutlined />
                <strong>等待生成</strong>
                <span>上传商品原图并填写信息后，AI 将在这里展示生成结果。</span>
              </section>
            )}
          </div>
        </section>
      </div>
      <button type="button" className="ecom-quick-set-help" aria-label="帮助" onClick={() => toast.info("上传商品图后，选择平台和套图数量即可生成电商套图。")}>?</button>
    </main>
  );

  const detailPreview = (
    <main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览" onWheel={handlePreviewWheel}>
      <div className="product-clone-preview__headline">
        <h1>A+/详情页</h1>
        <p>
          上传商品图，AI 即刻生成 <span>符合多电商平台规范</span> 的专业详情页。
        </p>
      </div>

      <section className="product-detail-demo-board">
        <div className="product-detail-source-stack">
          {detailSourcePreviewImages.map((src, index) => (
            <figure key={`${src}-${index}`}>
              <img src={src} alt={`商品原图 ${index + 1}`} />
            </figure>
          ))}
          <span>上传产品图</span>
        </div>
        <div className="product-detail-flow-arrow" aria-hidden="true" />
        <div className="product-detail-long-result">
          <img src={detailResultUrl ?? detailAssets.longPage} alt="生成电商长图" />
          <span>{detailStatus === "done" ? "已生成电商长图" : "生成电商长图"}</span>
        </div>
        <div className="product-detail-grid-result">
          {detailGridSamples.map((src, index) => (
            <img key={src} src={src} alt={`详情页模块 ${index + 1}`} />
          ))}
          <span>符合多电商平台规范</span>
        </div>
      </section>

      <button type="button" className="product-clone-help" aria-label="帮助">
        <QuestionCircleOutlined />
      </button>
    </main>
  );

  const tryOnPreview = (
    <main className="product-clone-preview product-clone-preview--try-on" aria-label="服饰穿搭预览" onWheel={handlePreviewWheel}>
      <div className="product-clone-preview__headline">
        <h1>AI服饰穿搭</h1>
        <p>上传服装图，定制专属模特，即刻生成多种场景不同姿势套图。</p>
      </div>

      {tryOnResultImages.length ? (
        <section className="product-try-on-generated" aria-label="生成结果">
          {tryOnResultImages.map((src, index) => (
            <figure key={src}>
              <img src={src} alt={`生成结果 ${index + 1}`} />
              <figcaption>{selectedScenes[index % Math.max(selectedScenes.length, 1)] || "智能场景"}</figcaption>
            </figure>
          ))}
        </section>
      ) : null}

      <section className="product-try-on-demo-board">
        {tryOnCards.map((card) => (
          <article key={card.title} className={`product-try-on-card product-try-on-card--${card.tone}`}>
            <h2>{card.title}</h2>
            <div className="product-try-on-inputs">
              {card.inputs.map((src, index) => (
                <div className="product-try-on-input-group" key={`${card.title}-${src}`}>
                  <img src={src} alt={`${card.title} 输入 ${index + 1}`} />
                  {index < card.inputs.length - 1 ? <span className="product-try-on-plus">+</span> : null}
                </div>
              ))}
            </div>
            <div className="product-try-on-arrow" aria-hidden="true" />
            <div className="product-try-on-results">
              {card.results.map((src, index) => (
                <img key={src} src={src} alt={`${card.title} 示例 ${index + 1}`} />
              ))}
            </div>
          </article>
        ))}
      </section>

      <button type="button" className="product-clone-help" aria-label="帮助">
        <QuestionCircleOutlined />
      </button>
    </main>
  );

  const placeholderPreview = (
    <main className="product-clone-preview product-clone-preview--placeholder" aria-label={`${pageLabel}预览`}>
      <div className="product-clone-preview__headline">
        <h1>{pageLabel}</h1>
        <p>选择左侧已接入的工具开始生成商品视觉内容。</p>
      </div>
      <button type="button" className="product-clone-help" aria-label="帮助">
        <QuestionCircleOutlined />
      </button>
    </main>
  );

  const copywritingPreview = (
    <div key="copywriting" className="ecom-quick-page-wrap ecom-tool-page-enter">
      <EcommerceCopywritingPanel onClose={closeCopywritingPage} />
    </div>
  );

  const activePreview = isSetTool
    ? setPreview
    : isDetail
      ? detailPreview
      : isTryOn
        ? tryOnPreview
        : isCloneTool
          ? isWatermarkTool
            ? watermarkPreview
            : isTranslateTool
              ? translatePreview
              : isImageEditTool
                ? imageWorkbenchPreview
                : isSmartCutoutTool
                  ? smartCutoutPreview
                  : isQuickDetailTool
                    ? (
                      <div key={`quick-${activeQuickTool}`} className="ecom-quick-page-wrap ecom-tool-page-enter">
                        {quickDetailPreview}
                      </div>
                    )
                    : isHotCloneTool
                      ? (
                        <div key={`quick-${activeQuickTool}`} className="ecom-quick-page-wrap ecom-tool-page-enter">
                          {hotClonePreview}
                        </div>
                      )
                      : isQuickSetTool
                        ? (
                          <div key={`quick-${activeQuickTool}`} className="ecom-quick-page-wrap ecom-tool-page-enter">
                            {quickSetGenPreview}
                          </div>
                        )
                        : isCopywritingTool
                          ? copywritingPreview
                          : clonePreview
          : placeholderPreview;
  const isMainCloneWorkspace = isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isQuickSetTool && !isCopywritingTool;
  const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId);
  const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0);
  const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null;
  const activeConversationTurns = activeHistoryRecord
    ? activeHistoryRecord.turns?.length
      ? activeHistoryRecord.turns
      : [buildHistoryTurnFromRecord(activeHistoryRecord)]
    : [];
  const getHistoryTurnSettingLabel = (turn: EcommerceHistoryTurn) => {
    if (turn.output === "set") {
      const total = cloneSetCountKeys.reduce((sum, key) => sum + (turn.setCounts?.[key] ?? 0), 0);
      return `套图 ${total || 1}张`;
    }
    if (turn.output === "detail") return `详情 ${turn.detailModules?.length || 1}项`;
    if (turn.output === "model") return `模特 ${turn.modelScenes?.length || 1}景`;
    return cloneOutputOptions.find((option) => option.key === turn.output)?.label || selectedCloneOutput.label;
  };
  const restoreHistoryTurnInputs = (turn: EcommerceHistoryTurn) => {
    setCloneOutput(turn.output);
    setPlatform(turn.platform);
    setMarket(turn.market);
    setLanguage(turn.language);
    setRatio(turn.ratio);
    setRequirement(turn.requirement);
    setProductImages(turn.productImages);
    setCloneSetCounts(turn.setCounts);
    setSelectedCloneDetailModules(turn.detailModules.slice(0, maxDetailModuleSelection));
    setSelectedCloneModelScenes(turn.modelScenes);
    setCloneReferenceImages(turn.referenceImages);
    setCloneReplicateLevel(turn.replicateLevel);
    toast.info("已恢复该轮参数，可继续发送");
  };

  return (
    <section
      className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isRecordDetailWorkspace && isCloneConversationCollapsed ? " is-conversation-collapsed" : ""}${isRecordDetailWorkspace ? " is-history-detail" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isTranslateTool ? " is-translate-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}${isHotCloneTool ? " is-hot-clone-page" : ""}${isQuickSetTool ? " is-quick-set-page" : ""}${isCopywritingTool ? " is-copywriting-page" : ""}`}
      data-tool={activeTool}
      aria-label={pageLabel}
    >
      <div className="product-clone-shell">
        <aside className="product-clone-rail" aria-label="商品工具">
          {sideTools.map((tool) => (
            <button key={tool.key} type="button" className={activeTool === tool.key ? "is-active" : ""} onClick={() => setActiveTool(tool.key)}>
              {tool.icon}
              <span>{tool.label}</span>
            </button>
          ))}
        </aside>

        <aside
          id={isCloneTool ? "ecommerce-clone-settings-panel" : undefined}
          className={`product-clone-panel tool-panel-enter`}
          key={activeTool}
          aria-label={`${pageLabel}参数`}
          aria-hidden={isCloneTool && isCloneSettingsCollapsed ? true : undefined}
        >
          {isSetTool ? setPanel : isDetail ? detailPanel : isTryOn ? tryOnPanel : isCopywritingTool ? placeholderPanel : isCloneTool ? clonePanel : placeholderPanel}
        </aside>

        {isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isHotCloneTool && !isQuickSetTool && !isCopywritingTool ? (
          <button
            type="button"
            className="clone-ai-settings-toggle"
            onClick={() => setIsCloneSettingsCollapsed((current) => !current)}
            aria-label={isCloneSettingsCollapsed ? "展开设置面板" : "收起设置面板"}
            aria-controls="ecommerce-clone-settings-panel"
            aria-expanded={!isCloneSettingsCollapsed}
            title={isCloneSettingsCollapsed ? "展开设置面板" : "收起设置面板"}
          >
            {isCloneSettingsCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
          </button>
        ) : null}

        {isRecordDetailWorkspace ? (
          <>
            <aside className="clone-ai-conversation-panel" aria-label="AI 对话">
              <header className="clone-ai-conversation-head">
                <div>
                  <strong>{activeHistoryRecord?.title || "生成详情"}</strong>
                  <span>{selectedCloneOutput.label} · {platform} · {language}</span>
                </div>
                <button
                  type="button"
                  onClick={() => setIsCloneConversationCollapsed(true)}
                  aria-label="收起对话"
                  title="收起对话"
                >
                  <MenuFoldOutlined />
                </button>
              </header>
              <div className="clone-ai-conversation-body">
                {activeConversationTurns.map((turn, index) => {
                  const turnResults = getTurnResults(turn);
                  const outputLabel = cloneOutputOptions.find((option) => option.key === turn.output)?.label || selectedCloneOutput.label;
                  const turnMeta = [
                    { label: "平台", value: turn.platform },
                    { label: "语种", value: turn.language },
                    { label: "比例", value: formatRatioDisplayValue(turn.ratio) },
                    { label: "设置", value: getHistoryTurnSettingLabel(turn) },
                  ];
                  const isCurrentGeneratingTurn = turn.status === "generating" && turn.id === activeHistoryTurnIdRef.current;
                  return (
                    <Fragment key={turn.id}>
                      <section className={`clone-ai-chat-message clone-ai-chat-message--user${index > 0 ? " clone-ai-chat-message--followup" : ""}`}>
                        <span>{index === 0 ? "需求" : `继续生成 ${index + 1}`}</span>
                        <p>{turn.requirement?.trim() || "上传商品素材，描述你想生成的商品图、详情图、模特图或短视频。"}</p>
                        <div className="clone-ai-chat-meta" aria-label="需求参数">
                          {turnMeta.map((item) => (
                            <em key={item.label}>
                              <span>{item.label}</span>
                              <strong>{item.value}</strong>
                            </em>
                          ))}
                        </div>
                        {turn.productImages.length ? (
                          <div className="clone-ai-chat-assets" aria-label="已上传素材">
                            {turn.productImages.slice(0, 4).map((image) => (
                              <img key={image.id} src={image.src} alt={image.name || "商品素材"} />
                            ))}
                            {turn.productImages.length > 4 ? <em>+{turn.productImages.length - 4}</em> : null}
                          </div>
                        ) : null}
                      </section>
                      <section className={`clone-ai-chat-message clone-ai-chat-message--assistant is-${turn.status}`}>
                        <span>电商图设计师</span>
                        <p>
                          {turn.status === "done" || turnResults.length
                            ? `已生成 ${turnResults.length} 张${outputLabel}，已同步到中间画布，可拖拽、缩放和预览。`
                            : turn.status === "generating"
                              ? `正在为 ${turn.platform} / ${turn.market} 生成${outputLabel}，完成后会自动追加到画布。`
                              : turn.errorMessage || "生成失败，请检查网络或参数后重试。"}
                        </p>
                        {isCurrentGeneratingTurn ? (
                          <EcommerceProgressBar status="generating" progress={generationProgress} onCancel={handleCancelGenerate} label={`${outputLabel}生成`} />
                        ) : null}
                        {turn.status === "failed" ? (
                          <button type="button" className="clone-ai-retry-btn" onClick={() => restoreHistoryTurnInputs(turn)}>
                            <ReloadOutlined /> 恢复参数
                          </button>
                        ) : null}
                        {turnResults.length ? (
                          <div className="clone-ai-chat-results" aria-label="生成结果缩略图">
                            {turnResults.slice(0, 6).map((item) => (
                              <button
                                key={item.id}
                                type="button"
                                onClick={() => openProductSetPreview(item)}
                                aria-label={`预览${item.label}`}
                              >
                                <img src={item.src} alt={item.label} />
                              </button>
                            ))}
                          </div>
                        ) : null}
                      </section>
                    </Fragment>
                  );
                })}
              </div>
            </aside>
            <button
              type="button"
              className="clone-ai-conversation-toggle"
              onClick={() => setIsCloneConversationCollapsed((current) => !current)}
              aria-label={isCloneConversationCollapsed ? "展开对话" : "收起对话"}
              title={isCloneConversationCollapsed ? "展开对话" : "收起对话"}
              aria-expanded={!isCloneConversationCollapsed}
            >
              {isCloneConversationCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
            </button>
          </>
        ) : null}

        {activePreview}

      </div>

      {isCloneTool && !isCommandHistoryCollapsed ? (
        <div
          className="ecom-command-history__backdrop"
          role="presentation"
          onClick={() => setIsCommandHistoryCollapsed(true)}
        />
      ) : null}

      <aside className="ecom-command-history" aria-label="生成历史">
        <div className="ecom-command-history__tools">
          <button
            type="button"
            className="ecom-command-history__toggle"
            onClick={() => setIsCommandHistoryCollapsed((current) => !current)}
            title={isCommandHistoryCollapsed ? "展开记录" : "收起记录"}
            aria-label={isCommandHistoryCollapsed ? "展开记录" : "收起记录"}
            aria-expanded={!isCommandHistoryCollapsed}
          >
            {isCommandHistoryCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
          </button>
          <button type="button" className="ecom-command-history__new" onClick={handleNewEcommerceConversation}>新对话</button>
          <button
            type="button"
            className={`ecom-command-history__refresh${isHistoryRefreshing ? " is-refreshing" : ""}`}
            aria-label={isHistoryRefreshing ? "刷新中" : "刷新历史"}
            title={isHistoryRefreshing ? "刷新中" : "刷新历史"}
            onPointerDown={refreshEcommerceHistory}
            onClick={refreshEcommerceHistory}
            disabled={isHistoryRefreshing}
          >
            <ReloadOutlined />
          </button>
        </div>
        <div className="ecom-command-history__heading">
          <strong>生成记录</strong>
          <span>{ecommerceHistoryRecords.length} 条</span>
        </div>
        {historyRefreshMessage ? (
          <p key={historyRefreshStamp} className="ecom-command-history__refresh-note" role="status">{historyRefreshMessage}</p>
        ) : null}
        <nav className="ecom-command-history__list" aria-label="历史对话">
          {ecommerceHistoryRecords.length ? (
            ecommerceHistoryRecords.map((record) => {
              const outputLabel = cloneOutputOptions.find((option) => option.key === record.output)?.label || "生成记录";
              const statusLabel = record.status === "generating" ? "生成中" : record.status === "failed" ? "失败" : formatHistoryTime(record.createdAt);
              return (
                <div key={`${record.id}-${historyRefreshTick}`} className={`ecom-command-history__item${activeHistoryRecordId === record.id ? " is-active" : ""}`}>
                  <button
                    type="button"
                    className="ecom-command-history__item-main"
                    onClick={() => openEcommerceHistoryRecord(record)}
                  >
                    <strong>{record.title}</strong>
                    <span>{outputLabel} · {statusLabel}</span>
                  </button>
                  <button
                    type="button"
                    className="ecom-command-history__item-delete"
                    aria-label="删除此记录"
                    title="删除"
                    onClick={(e) => deleteHistoryRecord(record.id, e)}
                  >
                    <DeleteOutlined />
                  </button>
                </div>
              );
            })
          ) : (
            <p className="ecom-command-history__empty">暂无生成记录</p>
          )}
        </nav>
      </aside>

      {selectedProductSetPreview && typeof document !== "undefined" ? createPortal((
        <div className="product-set-preview-backdrop" role="presentation" onClick={() => setSelectedProductSetPreview(null)}>
          <section
            className="product-set-preview-modal"
            role="dialog"
            aria-modal="true"
            aria-label={selectedProductSetPreview.label}
            onClick={(event) => event.stopPropagation()}
          >
            <button
              type="button"
              className="product-set-preview-close"
              onClick={() => setSelectedProductSetPreview(null)}
              aria-label="关闭预览"
            >
              <CloseOutlined />
            </button>
            <img src={selectedProductSetPreview.src} alt={selectedProductSetPreview.label} />
            <div className="product-set-preview-footer">
              <strong>{selectedProductSetPreview.label}</strong>
              <div className="product-set-preview-actions" aria-label="图片操作">
                <button
                  type="button"
                  className="product-set-preview-action"
                  onClick={() => {
                    void handleDownloadCanvasResult(selectedProductSetPreview);
                  }}
                >
                  <DownloadOutlined />
                  <span>下载</span>
                </button>
                {selectedProductSetPreview.removable ? (
                  <button
                    type="button"
                    className="product-set-preview-action product-set-preview-action--danger"
                    onClick={() => removeSelectedProductSetPreview(selectedProductSetPreview)}
                  >
                    <DeleteOutlined />
                    <span>移除</span>
                  </button>
                ) : null}
              </div>
            </div>
          </section>
        </div>
      ), document.body) : null}

      {showHostingModal ? (
        <div className="product-set-hosting-backdrop" role="presentation">
          <section className="product-set-hosting-modal" role="dialog" aria-modal="true" aria-label="批量托管上线">
            <img src={productSetAssets.hosting} alt="托管模式" />
            <div className="product-set-hosting-content">
              <button type="button" className="product-set-hosting-close" onClick={() => setShowHostingModal(false)} aria-label="关闭">
                ×
              </button>
              <h2>
                批量托管上线啦！
                <span>批量6折</span>
              </h2>
              <strong>睡一觉，图就做好了！</strong>
              <ul>
                <li>
                  <b>批量生产</b>
                  <span>支持多任务并行生成，效率直线提升。</span>
                </li>
                <li>
                  <b>成本立省40%</b>
                  <span>调度夜间闲置算力，享受专属离线点数折扣。</span>
                </li>
                <li>
                  <b>AI智能提取</b>
                  <span>自动识别图片卖点，生成高转化销售卖点。</span>
                </li>
              </ul>
              <button type="button" className="product-set-hosting-confirm" onClick={() => setShowHostingModal(false)}>
                我知道了
              </button>
            </div>
          </section>
        </div>
      ) : null}

      <EcommerceVideoHistoryPanel
        visible={videoHistoryVisible}
        onClose={() => setVideoHistoryVisible(false)}
      />
    </section>
  );
}

export default ProductClonePage;
