/* eslint-disable @next/next/no-img-element */
"use client"

import * as React from "react"
import {
  Activity, Archive, ArrowLeft, BadgeCheck, Building2, CheckCircle2, ChevronRight,
  CircleAlert, Database, Download, ExternalLink, FileArchive, FileCheck2,
  FileSpreadsheet, FolderKanban, History, ImageUp, LoaderCircle, LockKeyhole,
  MoreHorizontal, Pencil, Plus, RefreshCw, RotateCcw, Send, ShieldCheck, Trash2,
  UnlockKeyhole, UploadCloud, UserPlus, Users, XCircle,
} from "lucide-react"
import { toast } from "sonner"

import {
  AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
  AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
} from "@/components/ui/dialog"
import {
  DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Progress } from "@/components/ui/progress"
import {
  Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} from "@/components/ui/select"
import {
  Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
} from "@/components/ui/table"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Textarea } from "@/components/ui/textarea"
import { createCsvTemplate, importDefinitions } from "@/lib/geo-import"
import { AowangBrand } from "./brand-lockup"

export type OpsProject = {
  id: string
  slug: string
  projectCode: string
  projectType: "client" | "reference"
  projectName: string
  brandName: string
  clientName: string
  coreKeyword: string
  derivativeKeywordsJson: string
  platformsJson: string
  optimizationRegion: string
  serviceScope: string
  acceptanceRulesJson: string
  measurementProtocolJson: string
  configVersion: number
  configFingerprint: string
  frozenAt: string | null
  frozenBy: string | null
  startDate: string | null
  endDate: string | null
  baselineMonth: string | null
  currentMonth: string | null
  status: string
  dataMode: string
  logoKey: string | null
  updatedAt: string
  deletedAt: string | null
  deletedBy: string | null
  deleteAfter: string | null
  evidenceCount: number
  deliveryCount: number
  publishedCount: number
}

export type OpsSnapshot = {
  id: string
  projectId: string
  brandName: string
  reportMonth: string
  version: number
  status: string
  qualityScore: number
  answerCount: number
  deliveryCount: number
  evidenceCount: number
  sourceFilename: string | null
  summaryJson: string
  gateJson: string
  scopeFingerprint: string
  reviewedBy: string | null
  reviewNote: string | null
  publishedBy: string | null
  acceptanceStatus: string
  updatedAt: string
  publishedAt: string | null
}

export type OpsImport = {
  id: string
  projectId: string
  brandName: string
  snapshotId: string | null
  importType: string
  filename: string
  status: string
  totalRows: number
  validRows: number
  errorRows: number
  errorsJson: string
  createdAt: string
}

export type OpsMember = {
  id: string
  projectId: string
  brandName: string
  userEmail: string
  displayName: string | null
  role: string
  status: string
  createdAt: string
  updatedAt: string
}

export type OpsAudit = {
  id: string
  projectId: string | null
  brandName: string | null
  actorEmail: string
  action: string
  targetType: string
  targetId: string | null
  detailJson: string
  createdAt: string
}

export type OpsWorkspace = {
  user: {
    email: string
    displayName: string
    role: string
    canWrite: boolean
    canManageProjects: boolean
    canPublish: boolean
  }
  projects: OpsProject[]
  snapshots: OpsSnapshot[]
  imports: OpsImport[]
  members: OpsMember[]
  audits: OpsAudit[]
}

type ProjectForm = {
  templateSourceId: string
  projectName: string
  brandName: string
  brandAliases: string
  clientName: string
  coreKeyword: string
  derivativeKeywords: string
  platforms: string
  competitors: string
  optimizationRegion: string
  serviceScope: string
  targetMentionRate: string
  targetRecommendRate: string
  targetTop3Rate: string
  deliveryTarget: string
  minDeliveryCompletion: string
  minAnswerCount: string
  minValidResponseRate: string
  minEvidenceCoverage: string
  minCitationEvidenceCoverage: string
  minAccuracyCoverage: string
  promptPoolVersion: string
  detectionCount: string
  modelScope: string
  judgmentRulesVersion: string
  startDate: string
  endDate: string
  baselineMonth: string
  currentMonth: string
  changeReason: string
}

type GateCheck = { key: string; label: string; passed: boolean }

const emptyProject: ProjectForm = {
  templateSourceId: "", projectName: "", brandName: "", brandAliases: "", clientName: "",
  coreKeyword: "", derivativeKeywords: "", platforms: "", competitors: "",
  optimizationRegion: "", serviceScope: "domestic", targetMentionRate: "",
  targetRecommendRate: "", targetTop3Rate: "", deliveryTarget: "", minDeliveryCompletion: "100",
  minAnswerCount: "1", minValidResponseRate: "90", minEvidenceCoverage: "100",
  minCitationEvidenceCoverage: "100", minAccuracyCoverage: "100",
  promptPoolVersion: "V1.0", detectionCount: "1", modelScope: "以导入记录为准",
  judgmentRulesVersion: "V1.0", startDate: "", endDate: "", baselineMonth: "",
  currentMonth: "", changeReason: "",
}

function parseJson<T>(source: string | null | undefined, fallback: T): T {
  try { return JSON.parse(source ?? "") as T } catch { return fallback }
}

function formatTime(source: string | null) {
  if (!source) return "—"
  const date = new Date(source)
  return Number.isNaN(date.getTime()) ? source : date.toLocaleString("zh-CN", { hour12: false })
}

function modeLabel(mode: string) {
  if (mode === "live") return ["已接入", "green"] as const
  if (mode === "reference") return ["标准验收样板", "orange"] as const
  return ["待导入", "blue"] as const
}

function statusLabel(status: string) {
  if (status === "published") return "已发布"
  if (status === "superseded") return "历史版本"
  return "待复核"
}

function acceptanceLabel(status: string) {
  if (status === "accepted") return "甲方已验收"
  if (status === "disputed") return "甲方有异议"
  if (status === "awaiting_acceptance") return "待甲方验收"
  return "尚未发起"
}

function auditLabel(action: string) {
  const labels: Record<string, string> = {
    "project.created": "新建项目", "project.updated": "更新项目配置",
    "project.scope_frozen": "冻结测量口径", "project.scope_reopened": "解除口径冻结",
    "project.logo_uploaded": "更新品牌 Logo", "project.archived": "归档项目",
    "project.recycled": "移入回收站", "project.restored": "恢复项目",
    "member.granted": "授予项目权限", "member.revoked": "停用项目权限",
    "data.imported": "导入结构化数据", "evidence.uploaded": "归档回答证据",
    "snapshot.published": "发布月度快照", "acceptance.accepted": "确认验收",
    "acceptance.disputed": "提交验收异议",
  }
  return labels[action] ?? action
}

