// Create Project Wizard — upload-first flow with document intelligence.

function Wizard({ go, projectId: routeProjectId = "" }) {
  const { snapshot, reload } = window.useGGGSnapshot ? window.useGGGSnapshot() : { snapshot: null, reload: null };
  const [step, setStep] = React.useState(0);
  const [projectId, setProjectId] = React.useState(routeProjectId && routeProjectId !== "new" ? routeProjectId : "");
  const [draft, setDraft] = React.useState(() => projectToDraft(null));
  const [pendingFiles, setPendingFiles] = React.useState([]);
  const [uploadedFiles, setUploadedFiles] = React.useState([]);
  const [fileAnalyses, setFileAnalyses] = React.useState([]);
  const [saveError, setSaveError] = React.useState("");
  const [working, setWorking] = React.useState(false);
  const [assistantOpen, setAssistantOpen] = React.useState(false);
  const [titleEditing, setTitleEditing] = React.useState(false);
  const [titleDraft, setTitleDraft] = React.useState("");
  const [titleSaving, setTitleSaving] = React.useState(false);
  const [titleError, setTitleError] = React.useState("");
  const inputRef = React.useRef(null);
  const titleEditRef = React.useRef(null);
  const titleInputRef = React.useRef(null);

  const currentProject = snapshot?.projects?.find((item) => item.id === projectId) || null;
  const snapshotFiles = (snapshot?.files || []).filter((file) => file.project_id === projectId);
  const filesForProject = mergeFiles(uploadedFiles, snapshotFiles);
  const updateDraft = (key, value) => setDraft((current) => ({ ...current, [key]: value }));
  const projectTitle = currentProject?.name || (projectId ? "未命名项目" : "创建出海调研项目");

  React.useEffect(() => {
    const nextId = routeProjectId && routeProjectId !== "new" ? routeProjectId : "";
    if (!nextId || !snapshot?.projects?.length) return;
    const project = snapshot.projects.find((item) => item.id === nextId);
    if (!project) return;
    setProjectId(project.id);
    setDraft(projectToDraft(project));
    if (window.GGGApi?.setActiveProjectId) window.GGGApi.setActiveProjectId(project.id);
  }, [routeProjectId, snapshot?.projects?.length]);

  React.useEffect(() => {
    if (!titleEditing) setTitleDraft(currentProject?.name || "");
  }, [currentProject?.id, currentProject?.name, titleEditing]);

  React.useEffect(() => {
    if (titleEditing && titleInputRef.current) {
      titleInputRef.current.focus();
      titleInputRef.current.select();
    }
  }, [titleEditing]);

  const steps = [
    { lbl: "Files", sub: "上传产品资料", icon: "upload" },
    { lbl: "Product", sub: "产品基础信息", icon: "package" },
    { lbl: "Market", sub: "目标市场选择", icon: "map" },
    { lbl: "Customer & Channel", sub: "客群与渠道", icon: "tag" },
    { lbl: "Resources", sub: "资源评估", icon: "users" },
    { lbl: "Goals", sub: "1/3/5 年目标", icon: "rocket" },
    { lbl: "AI Follow-up", sub: "AI 追问补全", icon: "sparkles" },
    { lbl: "Review", sub: "生成报告", icon: "check" },
  ];

  const startTitleEditing = () => {
    if (!currentProject?.id || working) return;
    setTitleDraft(currentProject.name || "");
    setTitleError("");
    setTitleEditing(true);
  };

  const cancelTitleEditing = React.useCallback(() => {
    setTitleDraft(currentProject?.name || "");
    setTitleError("");
    setTitleEditing(false);
  }, [currentProject?.name]);

  const saveProjectTitle = React.useCallback(async () => {
    if (!titleEditing || !currentProject?.id || titleSaving) return;
    const nextTitle = String(titleDraft || "").trim();
    if (!nextTitle) {
      setTitleError("项目标题不能为空。");
      titleInputRef.current?.focus();
      return;
    }
    if (nextTitle === currentProject.name) {
      setTitleError("");
      setTitleEditing(false);
      return;
    }
    setTitleSaving(true);
    setTitleError("");
    try {
      await window.GGGApi.updateProject(currentProject.id, { name: nextTitle });
      setDraft((current) => ({ ...current, name: nextTitle }));
      setTitleEditing(false);
    } catch (err) {
      setTitleError(err.message || "项目标题保存失败");
      titleInputRef.current?.focus();
    } finally {
      setTitleSaving(false);
    }
  }, [currentProject?.id, currentProject?.name, titleDraft, titleEditing, titleSaving]);

  React.useEffect(() => {
    if (!titleEditing) return;
    const handleOutsidePointer = (event) => {
      if (titleEditRef.current?.contains(event.target)) return;
      saveProjectTitle();
    };
    document.addEventListener("mousedown", handleOutsidePointer);
    document.addEventListener("touchstart", handleOutsidePointer);
    return () => {
      document.removeEventListener("mousedown", handleOutsidePointer);
      document.removeEventListener("touchstart", handleOutsidePointer);
    };
  }, [titleEditing, saveProjectTitle]);

  const saveDraft = async ({ allowPlaceholder = false } = {}) => {
    setSaveError("");
    const effectiveDraft = titleEditing && currentProject?.id && String(titleDraft || "").trim()
      ? { ...draft, name: String(titleDraft || "").trim() }
      : draft;
    const payload = projectPayload(effectiveDraft, { allowPlaceholder, pendingFiles });
    if (!allowPlaceholder && !payload.productName && !payload.productCategory) {
      setSaveError("请至少确认产品名称或产品品类。");
      return null;
    }
    try {
      const saved = projectId
        ? await window.GGGApi.updateProject(projectId, payload)
        : await window.GGGApi.createProject(payload);
      setProjectId(saved.id);
      setDraft((current) => current.name ? current : { ...current, name: saved.name || payload.name });
      if (window.GGGApi?.setActiveProjectId) window.GGGApi.setActiveProjectId(saved.id);
      return saved;
    } catch (err) {
      setSaveError(err.message || "项目保存失败");
      return null;
    }
  };

  const addFiles = (fileList) => {
    const accepted = Array.from(fileList || []).filter(isSupportedProductFile);
    if (!accepted.length) {
      setSaveError("请选择 xlsx、csv、pdf、docx、txt、jpg、png 或 webp 文件。");
      return;
    }
    setSaveError("");
    setPendingFiles((current) => dedupeFiles([...current, ...accepted]));
  };

  const removePendingFile = (index) => {
    setPendingFiles((current) => current.filter((_, i) => i !== index));
  };

  const uploadQueuedFiles = async () => {
    if (!pendingFiles.length) return true;
    setWorking(true);
    setSaveError("");
    try {
      const saved = await saveDraft({ allowPlaceholder: true });
      if (!saved?.id) return false;
      const uploaded = await window.GGGApi.uploadProjectFilesDetailed(saved.id, pendingFiles);
      const savedFiles = uploaded.files || [];
      setUploadedFiles((current) => mergeFiles(current, savedFiles));
      setPendingFiles([]);

      const analyses = [];
      for (const file of savedFiles) {
        const analysis = await window.GGGApi.getFileAnalysis(file.id);
        analyses.push(analysis);
      }
      setFileAnalyses((current) => mergeAnalyses(current, analyses));
      const autofill = draftFromDocumentAnalyses(analyses);
      setDraft((current) => fillDraftFromAnalysis(current, autofill));
      return true;
    } catch (err) {
      setSaveError(err.message || "资料上传或解析失败");
      return false;
    } finally {
      setWorking(false);
      if (inputRef.current) inputRef.current.value = "";
    }
  };

  const recommendGoals = async () => {
    setWorking(true);
    setSaveError("");
    try {
      const saved = await saveDraft({ allowPlaceholder: true });
      if (!saved?.id) return;
      const goals = await window.GGGApi.recommendProjectGoals(saved.id, draft);
      setDraft((current) => ({
        ...current,
        oneYearGoal: goals.oneYearGoal || current.oneYearGoal,
        threeYearGoal: goals.threeYearGoal || current.threeYearGoal,
        fiveYearGoal: goals.fiveYearGoal || current.fiveYearGoal,
      }));
    } catch (err) {
      setSaveError(err.message || "AI 推荐失败");
    } finally {
      setWorking(false);
    }
  };

  const nextStep = async () => {
    if (step === 0) {
      if (!pendingFiles.length && !filesForProject.length) {
        setSaveError("请先上传至少一份产品资料，系统会从资料中识别产品基础信息。");
        return;
      }
      const ok = await uploadQueuedFiles();
      if (!ok) return;
    }
    if (step === 1) {
      const saved = await saveDraft();
      if (!saved) return;
    }
    setStep((current) => Math.min(current + 1, steps.length - 1));
  };

  const generateReport = async () => {
    setWorking(true);
    setSaveError("");
    try {
      const saved = await saveDraft();
      if (!saved?.id) return;
      await window.GGGApi.generateReportForProject(saved.id);
      if (reload) await reload();
      go("report", saved.id);
    } catch (err) {
      setSaveError(err.message || "报告生成失败");
    } finally {
      setWorking(false);
    }
  };

  return (
    <AppShell go={go} current="project" pageBreadcrumbs={[
      { label: "项目", go: () => go("project") },
      { label: currentProject?.name || (projectId ? "编辑项目" : "创建新项目") },
    ]}>
      <div className="page">
        <div className="page-header">
          <div className="project-title-block" ref={titleEditRef}>
            {currentProject ? (
              titleEditing ? (
                <div className="project-title-editor">
                  <input
                    ref={titleInputRef}
                    className="project-title-input"
                    value={titleDraft}
                    disabled={titleSaving}
                    onChange={(event) => setTitleDraft(event.target.value)}
                    onKeyDown={(event) => {
                      if (event.key === "Enter") {
                        event.preventDefault();
                        saveProjectTitle();
                      }
                      if (event.key === "Escape") {
                        event.preventDefault();
                        cancelTitleEditing();
                      }
                    }}
                    aria-label="项目标题"
                  />
                  <button className="btn btn-primary btn-sm" disabled={titleSaving} onClick={saveProjectTitle}>
                    <Icon name="check" size={14} /> 保存
                  </button>
                  <button className="btn btn-ghost btn-sm" disabled={titleSaving} onClick={cancelTitleEditing}>
                    <Icon name="x" size={14} /> 取消
                  </button>
                </div>
              ) : (
                <div className="project-title-display-row">
                  <button className="project-title-display" onClick={startTitleEditing} title="点击修改项目标题">
                    {projectTitle}
                  </button>
                  <button className="title-edit-trigger" onClick={startTitleEditing} title="修改项目标题" aria-label="修改项目标题">
                    <Icon name="edit" size={14} />
                  </button>
                </div>
              )
            ) : (
              <h1 className="page-title">创建出海调研项目</h1>
            )}
            {titleError && <div className="project-title-error">{titleError}</div>}
            <p className="page-sub">先上传产品资料，系统自动识别企业、产品、型号和报价数据，再由您确认并生成报告。</p>
          </div>
          <div className="row-flex">
            <button className="btn btn-ghost btn-sm" disabled={working} onClick={async () => { const saved = await saveDraft({ allowPlaceholder: true }); if (saved) go("project"); }}>保存草稿并退出</button>
          </div>
        </div>
        {saveError && <div className="form-alert" style={{ marginBottom: 16 }}>{saveError}</div>}

        <div className="wizard-shell">
          <div className="wizard-steps">
            {steps.map((s, i) => (
              <div key={i} className={"wizard-step " + (i === step ? "active" : i < step ? "done" : "")} onClick={() => !working && setStep(i)}>
                <div className="num">{i < step ? <Icon name="check" size={13} /> : i + 1}</div>
                <div>
                  <div className="lbl">{s.lbl}</div>
                  <div className="sublbl">{s.sub}</div>
                </div>
              </div>
            ))}
            <div className="wizard-progress-card">
              <div className="space-between">
                <span style={{ fontSize: 12, color: "var(--muted)", fontWeight: 500 }}>项目完成度</span>
                <b style={{ fontSize: 13, color: "var(--ocean)" }}>{Math.round(((step + 1) / steps.length) * 100)}%</b>
              </div>
              <div className="progress-bar" style={{ marginTop: 8 }}>
                <div className="progress-fill" style={{ width: ((step + 1) / steps.length) * 100 + "%" }} />
              </div>
            </div>
          </div>

          <div className="wizard-body">
            <div className="wizard-main">
              {step === 0 && (
                <StepFiles
                  inputRef={inputRef}
                  pendingFiles={pendingFiles}
                  uploadedFiles={filesForProject}
                  fileAnalyses={fileAnalyses}
                  addFiles={addFiles}
                  removePendingFile={removePendingFile}
                  working={working}
                />
              )}
              {step === 1 && <StepProduct draft={draft} update={updateDraft} fileAnalyses={fileAnalyses} />}
              {step === 2 && <StepMarket draft={draft} update={updateDraft} />}
              {step === 3 && <StepCustomerChannel draft={draft} update={updateDraft} />}
              {step === 4 && <StepResources draft={draft} update={updateDraft} />}
              {step === 5 && <StepGoals draft={draft} update={updateDraft} recommendGoals={recommendGoals} working={working} />}
              {step === 6 && <StepAIFlow />}
              {step === 7 && <StepReview draft={draft} files={filesForProject} fileAnalyses={fileAnalyses} working={working} />}

              <div className="wizard-actions">
                <button className="btn btn-ghost" disabled={step === 0 || working} onClick={() => setStep(step - 1)} style={{ opacity: step === 0 ? 0.4 : 1 }}>
                  <Icon name="arrow-left" size={14} /> 上一步
                </button>
                <div className="row-flex">
                  {step < steps.length - 1 ? (
                    <button className="btn btn-primary" disabled={working} onClick={nextStep}>
                      {working && step === 0 ? "正在上传并解析..." : "下一步"} <Icon name="arrow-right" size={14} />
                    </button>
                  ) : (
                    <button className="btn btn-primary" disabled={working} onClick={generateReport}>
                      <Icon name="spark" size={14} /> {working ? "正在调用 MCP 与 AI 生成报告..." : "生成报告"}
                    </button>
                  )}
                </div>
              </div>
            </div>
          </div>
          <WizardAssistantFloating
            open={assistantOpen}
            setOpen={setAssistantOpen}
            step={step}
            working={working}
            recommendGoals={recommendGoals}
          />
        </div>
      </div>
    </AppShell>
  );
}