function formFromProject(project: OpsProject): ProjectForm {
  const rules = parseJson<Record<string, number>>(project.acceptanceRulesJson, {})
  const protocol = parseJson<Record<string, unknown>>(project.measurementProtocolJson, {})
  return {
    ...emptyProject,
    projectName: project.projectName,
    brandName: project.brandName,
    brandAliases: Array.isArray(protocol.brandAliases) ? protocol.brandAliases.join("、") : project.brandName,
    clientName: project.clientName,
    coreKeyword: project.coreKeyword,
    derivativeKeywords: parseJson<string[]>(project.derivativeKeywordsJson, []).join("、"),
    platforms: parseJson<string[]>(project.platformsJson, []).join("、"),
    competitors: Array.isArray(protocol.competitorSet) ? protocol.competitorSet.join("、") : "",
    optimizationRegion: project.optimizationRegion,
    serviceScope: project.serviceScope,
    targetMentionRate: String(rules.mentionRate ?? ""),
    targetRecommendRate: String(rules.recommendRate ?? ""),
    targetTop3Rate: String(rules.top3Rate ?? ""),
    deliveryTarget: String(rules.deliveryTarget ?? ""),
    minDeliveryCompletion: String(rules.minDeliveryCompletion ?? 100),
    minAnswerCount: String(rules.minAnswerCount ?? 1),
    minValidResponseRate: String(rules.minValidResponseRate ?? 90),
    minEvidenceCoverage: String(rules.minEvidenceCoverage ?? 100),
    minCitationEvidenceCoverage: String(rules.minCitationEvidenceCoverage ?? 100),
    minAccuracyCoverage: String(rules.minAccuracyCoverage ?? 100),
    promptPoolVersion: String(protocol.promptPoolVersion ?? "V1.0"),
    detectionCount: String(protocol.detectionCount ?? 1),
    modelScope: String(protocol.modelScope ?? "以导入记录为准"),
    judgmentRulesVersion: String(protocol.judgmentRulesVersion ?? "V1.0"),
    startDate: project.startDate ?? "",
    endDate: project.endDate ?? "",
    baselineMonth: project.baselineMonth ?? "",
    currentMonth: project.currentMonth ?? "",
    changeReason: "",
  }
}

function withReferenceDefaults(base: ProjectForm, reference: OpsProject): ProjectForm {
  const defaults = formFromProject(reference)
  return {
    ...base,
    templateSourceId: reference.id,
    platforms: defaults.platforms,
    optimizationRegion: defaults.optimizationRegion,
    serviceScope: defaults.serviceScope,
    targetMentionRate: defaults.targetMentionRate,
    targetRecommendRate: defaults.targetRecommendRate,
    targetTop3Rate: defaults.targetTop3Rate,
    deliveryTarget: defaults.deliveryTarget,
    minDeliveryCompletion: defaults.minDeliveryCompletion,
    minAnswerCount: defaults.minAnswerCount,
    minValidResponseRate: defaults.minValidResponseRate,
    minEvidenceCoverage: defaults.minEvidenceCoverage,
    minCitationEvidenceCoverage: defaults.minCitationEvidenceCoverage,
    minAccuracyCoverage: defaults.minAccuracyCoverage,
    promptPoolVersion: defaults.promptPoolVersion,
    detectionCount: defaults.detectionCount,
    modelScope: defaults.modelScope,
    judgmentRulesVersion: defaults.judgmentRulesVersion,
  }
}

function downloadTemplate(type: "answers" | "competitors" | "deliveries" | "completion") {
  const names = {
    answers: "傲网GEO_AI回答导入模板.csv",
    competitors: "傲网GEO_竞品检测导入模板.csv",
    deliveries: "傲网GEO_交付链接导入模板.csv",
    completion: "傲网GEO_合同交付验收模板.csv",
  }
  const blob = new Blob([createCsvTemplate(type)], { type: "text/csv;charset=utf-8" })
  const url = URL.createObjectURL(blob)
  const link = document.createElement("a")
  link.href = url
  link.download = names[type]
  link.click()
  window.setTimeout(() => URL.revokeObjectURL(url), 300)
}