function WizardAssistantFloating({ open, setOpen, step, working, recommendGoals }) {
  return (
    <>
      <button
        className={"wizard-assistant-fab " + (open ? "active" : "")}
        onClick={() => setOpen(!open)}
        aria-expanded={open}
        aria-label={open ? "收起 AI 助手" : "展开 AI 助手"}
      >
        <Icon name="sparkles" size={18} />
        <span>AI 助手</span>
      </button>
      {open ? (
        <div className="wizard-assistant-float" role="dialog" aria-label="AI 助手">
          <div className="wizard-assistant-head">
            <h4><Icon name="sparkles" size={15} color="var(--teal)" /> AI 助手</h4>
            <button className="file-remove-btn" onClick={() => setOpen(false)} aria-label="关闭 AI 助手">
              <Icon name="x" size={14} />
            </button>
          </div>
          <p>{asideText[step]}</p>
          <div className="tip-list">
            {asideTips[step].map((tip, index) => <div key={index} className="tip">{tip}</div>)}
          </div>
          {step === 5 ? (
            <button className="btn btn-soft btn-sm btn-block" disabled={working} style={{ marginTop: 14 }} onClick={recommendGoals}>
              <Icon name="wand" size={14} /> AI 推荐目标
            </button>
          ) : null}
        </div>
      ) : null}
    </>
  );
}

function projectToDraft(project) {
  const resources = parseJson(project?.resources, {});
  const goals = parseJson(project?.goals, {});
  return {
    name: project?.name || "",
    productName: project?.product_name || "",
    productEnglishName: resources.productEnglishName || "",
    productCategory: project?.product_category || "",
    hsCode: resources.hsCode || "",
    productDescription: project?.product_description || "",
    factoryCost: project?.factory_cost || "",
    productPrice: project?.product_price || "",
    moq: resources.moq || "",
    monthlyCapacity: resources.monthlyCapacity || "",
    brand: resources.brand || "",
    certifications: resources.certifications || [],
    targetCountry: project?.target_country || "Canada",
    targetRegion: project?.target_region || "North America",
    targetCity: project?.target_city || "Toronto",
    targetCustomerSegment: project?.target_customer_segment || "",
    targetChannel: parseJson(project?.target_channel, []),
    budgetRange: resources.budgetRange || "10-30 万 CNY",
    launchTiming: resources.launchTiming || "未来 3 个月",
    teamStatus: resources.teamStatus || {},
    oneYearGoal: goals.oneYear || "",
    threeYearGoal: goals.threeYear || "",
    fiveYearGoal: goals.fiveYear || "",
  };
}

function projectPayload(draft, options = {}) {
  const firstFile = options.pendingFiles?.[0]?.name?.replace(/\.[^.]+$/, "") || "";
  const productName = draft.productName || (options.allowPlaceholder ? firstFile || "待识别产品" : "");
  const productCategory = draft.productCategory || (options.allowPlaceholder ? "待解析产品资料" : "");
  const marketName = draft.targetCountry || "Canada";
  const generatedName = `${productName || productCategory || "新产品"} — ${marketName} 市场进入`;
  return {
    name: draft.name || generatedName,
    productName,
    productCategory,
    productDescription: draft.productDescription,
    productPrice: draft.productPrice,
    factoryCost: draft.factoryCost,
    targetCountry: marketName,
    targetRegion: draft.targetRegion || "North America",
    targetCity: draft.targetCity,
    targetCustomerSegment: draft.targetCustomerSegment,
    targetChannel: draft.targetChannel,
    resources: {
      productEnglishName: draft.productEnglishName,
      hsCode: draft.hsCode,
      moq: draft.moq,
      monthlyCapacity: draft.monthlyCapacity,
      brand: draft.brand,
      certifications: draft.certifications,
      budgetRange: draft.budgetRange,
      launchTiming: draft.launchTiming,
      teamStatus: draft.teamStatus,
    },
    goals: {
      oneYear: draft.oneYearGoal,
      threeYear: draft.threeYearGoal,
      fiveYear: draft.fiveYearGoal,
    },
    status: "draft",
  };
}