export function OperationsCenter({
  workspace, loading, onWorkspaceChange, onRefresh, onReturn, onOpenReport,
}: {
  workspace: OpsWorkspace | null
  loading: boolean
  onWorkspaceChange: (workspace: OpsWorkspace) => void
  onRefresh: () => Promise<void>
  onReturn: () => void
  onOpenReport: (slug: string) => void
}) {
  const [tab, setTab] = React.useState("projects")
  const [projectOpen, setProjectOpen] = React.useState(false)
  const [editingProject, setEditingProject] = React.useState<OpsProject | null>(null)
  const [projectForm, setProjectForm] = React.useState<ProjectForm>(emptyProject)
  const [savingProject, setSavingProject] = React.useState(false)
  const [selectedProject, setSelectedProject] = React.useState("")
  const [reportMonth, setReportMonth] = React.useState(new Date().toISOString().slice(0, 7))
  const [importType, setImportType] = React.useState("answers")
  const [evidenceRecordId, setEvidenceRecordId] = React.useState("")
  const [file, setFile] = React.useState<File | null>(null)
  const [uploading, setUploading] = React.useState(false)
  const [validationOpen, setValidationOpen] = React.useState(false)
  const [validationErrors, setValidationErrors] = React.useState<Array<{ row: number; field: string; message: string }>>([])
  const [showRecycleBin, setShowRecycleBin] = React.useState(false)
  const [deleteCandidate, setDeleteCandidate] = React.useState<OpsProject | null>(null)
  const [permanentCandidate, setPermanentCandidate] = React.useState<OpsProject | null>(null)
  const [permanentName, setPermanentName] = React.useState("")
  const [reopenCandidate, setReopenCandidate] = React.useState<OpsProject | null>(null)
  const [reopenReason, setReopenReason] = React.useState("")
  const [publishCandidate, setPublishCandidate] = React.useState<OpsSnapshot | null>(null)
  const [reviewNote, setReviewNote] = React.useState("")
  const [gateChecks, setGateChecks] = React.useState<GateCheck[]>([])
  const [gateOpen, setGateOpen] = React.useState(false)
  const [memberEmail, setMemberEmail] = React.useState("")
  const [memberName, setMemberName] = React.useState("")
  const [memberRole, setMemberRole] = React.useState("viewer")
  const [pending, setPending] = React.useState(false)

  const activeProjectId = selectedProject || workspace?.projects[0]?.id || ""
  const projects = workspace?.projects ?? []
  const snapshots = workspace?.snapshots ?? []
  const imports = workspace?.imports ?? []
  const members = workspace?.members ?? []
  const audits = workspace?.audits ?? []
  const activeProject = projects.find((item) => item.id === activeProjectId)
  const writableProjects = projects.filter((item) => item.projectType === "client" && item.status === "active")
  const writeProjectId = writableProjects.some((item) => item.id === activeProjectId) ? activeProjectId : writableProjects[0]?.id ?? ""
  const writeProject = projects.find((item) => item.id === writeProjectId)
  const structuredImport = ["answers", "competitors", "deliveries", "completion"].includes(importType)

  const updateField = (key: keyof ProjectForm, value: string) => setProjectForm((current) => ({ ...current, [key]: value }))

  const openNewProject = (fromTemplate: boolean) => {
    const reference = projects.find((item) => item.projectType === "reference")
    setEditingProject(null)
    if (fromTemplate && reference) {
      setProjectForm(withReferenceDefaults(emptyProject, reference))
    } else {
      setProjectForm(emptyProject)
    }
    setProjectOpen(true)
  }

  const openEditProject = (project: OpsProject) => {
    setEditingProject(project)
    setProjectForm(formFromProject(project))
    setProjectOpen(true)
  }

  const saveProject = async () => {
    setSavingProject(true)
    try {
      const action = editingProject ? "update-project" : "create-project"
      const response = await fetch("/api/ops", {
        method: "POST", headers: { "content-type": "application/json" },
        body: JSON.stringify({ action, projectId: editingProject?.id, ...projectForm }),
      })
      const result = await response.json() as { error?: string; message?: string; workspace?: OpsWorkspace; projectId?: string }
      if (!response.ok || !result.workspace) throw new Error(result.error ?? "项目保存失败")
      onWorkspaceChange(result.workspace)
      setSelectedProject(result.projectId ?? editingProject?.id ?? result.workspace.projects[0]?.id ?? "")
      setProjectOpen(false)
      setEditingProject(null)
      setProjectForm(emptyProject)
      toast.success(result.message ?? (editingProject ? "项目配置已更新" : "项目已创建，请确认并冻结测量口径"))
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "项目保存失败")
    } finally { setSavingProject(false) }
  }

  const jsonAction = async (body: Record<string, unknown>, success?: string) => {
    const response = await fetch("/api/ops", {
      method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
    })
    const result = await response.json() as { error?: string; message?: string; workspace?: OpsWorkspace; gate?: { checks?: GateCheck[] } }
    if (!response.ok || !result.workspace) {
      if (result.gate?.checks) { setPublishCandidate(null); setGateChecks(result.gate.checks); setGateOpen(true) }
      throw new Error(result.error ?? "操作失败")
    }
    onWorkspaceChange(result.workspace)
    toast.success(result.message ?? success ?? "操作已完成")
    return result.workspace
  }

  const freezeProject = async (project: OpsProject) => {
    setPending(true)
    try { await jsonAction({ action: "freeze-project", projectId: project.id, changeReason: "项目启动测量口径确认" }) }
    catch (error) { toast.error(error instanceof Error ? error.message : "口径冻结失败") }
    finally { setPending(false) }
  }

  const reopenProject = async () => {
    if (!reopenCandidate) return
    setPending(true)
    try {
      await jsonAction({ action: "reopen-project", projectId: reopenCandidate.id, changeReason: reopenReason })
      setReopenCandidate(null); setReopenReason("")
    } catch (error) { toast.error(error instanceof Error ? error.message : "解除冻结失败") }
    finally { setPending(false) }
  }

  const lifecycleAction = async (action: "archive-project" | "delete-project" | "restore-project" | "permanent-delete-project", project: OpsProject) => {
    setPending(true)
    try {
      const next = await jsonAction({ action, projectId: project.id, confirmName: permanentName })
      setDeleteCandidate(null); setPermanentCandidate(null); setPermanentName("")
      if (selectedProject === project.id) setSelectedProject(next.projects.find((item) => item.status === "active")?.id ?? "")
    } catch (error) { toast.error(error instanceof Error ? error.message : "项目操作失败") }
    finally { setPending(false) }
  }

  const upload = async () => {
    if (!file || !writeProjectId) return toast.error("请先创建或选择一个客户项目")
    setUploading(true)
    const form = new FormData()
    form.set("projectId", writeProjectId); form.set("reportMonth", reportMonth)
    form.set("importType", importType); form.set("recordId", evidenceRecordId); form.set("file", file)
    try {
      const response = await fetch("/api/ops", { method: "POST", body: form })
      const result = await response.json() as { error?: string; message?: string; status?: string; errors?: Array<{ row: number; field: string; message: string }>; workspace?: OpsWorkspace }
      if (result.workspace) onWorkspaceChange(result.workspace)
      if (result.errors?.length) { setValidationErrors(result.errors); setValidationOpen(true) }
      if (!response.ok || !result.workspace) throw new Error(result.error ?? result.errors?.[0]?.message ?? "导入失败")
      setFile(null); setEvidenceRecordId("")
      const input = document.getElementById("geo-import-file") as HTMLInputElement | null
      if (input) input.value = ""
      toast.success(result.status === "partial" ? "有效数据已入库，请修正错误行后重新导入" : result.message ?? "数据导入完成")
      if (importType !== "brand_logo") setTab("snapshots")
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "导入失败")
      await onRefresh()
    } finally { setUploading(false) }
  }

  const publish = async () => {
    if (!publishCandidate) return
    setPending(true)
    try {
      await jsonAction({ action: "publish-snapshot", snapshotId: publishCandidate.id, reviewNote })
      setPublishCandidate(null); setReviewNote("")
    } catch (error) { toast.error(error instanceof Error ? error.message : "发布失败") }
    finally { setPending(false) }
  }

  const saveMember = async () => {
    if (!activeProject || !memberEmail) return
    setPending(true)
    try {
      await jsonAction({ action: "add-project-member", projectId: activeProject.id, userEmail: memberEmail, displayName: memberName, memberRole })
      setMemberEmail(""); setMemberName(""); setMemberRole("viewer")
    } catch (error) { toast.error(error instanceof Error ? error.message : "成员保存失败") }
    finally { setPending(false) }
  }

  const removeMember = async (member: OpsMember) => {
    setPending(true)
    try { await jsonAction({ action: "remove-project-member", projectId: member.projectId, userEmail: member.userEmail }) }
    catch (error) { toast.error(error instanceof Error ? error.message : "成员停用失败") }
    finally { setPending(false) }
  }

  if (loading && !workspace) return <div className="ops-loading"><LoaderCircle className="spin"/><strong>正在连接交付数据后台</strong><span>读取项目、快照与审计记录…</span></div>
  if (!workspace) return <div className="ops-loading error"><CircleAlert/><strong>运营工作台暂时不可用</strong><span>请刷新后重试，已发布报告不受影响。</span><Button variant="outline" onClick={onRefresh}><RefreshCw/>重新连接</Button></div>
  if (!workspace.user.canWrite) return <div className="ops-loading error"><ShieldCheck/><strong>当前账号仅可查看授权项目报告</strong><span>乙方运营控制台仅向傲网项目、数据与审核人员开放。</span><Button onClick={onReturn}><ArrowLeft/>返回项目报告</Button></div>

  const visibleProjects = projects.filter((item) => showRecycleBin ? item.status === "recycle_bin" : item.status !== "recycle_bin")
  const activeProjects = projects.filter((item) => item.status === "active" && item.projectType === "client").length
  const published = snapshots.filter((item) => item.status === "published").length
  const validRows = imports.reduce((sum, item) => sum + Number(item.validRows), 0)
  const activeMembers = members.filter((item) => item.projectId === activeProjectId)
  const activeAudits = audits.filter((item) => !activeProjectId || item.projectId === activeProjectId)

  return (
    <div className="ops-page">
      <section className="ops-hero">
        <div className="ops-hero-brand"><AowangBrand/><div className="ops-hero-divider"/><div><Badge variant="outline"><ShieldCheck/>乙方运营控制台</Badge><h1>GEO项目交付工作台</h1><p>项目配置、口径冻结、数据生产、质量审核、客户验收与审计统一管理。</p></div></div>
        <div className="ops-hero-actions"><Button variant="outline" onClick={onRefresh} disabled={loading}><RefreshCw className={loading ? "spin" : ""}/>刷新数据</Button><Button onClick={onReturn}><ArrowLeft/>查看项目报告</Button></div>
      </section>

      <section className="ops-kpi-grid">
        <div><span><FolderKanban/>在管客户项目</span><strong>{activeProjects}</strong><small>{projects.filter((item) => item.projectType === "client" && item.frozenAt).length}个已冻结口径</small></div>
        <div><span><FileCheck2/>已发布快照</span><strong>{published}</strong><small>{snapshots.filter((item) => item.status === "draft").length}个待复核</small></div>
        <div><span><Database/>有效导入记录</span><strong>{validRows}</strong><small>{imports.length}次导入任务</small></div>
        <div><span><BadgeCheck/>证据与链接</span><strong>{projects.reduce((sum, item) => sum + Number(item.evidenceCount) + Number(item.deliveryCount), 0)}</strong><small>回答证据 + 交付URL</small></div>
      </section>

      <section className="ops-flow">{["新建项目", "冻结口径", "导入与校验", "审核发布", "甲方验收"].map((item, index) => <React.Fragment key={item}><div className={index < 2 || imports.length ? "done" : ""}><span>{String(index + 1).padStart(2, "0")}</span><strong>{item}</strong></div>{index < 4 ? <ChevronRight/> : null}</React.Fragment>)}</section>

      <Tabs value={tab} onValueChange={setTab} className="ops-tabs">
        <TabsList variant="line"><TabsTrigger value="projects">项目与口径</TabsTrigger><TabsTrigger value="import">数据与证据</TabsTrigger><TabsTrigger value="snapshots">快照与发布</TabsTrigger><TabsTrigger value="governance">成员与审计</TabsTrigger></TabsList>

        <TabsContent value="projects">
          <section className="ops-panel">
            <div className="ops-panel-heading"><div><span>PROJECT PORTFOLIO</span><h2>项目、品牌与测量口径</h2><p>标准样板只用于验收对照；客户项目独立存储，并在导入前冻结Prompt池、平台、竞品与判定规则。</p></div><div className="ops-heading-actions"><Button variant="outline" onClick={() => setShowRecycleBin((value) => !value)}><Trash2/>{showRecycleBin ? "返回项目" : `回收站 ${projects.filter((item) => item.status === "recycle_bin").length || ""}`}</Button><Button variant="outline" onClick={() => openNewProject(true)} disabled={!workspace.user.canManageProjects}><BadgeCheck/>按验收样板新建</Button><Button onClick={() => openNewProject(false)} disabled={!workspace.user.canManageProjects}><Plus/>新建空白项目</Button></div></div>
            <div className="ops-project-grid">
              {visibleProjects.map((project) => {
                const [label, tone] = modeLabel(project.dataMode)
                return <article key={project.id} className={`ops-project-card ${activeProjectId === project.id ? "active" : ""} ${project.projectType === "reference" ? "reference" : ""}`}>
                  <button className="ops-project-select" onClick={() => setSelectedProject(project.id)} aria-label={`选择${project.projectName}`}>
                    <div className="ops-brand-mark">{project.projectType === "reference" ? <img src="/aowang-logo.png" alt="傲网 GEO Logo"/> : project.logoKey ? <img src={`/api/ops/files?key=${encodeURIComponent(project.logoKey)}`} alt={`${project.brandName} Logo`}/> : project.brandName.slice(0, 1)}</div>
                    <div className="ops-project-copy"><span>{project.projectCode || project.clientName}</span><strong>{project.projectName}</strong><p>{project.clientName} · 核心词：{project.coreKeyword || "待配置"}</p><div><Badge variant="outline" className={`ops-tone-${tone}`}>{label}</Badge>{project.projectType === "client" ? <Badge variant="outline" className={project.frozenAt ? "status-positive" : "status-warning"}>{project.frozenAt ? <LockKeyhole/> : <UnlockKeyhole/>}口径 v{project.configVersion} {project.frozenAt ? "已冻结" : "待冻结"}</Badge> : null}<small>{project.currentMonth || "未生成月份"}</small></div></div>
                    <div className="ops-project-counts"><b>{project.evidenceCount}</b><span>回答证据</span><b>{project.deliveryCount}</b><span>交付链接</span></div>
                  </button>
                  <div className="ops-project-tools">
                    {project.status !== "recycle_bin" ? <Button variant="ghost" size="sm" onClick={() => onOpenReport(project.slug)} disabled={project.projectType !== "reference" && !project.publishedCount}><ExternalLink/>查看报告</Button> : <span>保留至 {project.deleteAfter ? new Date(project.deleteAfter).toLocaleDateString("zh-CN") : "待清理"}</span>}
                    {project.projectType === "reference" ? <Badge variant="outline"><ShieldCheck/>系统保护</Badge> : <DropdownMenu><DropdownMenuTrigger asChild><Button variant="ghost" size="icon" aria-label="项目操作"><MoreHorizontal/></Button></DropdownMenuTrigger><DropdownMenuContent align="end"><DropdownMenuItem onClick={() => onOpenReport(project.slug)} disabled={!project.publishedCount}><ExternalLink/>查看已发布报告</DropdownMenuItem>{project.status === "recycle_bin" ? <><DropdownMenuItem disabled={!workspace.user.canManageProjects} onClick={() => void lifecycleAction("restore-project", project)}><RotateCcw/>恢复项目</DropdownMenuItem><DropdownMenuSeparator/><DropdownMenuItem disabled={!workspace.user.canManageProjects || workspace.user.role !== "owner"} variant="destructive" onClick={() => setPermanentCandidate(project)}><Trash2/>永久删除</DropdownMenuItem></> : <><DropdownMenuItem onClick={() => openEditProject(project)} disabled={!workspace.user.canManageProjects || Boolean(project.frozenAt) || project.status !== "active"}><Pencil/>编辑项目配置</DropdownMenuItem>{!project.frozenAt && project.status === "active" ? <DropdownMenuItem disabled={!workspace.user.canManageProjects} onClick={() => void freezeProject(project)}><LockKeyhole/>冻结测量口径</DropdownMenuItem> : null}{project.frozenAt && workspace.user.role === "owner" ? <DropdownMenuItem onClick={() => setReopenCandidate(project)}><UnlockKeyhole/>解除口径冻结</DropdownMenuItem> : null}<DropdownMenuItem onClick={() => { setSelectedProject(project.id); setTab("governance") }}><Users/>成员与审计</DropdownMenuItem><DropdownMenuSeparator/>{project.status === "archived" ? <DropdownMenuItem disabled={!workspace.user.canManageProjects} onClick={() => void lifecycleAction("restore-project", project)}><RotateCcw/>重新启用项目</DropdownMenuItem> : <DropdownMenuItem disabled={!workspace.user.canManageProjects} onClick={() => void lifecycleAction("archive-project", project)}><Archive/>归档项目</DropdownMenuItem>}<DropdownMenuItem disabled={!workspace.user.canManageProjects} variant="destructive" onClick={() => setDeleteCandidate(project)}><Trash2/>移入回收站</DropdownMenuItem></>}</DropdownMenuContent></DropdownMenu>}
                  </div>
                </article>
              })}
            </div>
            {!visibleProjects.length ? <div className="ops-empty-history"><Trash2/><strong>{showRecycleBin ? "回收站为空" : "尚未创建客户项目"}</strong><span>{showRecycleBin ? "被删除的项目会在这里保留30天。" : "可以从标准验收样板开始，也可以创建空白项目。"}</span></div> : null}
            {activeProject && activeProject.status !== "recycle_bin" ? <div className="ops-selected-project v8"><div><Building2/><span>当前操作项目</span><strong>{activeProject.brandName}</strong></div><div><span>口径版本</span><strong>v{activeProject.configVersion}</strong></div><div><span>口径状态</span><strong>{activeProject.projectType === "reference" ? "系统保护" : activeProject.frozenAt ? "已冻结" : "待冻结"}</strong></div><div><span>Baseline</span><strong>{activeProject.baselineMonth || "待配置"}</strong></div>{activeProject.projectType === "client" ? <Button variant="outline" onClick={() => { setTab("import"); setImportType("brand_logo") }}><ImageUp/>上传客户Logo</Button> : <Button variant="outline" onClick={() => onOpenReport(activeProject.slug)}><ExternalLink/>验收对照报告</Button>}</div> : null}
          </section>
        </TabsContent>

        <TabsContent value="import">
          <div className="ops-two-column">
            <section className="ops-panel">
              <div className="ops-panel-heading"><div><span>DATA & EVIDENCE INTAKE</span><h2>真实数据与回答证据</h2><p>AI回答、竞品、交付链接与证据文件分开归档；所有结构化数据先校验再进入指标。</p></div></div>
              <div className="ops-import-form">
                <div><Label>目标项目</Label><Select value={writeProjectId} onValueChange={setSelectedProject}><SelectTrigger><SelectValue placeholder="选择客户项目"/></SelectTrigger><SelectContent>{writableProjects.map((project) => <SelectItem key={project.id} value={project.id}>{project.brandName} · {project.projectName}</SelectItem>)}</SelectContent></Select></div>
                <div><Label>报告月份</Label><Input type="month" value={reportMonth} onChange={(event) => setReportMonth(event.target.value)}/></div>
                <div className="wide"><Label>数据类型</Label><Select value={importType} onValueChange={(value) => { setImportType(value); setFile(null); setEvidenceRecordId("") }}><SelectTrigger><SelectValue/></SelectTrigger><SelectContent>{Object.entries(importDefinitions).map(([entry, definition]) => <SelectItem key={entry} value={entry}>{definition.label}</SelectItem>)}</SelectContent></Select></div>
                {structuredImport ? <div className="ops-import-rule wide"><ShieldCheck/><span>{importType === "competitors" ? "竞品文件的 record_id 必须复用对应品牌回答 record_id，并保持 Prompt ID、平台完全一致。" : importType === "completion" ? "逐项填写合同数量、合格数量、单位、权重和证据链接；全部权重必须合计100%。" : "同一项目、同一月份、同一数据类型再次上传时，将以本次完整文件替换上一批次；请勿只上传增量。"}</span></div> : null}
                {importType === "evidence" ? <div className="wide"><Label htmlFor="evidence-record-id">关联AI回答记录ID</Label><Input id="evidence-record-id" value={evidenceRecordId} onChange={(event) => setEvidenceRecordId(event.target.value)} placeholder="例如 ANS-202609-001；留空则归档为项目级证据"/><small className="ops-field-help">建议填写记录ID，使截图、HTML或PDF能够从甲方报告直接下钻核验。</small></div> : null}
                <label className="ops-dropzone wide" htmlFor="geo-import-file"><UploadCloud/><strong>{file ? file.name : "选择需要归档和校验的文件"}</strong><span>{structuredImport ? "支持CSV／TSV／JSON，单次最多2000条" : importType === "brand_logo" ? "支持PNG／JPG／WebP" : "支持HTML／图片／PDF证据"}</span><input id="geo-import-file" type="file" accept={structuredImport ? ".csv,.tsv,.json" : importType === "brand_logo" ? "image/png,image/jpeg,image/webp" : ".html,.htm,image/png,image/jpeg,image/webp,.pdf"} onChange={(event) => setFile(event.target.files?.[0] ?? null)}/></label>
                {!writeProject?.frozenAt && importType !== "brand_logo" ? <div className="ops-import-lock wide"><UnlockKeyhole/><div><strong>该项目尚未冻结测量口径</strong><span>请先确认核心词、Prompt池、平台、竞品集合、检测次数和判定规则。</span></div><Button variant="outline" onClick={() => setTab("projects")}>返回项目配置</Button></div> : null}
                <div className="ops-import-actions wide"><div>{structuredImport ? <Button variant="outline" onClick={() => downloadTemplate(importType as "answers" | "competitors" | "deliveries" | "completion")}><Download/>下载Excel兼容空白模板</Button> : <span><FileArchive/>原始文件将进入项目证据库</span>}</div><Button onClick={upload} disabled={!workspace.user.canManageProjects || !file || !writeProjectId || uploading || (importType !== "brand_logo" && !writeProject?.frozenAt)}>{uploading ? <LoaderCircle className="spin"/> : <UploadCloud/>}{uploading ? "正在校验并入库" : importType === "brand_logo" ? "上传品牌Logo" : "上传并刷新快照"}</Button></div>
              </div>
            </section>

            <section className="ops-panel ops-check-panel"><div className="ops-panel-heading"><div><span>QUALITY GATE</span><h2>V8发布质量门</h2><p>发布由服务端实时复核，未满足任一阻断条件时不会进入甲方报告。</p></div></div><div className="ops-check-list">{["测量口径已冻结且快照指纹一致", "检测调用总数与冻结计划完全一致", "有效回答率及最低样本量达到门槛", "指标标准与V8计算引擎版本已锁定", "平台、关键词与模型版本匹配冻结范围", "最新整包导入成功且没有错误行", "回答证据与事实点核验达到合同门槛", "竞品按回答ID、Prompt、平台逐条对应", "有效交付达到合同数量并保留原文链接", "综合数据质量不低于90%"].map((item) => <div key={item}><CheckCircle2/><span>{item}</span></div>)}</div><div className="ops-boundary"><ShieldCheck/><div><strong>乙方生产与甲方查看隔离</strong><p>导入、修正、草稿和成员配置只在乙方端可见；甲方仅看到授权项目的正式发布快照。</p></div></div></section>
          </div>
        </TabsContent>

        <TabsContent value="snapshots">
          <div className="ops-two-column snapshots">
            <section className="ops-panel">
              <div className="ops-panel-heading"><div><span>SNAPSHOT REGISTER</span><h2>月度快照、发布与验收</h2><p>同月可迭代多个版本；发布前经过质量门，发布后进入甲方验收闭环。</p></div></div>
              <div className="table-shell"><Table><TableHeader><TableRow><TableHead>项目</TableHead><TableHead>月份 / 版本</TableHead><TableHead>质量与覆盖</TableHead><TableHead>回答 / 证据</TableHead><TableHead>状态</TableHead><TableHead>甲方验收</TableHead><TableHead>操作</TableHead></TableRow></TableHeader><TableBody>{snapshots.map((snapshot) => {
                const summary = parseJson<{ coverage?: { evidenceCoverage?: number; rawEvidenceCoverage?: number; citationEvidenceCoverage?: number; accuracyCoverage?: number } }>(snapshot.summaryJson, {})
                return <TableRow key={snapshot.id}><TableCell><strong>{snapshot.brandName}</strong></TableCell><TableCell>{snapshot.reportMonth} · r{snapshot.version}</TableCell><TableCell><div className="table-progress"><span>{snapshot.qualityScore}%</span><Progress value={snapshot.qualityScore}/><small>原始证据 {summary.coverage?.rawEvidenceCoverage ?? summary.coverage?.evidenceCoverage ?? 0}% · 引用证据 {summary.coverage?.citationEvidenceCoverage ?? 0}% · 事实核验 {summary.coverage?.accuracyCoverage ?? 0}%</small></div></TableCell><TableCell>{snapshot.answerCount} / {snapshot.evidenceCount}</TableCell><TableCell><Badge variant="outline" className={snapshot.status === "published" ? "status-positive" : snapshot.status === "draft" ? "status-warning" : ""}>{statusLabel(snapshot.status)}</Badge></TableCell><TableCell><Badge variant="outline" className={snapshot.acceptanceStatus === "accepted" ? "status-positive" : snapshot.acceptanceStatus === "disputed" ? "status-negative" : ""}>{acceptanceLabel(snapshot.acceptanceStatus)}</Badge></TableCell><TableCell>{snapshot.status === "draft" ? <Button size="sm" disabled={!workspace.user.canPublish} onClick={() => { setPublishCandidate(snapshot); setReviewNote("") }}><Send/>复核发布</Button> : <span className="ops-table-note">{formatTime(snapshot.publishedAt)}</span>}</TableCell></TableRow>
              })}</TableBody></Table></div>
            </section>

            <section className="ops-panel"><div className="ops-panel-heading"><div><span>IMPORT AUDIT</span><h2>最近导入记录</h2><p>失败和部分成功任务保留错误明细；修正后重新导入即可重新计算质量门。</p></div></div><div className="ops-import-history">{imports.length ? imports.slice(0, 10).map((job) => <div key={job.id}><span className={`ops-job-icon ${job.status}`}><FileSpreadsheet/></span><div><strong>{job.filename}</strong><p>{job.brandName} · {importDefinitions[job.importType as keyof typeof importDefinitions]?.label ?? job.importType}</p><small>{formatTime(job.createdAt)}</small></div><div><b>{job.validRows}/{job.totalRows}</b><span>有效记录</span>{job.errorRows ? <button className="ops-error-link" onClick={() => { setValidationErrors(parseJson(job.errorsJson, [])); setValidationOpen(true) }}>{job.errorRows}项错误</button> : <small className="ops-success-count">校验通过</small>}</div></div>) : <div className="ops-empty-history"><FileCheck2/><strong>暂无真实导入任务</strong><span>下载模板并导入首批数据后，审计记录会显示在这里。</span></div>}</div></section>
          </div>
        </TabsContent>

        <TabsContent value="governance">
          <div className="ops-two-column governance">
            <section className="ops-panel">
              <div className="ops-panel-heading"><div><span>PROJECT ACCESS</span><h2>甲方项目成员</h2><p>viewer仅查看，approver可查看并提交本期验收或异议；成员只获得当前项目的数据权限。</p></div></div>
              <div className="ops-member-project"><Label>管理项目</Label><Select value={activeProjectId} onValueChange={setSelectedProject}><SelectTrigger><SelectValue placeholder="选择项目"/></SelectTrigger><SelectContent>{projects.filter((item) => item.projectType === "client" && item.status !== "recycle_bin").map((project) => <SelectItem key={project.id} value={project.id}>{project.brandName} · {project.projectName}</SelectItem>)}</SelectContent></Select></div>
              {activeProject?.projectType === "client" ? <><div className="ops-member-form"><div><Label htmlFor="member-email">甲方登录邮箱</Label><Input id="member-email" type="email" value={memberEmail} onChange={(event) => setMemberEmail(event.target.value)} placeholder="client@example.com" disabled={!workspace.user.canManageProjects}/></div><div><Label htmlFor="member-name">姓名 / 角色名称</Label><Input id="member-name" value={memberName} onChange={(event) => setMemberName(event.target.value)} placeholder="例如：品牌负责人" disabled={!workspace.user.canManageProjects}/></div><div><Label>项目权限</Label><Select value={memberRole} onValueChange={setMemberRole} disabled={!workspace.user.canManageProjects}><SelectTrigger><SelectValue/></SelectTrigger><SelectContent><SelectItem value="viewer">查看人</SelectItem><SelectItem value="approver">验收人</SelectItem></SelectContent></Select></div><Button onClick={() => void saveMember()} disabled={!workspace.user.canManageProjects || !memberEmail || pending}><UserPlus/>保存成员</Button></div><div className="ops-access-note"><ShieldCheck/><span>项目权限与站点访问是两道独立门禁。保存成员后，还需由站点管理员为该邮箱开通站点访问。</span></div><div className="ops-member-list">{activeMembers.length ? activeMembers.map((member) => <div key={member.id}><span className={member.status === "active" ? "active" : "revoked"}><Users/></span><div><strong>{member.displayName || member.userEmail}</strong><p>{member.userEmail}</p></div><Badge variant="outline">{member.role === "approver" ? "验收人" : member.role === "owner" ? "项目负责人" : "查看人"}</Badge><Badge variant="outline" className={member.status === "active" ? "status-positive" : ""}>{member.status === "active" ? "有效" : "已停用"}</Badge>{member.role !== "owner" && member.status === "active" ? <Button variant="ghost" size="sm" onClick={() => void removeMember(member)} disabled={!workspace.user.canManageProjects || pending}><XCircle/>停用</Button> : null}</div>) : <div className="ops-empty-history"><Users/><strong>尚未添加甲方成员</strong><span>发布前可先完成项目账号与验收人配置。</span></div>}</div></> : <div className="ops-empty-history"><ShieldCheck/><strong>标准验收样板不配置甲方成员</strong><span>请选择一个客户项目继续。</span></div>}
            </section>

            <section className="ops-panel"><div className="ops-panel-heading"><div><span>AUDIT TRAIL</span><h2>项目审计日志</h2><p>项目、口径、数据、成员、发布与验收动作均留存操作者和时间。</p></div></div><div className="ops-audit-list">{activeAudits.length ? activeAudits.slice(0, 30).map((entry) => <div key={entry.id}><span><History/></span><div><strong>{auditLabel(entry.action)}</strong><p>{entry.actorEmail}</p><small>{formatTime(entry.createdAt)}</small></div><Badge variant="outline">{entry.targetType}</Badge></div>) : <div className="ops-empty-history"><Activity/><strong>当前项目暂无审计记录</strong><span>V8中的关键业务动作会自动记录在这里。</span></div>}</div></section>
          </div>
        </TabsContent>
      </Tabs>

      <Dialog open={projectOpen} onOpenChange={(open) => { setProjectOpen(open); if (!open) setEditingProject(null) }}>
        <DialogContent className="sm:max-w-3xl ops-project-dialog"><DialogHeader><DialogTitle>{editingProject ? `编辑 ${editingProject.projectName}` : "新建GEO交付项目"}</DialogTitle><DialogDescription>{editingProject ? "保存后生成新的配置版本；冻结前仍可继续调整。" : "创建客户独立数据空间。按样板新建只复制字段结构和验收口径，不复制任何回答、链接或指标结果。"}</DialogDescription></DialogHeader>
          <div className="ops-project-form">
            {!editingProject ? <div className="wide"><Label>创建方式</Label><Select value={projectForm.templateSourceId || "blank"} onValueChange={(value) => { if (value === "blank") setProjectForm((current) => ({ ...current, templateSourceId: "" })); else { const reference = projects.find((item) => item.id === value && item.projectType === "reference"); if (reference) setProjectForm((current) => withReferenceDefaults(current, reference)) } }}><SelectTrigger><SelectValue/></SelectTrigger><SelectContent><SelectItem value="blank">空白项目</SelectItem>{projects.filter((item) => item.projectType === "reference").map((item) => <SelectItem key={item.id} value={item.id}>傲网GEO标准验收模板</SelectItem>)}</SelectContent></Select></div> : null}
            <div className="wide"><Label htmlFor="project-name">项目名称</Label><Input id="project-name" value={projectForm.projectName} onChange={(event) => updateField("projectName", event.target.value)} placeholder="例如：某品牌 GEO 优化项目"/></div>
            <div><Label htmlFor="brand-name">品牌名称</Label><Input id="brand-name" value={projectForm.brandName} onChange={(event) => updateField("brandName", event.target.value)} placeholder="品牌标准名称"/></div><div><Label htmlFor="client-name">客户主体</Label><Input id="client-name" value={projectForm.clientName} onChange={(event) => updateField("clientName", event.target.value)} placeholder="签约公司主体"/></div>
            <div className="wide"><Label htmlFor="brand-aliases">品牌别名</Label><Input id="brand-aliases" value={projectForm.brandAliases} onChange={(event) => updateField("brandAliases", event.target.value)} placeholder="简称、英文名、历史名称；使用逗号或顿号分隔"/></div>
            <div><Label htmlFor="core-keyword">核心词</Label><Input id="core-keyword" value={projectForm.coreKeyword} onChange={(event) => updateField("coreKeyword", event.target.value)} placeholder="例如：早教管理系统"/></div><div><Label htmlFor="optimization-region">优化地域</Label><Input id="optimization-region" value={projectForm.optimizationRegion} onChange={(event) => updateField("optimizationRegion", event.target.value)} placeholder="全国／广东／海外"/></div>
            <div className="wide"><Label htmlFor="derivative-keywords">核心衍生词</Label><Input id="derivative-keywords" value={projectForm.derivativeKeywords} onChange={(event) => updateField("derivativeKeywords", event.target.value)} placeholder="使用逗号或顿号分隔"/></div>
            <div><Label>服务范围</Label><Select value={projectForm.serviceScope} onValueChange={(value) => updateField("serviceScope", value)}><SelectTrigger><SelectValue/></SelectTrigger><SelectContent><SelectItem value="domestic">国内GEO</SelectItem><SelectItem value="overseas">海外GEO</SelectItem><SelectItem value="hybrid">国内 + 海外</SelectItem></SelectContent></Select></div><div><Label htmlFor="platforms">检测平台</Label><Input id="platforms" value={projectForm.platforms} onChange={(event) => updateField("platforms", event.target.value)} placeholder="DeepSeek、豆包、通义千问……"/></div>
            <div className="wide"><Label htmlFor="competitors">竞品集合</Label><Input id="competitors" value={projectForm.competitors} onChange={(event) => updateField("competitors", event.target.value)} placeholder="填写已由甲乙双方确认的竞品实体，使用逗号分隔"/></div>
            <div><Label htmlFor="start-date">项目开始</Label><Input id="start-date" type="date" value={projectForm.startDate} onChange={(event) => updateField("startDate", event.target.value)}/></div><div><Label htmlFor="end-date">项目结束</Label><Input id="end-date" type="date" value={projectForm.endDate} onChange={(event) => updateField("endDate", event.target.value)}/></div><div><Label htmlFor="baseline-month">Baseline月份</Label><Input id="baseline-month" type="month" value={projectForm.baselineMonth} onChange={(event) => updateField("baselineMonth", event.target.value)}/></div><div><Label htmlFor="current-month">首个报告月份</Label><Input id="current-month" type="month" value={projectForm.currentMonth} onChange={(event) => updateField("currentMonth", event.target.value)}/></div>
            <div className="ops-form-section-title wide"><span>测量协议</span><small>冻结后作为月度对比的唯一口径；若需变更，必须解除冻结并留存原因。</small></div>
            <div><Label htmlFor="prompt-version">Prompt池版本</Label><Input id="prompt-version" value={projectForm.promptPoolVersion} onChange={(event) => updateField("promptPoolVersion", event.target.value)}/></div><div><Label htmlFor="detection-count">计划检测调用总数</Label><Input id="detection-count" type="number" min="1" value={projectForm.detectionCount} onChange={(event) => updateField("detectionCount", event.target.value)}/></div><div><Label htmlFor="model-scope">模型范围</Label><Input id="model-scope" value={projectForm.modelScope} onChange={(event) => updateField("modelScope", event.target.value)}/></div><div><Label htmlFor="judgment-version">判定规则版本</Label><Input id="judgment-version" value={projectForm.judgmentRulesVersion} onChange={(event) => updateField("judgmentRulesVersion", event.target.value)}/></div>
            <div className="ops-form-section-title wide"><span>合同验收与发布门槛</span><small>效果目标与数据质量门槛分开管理，避免“指标好看但证据不足”。</small></div>
            <div><Label htmlFor="target-mention">提及率目标（%）</Label><Input id="target-mention" type="number" min="0" max="100" value={projectForm.targetMentionRate} onChange={(event) => updateField("targetMentionRate", event.target.value)}/></div><div><Label htmlFor="target-recommend">推荐率目标（%）</Label><Input id="target-recommend" type="number" min="0" max="100" value={projectForm.targetRecommendRate} onChange={(event) => updateField("targetRecommendRate", event.target.value)}/></div><div><Label htmlFor="target-top3">Top3目标（%）</Label><Input id="target-top3" type="number" min="0" max="100" value={projectForm.targetTop3Rate} onChange={(event) => updateField("targetTop3Rate", event.target.value)}/></div><div><Label htmlFor="delivery-target">交付内容目标</Label><Input id="delivery-target" type="number" min="0" value={projectForm.deliveryTarget} onChange={(event) => updateField("deliveryTarget", event.target.value)}/></div><div><Label htmlFor="delivery-completion">总交付完成度门槛（%）</Label><Input id="delivery-completion" type="number" min="0" max="100" value={projectForm.minDeliveryCompletion} onChange={(event) => updateField("minDeliveryCompletion", event.target.value)}/></div><div><Label htmlFor="minimum-answers">最低有效回答数</Label><Input id="minimum-answers" type="number" min="1" value={projectForm.minAnswerCount} onChange={(event) => updateField("minAnswerCount", event.target.value)}/></div><div><Label htmlFor="valid-response-rate">有效回答率门槛（%）</Label><Input id="valid-response-rate" type="number" min="0" max="100" value={projectForm.minValidResponseRate} onChange={(event) => updateField("minValidResponseRate", event.target.value)}/></div><div><Label htmlFor="evidence-coverage">原始回答证据覆盖（%）</Label><Input id="evidence-coverage" type="number" min="0" max="100" value={projectForm.minEvidenceCoverage} onChange={(event) => updateField("minEvidenceCoverage", event.target.value)}/></div><div><Label htmlFor="citation-evidence-coverage">引用信源证据覆盖（%）</Label><Input id="citation-evidence-coverage" type="number" min="0" max="100" value={projectForm.minCitationEvidenceCoverage} onChange={(event) => updateField("minCitationEvidenceCoverage", event.target.value)}/></div><div><Label htmlFor="accuracy-coverage">事实点核验覆盖（%）</Label><Input id="accuracy-coverage" type="number" min="0" max="100" value={projectForm.minAccuracyCoverage} onChange={(event) => updateField("minAccuracyCoverage", event.target.value)}/></div>
            {editingProject ? <div className="wide"><Label htmlFor="change-reason">本次变更说明</Label><Input id="change-reason" value={projectForm.changeReason} onChange={(event) => updateField("changeReason", event.target.value)} placeholder="说明为何调整口径或项目配置"/></div> : null}
          </div><DialogFooter><Button variant="outline" onClick={() => setProjectOpen(false)}>取消</Button><Button onClick={() => void saveProject()} disabled={savingProject || !projectForm.projectName || !projectForm.brandName || !projectForm.clientName}>{savingProject ? <LoaderCircle className="spin"/> : editingProject ? <Pencil/> : <Plus/>}{editingProject ? "保存并生成新版本" : "创建项目"}</Button></DialogFooter>
        </DialogContent>
      </Dialog>

      <Dialog open={Boolean(reopenCandidate)} onOpenChange={(open) => { if (!open) { setReopenCandidate(null); setReopenReason("") } }}><DialogContent><DialogHeader><DialogTitle>解除测量口径冻结</DialogTitle><DialogDescription>解除后不可继续导入正式数据，必须修改完成并重新冻结。操作原因将进入审计日志。</DialogDescription></DialogHeader><div><Label htmlFor="reopen-reason">变更原因</Label><Textarea id="reopen-reason" value={reopenReason} onChange={(event) => setReopenReason(event.target.value)} placeholder="例如：甲乙双方书面确认新增竞品与AI平台"/></div><DialogFooter><Button variant="outline" onClick={() => setReopenCandidate(null)}>取消</Button><Button onClick={() => void reopenProject()} disabled={!reopenReason.trim() || pending}><UnlockKeyhole/>确认解除</Button></DialogFooter></DialogContent></Dialog>

      <Dialog open={Boolean(publishCandidate)} onOpenChange={(open) => { if (!open) setPublishCandidate(null) }}><DialogContent><DialogHeader><DialogTitle>复核并发布月度快照</DialogTitle><DialogDescription>{publishCandidate ? `${publishCandidate.brandName} · ${publishCandidate.reportMonth}-r${publishCandidate.version}` : ""}。服务端将再次检查口径、样本、错误行、证据、事实点、竞品SOV、交付数量和综合质量。</DialogDescription></DialogHeader><div className="ops-publish-summary"><div><span>质量分</span><strong>{publishCandidate?.qualityScore ?? 0}%</strong></div><div><span>有效回答</span><strong>{publishCandidate?.answerCount ?? 0}</strong></div><div><span>证据记录</span><strong>{publishCandidate?.evidenceCount ?? 0}</strong></div></div><div><Label htmlFor="review-note">审核备注</Label><Textarea id="review-note" value={reviewNote} onChange={(event) => setReviewNote(event.target.value)} placeholder="记录本次数据范围、异常说明或人工复核结论"/></div><DialogFooter><Button variant="outline" onClick={() => setPublishCandidate(null)}>取消</Button><Button onClick={() => void publish()} disabled={pending}>{pending ? <LoaderCircle className="spin"/> : <Send/>}通过质量门并发布</Button></DialogFooter></DialogContent></Dialog>

      <Dialog open={gateOpen} onOpenChange={setGateOpen}><DialogContent><DialogHeader><DialogTitle>发布质量门未通过</DialogTitle><DialogDescription>红色项目属于发布阻断项，请完成修正并刷新快照后再次复核。</DialogDescription></DialogHeader><div className="ops-gate-result">{gateChecks.map((check) => <div key={check.key} className={check.passed ? "passed" : "blocked"}>{check.passed ? <CheckCircle2/> : <XCircle/>}<span>{check.label}</span><Badge variant="outline">{check.passed ? "通过" : "阻断"}</Badge></div>)}</div><DialogFooter><Button onClick={() => setGateOpen(false)}>返回处理</Button></DialogFooter></DialogContent></Dialog>

      <AlertDialog open={Boolean(deleteCandidate)} onOpenChange={(open) => !open && setDeleteCandidate(null)}><AlertDialogContent><AlertDialogHeader><AlertDialogTitle>将项目移入回收站？</AlertDialogTitle><AlertDialogDescription>“{deleteCandidate?.projectName}”将停止导入和甲方访问，并在回收站保留30天。期间可以恢复，项目快照和证据不会立即删除。</AlertDialogDescription></AlertDialogHeader><AlertDialogFooter><AlertDialogCancel>取消</AlertDialogCancel><AlertDialogAction variant="destructive" disabled={!deleteCandidate || pending} onClick={() => deleteCandidate && void lifecycleAction("delete-project", deleteCandidate)}><Trash2/>移入回收站</AlertDialogAction></AlertDialogFooter></AlertDialogContent></AlertDialog>

      <Dialog open={Boolean(permanentCandidate)} onOpenChange={(open) => { if (!open) { setPermanentCandidate(null); setPermanentName("") } }}><DialogContent><DialogHeader><DialogTitle>永久删除项目</DialogTitle><DialogDescription>此操作会删除项目、月度快照、AI回答、竞品记录、交付记录及项目附件，无法恢复。请输入完整项目名称确认。</DialogDescription></DialogHeader><div className="ops-permanent-delete"><Label htmlFor="confirm-project-name">项目名称</Label><strong>{permanentCandidate?.projectName}</strong><Input id="confirm-project-name" value={permanentName} onChange={(event) => setPermanentName(event.target.value)} placeholder="输入上方完整项目名称"/></div><DialogFooter><Button variant="outline" onClick={() => { setPermanentCandidate(null); setPermanentName("") }}>取消</Button><Button variant="destructive" disabled={!permanentCandidate || permanentName !== permanentCandidate.projectName || pending} onClick={() => permanentCandidate && void lifecycleAction("permanent-delete-project", permanentCandidate)}><Trash2/>永久删除</Button></DialogFooter></DialogContent></Dialog>

      <Dialog open={validationOpen} onOpenChange={setValidationOpen}><DialogContent className="sm:max-w-2xl"><DialogHeader><DialogTitle>导入数据校验明细</DialogTitle><DialogDescription>以下记录未进入指标分母。请按行号和字段修正模板后重新导入。</DialogDescription></DialogHeader><div className="ops-validation-summary"><CircleAlert/><div><strong>{validationErrors.length}项字段错误</strong><span>有效行已正常入库；错误行保持隔离。</span></div></div><div className="table-shell ops-validation-table"><Table><TableHeader><TableRow><TableHead>Excel行号</TableHead><TableHead>字段</TableHead><TableHead>问题</TableHead></TableRow></TableHeader><TableBody>{validationErrors.map((entry, index) => <TableRow key={`${entry.row}-${entry.field}-${index}`}><TableCell>{entry.row}</TableCell><TableCell><code>{entry.field}</code></TableCell><TableCell>{entry.message}</TableCell></TableRow>)}</TableBody></Table></div><DialogFooter><Button onClick={() => setValidationOpen(false)}>知道了，返回修改</Button></DialogFooter></DialogContent></Dialog>
    </div>
  )
}