const asideText = [
  "先上传已有资料。系统会保存原件到 R2，解析结构化数据到 D1，并生成可检索的 RAG chunk。",
  "这里展示 AI 从文件中识别出的产品、型号、价格、认证和产能。请人工检查后确认。",
  "目标国家和城市会决定 MCP 采集的数据源，包括市场、税务、合规、运输和通关信息。",
  "客群和渠道决定价格带、销售动作和入驻门槛。大型商超、批发、电商的资料要求完全不同。",
  "资源评估用于判断哪些事情自建，哪些要外包给报关行、CPA、律师、仓配和代理商。",
  "目标越具体，报告路线图越可执行。可以点 tag 快速填入，也可以让 OpenAI 生成建议。",
  "AI 会根据当前资料列出关键追问。暂时不知道也可以继续生成报告，报告会标注假设边界。",
  "点击生成后，后端会先调用 MCP 获取目标市场资料，再结合用户数据和 OpenAI 生成完整报告。",
];

const asideTips = [
  ["支持 xlsx / csv / pdf / docx / txt / jpg / png / webp", "拖入后可先检查列表，点击 x 删除", "点下一步才会正式上传和解析"],
  ["自动填充只作为初稿，最终以人工确认为准", "型号表和报价表会抽取为结构化记录", "图片已保留原件，后续接 OCR"],
  ["加拿大 / 美国报告深度最高", "可先选主市场，后续生成多市场专项报告", "城市会影响渠道和税务判断"],
  ["Walmart / Costco 等大型商超更看重准入材料", "批发渠道关注 MOQ、交期和稳定供货", "DTC 更看重品牌、内容和退货体验"],
  ["缺少本地 CPA / 报关行会被标记为风险", "已有认证和产能会转化为渠道准入材料", "预算会影响首批试单建议"],
  ["1 年目标建议聚焦验证", "3 年目标关注稳态渠道和利润率", "5 年目标考虑品牌与多市场扩张"],
  ["追问答案会影响报告章节质量", "不确定事项会写入专家确认清单", "可继续上传补充资料后重生成"],
  ["报告会持久化到 D1", "支持页面展示、PDF、DOCX、Markdown 下载", "专业事项会保留免责声明和专家确认项"],
];

function StepFiles({ inputRef, pendingFiles, uploadedFiles, fileAnalyses, addFiles, removePendingFile, working }) {
  const [dragging, setDragging] = React.useState(false);
  return (
    <>
      <h2>上传产品资料</h2>
      <p className="lead">请先上传产品目录、报价表、企业介绍、认证文件、产品图片等资料。下一步会基于解析结果自动填写产品基础信息。</p>
      <input
        ref={inputRef}
        type="file"
        multiple
        accept=".xlsx,.csv,.pdf,.docx,.txt,.jpg,.jpeg,.png,.webp,.md"
        style={{ display: "none" }}
        onChange={(event) => addFiles(event.target.files)}
      />
      <div
        className={"upload-zone wizard-upload-zone " + (dragging ? "dragging" : "")}
        onClick={() => inputRef.current?.click()}
        onDragOver={(event) => { event.preventDefault(); setDragging(true); }}
        onDragLeave={() => setDragging(false)}
        onDrop={(event) => {
          event.preventDefault();
          setDragging(false);
          addFiles(event.dataTransfer.files);
        }}
      >
        <Icon name="upload" size={30} color="var(--ocean)" />
        <h4>{dragging ? "松开后加入上传列表" : "拖入文件，或点击浏览"}</h4>
        <p>支持 .xlsx · .csv · .pdf · .docx · .txt · .jpg · .png · .webp</p>
      </div>

      <FileQueue title="待上传文件" empty="尚未选择文件" files={pendingFiles} removable removePendingFile={removePendingFile} />
      <UploadedFileList title="已上传并解析" files={uploadedFiles} />

      <div className="analysis-grid">
        <MiniMetric label="待上传" value={pendingFiles.length} />
        <MiniMetric label="已保存" value={uploadedFiles.length} />
        <MiniMetric label="已分析" value={fileAnalyses.length} />
        <MiniMetric label="状态" value={working ? "处理中" : "就绪"} />
      </div>
    </>
  );
}

function FileQueue({ title, empty, files, removable, removePendingFile }) {
  return (
    <div className="wizard-file-panel">
      <div className="space-between">
        <h3>{title}</h3>
        <span className="badge">{files.length} 个</span>
      </div>
      <div className="col-flex" style={{ marginTop: 12, gap: 8 }}>
        {files.length ? files.map((file, index) => (
          <div key={`${file.name}-${file.size}-${index}`} className="file-row">
            <div className="ico">{fileExt(file.name)}</div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="name">{file.name}</div>
              <div className="meta">{formatBytes(file.size)} · 点击下一步后上传</div>
            </div>
            {removable ? (
              <button className="file-remove-btn" onClick={() => removePendingFile(index)} aria-label={`删除 ${file.name}`}>
                <Icon name="x" size={14} />
              </button>
            ) : null}
          </div>
        )) : <div className="empty-inline">{empty}</div>}
      </div>
    </div>
  );
}

function UploadedFileList({ title, files }) {
  return (
    <div className="wizard-file-panel">
      <div className="space-between">
        <h3>{title}</h3>
        <span className="badge badge-green"><span className="badge-dot"></span>{files.length} 个</span>
      </div>
      <div className="col-flex" style={{ marginTop: 12, gap: 8 }}>
        {files.length ? files.map((file) => {
          const meta = parseJson(file.extracted_metadata, {});
          return (
            <div key={file.id} className="file-row">
              <div className="ico">{meta.ext || fileExt(file.file_name)}</div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="name">{file.file_name}</div>
                <div className="meta">{documentTypeLabel(meta.documentType)} · {meta.structuredRecordCount || 0} 条结构化记录</div>
              </div>
              <span className={"badge badge-" + (file.status === "parsed" ? "green" : "orange")}>{file.status}</span>
            </div>
          );
        }) : <div className="empty-inline">下一步上传后显示解析结果</div>}
      </div>
    </div>
  );
}

function StepProduct({ draft, update, fileAnalyses }) {
  const extracted = buildExtractedSummary(fileAnalyses);
  return (
    <>
      <h2>产品基础信息</h2>
      <p className="lead">系统已根据上传文件自动填入初稿。请检查、修改并确认这些信息。</p>
      {extracted.length ? (
        <div className="callout info" style={{ marginTop: 18 }}>
          <div className="callout-title">从资料中识别到的重点</div>
          {extracted.slice(0, 5).map((item, index) => <div key={index} style={{ marginTop: index ? 6 : 0 }}>{item}</div>)}
        </div>
      ) : null}
      <div className="form-grid">
        <Field label="产品中文名称" required><input className="input" value={draft.productName} onChange={(e) => update("productName", e.target.value)} /></Field>
        <Field label="产品英文名称"><input className="input" placeholder="Women's lightweight jacket" value={draft.productEnglishName} onChange={(e) => update("productEnglishName", e.target.value)} /></Field>
        <Field label="产品品类" required>
          <select className="select" value={draft.productCategory} onChange={(e) => update("productCategory", e.target.value)}>
            <option value="">请选择 / 自动识别</option>
            <option>服装成衣 · Apparel / Garments</option>
            <option>家居收纳用品 · Home Storage</option>
            <option>厨具及餐饮用品</option>
            <option>清洁与日用</option>
            <option>电子配件</option>
            <option>工业品 / B2B</option>
          </select>
        </Field>
        <Field label="海关编码 (HS Code)"><input className="input" value={draft.hsCode} onChange={(e) => update("hsCode", e.target.value)} placeholder="不知道可留空，报告中会标记待报关行确认" /></Field>
        <Field label="产品描述" full><textarea className="textarea" value={draft.productDescription} onChange={(e) => update("productDescription", e.target.value)} /></Field>
        <Field label="出厂价 / FOB"><input className="input" value={draft.factoryCost} onChange={(e) => update("factoryCost", e.target.value)} /></Field>
        <Field label="目标销售价 / 报价"><input className="input" value={draft.productPrice} onChange={(e) => update("productPrice", e.target.value)} /></Field>
        <Field label="最小起订量 MOQ"><input className="input" value={draft.moq} onChange={(e) => update("moq", e.target.value)} /></Field>
        <Field label="月生产能力"><input className="input" value={draft.monthlyCapacity} onChange={(e) => update("monthlyCapacity", e.target.value)} /></Field>
        <Field label="品牌"><input className="input" value={draft.brand} onChange={(e) => update("brand", e.target.value)} placeholder="OEM / ODM / 自有品牌" /></Field>
        <Field label="现有认证">
          <TagEditor selected={draft.certifications || []} tags={["ISO 9001", "BSCI", "SEDEX", "WRAP", "OEKO-TEX", "GRS", "GOTS", "SGS", "FDA", "不确定"]} onChange={(next) => update("certifications", next)} />
        </Field>
      </div>
    </>
  );
}

function StepMarket({ draft, update }) {
  const markets = [
    { id: "ca", country: "Canada", city: "Toronto", flag: "🇨🇦", name: "加拿大", sub: "成熟市场 · English / French", opp: 8.4, diff: "中等", comp: "中高" },
    { id: "us", country: "United States", city: "Los Angeles", flag: "🇺🇸", name: "美国", sub: "成熟市场 · English", opp: 9.1, diff: "中高", comp: "高" },
    { id: "uk", country: "United Kingdom", city: "London", flag: "🇬🇧", name: "英国", sub: "成熟市场 · English", opp: 7.6, diff: "中等", comp: "中高" },
    { id: "ae", country: "United Arab Emirates", city: "Dubai", flag: "🇦🇪", name: "阿联酋", sub: "高潜力 · English / Arabic", opp: 7.2, diff: "中等", comp: "中" },
    { id: "sg", country: "Singapore", city: "Singapore", flag: "🇸🇬", name: "新加坡", sub: "枢纽 · English / 中文", opp: 7.0, diff: "低", comp: "中" },
    { id: "de", country: "Germany", city: "Berlin", flag: "🇩🇪", name: "德国", sub: "成熟市场 · German", opp: 7.8, diff: "高", comp: "高" },
  ];
  const selected = markets.find((item) => item.country === draft.targetCountry) || markets[0];
  const cityTags = selected.country === "United States"
    ? ["Los Angeles", "New York", "Chicago", "Dallas", "全国分销"]
    : ["Toronto", "Montreal", "Vancouver", "Ottawa", "Calgary", "全国分销"];
  return (
    <>
      <h2>目标市场选择</h2>
      <p className="lead">选择主市场和目标城市。生成报告时 MCP 会按目标市场抓取公开资料和官方来源。</p>
      <div className="country-grid">
        {markets.map((m) => (
          <div key={m.id} className={"country-card " + (selected.id === m.id ? "active" : "")} onClick={() => { update("targetCountry", m.country); update("targetCity", m.city); }}>
            <div className="space-between">
              <span className="flag">{m.flag}</span>
              {selected.id === m.id && <span className="country-check"><Icon name="check" size={12} /></span>}
            </div>
            <h4>{m.name}</h4>
            <div className="sub">{m.sub}</div>
            <div className="scores">
              <span>机会 <b>{m.opp}</b>/10</span>
              <span>难度 <b>{m.diff}</b></span>
              <span>合规 <b>{m.comp}</b></span>
            </div>
          </div>
        ))}
      </div>
      <div style={{ marginTop: 22 }}>
        <label className="field-label" style={{ display: "block", marginBottom: 8 }}>目标城市 / 区域</label>
        <div className="row-flex">
          {cityTags.map((city) => <button key={city} className={"chip " + (draft.targetCity === city ? "active" : "")} onClick={() => update("targetCity", city)}>{city}</button>)}
        </div>
      </div>
    </>
  );
}

function StepCustomerChannel({ draft, update }) {
  const customerSegments = ["大型商超采购", "批发商", "经销商", "独立零售店", "Amazon 卖家", "DTC 消费者", "中端家庭", "华人社区", "本地主流"];
  const selectedCustomers = String(draft.targetCustomerSegment || "").split(/[·,，]/).map((item) => item.trim()).filter(Boolean);
  const toggleCustomer = (label) => {
    const next = selectedCustomers.includes(label) ? selectedCustomers.filter((item) => item !== label) : [...selectedCustomers, label];
    update("targetCustomerSegment", next.join(" · "));
  };
  const channels = [
    { lbl: "Walmart", icon: "store" },
    { lbl: "Costco", icon: "store" },
    { lbl: "Target", icon: "store" },
    { lbl: "Amazon", icon: "store" },
    { lbl: "Shopify 独立站", icon: "globe" },
    { lbl: "本地批发", icon: "package" },
    { lbl: "本地零售", icon: "store" },
    { lbl: "代理商", icon: "users" },
    { lbl: "海外仓", icon: "package" },
    { lbl: "展会", icon: "tag" },
    { lbl: "本地销售团队", icon: "users" },
    { lbl: "社媒内容", icon: "trend" },
  ];
  const targetChannel = draft.targetChannel || [];
  const toggleChannel = (label) => update("targetChannel", targetChannel.includes(label) ? targetChannel.filter((item) => item !== label) : [...targetChannel, label]);
  return (
    <>
      <h2>客群与渠道</h2>
      <p className="lead">选择目标客群和渠道。大型商超、批发、电商和本地零售对应不同资料、费用和风险。</p>
      <div style={{ marginTop: 22 }}>
        <label className="field-label" style={{ display: "block", marginBottom: 10 }}>目标客群</label>
        <div className="row-flex">{customerSegments.map((label) => <button key={label} className={"chip " + (selectedCustomers.includes(label) ? "active" : "")} onClick={() => toggleCustomer(label)}>{label}</button>)}</div>
      </div>
      <div style={{ marginTop: 26 }}>
        <label className="field-label" style={{ display: "block", marginBottom: 10 }}>目标渠道</label>
        <div className="channel-grid">
          {channels.map((c) => (
            <button key={c.lbl} className={"chip channel-chip " + (targetChannel.includes(c.lbl) ? "active" : "")} onClick={() => toggleChannel(c.lbl)}>
              <Icon name={c.icon} size={14} /> {c.lbl}
            </button>
          ))}
        </div>
      </div>
      <div className="callout info" style={{ marginTop: 24 }}>
        <div className="callout-title">渠道提醒</div>
        若选择 Walmart / Costco 等大型商超，报告会重点分析供应商准入、条码、保险、标签、审核、贸易条款、代理和履约成本。
      </div>
    </>
  );
}

function StepResources({ draft, update }) {
  const fields = [
    ["factory", "自有工厂", ["已有", "合作工厂", "暂无", "不确定"]],
    ["exportTeam", "外贸 / 业务团队", ["已有 2 人以上", "兼职负责", "暂无", "不确定"]],
    ["overseasEntity", "海外公司", ["已有", "筹备中", "暂无", "不确定"]],
    ["warehouse", "海外仓 / 仓配", ["已有合作", "询价中", "暂无", "不确定"]],
    ["customsBroker", "报关行", ["已有", "国内有", "海外暂无", "不确定"]],
    ["localCPA", "北美 CPA", ["已有", "需要推荐", "暂无", "不确定"]],
    ["lawyer", "律师 / 商标代理", ["已有", "需要推荐", "暂无", "不确定"]],
    ["retailAgent", "渠道代理", ["已有", "寻找中", "暂无", "不确定"]],
  ];
  const teamStatus = draft.teamStatus || {};
  return (
    <>
      <h2>资源与现状</h2>
      <p className="lead">这些信息会影响报告中的自建/外包建议、成本项和专家确认清单。</p>
      <div className="form-grid">
        {fields.map(([key, label, options]) => (
          <Field key={key} label={label}>
            <select className="select" value={teamStatus[key] || ""} onChange={(e) => update("teamStatus", { ...teamStatus, [key]: e.target.value })}>
              <option value="">请选择</option>
              {options.map((option) => <option key={option}>{option}</option>)}
            </select>
          </Field>
        ))}
        <Field label="出海预算"><select className="select" value={draft.budgetRange} onChange={(e) => update("budgetRange", e.target.value)}><option>10-30 万 CNY</option><option>30-100 万 CNY</option><option>100-300 万 CNY</option><option>300 万 CNY 以上</option></select></Field>
        <Field label="预计启动时间"><select className="select" value={draft.launchTiming} onChange={(e) => update("launchTiming", e.target.value)}><option>未来 3 个月</option><option>未来 6 个月</option><option>未来 12 个月</option><option>先做可行性调研</option></select></Field>
      </div>
    </>
  );
}

function StepGoals({ draft, update, recommendGoals, working }) {
  const goals = [
    { key: "oneYearGoal", period: "1 年", color: "var(--teal)", tags: ["完成首批试单", "进入 2-3 个批发客户", "完成大型商超准入准备", "验证 Amazon / Shopify 销售", "完成标签与合规复核"] },
    { key: "threeYearGoal", period: "3 年", color: "var(--ocean)", tags: ["建立稳定经销网络", "形成海外仓履约体系", "进入 1 个大型连锁渠道", "年营收达到 CAD 1M", "建立本地售后流程"] },
    { key: "fiveYearGoal", period: "5 年", color: "var(--orange)", tags: ["形成北美品牌化经营", "覆盖美国和加拿大", "建立本地团队", "多渠道收入结构", "推出本地化产品线"] },
  ];
  const applyTag = (key, tag) => {
    const current = draft[key] || "";
    update(key, current.includes(tag) ? current : current ? `${current}；${tag}` : tag);
  };
  return (
    <>
      <div className="space-between goal-title-row">
        <div>
          <h2>1 / 3 / 5 年目标</h2>
          <p className="lead">点击 tag 可快速填入。点击 AI 推荐会调用 OpenAI 基于产品资料生成阶段目标。</p>
        </div>
        <button className="btn btn-soft btn-sm" disabled={working} onClick={recommendGoals}><Icon name="wand" size={14} /> AI 推荐</button>
      </div>
      <div className="goal-stack">
        {goals.map((g) => (
          <div key={g.key} className="goal-card">
            <div className="space-between">
              <div className="row-flex">
                <span className="goal-period" style={{ background: g.color }}>{g.period}</span>
                <span style={{ fontWeight: 600, fontSize: 15 }}>{g.period} 目标</span>
              </div>
            </div>
            <div className="goal-tags">{g.tags.map((tag) => <button key={tag} className="chip" onClick={() => applyTag(g.key, tag)}>{tag}</button>)}</div>
            <textarea className="textarea" value={draft[g.key] || ""} onChange={(e) => update(g.key, e.target.value)} style={{ marginTop: 12, minHeight: 82 }} />
          </div>
        ))}
      </div>
    </>
  );
}

function StepAIFlow() {
  return (
    <>
      <h2>AI 追问补全</h2>
      <p className="lead">当前版本会在生成报告前自动生成 AI 追问，并把缺失信息写入报告的风险与专家确认部分。</p>
      <div className="ai-flow" style={{ marginTop: 22 }}>
        <div className="ai-bubble from-ai">
          <div className="who"><span className="dot" /> 调研 AI · 关于合规</div>
          <p>若产品进入美国或加拿大的大型商超，是否已有英文标签、纤维成分、原产地、条码和产品责任险资料？</p>
          <div className="ai-options">{["已准备", "部分准备", "不确定", "需要专家确认"].map((o, i) => <button key={i} className={"chip " + (i === 2 ? "active" : "")}>{o}</button>)}</div>
        </div>
        <div className="ai-bubble from-ai">
          <div className="who"><span className="dot" /> 调研 AI · 关于成本</div>
          <p>如果缺少物流、清关、税费和代理费用，报告会用 MCP 采集的公开资料建立核验框架，并标注“需报价确认”。</p>
        </div>
      </div>
    </>
  );
}

function StepReview({ draft, files, fileAnalyses, working }) {
  const sourceTypes = uniqueStrings(fileAnalyses.map((item) => documentTypeLabel(item.ingestion?.detected_document_type)).filter(Boolean));
  return (
    <>
      <h2>确认并生成报告</h2>
      <p className="lead">点击生成后，系统会先调用 MCP 抓取目标国家和目标市场资料，再综合用户资料与公开来源生成完整报告。</p>
      <div className="review-grid">
        {[
          { lbl: "项目名称", val: `${draft.productName || draft.productCategory || "新产品"} — ${draft.targetCountry || "Canada"} 市场进入` },
          { lbl: "产品", val: `${draft.productName || "未填写"} · ${draft.productCategory || "未填写"}` },
          { lbl: "目标市场", val: `${draft.targetCountry || "Canada"} · ${draft.targetCity || "未填写"}` },
          { lbl: "客群", val: draft.targetCustomerSegment || "未填写" },
          { lbl: "渠道", val: (draft.targetChannel || []).join(" + ") || "未填写" },
          { lbl: "资料", val: `${files.length} 份 · ${sourceTypes.join(" / ") || "待解析"}` },
          { lbl: "1 年目标", val: draft.oneYearGoal || "未填写" },
          { lbl: "3 年目标", val: draft.threeYearGoal || "未填写" },
        ].map((r) => (
          <div key={r.lbl} className="review-item">
            <div>{r.lbl}</div>
            <b>{r.val}</b>
          </div>
        ))}
      </div>
      <div className="divider" />
      <h3 style={{ fontSize: 16, fontWeight: 600 }}>生成链路</h3>
      <div className="report-pipeline">
        {[
          ["资料解析", "读取 D1/R2 中的产品、企业、报价、型号结构化数据"],
          ["MCP 采集", "抓取目标市场、渠道、税务、合规、运输、通关等公开资料"],
          ["OpenAI 分析", "综合用户资料与 MCP 来源，生成完整市场进入报告"],
          ["持久化下载", "报告写入 D1，支持页面展示、PDF、DOCX、Markdown 下载"],
        ].map(([title, body], index) => (
          <div key={title} className="pipeline-step">
            <span>{index + 1}</span>
            <div><b>{title}</b><p>{body}</p></div>
          </div>
        ))}
      </div>
      <div className="callout" style={{ marginTop: 18 }}>
        <div className="callout-title">合规边界</div>
        报告用于市场调研和商业决策参考，不构成法律、税务、投资、报关、认证或移民意见。高风险事项会列入专家确认清单。
      </div>
      {working ? <div className="callout info" style={{ marginTop: 14 }}>正在执行 MCP 资料采集与 OpenAI 报告生成，请稍候。</div> : null}
    </>
  );
}

function Field({ label, required, full, children }) {
  return <div className={"field " + (full ? "full" : "")}><label className="field-label">{label} {required ? <span style={{ color: "var(--red)" }}>*</span> : null}</label>{children}</div>;
}

function TagEditor({ selected, tags, onChange }) {
  const toggle = (tag) => {
    const next = selected.includes(tag) ? selected.filter((item) => item !== tag) : [...selected, tag];
    onChange(next);
  };
  return <div className="row-flex" style={{ marginTop: 4 }}>{tags.map((tag) => <button key={tag} className={"chip " + (selected.includes(tag) ? "active" : "")} onClick={() => toggle(tag)}>{tag}</button>)}</div>;
}

function MiniMetric({ label, value }) {
  return <div className="mini-metric"><span>{label}</span><b>{value}</b></div>;
}

function fileExt(name = "") {
  const ext = name.split(".").pop() || "FILE";
  return ext.slice(0, 4).toUpperCase();
}

function isSupportedProductFile(file) {
  return /\.(xlsx|csv|pdf|docx|txt|jpg|jpeg|png|webp|md)$/i.test(file.name || "");
}

function dedupeFiles(files) {
  const seen = new Set();
  return files.filter((file) => {
    const key = `${file.name}:${file.size}:${file.lastModified}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

function mergeFiles(a = [], b = []) {
  const map = new Map();
  [...a, ...b].forEach((file) => map.set(file.id || `${file.name}-${file.size}`, file));
  return Array.from(map.values());
}

function mergeAnalyses(a = [], b = []) {
  const map = new Map();
  [...a, ...b].forEach((item) => map.set(item.file?.id || item.ingestion?.file_id || Math.random(), item));
  return Array.from(map.values());
}

function formatBytes(size = 0) {
  if (size < 1024) return `${size} B`;
  if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
  return `${(size / 1024 / 1024).toFixed(1)} MB`;
}

function documentTypeLabel(type = "") {
  const labels = {
    enterprise_introduction: "企业介绍",
    product_introduction: "产品介绍",
    product_model_data: "产品型号数据",
    product_quote_data: "产品报价数据",
    mixed_business_pack: "混合商务资料",
    unknown_business_document: "商务文档",
  };
  return labels[type] || type || "资料";
}

function draftFromDocumentAnalyses(analyses = []) {
  const records = analyses.flatMap((item) => item.records || []);
  const productRecords = records.filter((record) => ["product_quote", "product_model", "product_intro"].includes(record.record_type));
  const enterprise = records.find((record) => record.record_type === "enterprise_profile")?.data || {};
  const firstProduct = productRecords.find((record) => record.data?.productName || record.title)?.data || {};
  const quote = productRecords.find((record) => record.record_type === "product_quote")?.data || {};
  const text = analyses.map((item) => item.file?.extracted_text || item.records?.map((record) => record.source_excerpt).join("\n") || "").join("\n");
  const certifications = uniqueStrings([
    ...(enterprise.certifications || []),
    ...Array.from(text.matchAll(/\b(ISO\s?9001|ISO\s?14001|BSCI|SEDEX|WRAP|OEKO[- ]?TEX|GRS|GOTS|SGS|FDA)\b/gi)).map((m) => m[1].toUpperCase().replace(/\s+/g, " "))
  ]);
  return {
    productName: firstProduct.productName || productRecords[0]?.title || "",
    productEnglishName: inferEnglishProductName(firstProduct.productName || productRecords[0]?.title || ""),
    productCategory: firstProduct.productType || inferProductCategory(text),
    productDescription: buildProductDescription(records, text),
    factoryCost: quote.price || "",
    productPrice: quote.price || "",
    moq: quote.moq || firstProduct.moq || "",
    monthlyCapacity: Array.isArray(enterprise.capacity) ? enterprise.capacity[0] : "",
    brand: firstProduct.brand || "",
    certifications,
  };
}

function fillDraftFromAnalysis(current, autofill) {
  const next = { ...current };
  for (const [key, value] of Object.entries(autofill)) {
    if (Array.isArray(value)) {
      next[key] = uniqueStrings([...(current[key] || []), ...value]);
    } else if (!current[key] && value) {
      next[key] = value;
    }
  }
  return next;
}

function buildExtractedSummary(analyses = []) {
  const records = analyses.flatMap((item) => item.records || []);
  const counts = records.reduce((acc, record) => {
    acc[record.record_type] = (acc[record.record_type] || 0) + 1;
    return acc;
  }, {});
  const summaries = analyses.map((item) => item.ingestion?.value_summary).filter(Boolean);
  return [
    summaries[0],
    counts.enterprise_profile ? `企业资料：${counts.enterprise_profile} 条` : "",
    counts.product_model ? `产品型号：${counts.product_model} 条` : "",
    counts.product_quote ? `报价记录：${counts.product_quote} 条` : "",
  ].filter(Boolean);
}

function buildProductDescription(records, text) {
  const products = records.filter((record) => ["product_model", "product_quote"].includes(record.record_type)).slice(0, 5).map((record) => record.title).filter(Boolean);
  const enterpriseExcerpt = records.find((record) => record.record_type === "enterprise_profile")?.data?.sourceExcerpt || "";
  return [
    products.length ? `主要产品：${uniqueStrings(products).join("、")}。` : "",
    enterpriseExcerpt ? String(enterpriseExcerpt).slice(0, 260) : "",
    !products.length && !enterpriseExcerpt ? text.slice(0, 360) : ""
  ].filter(Boolean).join("\n");
}

function inferProductCategory(text = "") {
  if (/apparel|garment|clothing|jacket|pants|shirt|dress|服装|成衣|夹克|裤|衬衫|连衣裙/i.test(text)) return "服装成衣 · Apparel / Garments";
  if (/storage|box|container|收纳/i.test(text)) return "家居收纳用品 · Home Storage";
  if (/kitchen|cookware|餐|厨/i.test(text)) return "厨具及餐饮用品";
  return "";
}

function inferEnglishProductName(value = "") {
  return /[a-z]/i.test(value) ? value : "";
}

function uniqueStrings(values = []) {
  return Array.from(new Set(values.map((value) => String(value || "").trim()).filter(Boolean)));
}

window.Wizard = Wizard;
