import { useEffect, useRef, useState, type ReactNode } from "react";
import { Search, Plus, Download, ChevronDown, Check, Building, Calendar, DollarSign, Activity, X, Clock, FileText, User, Tag, AlertTriangle, ListFilter } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
import { createPortal } from "react-dom";
import { useLocation } from "react-router-dom";
import {
  checkChannelExpansionDuplicate,
  checkSalesExpansionDuplicate,
  createChannelExpansion,
  createOpportunity,
  createSalesExpansion,
  getExpansionCityOptions,
  getExpansionMeta,
  getExpansionOverview,
  getOpportunityMeta,
  getOpportunityOmsPreSalesOptions,
  getOpportunityOverview,
  getStoredCurrentUserId,
  pushOpportunityToOms,
  updateOpportunity,
  type ChannelExpansionContact,
  type ChannelExpansionItem,
  type CreateChannelExpansionPayload,
  type CreateOpportunityPayload,
  type CreateSalesExpansionPayload,
  type ExpansionDictOption,
  type OmsPreSalesOption,
  type OpportunityDictOption,
  type OpportunityFollowUp,
  type OpportunityItem,
  type PushOpportunityToOmsPayload,
  type SalesExpansionItem,
} from "@/lib/auth";
import { AdaptiveSelect } from "@/components/AdaptiveSelect";
import {
  QuickChannelForm as SharedQuickChannelForm,
  QuickSalesForm as SharedQuickSalesForm,
  type ChannelField,
  type SalesCreateField,
  createEmptyChannelContact as createSharedEmptyChannelContact,
  defaultQuickChannelForm as sharedDefaultQuickChannelForm,
  defaultQuickSalesForm as sharedDefaultQuickSalesForm,
  isOtherOption as isSharedOtherOption,
  normalizeChannelPayload as normalizeSharedChannelPayload,
  normalizeSalesPayload as normalizeSharedSalesPayload,
  validateChannelForm as validateSharedChannelForm,
  validateSalesCreateForm as validateSharedSalesCreateForm,
} from "@/features/crmQuickCreate/shared";
import { useIsWecomBrowser } from "@/hooks/useIsWecomBrowser";
import { cn } from "@/lib/utils";

const FALLBACK_CONFIDENCE_OPTIONS = [
  { value: "A", label: "A" },
  { value: "B", label: "B" },
  { value: "C", label: "C" },
] as const;

const FALLBACK_OPPORTUNITY_TYPE_OPTIONS = [
  { value: "新建", label: "新建" },
  { value: "扩容", label: "扩容" },
  { value: "替换", label: "替换" },
] as const;

const COMPETITOR_OPTIONS = [
  "深信服",
  "锐捷",
  "华为",
  "中兴",
  "噢易云",
  "无",
  "其他",
] as const;

type CompetitorOption = (typeof COMPETITOR_OPTIONS)[number];
type OperatorMode = "none" | "h3c" | "channel" | "both";
type OpportunityArchiveTab = "active" | "archived";
type OpportunityLocationState = { selectedId?: number; archiveTab?: OpportunityArchiveTab } | null;
type OpportunityExportFilters = {
  keyword?: string;
  expectedStartDate?: string;
  expectedEndDate?: string;
  stage?: string;
  confidence?: string;
  projectLocation?: string;
  opportunityType?: string;
  operatorName?: string;
  hasSalesExpansion?: string;
  hasChannelExpansion?: string;
};
type OpportunityField =
  | "projectLocation"
  | "opportunityName"
  | "customerName"
  | "operatorName"
  | "salesExpansionId"
  | "channelExpansionId"
  | "amount"
  | "expectedCloseDate"
  | "confidencePct"
  | "stage"
  | "competitorName"
  | "opportunityType";
type QuickCreateType = "sales" | "channel";

const defaultForm: CreateOpportunityPayload = {
  opportunityName: "",
  customerName: "",
  projectLocation: "",
  operatorName: "",
  amount: 0,
  expectedCloseDate: "",
  confidencePct: "C",
  stage: "",
  opportunityType: "",
  productType: "VDI云桌面",
  source: "主动开发",
  salesExpansionId: undefined,
  channelExpansionId: undefined,
  competitorName: "",
  description: "",
};

function formatAmount(value?: number) {
  if (value === undefined || value === null || Number.isNaN(Number(value))) {
    return "0";
  }
  return new Intl.NumberFormat("zh-CN").format(Number(value));
}

function formatOpportunityBoolean(value?: boolean, trueLabel = "是", falseLabel = "否") {
  if (value === null || value === undefined) {
    return "";
  }
  return value ? trueLabel : falseLabel;
}

function normalizeOpportunityExportText(value?: string | number | boolean | null) {
  if (value === null || value === undefined) {
    return "";
  }
  const normalized = String(value).replace(/\r?\n/g, " ").trim();
  if (!normalized || normalized === "无" || normalized === "待定" || normalized === "未关联") {
    return "";
  }
  return normalized;
}

function downloadOpportunityExcelFile(filename: string, content: BlobPart) {
  const blob = new Blob([content], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
  const objectUrl = window.URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = objectUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  window.URL.revokeObjectURL(objectUrl);
}

function formatOpportunityExportFilenameTime(date = new Date()) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");
  return `${year}${month}${day}_${hours}${minutes}${seconds}`;
}

function normalizeOpportunityExportFilterText(value?: string | number | boolean | null) {
  return normalizeOpportunityExportText(value).toLowerCase();
}

function matchesOpportunityExportKeyword(value: string, keyword?: string) {
  const normalizedKeyword = normalizeOpportunityExportFilterText(keyword);
  return !normalizedKeyword || value.toLowerCase().includes(normalizedKeyword);
}

function matchesOpportunityTextFilter(values: Array<string | number | boolean | null | undefined>, filterValue?: string) {
  const normalizedFilter = normalizeOpportunityExportFilterText(filterValue);
  if (!normalizedFilter) {
    return true;
  }
  return values.some((value) => normalizeOpportunityExportFilterText(value).includes(normalizedFilter));
}

function matchesOpportunityDateRange(value?: string, startDate?: string, endDate?: string) {
  const normalizedValue = normalizeOpportunityExportText(value).slice(0, 10);
  if (!normalizedValue) {
    return !startDate && !endDate;
  }
  if (startDate && normalizedValue < startDate) {
    return false;
  }
  if (endDate && normalizedValue > endDate) {
    return false;
  }
  return true;
}

function matchesOpportunityRelationFilter(hasRelation: boolean, filterValue?: string) {
  if (filterValue === "yes") {
    return hasRelation;
  }
  if (filterValue === "no") {
    return !hasRelation;
  }
  return true;
}

function matchesOpportunityExportFilters(
  item: OpportunityItem,
  filters: OpportunityExportFilters,
  confidenceOptions: OpportunityDictOption[],
) {
  const keywordText = [
    item.code,
    item.name,
    item.client,
    item.owner,
    item.projectLocation,
    item.operatorName,
    item.stage,
    item.type,
    item.salesExpansionName,
    item.channelExpansionName,
    item.preSalesName,
    item.competitorName,
    item.latestProgress,
    item.nextPlan,
    item.notes,
    item.followUps?.map((followUp) => `${followUp.content ?? ""} ${followUp.latestProgress ?? ""} ${followUp.nextAction ?? ""}`).join(" "),
  ].map(normalizeOpportunityExportText).filter(Boolean).join(" ");

  if (!matchesOpportunityExportKeyword(keywordText, filters.keyword)) {
    return false;
  }
  if (!matchesOpportunityDateRange(item.date, filters.expectedStartDate, filters.expectedEndDate)) {
    return false;
  }
  if (!matchesOpportunityTextFilter([item.stageCode, item.stage], filters.stage)) {
    return false;
  }
  if (filters.confidence && normalizeConfidenceValue(item.confidence, confidenceOptions) !== filters.confidence) {
    return false;
  }
  if (!matchesOpportunityTextFilter([item.projectLocation], filters.projectLocation)) {
    return false;
  }
  if (!matchesOpportunityTextFilter([item.type], filters.opportunityType)) {
    return false;
  }
  if (!matchesOpportunityTextFilter([item.operatorCode, item.operatorName], filters.operatorName)) {
    return false;
  }
  if (!matchesOpportunityRelationFilter(Boolean(item.salesExpansionId || item.salesExpansionName), filters.hasSalesExpansion)) {
    return false;
  }
  return matchesOpportunityRelationFilter(Boolean(item.channelExpansionId || item.channelExpansionName), filters.hasChannelExpansion);
}

function toFormFromItem(item: OpportunityItem, confidenceOptions: OpportunityDictOption[]): CreateOpportunityPayload {
  return {
    opportunityName: item.name || "",
    customerName: item.client || "",
    projectLocation: item.projectLocation || "",
    amount: item.amount || 0,
    expectedCloseDate: item.date || "",
    confidencePct: normalizeConfidenceValue(item.confidence, confidenceOptions),
    stage: item.stageCode || item.stage || "",
    opportunityType: item.type || "",
    productType: item.product || "VDI云桌面",
    source: item.source || "主动开发",
    salesExpansionId: item.salesExpansionId,
    channelExpansionId: item.channelExpansionId,
    operatorName: item.operatorCode || item.operatorName || "",
    competitorName: item.competitorName || "",
    description: item.notes || "",
  };
}

function normalizeLegacyConfidenceGrade(value?: string | number | null) {
  if (value === null || value === undefined) {
    return "";
  }

  if (typeof value === "number") {
    if (value >= 80) return "A";
    if (value >= 60) return "B";
    return "C";
  }

  const normalized = value.trim().toUpperCase();
  if (normalized === "A" || normalized === "B" || normalized === "C") {
    return normalized;
  }
  if (normalized === "80") {
    return "A";
  }
  if (normalized === "60") {
    return "B";
  }
  return "";
}

function getEffectiveConfidenceOptions(confidenceOptions: OpportunityDictOption[]) {
  return confidenceOptions.length > 0 ? confidenceOptions : [...FALLBACK_CONFIDENCE_OPTIONS];
}

function normalizeConfidenceValue(score: string | number | null | undefined, confidenceOptions: OpportunityDictOption[]) {
  const rawValue = typeof score === "string" ? score.trim() : typeof score === "number" ? String(score) : "";
  const options = getEffectiveConfidenceOptions(confidenceOptions);

  if (!rawValue) {
    return "";
  }

  const matchedByValue = options.find((item) => (item.value || "").trim() === rawValue);
  if (matchedByValue?.value) {
    return matchedByValue.value;
  }

  const matchedByLabel = options.find((item) => (item.label || "").trim() === rawValue);
  if (matchedByLabel?.value) {
    return matchedByLabel.value;
  }

  const legacyGrade = normalizeLegacyConfidenceGrade(score);
  if (!legacyGrade) {
    return rawValue;
  }

  const legacyValueMatch = options.find((item) => (item.value || "").trim().toUpperCase() === legacyGrade);
  if (legacyValueMatch?.value) {
    return legacyValueMatch.value;
  }

  const legacyLabelMatch = options.find((item) => (item.label || "").trim().toUpperCase() === legacyGrade);
  if (legacyLabelMatch?.value) {
    return legacyLabelMatch.value;
  }

  return legacyGrade;
}

function getConfidenceLabel(score: string | number | null | undefined, confidenceOptions: OpportunityDictOption[]) {
  const normalizedValue = normalizeConfidenceValue(score, confidenceOptions);
  const matchedOption = getEffectiveConfidenceOptions(confidenceOptions).find((item) => (item.value || "") === normalizedValue);
  return matchedOption?.label || matchedOption?.value || normalizedValue || "无";
}

function getConfidenceBadgeClass(score: string | number | null | undefined, confidenceOptions: OpportunityDictOption[]) {
  const options = getEffectiveConfidenceOptions(confidenceOptions);
  const normalizedValue = normalizeConfidenceValue(score, confidenceOptions);
  const matchedIndex = options.findIndex((item) => (item.value || "") === normalizedValue);
  if (matchedIndex < 0) return "crm-pill crm-pill-rose";
  if (matchedIndex === 0) return "crm-pill crm-pill-emerald";
  if (matchedIndex === 1) return "crm-pill crm-pill-amber";
  return "crm-pill crm-pill-rose";
}

function normalizeCompetitorSelections(selected: CompetitorOption[]) {
  const deduped = Array.from(new Set(selected));
  if (deduped.includes("无")) {
    return ["无"] as CompetitorOption[];
  }
  return deduped;
}

function parseCompetitorState(value?: string) {
  const raw = value?.trim() || "";
  if (!raw) {
    return {
      selections: [] as CompetitorOption[],
      customName: "",
    };
  }

  const tokens = raw
    .split(/[，,、;；\n]+/)
    .map((item) => item.trim())
    .filter(Boolean);

  const selections: CompetitorOption[] = [];
  const customValues: string[] = [];

  tokens.forEach((token) => {
    if ((COMPETITOR_OPTIONS as readonly string[]).includes(token) && token !== "其他") {
      selections.push(token as CompetitorOption);
      return;
    }
    customValues.push(token);
  });

  if (customValues.length > 0) {
    selections.push("其他");
  }

  return {
    selections: normalizeCompetitorSelections(selections),
    customName: customValues.join("、"),
  };
}

function buildCompetitorValue(selected: CompetitorOption[], customName?: string) {
  const normalizedSelections = normalizeCompetitorSelections(selected);
  if (!normalizedSelections.length) {
    return undefined;
  }
  if (normalizedSelections.includes("无")) {
    return "无";
  }

  const values = normalizedSelections
    .filter((item) => item !== "其他")
    .map((item) => item.trim());

  if (normalizedSelections.includes("其他")) {
    const trimmedCustomName = customName?.trim();
    if (trimmedCustomName) {
      values.push(trimmedCustomName);
    }
  }

  const deduped = Array.from(new Set(values.filter(Boolean)));
  return deduped.length ? deduped.join("、") : undefined;
}

function normalizeOperatorToken(value?: string) {
  return (value || "")
    .trim()
    .toLowerCase()
    .replace(/\s+/g, "")
    .replace(/＋/g, "+");
}

function resolveOperatorMode(operatorValue: string | undefined, operatorOptions: OpportunityDictOption[]): OperatorMode {
  const selectedOption = operatorOptions.find((item) => (item.value || "") === (operatorValue || ""));
  const candidates = [selectedOption?.label, selectedOption?.value, operatorValue]
    .filter(Boolean)
    .map((item) => normalizeOperatorToken(item));

  if (!candidates.length || candidates.every((item) => !item)) {
    return "none";
  }

  const hasH3c = candidates.some((item) => item.includes("新华三") || item.includes("h3c"));
  const hasChannel = candidates.some((item) => item.includes("渠道") || item.includes("channel"));

  if (hasH3c && hasChannel) {
    return "both";
  }
  if (hasH3c) {
    return "h3c";
  }
  if (hasChannel) {
    return "channel";
  }
  return "none";
}

function buildOpportunitySubmitPayload(
  form: CreateOpportunityPayload,
  selectedCompetitors: CompetitorOption[],
  customCompetitorName: string,
  operatorMode: OperatorMode,
): CreateOpportunityPayload {
  return {
    ...form,
    salesExpansionId: operatorMode === "h3c" || operatorMode === "both" ? form.salesExpansionId : undefined,
    channelExpansionId: operatorMode === "channel" || operatorMode === "both" ? form.channelExpansionId : undefined,
    competitorName: buildCompetitorValue(selectedCompetitors, customCompetitorName),
  };
}

function validateOperatorRelations(
  operatorMode: OperatorMode,
  salesExpansionId: number | undefined,
  channelExpansionId: number | undefined,
) {
  if (operatorMode === "h3c" && !salesExpansionId) {
    throw new Error("运作方选择“新华三”时，新华三负责人必须填写");
  }
  if (operatorMode === "channel" && !channelExpansionId) {
    throw new Error("运作方选择“渠道”时，渠道名称必须填写");
  }
  if (operatorMode === "both" && !salesExpansionId && !channelExpansionId) {
    throw new Error("运作方选择“新华三+渠道”时，新华三负责人和渠道名称都必须填写");
  }
  if (operatorMode === "both" && !salesExpansionId) {
    throw new Error("运作方选择“新华三+渠道”时，新华三负责人必须填写");
  }
  if (operatorMode === "both" && !channelExpansionId) {
    throw new Error("运作方选择“新华三+渠道”时，渠道名称必须填写");
  }
}

function getFieldInputClass(hasError: boolean) {
  return cn(
    "crm-input-box crm-input-text w-full border bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:bg-slate-900/50",
    hasError
      ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10"
      : "border-slate-200 dark:border-slate-800",
  );
}

function RequiredMark() {
  return <span className="ml-1 text-rose-500">*</span>;
}

function validateOpportunityForm(
  form: CreateOpportunityPayload,
  selectedCompetitors: CompetitorOption[],
  customCompetitorName: string,
  operatorMode: OperatorMode,
) {
  const errors: Partial<Record<OpportunityField, string>> = {};

  if (!form.projectLocation?.trim()) {
    errors.projectLocation = "请选择项目地";
  }
  if (!form.opportunityName?.trim()) {
    errors.opportunityName = "请填写项目名称";
  }
  if (!form.customerName?.trim()) {
    errors.customerName = "请填写最终用户";
  }
  if (!form.operatorName?.trim()) {
    errors.operatorName = "请选择运作方";
  }
  if (!form.amount || form.amount <= 0) {
    errors.amount = "请填写预计金额";
  }
  if (!form.expectedCloseDate?.trim()) {
    errors.expectedCloseDate = "请选择预计下单时间";
  }
  if (!form.confidencePct?.trim()) {
    errors.confidencePct = "请选择项目把握度";
  }
  if (!form.stage?.trim()) {
    errors.stage = "请选择项目阶段";
  }
  if (!form.opportunityType?.trim()) {
    errors.opportunityType = "请选择建设类型";
  }
  if (selectedCompetitors.length === 0) {
    errors.competitorName = "请至少选择一个竞争对手";
  } else if (selectedCompetitors.includes("其他") && !customCompetitorName.trim()) {
    errors.competitorName = "已选择“其他”，请填写其他竞争对手";
  }

  if (operatorMode === "h3c" && !form.salesExpansionId) {
    errors.salesExpansionId = "请选择新华三负责人";
  }
  if (operatorMode === "channel" && !form.channelExpansionId) {
    errors.channelExpansionId = "请选择渠道名称";
  }
  if (operatorMode === "both" && !form.salesExpansionId) {
    errors.salesExpansionId = "请选择新华三负责人";
  }
  if (operatorMode === "both" && !form.channelExpansionId) {
    errors.channelExpansionId = "请选择渠道名称";
  }

  return errors;
}

function ModalShell({
  title,
  subtitle,
  onClose,
  children,
  footer,
}: {
  title: string;
  subtitle: string;
  onClose: () => void;
  children: ReactNode;
  footer: ReactNode;
}) {
  const isMobileViewport = useIsMobileViewport();
  const isWecomBrowser = useIsWecomBrowser();
  const disableMobileMotion = isMobileViewport || isWecomBrowser;

  return (
    <>
      <motion.div
        initial={disableMobileMotion ? false : { opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={disableMobileMotion ? undefined : { opacity: 0 }}
        onClick={onClose}
        className={cn("fixed inset-0 z-[70] bg-slate-900/35 dark:bg-slate-950/70", !disableMobileMotion && "backdrop-blur-sm")}
      />
      <motion.div
        initial={disableMobileMotion ? false : { opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        exit={disableMobileMotion ? undefined : { opacity: 0, y: 20 }}
        transition={disableMobileMotion ? { duration: 0 } : undefined}
        className="fixed inset-0 z-[80] px-0 pb-0 pt-[env(safe-area-inset-top)] sm:p-6"
      >
        <div className="mx-auto flex h-[calc(100dvh-env(safe-area-inset-top))] w-full items-end sm:h-full sm:max-w-3xl sm:items-center">
          <div className="flex h-[92dvh] w-full flex-col overflow-hidden rounded-t-3xl border border-slate-200 bg-white shadow-2xl dark:border-slate-800 dark:bg-slate-900 sm:h-full sm:rounded-3xl">
            <div className="flex items-center justify-between border-b border-slate-100 px-5 py-4 dark:border-slate-800 sm:px-6">
              <div>
                <h2 className="text-base font-semibold text-slate-900 dark:text-white sm:text-lg">{title}</h2>
                <p className="mt-1 text-xs leading-5 text-slate-500 dark:text-slate-400">{subtitle}</p>
              </div>
              <button onClick={onClose} className="rounded-full p-2 text-slate-400 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800">
                <X className="h-5 w-5" />
              </button>
            </div>
            <div className="flex-1 overflow-y-auto px-5 py-5 sm:px-6">{children}</div>
            <div className="border-t border-slate-100 px-5 pb-[calc(1rem+env(safe-area-inset-bottom))] pt-4 dark:border-slate-800 sm:px-6 sm:pb-4">{footer}</div>
          </div>
        </div>
      </motion.div>
    </>
  );
}

function DetailItem({
  label,
  value,
  icon,
  className = "",
}: {
  label: string;
  value: ReactNode;
  icon?: ReactNode;
  className?: string;
}) {
  return (
    <div className={`crm-detail-item ${className}`.trim()}>
      <p className="crm-detail-label">
        {icon}
        {label}
      </p>
      <div className="crm-detail-value">{value}</div>
    </div>
  );
}

function OpportunityExportFilterModal({
  initialFilters,
  exporting,
  exportError,
  archiveTab,
  stageOptions,
  confidenceOptions,
  projectLocationOptions,
  opportunityTypeOptions,
  operatorOptions,
  onClose,
  onConfirm,
}: {
  initialFilters: OpportunityExportFilters;
  exporting: boolean;
  exportError: string;
  archiveTab: OpportunityArchiveTab;
  stageOptions: OpportunityDictOption[];
  confidenceOptions: OpportunityDictOption[];
  projectLocationOptions: OpportunityDictOption[];
  opportunityTypeOptions: OpportunityDictOption[];
  operatorOptions: OpportunityDictOption[];
  onClose: () => void;
  onConfirm: (filters: OpportunityExportFilters) => void;
}) {
  const [draftFilters, setDraftFilters] = useState<OpportunityExportFilters>(initialFilters);
  const hasDraftFilters = Boolean(
    draftFilters.keyword
      || draftFilters.expectedStartDate
      || draftFilters.expectedEndDate
      || draftFilters.stage
      || draftFilters.confidence
      || draftFilters.projectLocation
      || draftFilters.opportunityType
      || draftFilters.operatorName
      || draftFilters.hasSalesExpansion
      || draftFilters.hasChannelExpansion,
  );
  const handleFilterChange = (key: keyof OpportunityExportFilters, value: string) => {
    setDraftFilters((current) => ({ ...current, [key]: value }));
  };
  const renderOption = (option: OpportunityDictOption) => {
    const value = option.value || option.label || "";
    const label = option.label || option.value || "";
    return value ? <option key={value} value={value}>{label}</option> : null;
  };
  const toSearchableOptions = (options: OpportunityDictOption[], allLabel: string) => [
    { value: "", label: allLabel },
    ...options
      .map((option) => {
        const value = option.value || option.label || "";
        const label = option.label || option.value || "";
        return value ? { value, label } : null;
      })
      .filter((option): option is { value: string; label: string } => Boolean(option)),
  ];

  return (
    <ModalShell
      title={`导出${archiveTab === "active" ? "未签单" : "已签单"}商机`}
      subtitle="选择条件后导出 Excel；不填条件则导出当前签单页签下的全部可见商机。"
      onClose={onClose}
      footer={(
        <div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
          <button
            type="button"
            onClick={() => setDraftFilters({})}
            disabled={!hasDraftFilters}
            className="crm-btn crm-btn-secondary disabled:cursor-not-allowed disabled:opacity-60"
          >
            清空条件
          </button>
          <button
            type="button"
            onClick={() => onConfirm(draftFilters)}
            disabled={exporting}
            className="crm-btn crm-btn-primary disabled:cursor-not-allowed disabled:opacity-60"
          >
            {exporting ? "导出中..." : "确认导出"}
          </button>
        </div>
      )}
    >
      <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
        <label className="space-y-1.5 sm:col-span-2">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">关键词</span>
          <input
            value={draftFilters.keyword ?? ""}
            onChange={(event) => handleFilterChange("keyword", event.target.value)}
            placeholder="搜索项目编号、项目名称、最终用户、负责人、售前"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">预计下单开始日期</span>
          <input
            type="date"
            value={draftFilters.expectedStartDate ?? ""}
            onChange={(event) => handleFilterChange("expectedStartDate", event.target.value)}
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">预计下单结束日期</span>
          <input
            type="date"
            value={draftFilters.expectedEndDate ?? ""}
            onChange={(event) => handleFilterChange("expectedEndDate", event.target.value)}
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目阶段</span>
          <AdaptiveSelect
            value={draftFilters.stage ?? ""}
            options={toSearchableOptions(stageOptions, "全部阶段")}
            placeholder="全部阶段"
            sheetTitle="选择项目阶段"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
            onChange={(value) => handleFilterChange("stage", value)}
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目把握度</span>
          <AdaptiveSelect
            value={draftFilters.confidence ?? ""}
            options={[
              { value: "", label: "全部把握度" },
              ...getEffectiveConfidenceOptions(confidenceOptions).map((option) => ({
                value: option.value || "",
                label: option.label || option.value || "",
              })),
            ]}
            placeholder="全部把握度"
            sheetTitle="选择项目把握度"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
            onChange={(value) => handleFilterChange("confidence", value)}
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目地</span>
          <AdaptiveSelect
            value={draftFilters.projectLocation ?? ""}
            options={toSearchableOptions(projectLocationOptions, "全部项目地")}
            placeholder="全部项目地"
            sheetTitle="选择项目地"
            searchable
            searchPlaceholder="搜索项目地"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
            onChange={(value) => handleFilterChange("projectLocation", value)}
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">建设类型</span>
          <AdaptiveSelect
            value={draftFilters.opportunityType ?? ""}
            options={toSearchableOptions(opportunityTypeOptions, "全部建设类型")}
            placeholder="全部建设类型"
            sheetTitle="选择建设类型"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
            onChange={(value) => handleFilterChange("opportunityType", value)}
          />
        </label>
        <label className="space-y-1.5 sm:col-span-2">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">运作方</span>
          <AdaptiveSelect
            value={draftFilters.operatorName ?? ""}
            options={toSearchableOptions(operatorOptions, "全部运作方")}
            placeholder="全部运作方"
            sheetTitle="选择运作方"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
            onChange={(value) => handleFilterChange("operatorName", value)}
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">新华三负责人</span>
          <AdaptiveSelect
            value={draftFilters.hasSalesExpansion ?? ""}
            options={[
              { value: "", label: "全部" },
              { value: "yes", label: "已关联" },
              { value: "no", label: "未关联" },
            ]}
            placeholder="全部"
            sheetTitle="选择新华三负责人关联状态"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
            onChange={(value) => handleFilterChange("hasSalesExpansion", value)}
          />
        </label>
        <label className="space-y-1.5">
          <span className="text-sm font-medium text-slate-700 dark:text-slate-300">拓展渠道</span>
          <AdaptiveSelect
            value={draftFilters.hasChannelExpansion ?? ""}
            options={[
              { value: "", label: "全部" },
              { value: "yes", label: "已关联" },
              { value: "no", label: "未关联" },
            ]}
            placeholder="全部"
            sheetTitle="选择拓展渠道关联状态"
            className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50"
            onChange={(value) => handleFilterChange("hasChannelExpansion", value)}
          />
        </label>
      </div>
      {exportError ? <div className="crm-alert crm-alert-error mt-4">{exportError}</div> : null}
    </ModalShell>
  );
}

type SearchableOption = {
  value: number | string;
  label: string;
  keywords?: string[];
};

function getSearchableOptionLabel(option: SearchableOption) {
  const normalizedLabel = typeof option.label === "string" ? option.label.trim() : "";
  if (normalizedLabel) {
    return normalizedLabel;
  }
  return String(option.value ?? "");
}

function dedupeSearchableOptions(options: SearchableOption[]) {
  const seenValues = new Set<SearchableOption["value"]>();
  return options.filter((option) => {
    if (seenValues.has(option.value)) {
      return false;
    }
    seenValues.add(option.value);
    return true;
  });
}

function useIsMobileViewport() {
  const [isMobile, setIsMobile] = useState(() => {
    if (typeof window === "undefined") {
      return false;
    }
    return window.matchMedia("(max-width: 639px)").matches;
  });

  useEffect(() => {
    if (typeof window === "undefined") {
      return;
    }

    const mediaQuery = window.matchMedia("(max-width: 639px)");
    const handleChange = () => setIsMobile(mediaQuery.matches);
    handleChange();

    if (typeof mediaQuery.addEventListener === "function") {
      mediaQuery.addEventListener("change", handleChange);
      return () => mediaQuery.removeEventListener("change", handleChange);
    }

    mediaQuery.addListener(handleChange);
    return () => mediaQuery.removeListener(handleChange);
  }, []);

  return isMobile;
}

function SearchableSelect({
  value,
  options,
  placeholder,
  searchPlaceholder,
  emptyText,
  createActionLabel,
  className,
  onChange,
  onCreate,
  onQueryChange,
}: {
  value?: number;
  options: SearchableOption[];
  placeholder: string;
  searchPlaceholder: string;
  emptyText: string;
  createActionLabel?: string;
  className?: string;
  onChange: (value?: number) => void;
  onCreate?: (query: string) => void;
  onQueryChange?: (query: string) => void;
}) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const [desktopDropdownPlacement, setDesktopDropdownPlacement] = useState<"top" | "bottom">("bottom");
  const [desktopDropdownMaxHeight, setDesktopDropdownMaxHeight] = useState(256);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const desktopDropdownRef = useRef<HTMLDivElement | null>(null);
  const isMobile = useIsMobileViewport();
  const [desktopDropdownStyle, setDesktopDropdownStyle] = useState<{ top: number; left: number; width: number } | null>(null);
  const normalizedOptions = dedupeSearchableOptions(options);
  const selectedOption = normalizedOptions.find((item) => item.value === value);
  const normalizedQuery = query.trim().toLowerCase();
  const filteredOptions = normalizedOptions.filter((item) => {
    if (!normalizedQuery) {
      return true;
    }

    const haystacks = [getSearchableOptionLabel(item), ...(item.keywords ?? [])]
      .filter(Boolean)
      .map((entry) => entry.toLowerCase());

    return haystacks.some((entry) => entry.includes(normalizedQuery));
  });

  useEffect(() => {
    if (!open || isMobile) {
      setDesktopDropdownStyle(null);
      return;
    }

    const updateDesktopDropdownLayout = () => {
      const rect = containerRef.current?.getBoundingClientRect();
      if (!rect || typeof window === "undefined") {
        return;
      }

      const viewportHeight = window.innerHeight;
      const safePadding = 24;
      const panelPadding = 96;
      const availableBelow = Math.max(160, viewportHeight - rect.bottom - safePadding - panelPadding);
      const availableAbove = Math.max(160, rect.top - safePadding - panelPadding);
      const shouldOpenUpward = availableBelow < 280 && availableAbove > availableBelow;

      setDesktopDropdownPlacement(shouldOpenUpward ? "top" : "bottom");
      setDesktopDropdownMaxHeight(Math.min(320, shouldOpenUpward ? availableAbove : availableBelow));
      setDesktopDropdownStyle({
        top: shouldOpenUpward ? Math.max(safePadding, rect.top - 8) : rect.bottom + 8,
        left: rect.left,
        width: rect.width,
      });
    };

    updateDesktopDropdownLayout();

    const handlePointerDown = (event: MouseEvent) => {
      const targetNode = event.target as Node;
      if (!containerRef.current?.contains(targetNode) && !desktopDropdownRef.current?.contains(targetNode)) {
        setOpen(false);
        setQuery("");
      }
    };

    const handleViewportChange = () => {
      updateDesktopDropdownLayout();
    };

    document.addEventListener("mousedown", handlePointerDown);
    window.addEventListener("resize", handleViewportChange);
    window.addEventListener("scroll", handleViewportChange, true);
    return () => {
      document.removeEventListener("mousedown", handlePointerDown);
      window.removeEventListener("resize", handleViewportChange);
      window.removeEventListener("scroll", handleViewportChange, true);
    };
  }, [isMobile, open]);

  useEffect(() => {
    if (!open || !isMobile) {
      return;
    }

    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = previousOverflow;
    };
  }, [isMobile, open]);

  const renderSearchBody = () => (
    <>
      <div className="relative">
        <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
        <input
          autoFocus
          value={query}
          onChange={(event) => {
            const nextQuery = event.target.value;
            setQuery(nextQuery);
            onQueryChange?.(nextQuery);
          }}
          placeholder={searchPlaceholder}
          className="crm-input-text w-full rounded-xl border border-slate-200 bg-slate-50 py-2.5 pl-10 pr-3 text-slate-900 outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-800/60 dark:text-white"
        />
      </div>

      <div className="mt-3 max-h-64 space-y-1 overflow-y-auto overscroll-contain pr-1">
        <button
          type="button"
          onClick={() => {
            onChange(undefined);
            setOpen(false);
            setQuery("");
          }}
          className="w-full rounded-xl px-3 py-2 text-left text-sm text-slate-500 transition-colors hover:bg-slate-50 dark:text-slate-400 dark:hover:bg-slate-800"
        >
          不选择
        </button>

        {filteredOptions.length > 0 ? (
          filteredOptions.map((item) => (
            <button
              type="button"
              key={item.value}
              onClick={() => {
                onChange(Number(item.value));
                setOpen(false);
                setQuery("");
              }}
              className={`flex w-full items-center justify-between rounded-xl px-3 py-2 text-left text-sm transition-colors ${
                item.value === value
                  ? "bg-violet-50 text-violet-700 dark:bg-violet-500/10 dark:text-violet-300"
                  : "text-slate-700 hover:bg-slate-50 dark:text-slate-200 dark:hover:bg-slate-800"
              }`}
            >
              <span>{getSearchableOptionLabel(item)}</span>
              {item.value === value ? <Check className="h-4 w-4 shrink-0" /> : null}
            </button>
          ))
        ) : (
          <div className="crm-empty-state px-3 py-6">
            <p>{emptyText}</p>
            {onCreate ? (
              <button
                type="button"
                onClick={() => {
                  onCreate(query);
                  setOpen(false);
                  setQuery("");
                }}
                className="mt-3 rounded-xl bg-violet-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-violet-700"
              >
                {createActionLabel || "新增并选中"}
              </button>
            ) : null}
          </div>
        )}
      </div>
    </>
  );

  return (
    <div ref={containerRef} className="relative">
      <button
        type="button"
        onClick={() => {
          setOpen((current) => {
            const next = !current;
            if (!next) {
              setQuery("");
            }
            return next;
          });
        }}
        className={cn(
          "crm-btn-sm crm-input-text flex w-full items-center justify-between rounded-xl border border-slate-200 bg-white text-left outline-none transition-colors hover:border-slate-300 focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50 dark:hover:border-slate-700",
          className,
        )}
      >
        <span className={selectedOption ? "text-slate-900 dark:text-white" : "text-slate-400 dark:text-slate-500"}>
          {selectedOption ? getSearchableOptionLabel(selectedOption) : placeholder}
        </span>
        <ChevronDown className={`h-4 w-4 shrink-0 text-slate-400 transition-transform ${open ? "rotate-180" : ""}`} />
      </button>

      {open && !isMobile && desktopDropdownStyle && typeof document !== "undefined"
        ? createPortal(
            <div className="pointer-events-none fixed inset-0 z-[220]">
              <div
                ref={desktopDropdownRef}
                style={{
                  position: "absolute",
                  top: desktopDropdownPlacement === "top"
                    ? Math.max(24, desktopDropdownStyle.top - Math.min(desktopDropdownMaxHeight, 320))
                    : desktopDropdownStyle.top,
                  left: desktopDropdownStyle.left,
                  width: desktopDropdownStyle.width,
                }}
                className="pointer-events-auto rounded-2xl border border-slate-200 bg-white p-3 shadow-2xl dark:border-slate-800 dark:bg-slate-900"
              >
                <div
                  style={{ maxHeight: `${desktopDropdownMaxHeight}px` }}
                  className="overflow-y-auto overscroll-contain pr-1"
                >
                  {renderSearchBody()}
                </div>
              </div>
            </div>,
            document.body,
          )
        : null}

      <AnimatePresence>
        {open && isMobile ? (
          <>
            <motion.div
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              className="fixed inset-0 z-[120] bg-slate-900/35 backdrop-blur-sm dark:bg-slate-950/70"
              onClick={() => {
                setOpen(false);
                setQuery("");
              }}
            />
            <motion.div
              initial={{ opacity: 0, y: 24 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 24 }}
              className="fixed inset-x-0 bottom-0 z-[130] px-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] pt-3"
            >
              <div className="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white shadow-2xl dark:border-slate-800 dark:bg-slate-900">
                <div className="flex items-center justify-between border-b border-slate-100 px-5 py-4 dark:border-slate-800">
                  <div>
                    <p className="text-base font-semibold text-slate-900 dark:text-white">{placeholder}</p>
                    <p className="crm-field-note mt-1">搜索并选择一个对象</p>
                  </div>
                  <button
                    type="button"
                    onClick={() => {
                      setOpen(false);
                      setQuery("");
                    }}
                    className="rounded-full p-2 text-slate-400 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800"
                  >
                    <X className="h-5 w-5" />
                  </button>
                </div>
                <div className="px-4 py-4 pb-[calc(1rem+env(safe-area-inset-bottom))]">
                  {renderSearchBody()}
                </div>
              </div>
            </motion.div>
          </>
        ) : null}
      </AnimatePresence>
    </div>
  );
}

function CompetitorMultiSelect({
  value,
  options,
  placeholder,
  className,
  onChange,
}: {
  value: CompetitorOption[];
  options: readonly CompetitorOption[];
  placeholder: string;
  className?: string;
  onChange: (value: CompetitorOption[]) => void;
}) {
  const [open, setOpen] = useState(false);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const desktopDropdownRef = useRef<HTMLDivElement | null>(null);
  const isMobile = useIsMobileViewport();
  const [desktopDropdownPlacement, setDesktopDropdownPlacement] = useState<"top" | "bottom">("bottom");
  const [desktopDropdownMaxHeight, setDesktopDropdownMaxHeight] = useState(320);
  const [desktopDropdownStyle, setDesktopDropdownStyle] = useState<{ left: number; width: number; top: number } | null>(null);

  const summary = value.length > 0 ? value.join("、") : placeholder;

  useEffect(() => {
    if (!open || isMobile) {
      setDesktopDropdownStyle(null);
      return;
    }

    const updateDesktopDropdownPosition = () => {
      const rect = containerRef.current?.getBoundingClientRect();
      if (!rect) {
        return;
      }

      const viewportHeight = window.innerHeight;
      const safePadding = 24;
      const panelChromeHeight = 132;
      const availableBelow = Math.max(180, viewportHeight - rect.bottom - safePadding - panelChromeHeight);
      const availableAbove = Math.max(180, rect.top - safePadding - panelChromeHeight);
      const shouldOpenUpward = availableBelow < 300 && availableAbove > availableBelow;
      const popupContentHeight = Math.min(360, shouldOpenUpward ? availableAbove : availableBelow);

      setDesktopDropdownPlacement(shouldOpenUpward ? "top" : "bottom");
      setDesktopDropdownMaxHeight(popupContentHeight);

      setDesktopDropdownStyle({
        left: rect.left,
        width: rect.width,
        top: shouldOpenUpward
          ? Math.max(safePadding, rect.top - 8 - (popupContentHeight + panelChromeHeight))
          : rect.bottom + 8,
      });
    };

    updateDesktopDropdownPosition();

    const handlePointerDown = (event: MouseEvent) => {
      const targetNode = event.target as Node;
      if (!containerRef.current?.contains(targetNode) && !desktopDropdownRef.current?.contains(targetNode)) {
        setOpen(false);
      }
    };

    window.addEventListener("resize", updateDesktopDropdownPosition);
    window.addEventListener("scroll", updateDesktopDropdownPosition, true);
    document.addEventListener("mousedown", handlePointerDown);
    return () => {
      window.removeEventListener("resize", updateDesktopDropdownPosition);
      window.removeEventListener("scroll", updateDesktopDropdownPosition, true);
      document.removeEventListener("mousedown", handlePointerDown);
    };
  }, [isMobile, open]);

  useEffect(() => {
    if (!open || !isMobile) {
      return;
    }

    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = previousOverflow;
    };
  }, [isMobile, open]);

  const toggleOption = (option: CompetitorOption) => {
    const exists = value.includes(option);
    if (option === "无") {
      onChange(exists ? [] : ["无"]);
      return;
    }

    const next = exists
      ? value.filter((item) => item !== option)
      : normalizeCompetitorSelections([...value.filter((item) => item !== "无"), option]);
    onChange(next);
  };

  const renderOptions = () => (
    <>
      <div className="space-y-1">
        {options.map((option) => {
          const active = value.includes(option);
          return (
            <button
              key={option}
              type="button"
              onClick={() => toggleOption(option)}
              className={cn(
                "flex w-full items-center justify-between rounded-xl px-3 py-2.5 text-left text-sm transition-colors",
                active
                  ? "bg-violet-50 text-violet-700 dark:bg-violet-500/10 dark:text-violet-300"
                  : "text-slate-700 hover:bg-slate-50 dark:text-slate-200 dark:hover:bg-slate-800",
              )}
            >
              <span>{option}</span>
              {active ? <Check className="h-4 w-4 shrink-0" /> : null}
            </button>
          );
        })}
      </div>
      <div className="mt-3 flex items-center justify-between gap-2 border-t border-slate-100 pt-3 dark:border-slate-800">
        <button
          type="button"
          onClick={() => onChange([])}
          className="rounded-xl px-3 py-2 text-sm text-slate-500 transition-colors hover:bg-slate-50 dark:text-slate-400 dark:hover:bg-slate-800"
        >
          清空
        </button>
        <button
          type="button"
          onClick={() => setOpen(false)}
          className="rounded-xl bg-violet-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-violet-700"
        >
          完成
        </button>
      </div>
    </>
  );

  return (
    <div ref={containerRef} className="relative">
      <button
        type="button"
        onClick={() => setOpen((current) => !current)}
        className={cn(
          "crm-btn-sm crm-input-text flex w-full items-center justify-between rounded-xl border border-slate-200 bg-white text-left outline-none transition-colors hover:border-slate-300 focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50 dark:hover:border-slate-700",
          className,
        )}
      >
        <span className={value.length > 0 ? "truncate text-slate-900 dark:text-white" : "text-slate-400 dark:text-slate-500"}>
          {summary}
        </span>
        <ChevronDown className={cn("h-4 w-4 shrink-0 text-slate-400 transition-transform", open && "rotate-180")} />
      </button>

      {open && !isMobile && desktopDropdownStyle && typeof document !== "undefined"
        ? createPortal(
            <div className="pointer-events-none fixed inset-0 z-[220]">
              <div
                ref={desktopDropdownRef}
                style={{
                  position: "absolute",
                  left: desktopDropdownStyle.left,
                  width: desktopDropdownStyle.width,
                  top: desktopDropdownStyle.top,
                }}
                className="pointer-events-auto rounded-2xl border border-slate-200 bg-white p-3 shadow-2xl dark:border-slate-800 dark:bg-slate-900"
              >
                <div className="mb-3 shrink-0">
                  <p className="text-sm font-semibold text-slate-900 dark:text-white">竞争对手</p>
                  <p className="crm-field-note mt-1">支持多选，选择“其他”后可手动录入。</p>
                </div>
                <div style={{ maxHeight: `${desktopDropdownMaxHeight}px` }} className="overflow-y-auto pr-1">
                  {renderOptions()}
                </div>
              </div>
            </div>,
            document.body,
          )
        : null}

      <AnimatePresence>
        {open && isMobile ? (
          <>
            <motion.div
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              className="fixed inset-0 z-[120] bg-slate-900/35 backdrop-blur-sm dark:bg-slate-950/70"
              onClick={() => setOpen(false)}
            />
            <motion.div
              initial={{ opacity: 0, y: 24 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 24 }}
              className="fixed inset-x-0 bottom-0 z-[130] px-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] pt-3"
            >
              <div className="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white shadow-2xl dark:border-slate-800 dark:bg-slate-900">
                <div className="flex items-center justify-between border-b border-slate-100 px-5 py-4 dark:border-slate-800">
                  <div>
                    <p className="text-base font-semibold text-slate-900 dark:text-white">竞争对手</p>
                    <p className="crm-field-note mt-1">支持多选，选择“其他”后可手动录入。</p>
                  </div>
                  <button
                    type="button"
                    onClick={() => setOpen(false)}
                    className="rounded-full p-2 text-slate-400 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800"
                  >
                    <X className="h-5 w-5" />
                  </button>
                </div>
                <div className="px-4 py-4 pb-[calc(1rem+env(safe-area-inset-bottom))]">
                  {renderOptions()}
                </div>
              </div>
            </motion.div>
          </>
        ) : null}
      </AnimatePresence>
    </div>
  );
}

export default function Opportunities() {
  const location = useLocation();
  const currentUserId = getStoredCurrentUserId();
  const isMobileViewport = useIsMobileViewport();
  const isWecomBrowser = useIsWecomBrowser();
  const disableMobileMotion = isMobileViewport || isWecomBrowser;
  const [archiveTab, setArchiveTab] = useState<OpportunityArchiveTab>("active");
  const [filter, setFilter] = useState("全部");
  const [stageFilterOpen, setStageFilterOpen] = useState(false);
  const [keyword, setKeyword] = useState("");
  const [selectedItem, setSelectedItem] = useState<OpportunityItem | null>(null);
  const [createOpen, setCreateOpen] = useState(false);
  const [editOpen, setEditOpen] = useState(false);
  const [pushConfirmOpen, setPushConfirmOpen] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [pushingOms, setPushingOms] = useState(false);
  const [exporting, setExporting] = useState(false);
  const [exportFilterOpen, setExportFilterOpen] = useState(false);
  const [exportFilters, setExportFilters] = useState<OpportunityExportFilters>({});
  const [error, setError] = useState("");
  const [exportError, setExportError] = useState("");
  const [items, setItems] = useState<OpportunityItem[]>([]);
  const [salesExpansionOptions, setSalesExpansionOptions] = useState<SalesExpansionItem[]>([]);
  const [channelExpansionOptions, setChannelExpansionOptions] = useState<ChannelExpansionItem[]>([]);
  const [omsPreSalesOptions, setOmsPreSalesOptions] = useState<OmsPreSalesOption[]>([]);
  const [stageOptions, setStageOptions] = useState<OpportunityDictOption[]>([]);
  const [operatorOptions, setOperatorOptions] = useState<OpportunityDictOption[]>([]);
  const [projectLocationOptions, setProjectLocationOptions] = useState<OpportunityDictOption[]>([]);
  const [opportunityTypeOptions, setOpportunityTypeOptions] = useState<OpportunityDictOption[]>([]);
  const [confidenceOptions, setConfidenceOptions] = useState<OpportunityDictOption[]>([]);
  const [form, setForm] = useState<CreateOpportunityPayload>(defaultForm);
  const [pushPreSalesId, setPushPreSalesId] = useState<number | undefined>(undefined);
  const [pushPreSalesName, setPushPreSalesName] = useState("");
  const [loadingOmsPreSales, setLoadingOmsPreSales] = useState(false);
  const [selectedCompetitors, setSelectedCompetitors] = useState<CompetitorOption[]>([]);
  const [customCompetitorName, setCustomCompetitorName] = useState("");
  const [fieldErrors, setFieldErrors] = useState<Partial<Record<OpportunityField, string>>>({});
  const [detailTab, setDetailTab] = useState<"sales" | "channel" | "followups">("sales");
  const [quickCreateOpen, setQuickCreateOpen] = useState(false);
  const [quickCreateType, setQuickCreateType] = useState<QuickCreateType>("sales");
  const [quickCreateSubmitting, setQuickCreateSubmitting] = useState(false);
  const [quickCreateError, setQuickCreateError] = useState("");
  const [quickSalesForm, setQuickSalesForm] = useState<CreateSalesExpansionPayload>(sharedDefaultQuickSalesForm);
  const [quickChannelForm, setQuickChannelForm] = useState<CreateChannelExpansionPayload>(sharedDefaultQuickChannelForm);
  const [quickSalesFieldErrors, setQuickSalesFieldErrors] = useState<Partial<Record<SalesCreateField, string>>>({});
  const [quickChannelFieldErrors, setQuickChannelFieldErrors] = useState<Partial<Record<ChannelField, string>>>({});
  const [quickInvalidChannelContactRows, setQuickInvalidChannelContactRows] = useState<number[]>([]);
  const [quickOfficeOptions, setQuickOfficeOptions] = useState<ExpansionDictOption[]>([]);
  const [quickIndustryOptions, setQuickIndustryOptions] = useState<ExpansionDictOption[]>([]);
  const [quickProvinceOptions, setQuickProvinceOptions] = useState<ExpansionDictOption[]>([]);
  const [quickCityOptions, setQuickCityOptions] = useState<ExpansionDictOption[]>([]);
  const [quickCertificationLevelOptions, setQuickCertificationLevelOptions] = useState<ExpansionDictOption[]>([]);
  const [quickChannelAttributeOptions, setQuickChannelAttributeOptions] = useState<ExpansionDictOption[]>([]);
  const [quickInternalAttributeOptions, setQuickInternalAttributeOptions] = useState<ExpansionDictOption[]>([]);
  const [quickSalesDuplicateMessage, setQuickSalesDuplicateMessage] = useState("");
  const [quickChannelDuplicateMessage, setQuickChannelDuplicateMessage] = useState("");
  const [salesExpansionQuery, setSalesExpansionQuery] = useState("");
  const [channelExpansionQuery, setChannelExpansionQuery] = useState("");
  const hasForegroundModal = createOpen || editOpen || pushConfirmOpen || exportFilterOpen || quickCreateOpen;

  useEffect(() => {
    let cancelled = false;

    async function load() {
      try {
        const data = await getOpportunityOverview(keyword, filter);
        if (!cancelled) {
          setItems(data.items ?? []);
          setSelectedItem(null);
        }
      } catch {
        if (!cancelled) {
          setItems([]);
          setSelectedItem(null);
        }
      }
    }

    void load();
    return () => {
      cancelled = true;
    };
  }, [keyword, filter]);

  useEffect(() => {
    const requestedState = location.state as OpportunityLocationState;
    const requestedArchiveTab = requestedState?.archiveTab;
    if (requestedArchiveTab === "active" || requestedArchiveTab === "archived") {
      setArchiveTab(requestedArchiveTab);
    }
  }, [location.state]);

  useEffect(() => {
    const requestedState = location.state as OpportunityLocationState;
    const requestedId = requestedState?.selectedId;
    if (!requestedId) {
      return;
    }

    const matched = items.find((item) => item.id === requestedId);
    if (matched) {
      setSelectedItem(matched);
    }
  }, [location.state, items]);

  useEffect(() => {
    let cancelled = false;

    async function loadSalesExpansionOptions() {
      try {
        const data = await getExpansionOverview("");
        if (!cancelled) {
          setSalesExpansionOptions(data.salesItems ?? []);
          setChannelExpansionOptions(data.channelItems ?? []);
        }
      } catch {
        if (!cancelled) {
          setSalesExpansionOptions([]);
          setChannelExpansionOptions([]);
        }
      }
    }

    void loadSalesExpansionOptions();
    return () => {
      cancelled = true;
    };
  }, []);

  const refreshExpansionOptions = async () => {
    const data = await getExpansionOverview("");
    setSalesExpansionOptions(data.salesItems ?? []);
    setChannelExpansionOptions(data.channelItems ?? []);
    return data;
  };

  useEffect(() => {
    let cancelled = false;

    async function loadMeta() {
      try {
        const data = await getOpportunityMeta();
        if (!cancelled) {
          setStageOptions((data.stageOptions ?? []).filter((item) => item.value));
          setOperatorOptions((data.operatorOptions ?? []).filter((item) => item.value));
          setProjectLocationOptions((data.projectLocationOptions ?? []).filter((item) => item.value));
          setOpportunityTypeOptions((data.opportunityTypeOptions ?? []).filter((item) => item.value));
          setConfidenceOptions((data.confidenceOptions ?? []).filter((item) => item.value));
        }
      } catch {
        if (!cancelled) {
          setStageOptions([]);
          setOperatorOptions([]);
          setProjectLocationOptions([]);
          setOpportunityTypeOptions([]);
          setConfidenceOptions([]);
        }
      }
    }

    void loadMeta();
    return () => {
      cancelled = true;
    };
  }, []);

  useEffect(() => {
    if (!stageOptions.length) {
      return;
    }

    const defaultStage = stageOptions[0]?.value || "";
    setForm((current) => (current.stage ? current : { ...current, stage: defaultStage }));
  }, [stageOptions]);

  useEffect(() => {
    if (!opportunityTypeOptions.length) {
      return;
    }

    const defaultOpportunityType = opportunityTypeOptions[0]?.value || "";
    setForm((current) => (current.opportunityType ? current : { ...current, opportunityType: defaultOpportunityType }));
  }, [opportunityTypeOptions]);

  useEffect(() => {
    const effectiveConfidenceOptions = getEffectiveConfidenceOptions(confidenceOptions);
    if (!effectiveConfidenceOptions.length) {
      return;
    }

    const defaultConfidence = effectiveConfidenceOptions[0]?.value || "";
    setForm((current) => ({
      ...current,
      confidencePct: current.confidencePct
        ? normalizeConfidenceValue(current.confidencePct, effectiveConfidenceOptions)
        : defaultConfidence,
    }));
  }, [confidenceOptions]);

  const resetQuickCreateState = () => {
    setQuickCreateOpen(false);
    setQuickCreateSubmitting(false);
    setQuickCreateError("");
    setQuickSalesForm(sharedDefaultQuickSalesForm);
    setQuickChannelForm(sharedDefaultQuickChannelForm);
    setQuickSalesFieldErrors({});
    setQuickChannelFieldErrors({});
    setQuickInvalidChannelContactRows([]);
    setQuickCityOptions([]);
    setQuickSalesDuplicateMessage("");
    setQuickChannelDuplicateMessage("");
  };

  const loadQuickCreateMeta = async () => {
    const data = await getExpansionMeta();
    setQuickOfficeOptions(data.officeOptions ?? []);
    setQuickIndustryOptions(data.industryOptions ?? []);
    setQuickProvinceOptions(data.provinceOptions ?? []);
    setQuickCertificationLevelOptions(data.certificationLevelOptions ?? []);
    setQuickChannelAttributeOptions(data.channelAttributeOptions ?? []);
    setQuickInternalAttributeOptions(data.internalAttributeOptions ?? []);
    setQuickChannelForm((current) => ({
      ...current,
      channelCode: current.channelCode || data.nextChannelCode || "",
    }));
    return data;
  };

  const loadQuickCityOptions = async (provinceName?: string) => {
    const normalizedProvinceName = provinceName?.trim();
    if (!normalizedProvinceName) {
      setQuickCityOptions([]);
      return [];
    }
    const cityOptions = await getExpansionCityOptions(normalizedProvinceName);
    setQuickCityOptions(cityOptions ?? []);
    return cityOptions ?? [];
  };

  const openQuickCreateModal = async (type: QuickCreateType, initialKeyword?: string) => {
    setQuickCreateType(type);
    setQuickCreateError("");
    setQuickSalesFieldErrors({});
    setQuickChannelFieldErrors({});
    setQuickInvalidChannelContactRows([]);
    setQuickSalesDuplicateMessage("");
    setQuickChannelDuplicateMessage("");
    const normalizedKeyword = initialKeyword?.trim() || "";
    try {
      const meta = await loadQuickCreateMeta();
      if (type === "channel") {
        setQuickChannelForm({
          ...sharedDefaultQuickChannelForm,
          channelCode: meta.nextChannelCode || "",
          channelName: normalizedKeyword,
        });
      } else {
        setQuickSalesForm({
          ...sharedDefaultQuickSalesForm,
          candidateName: normalizedKeyword,
        });
      }
    } catch (metaError) {
      setQuickCreateError(metaError instanceof Error ? metaError.message : "加载拓展配置失败");
    }
    setQuickCreateOpen(true);
  };

  const followUpRecords: OpportunityFollowUp[] = selectedItem?.followUps ?? [];
  const visibleItems = items.filter((item) => (archiveTab === "active" ? !item.archived : Boolean(item.archived)));
  const stageFilterOptions = [
    { label: "全部", value: "全部" },
    ...stageOptions.map((item) => ({ label: item.label || item.value || "", value: item.value || "" })),
  ].filter((item) => item.value);
  const normalizedProjectLocation = form.projectLocation?.trim() || "";
  const projectLocationSelectOptions = [
    { value: "", label: "请选择" },
    ...projectLocationOptions.map((item) => ({
      label: item.label || item.value || "",
      value: item.value || "",
    })),
    ...(
      normalizedProjectLocation
      && !projectLocationOptions.some((item) => (item.value || "").trim() === normalizedProjectLocation)
        ? [{ value: normalizedProjectLocation, label: normalizedProjectLocation }]
        : []
    ),
  ];
  const normalizedOpportunityType = form.opportunityType?.trim() || "";
  const effectiveOpportunityTypeOptions = opportunityTypeOptions.length > 0
    ? opportunityTypeOptions
    : FALLBACK_OPPORTUNITY_TYPE_OPTIONS;
  const effectiveConfidenceOptions = getEffectiveConfidenceOptions(confidenceOptions);
  const buildEmptyForm = (): CreateOpportunityPayload => ({
    ...defaultForm,
    confidencePct: effectiveConfidenceOptions[0]?.value || defaultForm.confidencePct,
  });
  const opportunityTypeSelectOptions = [
    { value: "", label: "请选择" },
    ...effectiveOpportunityTypeOptions.map((item) => ({
      label: item.label || item.value || "",
      value: item.value || "",
    })),
    ...(
      normalizedOpportunityType
      && !effectiveOpportunityTypeOptions.some((item) => (item.value || "").trim() === normalizedOpportunityType)
        ? [{ value: normalizedOpportunityType, label: normalizedOpportunityType }]
        : []
    ),
  ];
  const activeStageFilterLabel = stageFilterOptions.find((item) => item.value === filter)?.label || "全部";
  const selectedSalesExpansion = selectedItem?.salesExpansionId
    ? salesExpansionOptions.find((item) => item.id === selectedItem.salesExpansionId) ?? null
    : null;
  const selectedChannelExpansion = selectedItem?.channelExpansionId
    ? channelExpansionOptions.find((item) => item.id === selectedItem.channelExpansionId) ?? null
    : null;
  const selectedSalesExpansionName = selectedItem?.salesExpansionName || selectedSalesExpansion?.name || "";
  const selectedChannelExpansionName = selectedItem?.channelExpansionName || selectedChannelExpansion?.name || "";
  const hasSelectedSalesExpansion = Boolean(selectedSalesExpansionName);
  const hasSelectedChannelExpansion = Boolean(selectedChannelExpansionName);
  const selectedPreSalesName = selectedItem?.preSalesName || "无";
  const canEditSelectedItem = Boolean(selectedItem && currentUserId !== undefined && selectedItem.ownerUserId === currentUserId);
  const canPushSelectedItem = canEditSelectedItem;
  const operatorMode = resolveOperatorMode(form.operatorName, operatorOptions);
  const showSalesExpansionField = operatorMode === "h3c" || operatorMode === "both";
  const showChannelExpansionField = operatorMode === "channel" || operatorMode === "both";
  const showCustomCompetitorInput = selectedCompetitors.includes("其他");
  const salesExpansionSearchOptions: SearchableOption[] = salesExpansionOptions.map((item) => ({
    value: item.id,
    label: item.name || `拓展人员#${item.id}`,
    keywords: [item.employeeNo || "", item.officeName || "", item.phone || "", item.title || ""],
  }));
  const channelExpansionSearchOptions: SearchableOption[] = channelExpansionOptions.map((item) => ({
    value: item.id,
    label: item.name || `渠道#${item.id}`,
    keywords: [item.channelCode || "", item.province || "", item.primaryContactName || "", item.primaryContactMobile || ""],
  }));
  const quickChannelOtherOptionValue = quickChannelAttributeOptions.find(isSharedOtherOption)?.value;
  const omsPreSalesSearchOptions: SearchableOption[] = [
    ...(pushPreSalesId && pushPreSalesName && !omsPreSalesOptions.some((item) => item.userId === pushPreSalesId)
      ? [{ value: pushPreSalesId, label: pushPreSalesName, keywords: [] }]
      : []),
    ...omsPreSalesOptions.map((item) => ({
      value: item.userId,
      label: item.userName || item.loginName || `售前#${item.userId}`,
      keywords: [item.loginName || ""],
    })),
  ];

  useEffect(() => {
    if (selectedItem) {
      setDetailTab("sales");
    } else {
      setPushConfirmOpen(false);
      setPushPreSalesId(undefined);
      setPushPreSalesName("");
    }
  }, [selectedItem]);

  useEffect(() => {
    if (selectedItem && !visibleItems.some((item) => item.id === selectedItem.id)) {
      setSelectedItem(null);
    }
  }, [archiveTab, selectedItem, visibleItems]);

  const getConfidenceColor = (score?: string | number | null) => {
    const normalizedValue = normalizeConfidenceValue(score, effectiveConfidenceOptions);
    const matchedIndex = effectiveConfidenceOptions.findIndex((item) => (item.value || "") === normalizedValue);
    if (matchedIndex < 0) return "text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/10 border-rose-200 dark:border-rose-500/20";
    if (matchedIndex === 0) return "text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border-emerald-200 dark:border-emerald-500/20";
    if (matchedIndex === 1) return "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border-amber-200 dark:border-amber-500/20";
    return "text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/10 border-rose-200 dark:border-rose-500/20";
  };

  const handleExport = async (filters: OpportunityExportFilters) => {
    if (exporting) {
      return;
    }

    setExporting(true);
    setExportError("");
    setExportFilters(filters);

    try {
      const overview = await getOpportunityOverview("", "全部");
      const exportItems = (overview.items ?? [])
        .filter((item) => (archiveTab === "active" ? !item.archived : Boolean(item.archived)))
        .filter((item) => matchesOpportunityExportFilters(item, filters, effectiveConfidenceOptions));
      if (exportItems.length <= 0) {
        throw new Error(`当前筛选条件下暂无可导出的${archiveTab === "active" ? "未签单" : "已签单"}商机`);
      }

      const ExcelJS = await import("exceljs");
      const workbook = new ExcelJS.Workbook();
      const worksheet = workbook.addWorksheet("商机储备");
      const headers = [
        "项目编号",
        "项目名称",
        "项目地",
        "最终用户",
        "建设类型",
        "运作方",
        "项目阶段",
        "项目把握度",
        "预计金额（元）",
        "预计下单时间",
        "销售拓展人员姓名",
        "销售拓展人员合作意向",
        "销售拓展人员是否在职",
        "拓展渠道名称",
        "拓展渠道合作意向",
        "拓展渠道建立联系时间",
        "新华三负责人",
        "售前",
        "竞争对手",
        "项目最新进展",
        "后续规划",
        "备注说明",
        "创建人",
        "更新修改时间",
        "是否签单",
        "是否推送OMS",
        "跟进记录",
      ];

      worksheet.addRow(headers);

      exportItems.forEach((item) => {
        const relatedSales = item.salesExpansionId
          ? salesExpansionOptions.find((option) => option.id === item.salesExpansionId) ?? null
          : null;
        const relatedChannel = item.channelExpansionId
          ? channelExpansionOptions.find((option) => option.id === item.channelExpansionId) ?? null
          : null;
        const followUpText = (item.followUps ?? [])
          .map((record) => {
            const summary = getOpportunityFollowUpSummary(record);
            const lines = [
              [normalizeOpportunityExportText(record.date), normalizeOpportunityExportText(record.user)].filter(Boolean).join(" / "),
              normalizeOpportunityExportText(summary.communicationContent),
            ].filter(Boolean);
            return lines.join("\n");
          })
          .filter(Boolean)
          .join("\n\n");

        worksheet.addRow([
          normalizeOpportunityExportText(item.code),
          normalizeOpportunityExportText(item.name),
          normalizeOpportunityExportText(item.projectLocation),
          normalizeOpportunityExportText(item.client),
          normalizeOpportunityExportText(item.type || "新建"),
          normalizeOpportunityExportText(item.operatorName),
          normalizeOpportunityExportText(item.stage),
          getConfidenceLabel(item.confidence, effectiveConfidenceOptions),
          item.amount === null || item.amount === undefined ? "" : `¥${formatAmount(item.amount)}`,
          normalizeOpportunityExportText(item.date),
          normalizeOpportunityExportText(item.salesExpansionName || relatedSales?.name),
          normalizeOpportunityExportText(relatedSales?.intent),
          relatedSales ? (relatedSales.active ? "是" : "否") : "",
          normalizeOpportunityExportText(item.channelExpansionName || relatedChannel?.name),
          normalizeOpportunityExportText(relatedChannel?.intent),
          normalizeOpportunityExportText(relatedChannel?.establishedDate),
          normalizeOpportunityExportText(item.salesExpansionName || relatedSales?.name),
          normalizeOpportunityExportText(item.preSalesName),
          normalizeOpportunityExportText(item.competitorName),
          normalizeOpportunityExportText(item.latestProgress),
          normalizeOpportunityExportText(item.nextPlan),
          normalizeOpportunityExportText(item.notes),
          normalizeOpportunityExportText(item.owner),
          normalizeOpportunityExportText(item.updatedAt),
          formatOpportunityBoolean(item.archived, "已签单", "未签单"),
          formatOpportunityBoolean(item.pushedToOms, "已推送", "未推送"),
          followUpText,
        ]);
      });

      worksheet.views = [{ state: "frozen", ySplit: 1 }];
      const followUpColumnIndex = headers.indexOf("跟进记录") + 1;
      worksheet.getRow(1).height = 24;
      worksheet.getRow(1).font = { bold: true };
      worksheet.getRow(1).alignment = { vertical: "middle", horizontal: "center" };

      headers.forEach((header, index) => {
        const column = worksheet.getColumn(index + 1);
        if (header === "跟进记录") {
          column.width = 42;
        } else if (header.includes("项目最新进展") || header.includes("后续规划") || header.includes("备注")) {
          column.width = 24;
        } else if (header.includes("项目名称") || header.includes("最终客户") || header.includes("最终用户")) {
          column.width = 20;
        } else {
          column.width = 16;
        }
      });

      worksheet.eachRow((row, rowNumber) => {
        row.eachCell((cell, columnNumber) => {
          cell.border = {
            top: { style: "thin", color: { argb: "FFE2E8F0" } },
            left: { style: "thin", color: { argb: "FFE2E8F0" } },
            bottom: { style: "thin", color: { argb: "FFE2E8F0" } },
            right: { style: "thin", color: { argb: "FFE2E8F0" } },
          };
          cell.alignment = {
            vertical: "top",
            horizontal: rowNumber === 1 ? "center" : "left",
            wrapText: headers[columnNumber - 1] === "跟进记录",
          };
        });
        if (rowNumber > 1 && followUpColumnIndex > 0) {
          const followUpText = normalizeOpportunityExportText(row.getCell(followUpColumnIndex).value as string | null | undefined);
          const lineCount = followUpText ? followUpText.split("\n").length : 1;
          row.height = Math.max(22, lineCount * 16);
        }
      });

      const buffer = await workbook.xlsx.writeBuffer();
      const filename = `商机储备_${archiveTab === "active" ? "未签单" : "已签单"}_${formatOpportunityExportFilenameTime()}.xlsx`;
      downloadOpportunityExcelFile(filename, buffer);
      setExportFilterOpen(false);
    } catch (exportErr) {
      setExportError(exportErr instanceof Error ? exportErr.message : "导出失败，请稍后重试");
    } finally {
      setExporting(false);
    }
  };

  const handleChange = <K extends keyof CreateOpportunityPayload>(key: K, value: CreateOpportunityPayload[K]) => {
    setForm((current) => ({ ...current, [key]: value }));
    if (key in fieldErrors) {
      setFieldErrors((current) => {
        const next = { ...current };
        delete next[key as OpportunityField];
        return next;
      });
    }
  };

  const handleQuickSalesChange = <K extends keyof CreateSalesExpansionPayload>(key: K, value: CreateSalesExpansionPayload[K]) => {
    setQuickSalesForm((current) => ({ ...current, [key]: value }));
    if (key in quickSalesFieldErrors) {
      setQuickSalesFieldErrors((current) => {
        const next = { ...current };
        delete next[key as SalesCreateField];
        return next;
      });
    }
    if (key === "employeeNo") {
      setQuickSalesDuplicateMessage("");
    }
  };

  const handleQuickChannelChange = <K extends keyof CreateChannelExpansionPayload>(key: K, value: CreateChannelExpansionPayload[K]) => {
    setQuickChannelForm((current) => ({ ...current, [key]: value }));
    if (key in quickChannelFieldErrors) {
      setQuickChannelFieldErrors((current) => {
        const next = { ...current };
        delete next[key as ChannelField];
        return next;
      });
    }
    if (key === "channelName") {
      setQuickChannelDuplicateMessage("");
    }
  };

  const handleQuickChannelContactChange = (index: number, key: keyof ChannelExpansionContact, value: string) => {
    setQuickChannelForm((current) => {
      const nextContacts = [...(current.contacts ?? [])];
      nextContacts[index] = {
        ...(nextContacts[index] ?? createSharedEmptyChannelContact()),
        [key]: value,
      };
      return {
        ...current,
        contacts: nextContacts,
      };
    });
    if (quickInvalidChannelContactRows.includes(index)) {
      setQuickInvalidChannelContactRows((current) => current.filter((rowIndex) => rowIndex !== index));
    }
    if (quickChannelFieldErrors.contacts) {
      setQuickChannelFieldErrors((current) => {
        const next = { ...current };
        delete next.contacts;
        return next;
      });
    }
  };

  const addQuickChannelContact = () => {
    setQuickChannelForm((current) => ({
      ...current,
      contacts: [...(current.contacts ?? []), createSharedEmptyChannelContact()],
    }));
  };

  const removeQuickChannelContact = (index: number) => {
    setQuickChannelForm((current) => {
      const currentContacts = current.contacts ?? [];
      const nextContacts = currentContacts.filter((_, contactIndex) => contactIndex !== index);
      return {
        ...current,
        contacts: nextContacts.length > 0 ? nextContacts : [createSharedEmptyChannelContact()],
      };
    });
    setQuickInvalidChannelContactRows((current) => current.filter((rowIndex) => rowIndex !== index).map((rowIndex) => (rowIndex > index ? rowIndex - 1 : rowIndex)));
  };

  const handleQuickChannelProvinceChange = (value: string) => {
    const nextProvince = value || "";
    handleQuickChannelChange("province", nextProvince || undefined);
    handleQuickChannelChange("city", undefined);
    void loadQuickCityOptions(nextProvince);
  };

  const handleQuickCreateSubmit = async () => {
    if (quickCreateSubmitting) {
      return;
    }

    setQuickCreateError("");
    setQuickSalesDuplicateMessage("");
    setQuickChannelDuplicateMessage("");

    if (quickCreateType === "sales") {
      const validationErrors = validateSharedSalesCreateForm(quickSalesForm);
      if (Object.keys(validationErrors).length > 0) {
        setQuickSalesFieldErrors(validationErrors);
        setQuickCreateError("请先完整填写销售人员拓展必填字段");
        return;
      }
    } else {
      const channelOtherOptionValue = quickChannelAttributeOptions.find(isSharedOtherOption)?.value;
      const { errors: validationErrors, invalidContactRows } = validateSharedChannelForm(quickChannelForm, channelOtherOptionValue);
      if (Object.keys(validationErrors).length > 0) {
        setQuickChannelFieldErrors(validationErrors);
        setQuickInvalidChannelContactRows(invalidContactRows);
        setQuickCreateError("请先完整填写渠道拓展必填字段");
        return;
      }
    }

    setQuickCreateSubmitting(true);

    try {
      let createdId: number;
      if (quickCreateType === "sales") {
        const duplicateResult = await checkSalesExpansionDuplicate(quickSalesForm.employeeNo.trim());
        if (duplicateResult.duplicated) {
          const duplicateMessage = duplicateResult.message || "工号重复，请确认该人员是否已存在！";
          setQuickSalesDuplicateMessage(duplicateMessage);
          setQuickSalesFieldErrors((current) => ({ ...current, employeeNo: duplicateMessage }));
          setQuickCreateError(duplicateMessage);
          setQuickCreateSubmitting(false);
          return;
        }
        createdId = await createSalesExpansion(normalizeSharedSalesPayload(quickSalesForm));
      } else {
        const duplicateResult = await checkChannelExpansionDuplicate(quickChannelForm.channelName.trim());
        if (duplicateResult.duplicated) {
          const duplicateMessage = duplicateResult.message || "渠道重复，请确认该渠道是否已存在！";
          setQuickChannelDuplicateMessage(duplicateMessage);
          setQuickChannelFieldErrors((current) => ({ ...current, channelName: duplicateMessage }));
          setQuickCreateError(duplicateMessage);
          setQuickCreateSubmitting(false);
          return;
        }
        createdId = await createChannelExpansion(normalizeSharedChannelPayload(quickChannelForm));
      }

      const latestOverview = await refreshExpansionOptions();
      if (quickCreateType === "sales") {
        const createdItem = (latestOverview.salesItems ?? []).find((item) => item.id === createdId);
        handleChange("salesExpansionId", createdItem?.id ?? createdId);
      } else {
        const createdItem = (latestOverview.channelItems ?? []).find((item) => item.id === createdId);
        handleChange("channelExpansionId", createdItem?.id ?? createdId);
      }
      resetQuickCreateState();
    } catch (quickCreateSubmitError) {
      setQuickCreateError(quickCreateSubmitError instanceof Error ? quickCreateSubmitError.message : "新增失败");
      setQuickCreateSubmitting(false);
    }
  };

  const handleOpenCreate = () => {
    setError("");
    setFieldErrors({});
    setForm(buildEmptyForm());
    setSelectedCompetitors([]);
    setCustomCompetitorName("");
    setCreateOpen(true);
  };

  const resetCreateState = () => {
    setCreateOpen(false);
    setEditOpen(false);
    setSubmitting(false);
    setError("");
    setFieldErrors({});
    setForm(buildEmptyForm());
    setSelectedCompetitors([]);
    setCustomCompetitorName("");
  };

  const reload = async (preferredSelectedId?: number) => {
    const data = await getOpportunityOverview(keyword, filter);
    const nextItems = data.items ?? [];
    setItems(nextItems);
    if (preferredSelectedId) {
      setSelectedItem(nextItems.find((item) => item.id === preferredSelectedId) ?? null);
    }
  };

  const handleCreateSubmit = async () => {
    if (submitting) {
      return;
    }

    setError("");
    const validationErrors = validateOpportunityForm(form, selectedCompetitors, customCompetitorName, operatorMode);
    if (Object.keys(validationErrors).length > 0) {
      setFieldErrors(validationErrors);
      setError("请先完整填写商机必填字段");
      return;
    }

    setSubmitting(true);

    try {
      await createOpportunity(buildOpportunitySubmitPayload(form, selectedCompetitors, customCompetitorName, operatorMode));
      await reload();
      resetCreateState();
    } catch (createError) {
      setError(createError instanceof Error ? createError.message : "新增商机失败");
      setSubmitting(false);
    }
  };

  const handleOpenEdit = () => {
    if (!selectedItem) {
      return;
    }
    if (!canEditSelectedItem) {
      setError("仅可编辑本人负责的商机");
      return;
    }
    setError("");
    setFieldErrors({});
    setForm(toFormFromItem(selectedItem, effectiveConfidenceOptions));
    const competitorState = parseCompetitorState(selectedItem.competitorName);
    setSelectedCompetitors(competitorState.selections);
    setCustomCompetitorName(competitorState.customName);
    setEditOpen(true);
  };

  const handleEditSubmit = async () => {
    if (!selectedItem || submitting) {
      return;
    }
    if (!canEditSelectedItem) {
      setError("仅可编辑本人负责的商机");
      return;
    }

    setError("");
    const validationErrors = validateOpportunityForm(form, selectedCompetitors, customCompetitorName, operatorMode);
    if (Object.keys(validationErrors).length > 0) {
      setFieldErrors(validationErrors);
      setError("请先完整填写商机必填字段");
      return;
    }

    setSubmitting(true);

    try {
      await updateOpportunity(selectedItem.id, buildOpportunitySubmitPayload(form, selectedCompetitors, customCompetitorName, operatorMode));
      await reload(selectedItem.id);
      resetCreateState();
    } catch (updateError) {
      setError(updateError instanceof Error ? updateError.message : "编辑商机失败");
      setSubmitting(false);
    }
  };

  const handlePushToOms = async () => {
    if (!selectedItem || pushingOms) {
      return;
    }
    if (!canPushSelectedItem) {
      setError("仅可推送本人负责的商机");
      return;
    }

    setPushingOms(true);
    setError("");

    try {
      const payload: PushOpportunityToOmsPayload = {
        preSalesId: pushPreSalesId,
        preSalesName: pushPreSalesName.trim() || undefined,
      };
      await pushOpportunityToOms(selectedItem.id, payload);
      await reload(selectedItem.id);
    } catch (pushError) {
      setError(pushError instanceof Error ? pushError.message : "推送 OMS 失败");
    } finally {
      setPushingOms(false);
    }
  };

  const syncPushPreSalesSelection = (item: OpportunityItem | null, options: OmsPreSalesOption[]) => {
    if (!item) {
      setPushPreSalesId(undefined);
      setPushPreSalesName("");
      return;
    }

    const matchedById = item.preSalesId
      ? options.find((option) => option.userId === item.preSalesId)
      : undefined;
    if (matchedById) {
      setPushPreSalesId(matchedById.userId);
      setPushPreSalesName(matchedById.userName || matchedById.loginName || "");
      return;
    }

    const matchedByName = item.preSalesName
      ? options.find((option) => (option.userName || "") === item.preSalesName)
      : undefined;
    if (matchedByName) {
      setPushPreSalesId(matchedByName.userId);
      setPushPreSalesName(matchedByName.userName || matchedByName.loginName || "");
      return;
    }

    setPushPreSalesId(item.preSalesId);
    setPushPreSalesName(item.preSalesName || "");
  };

  const handleOpenPushConfirm = async () => {
    if (!selectedItem || pushingOms) {
      return;
    }
    if (!canPushSelectedItem) {
      setError("仅可推送本人负责的商机");
      return;
    }

    setError("");
    syncPushPreSalesSelection(selectedItem, omsPreSalesOptions);
    setPushConfirmOpen(true);
    setLoadingOmsPreSales(true);

    try {
      const data = await getOpportunityOmsPreSalesOptions();
      setOmsPreSalesOptions(data);
      syncPushPreSalesSelection(selectedItem, data);
    } catch (loadError) {
      setOmsPreSalesOptions([]);
      setError(loadError instanceof Error ? loadError.message : "加载售前人员失败");
    } finally {
      setLoadingOmsPreSales(false);
    }
  };

  const handleConfirmPushToOms = async () => {
    if (!canPushSelectedItem) {
      setError("仅可推送本人负责的商机");
      return;
    }
    if (!pushPreSalesId && !pushPreSalesName.trim()) {
      setError("请选择售前人员");
      return;
    }
    setPushConfirmOpen(false);
    await handlePushToOms();
  };

  const renderEmpty = () => (
    <div className="crm-empty-panel">
      {archiveTab === "active" ? "暂无未签单商机，先新增一条试试。" : "暂无已签单商机。"}
    </div>
  );

  return (
    <div className="crm-page-stack">
      <header className="crm-page-header">
        <div className="crm-page-heading">
          <h1 className="crm-page-title">商机储备</h1>
        </div>
        <div className="flex items-center gap-2">
          <button
            onClick={() => {
              setExportError("");
              setExportFilterOpen(true);
            }}
            disabled={exporting}
            className={cn("crm-btn-sm crm-btn-secondary flex items-center gap-2 disabled:cursor-not-allowed disabled:opacity-60", disableMobileMotion ? "active:scale-100" : "active:scale-95")}
          >
            <Download className="crm-icon-md" />
            <span className="hidden sm:inline">{exporting ? "导出中..." : "导出"}</span>
          </button>
          <button
            onClick={handleOpenCreate}
            className={cn("crm-btn-sm crm-btn-primary flex items-center gap-2", disableMobileMotion ? "active:scale-100" : "active:scale-95")}
          >
            <Plus className="crm-icon-md" />
            <span className="hidden sm:inline">新增商机</span>
          </button>
        </div>
      </header>

      <div className={cn("crm-filter-bar flex", !disableMobileMotion && "backdrop-blur-sm")}>
        <button
          onClick={() => {
            setArchiveTab("active");
            setExportError("");
          }}
          className={`flex-1 rounded-lg py-2 text-sm font-medium transition-colors duration-200 ${
            archiveTab === "active"
              ? "bg-white text-violet-600 shadow-sm dark:bg-slate-800 dark:text-violet-400"
              : "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white"
          }`}
        >
          未签单
        </button>
        <button
          onClick={() => {
            setArchiveTab("archived");
            setExportError("");
          }}
          className={`flex-1 rounded-lg py-2 text-sm font-medium transition-colors duration-200 ${
            archiveTab === "archived"
              ? "bg-white text-violet-600 shadow-sm dark:bg-slate-800 dark:text-violet-400"
              : "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white"
          }`}
        >
          已签单
        </button>
      </div>

      <div className="flex items-center gap-3">
        <div className="relative group min-w-0 flex-1">
          <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400 group-focus-within:text-violet-500 transition-colors" />
          <input
            type="text"
            placeholder="搜索项目名称、最终用户、编码..."
            value={keyword}
            onChange={(event) => {
              setKeyword(event.target.value);
              setExportError("");
            }}
            className="crm-input-text w-full rounded-xl border border-slate-200 bg-white py-2.5 pl-10 pr-4 text-slate-900 outline-none transition-all focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50 dark:text-white"
          />
        </div>
        <button
          type="button"
          onClick={() => setStageFilterOpen(true)}
          className={`relative inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border transition-all ${
            filter !== "全部"
              ? "border-violet-200 bg-violet-50 text-violet-600 shadow-sm dark:border-violet-500/30 dark:bg-violet-500/10 dark:text-violet-300"
              : "border-slate-200 bg-white text-slate-500 hover:bg-slate-50 dark:border-slate-800 dark:bg-slate-900/50 dark:text-slate-400 dark:hover:bg-slate-800"
          }`}
          aria-label={`项目阶段筛选，当前：${activeStageFilterLabel}`}
          title={`项目阶段筛选：${activeStageFilterLabel}`}
        >
          <ListFilter className="h-5 w-5" />
          {filter !== "全部" ? <span className="absolute right-2 top-2 h-2 w-2 rounded-full bg-violet-500" /> : null}
        </button>
      </div>

      {exportError ? <div className="crm-alert crm-alert-error">{exportError}</div> : null}

      <div className="crm-list-stack">
        {visibleItems.length > 0 ? (
          visibleItems.map((opp, i) => {
            const isOwnedByCurrentUser = currentUserId !== undefined && opp.ownerUserId === currentUserId;
            return (
              <motion.div
                initial={disableMobileMotion ? false : { opacity: 0, y: 10 }}
                animate={{ opacity: 1, y: 0 }}
                transition={disableMobileMotion ? { duration: 0 } : { delay: i * 0.05 }}
                key={opp.id}
                onClick={() => setSelectedItem(opp)}
                className={cn(
                  "crm-card crm-card-pad group relative rounded-2xl transition-shadow transition-colors",
                  isOwnedByCurrentUser
                    ? "cursor-pointer hover:border-violet-100 hover:shadow-md dark:hover:border-violet-900/50"
                    : "cursor-pointer border-slate-100 bg-slate-50/45 hover:border-slate-200 hover:shadow-sm dark:border-slate-800/80 dark:bg-slate-900/35 dark:hover:border-slate-700",
                )}
              >
                {isOwnedByCurrentUser ? (
                  <div className="pointer-events-none absolute inset-x-5 top-0 h-1.5 rounded-b-full bg-gradient-to-r from-violet-400/85 via-fuchsia-400/70 to-indigo-400/85 shadow-[0_6px_16px_rgba(124,58,237,0.18)] dark:from-violet-400/70 dark:via-fuchsia-400/55 dark:to-indigo-400/70" />
                ) : null}
                <div className="flex items-start gap-3">
                  <div className="min-w-0 flex-1">
                    <h3 className="truncate text-base font-semibold text-slate-900 dark:text-white sm:text-lg">{opp.name || "未命名商机"}</h3>
                    <p className="mt-1 truncate text-xs leading-5 text-slate-500 dark:text-slate-400 sm:text-sm">项目编号：{opp.code || "待生成"}</p>
                    <div className="mt-2 flex flex-wrap items-center gap-2">
                      <span className={getConfidenceBadgeClass(opp.confidence, effectiveConfidenceOptions)}>
                        {getConfidenceLabel(opp.confidence, effectiveConfidenceOptions)}
                      </span>
                      <span className="crm-pill crm-pill-neutral">
                        {opp.stage || "初步沟通"}
                      </span>
                      <span
                        className={cn(
                          "rounded-full border px-2.5 py-1 text-[11px] font-semibold leading-none",
                          opp.pushedToOms
                            ? "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300"
                            : "border-slate-200 bg-slate-50 text-slate-500 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-300",
                        )}
                      >
                        {opp.pushedToOms ? "已推送 OMS" : "未推送 OMS"}
                      </span>
                    </div>
                  </div>
                </div>

                <div className="mt-4 space-y-3 text-xs sm:text-sm">
                  <div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
                    <span className="shrink-0 text-slate-400 dark:text-slate-500">预计下单时间:</span>
                    <span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">{opp.date || "待定"}</span>
                  </div>
                  <div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
                    <span className="shrink-0 text-slate-400 dark:text-slate-500">预计金额:</span>
                    <span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">¥{formatAmount(opp.amount)}</span>
                  </div>
                  <div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
                    <span className="shrink-0 text-slate-400 dark:text-slate-500">项目最新进展:</span>
                    <span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">{opp.latestProgress || "暂无回写进展"}</span>
                  </div>
                  <div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
                    <span className="shrink-0 text-slate-400 dark:text-slate-500">后续规划:</span>
                    <span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">{opp.nextPlan || "暂无回写规划"}</span>
                  </div>
                  <div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
                    <span className="shrink-0 text-slate-400 dark:text-slate-500">创建人:</span>
                    <span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">{opp.owner || "无"}</span>
                  </div>
                </div>

              </motion.div>
            );
          })
        ) : renderEmpty()}
      </div>

      <AnimatePresence>
        {exportFilterOpen ? (
          <OpportunityExportFilterModal
            initialFilters={exportFilters}
            exporting={exporting}
            exportError={exportError}
            archiveTab={archiveTab}
            stageOptions={stageOptions}
            confidenceOptions={effectiveConfidenceOptions}
            projectLocationOptions={projectLocationOptions}
            opportunityTypeOptions={opportunityTypeOptions}
            operatorOptions={operatorOptions}
            onClose={() => setExportFilterOpen(false)}
            onConfirm={(filters) => void handleExport(filters)}
          />
        ) : null}

        {stageFilterOpen ? (
          <>
            <motion.div
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              onClick={() => setStageFilterOpen(false)}
              className="fixed inset-0 z-[90] bg-slate-900/35 backdrop-blur-sm dark:bg-slate-950/70"
            />
            <motion.div
              initial={{ opacity: 0, y: 24 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 24 }}
              className="fixed inset-x-0 bottom-0 z-[100] px-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] pt-3 sm:inset-0 sm:flex sm:items-center sm:justify-center sm:p-6"
            >
              <div className="mx-auto w-full max-w-md rounded-3xl border border-slate-200 bg-white shadow-2xl dark:border-slate-800 dark:bg-slate-900">
                <div className="border-b border-slate-100 px-5 py-4 dark:border-slate-800 sm:px-6">
                  <div className="mx-auto mb-3 h-1.5 w-10 rounded-full bg-slate-200 dark:bg-slate-700 sm:hidden" />
                  <div className="flex items-start justify-between gap-3">
                    <div className="min-w-0">
                      <h3 className="text-base font-semibold text-slate-900 dark:text-white">项目阶段筛选</h3>
                      <p className="mt-1 text-sm leading-6 text-slate-500 dark:text-slate-400">按项目阶段字典筛选商机数据。</p>
                    </div>
                    <button
                      type="button"
                      onClick={() => setStageFilterOpen(false)}
                      className="rounded-full p-2 text-slate-400 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800"
                    >
                      <X className="h-5 w-5" />
                    </button>
                  </div>
                </div>
                <div className="px-4 py-4">
                  <div className="space-y-2">
                    {stageFilterOptions.map((stage) => (
                      <button
                        key={stage.value}
                        type="button"
                        onClick={() => {
                          setFilter(stage.value);
                          setExportError("");
                          setStageFilterOpen(false);
                        }}
                        className={`flex w-full items-center justify-between rounded-2xl border px-4 py-3 text-left text-sm font-medium transition-colors ${
                          filter === stage.value
                            ? "border-violet-200 bg-violet-50 text-violet-700 dark:border-violet-500/30 dark:bg-violet-500/10 dark:text-violet-300"
                            : "border-slate-200 bg-white text-slate-700 hover:bg-slate-50 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800"
                        }`}
                      >
                        <span>{stage.label}</span>
                        {filter === stage.value ? <Check className="h-4 w-4 shrink-0" /> : null}
                      </button>
                    ))}
                  </div>
                </div>
              </div>
            </motion.div>
          </>
        ) : null}
      </AnimatePresence>

      <AnimatePresence>
        {(createOpen || editOpen) && (
          <ModalShell
            title={editOpen ? "编辑商机" : "新增商机"}
            subtitle={editOpen ? "支持手机与电脑端修改商机资料，保存后会同步刷新详情与列表。" : "支持手机与电脑端填写，提交后会自动刷新商机列表。"}
            onClose={resetCreateState}
            footer={(
              <div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-end">
                <button onClick={resetCreateState} className="crm-btn crm-btn-secondary">取消</button>
                <button onClick={() => void (editOpen ? handleEditSubmit() : handleCreateSubmit())} disabled={submitting} className="crm-btn crm-btn-primary disabled:cursor-not-allowed disabled:opacity-60">{submitting ? "提交中..." : editOpen ? "保存修改" : "确认新增"}</button>
              </div>
            )}
          >
            <div className="crm-form-grid">
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目地<RequiredMark /></span>
                <AdaptiveSelect
                  value={form.projectLocation || ""}
                  placeholder="请选择"
                  sheetTitle="项目地"
                  searchable
                  searchPlaceholder="搜索项目地"
                  options={projectLocationSelectOptions}
                  className={getFieldInputClass(Boolean(fieldErrors.projectLocation))}
                  onChange={(value) => handleChange("projectLocation", value)}
                />
                {fieldErrors.projectLocation ? <p className="text-xs text-rose-500">{fieldErrors.projectLocation}</p> : null}
              </label>
              <label className="space-y-2 sm:col-span-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目名称<RequiredMark /></span>
                <input value={form.opportunityName} onChange={(e) => handleChange("opportunityName", e.target.value)} className={getFieldInputClass(Boolean(fieldErrors.opportunityName))} />
                {fieldErrors.opportunityName ? <p className="text-xs text-rose-500">{fieldErrors.opportunityName}</p> : null}
              </label>
              <label className="space-y-2 sm:col-span-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">最终用户<RequiredMark /></span>
                <input value={form.customerName} onChange={(e) => handleChange("customerName", e.target.value)} className={getFieldInputClass(Boolean(fieldErrors.customerName))} />
                {fieldErrors.customerName ? <p className="text-xs text-rose-500">{fieldErrors.customerName}</p> : null}
              </label>
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">运作方<RequiredMark /></span>
                <AdaptiveSelect
                  value={form.operatorName || ""}
                  placeholder="请选择"
                  sheetTitle="运作方"
                  options={[
                    { value: "", label: "请选择" },
                    ...operatorOptions.map((item) => ({
                      value: item.value || "",
                      label: item.label || item.value || "无",
                    })),
                  ]}
                  className={cn(
                    fieldErrors.operatorName ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
                  )}
                  onChange={(value) => handleChange("operatorName", value)}
                />
                {fieldErrors.operatorName ? <p className="text-xs text-rose-500">{fieldErrors.operatorName}</p> : null}
              </label>
              {showSalesExpansionField ? (
                <label className="space-y-2">
                  <span className="text-sm font-medium text-slate-700 dark:text-slate-300">新华三负责人<RequiredMark /></span>
                    <SearchableSelect
                      value={form.salesExpansionId}
                      options={salesExpansionSearchOptions}
                      placeholder="请选择新华三负责人"
                      searchPlaceholder="搜索姓名、工号、办事处、电话"
                      emptyText="未找到匹配的销售拓展人员"
                      createActionLabel={salesExpansionQuery.trim() ? `新增负责人“${salesExpansionQuery.trim()}”并选中` : "新增负责人并选中"}
                      className={cn(
                        fieldErrors.salesExpansionId ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
                      )}
                    onChange={(value) => handleChange("salesExpansionId", value)}
                    onQueryChange={setSalesExpansionQuery}
                    onCreate={(query) => void openQuickCreateModal("sales", query)}
                  />
                  <p className="text-xs text-slate-400 dark:text-slate-500">未找到负责人时，可直接新增并自动回填。</p>
                  {fieldErrors.salesExpansionId ? <p className="text-xs text-rose-500">{fieldErrors.salesExpansionId}</p> : null}
                </label>
              ) : null}
              {showChannelExpansionField ? (
                <label className="space-y-2">
                  <span className="text-sm font-medium text-slate-700 dark:text-slate-300">渠道名称<RequiredMark /></span>
                    <SearchableSelect
                      value={form.channelExpansionId}
                      options={channelExpansionSearchOptions}
                      placeholder="请选择渠道名称"
                      searchPlaceholder="搜索渠道名称、编码、省份、联系人"
                      emptyText="未找到匹配的渠道"
                      createActionLabel={channelExpansionQuery.trim() ? `新增渠道“${channelExpansionQuery.trim()}”并选中` : "新增渠道并选中"}
                      className={cn(
                        fieldErrors.channelExpansionId ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
                      )}
                    onChange={(value) => handleChange("channelExpansionId", value)}
                    onQueryChange={setChannelExpansionQuery}
                    onCreate={(query) => void openQuickCreateModal("channel", query)}
                  />
                  <p className="text-xs text-slate-400 dark:text-slate-500">未找到渠道时，可直接新增并自动回填。</p>
                  {fieldErrors.channelExpansionId ? <p className="text-xs text-rose-500">{fieldErrors.channelExpansionId}</p> : null}
                </label>
              ) : null}
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">预计金额（元）<RequiredMark /></span>
                <input type="number" min="0" value={form.amount || ""} onChange={(e) => handleChange("amount", Number(e.target.value) || 0)} className={getFieldInputClass(Boolean(fieldErrors.amount))} />
                {fieldErrors.amount ? <p className="text-xs text-rose-500">{fieldErrors.amount}</p> : null}
              </label>
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">预计下单时间<RequiredMark /></span>
                <input type="date" value={form.expectedCloseDate} onChange={(e) => handleChange("expectedCloseDate", e.target.value)} className={`${getFieldInputClass(Boolean(fieldErrors.expectedCloseDate))} min-w-0`} />
                {fieldErrors.expectedCloseDate ? <p className="text-xs text-rose-500">{fieldErrors.expectedCloseDate}</p> : null}
              </label>
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目把握度<RequiredMark /></span>
                <AdaptiveSelect
                  value={normalizeConfidenceValue(form.confidencePct, effectiveConfidenceOptions)}
                  placeholder="请选择"
                  sheetTitle="项目把握度"
                  options={effectiveConfidenceOptions.map((item) => ({
                    value: item.value || "",
                    label: item.label || item.value || "",
                  }))}
                  className={cn(
                    fieldErrors.confidencePct ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
                  )}
                  onChange={(value) => handleChange("confidencePct", normalizeConfidenceValue(value, effectiveConfidenceOptions))}
                />
                {fieldErrors.confidencePct ? <p className="text-xs text-rose-500">{fieldErrors.confidencePct}</p> : null}
              </label>
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目阶段<RequiredMark /></span>
                <AdaptiveSelect
                  value={form.stage}
                  placeholder="请选择"
                  sheetTitle="项目阶段"
                  options={stageOptions.map((item) => ({
                    value: item.value || "",
                    label: item.label || item.value || "无",
                  }))}
                  className={cn(
                    fieldErrors.stage ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
                  )}
                  onChange={(value) => handleChange("stage", value)}
                />
                {fieldErrors.stage ? <p className="text-xs text-rose-500">{fieldErrors.stage}</p> : null}
              </label>
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">竞争对手<RequiredMark /></span>
                <CompetitorMultiSelect
                  value={selectedCompetitors}
                  options={COMPETITOR_OPTIONS}
                  placeholder="请选择竞争对手"
                  className={cn(
                    fieldErrors.competitorName ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
                  )}
                  onChange={(nextValue) => {
                    setFieldErrors((current) => {
                      if (!current.competitorName) {
                        return current;
                      }
                      const next = { ...current };
                      delete next.competitorName;
                      return next;
                    });
                    setSelectedCompetitors(nextValue);
                    if (!nextValue.includes("其他")) {
                      setCustomCompetitorName("");
                    }
                  }}
                />
                {fieldErrors.competitorName && !showCustomCompetitorInput ? <p className="text-xs text-rose-500">{fieldErrors.competitorName}</p> : null}
              </label>
              {showCustomCompetitorInput ? (
                <label className="space-y-2">
                  <span className="text-sm font-medium text-slate-700 dark:text-slate-300">其他竞争对手<RequiredMark /></span>
                  <input
                    value={customCompetitorName}
                    onChange={(e) => setCustomCompetitorName(e.target.value)}
                    placeholder="请输入其他竞争对手"
                    className={getFieldInputClass(Boolean(fieldErrors.competitorName))}
                  />
                  {fieldErrors.competitorName ? <p className="text-xs text-rose-500">{fieldErrors.competitorName}</p> : null}
                </label>
              ) : null}
              <label className="space-y-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">建设类型<RequiredMark /></span>
                <AdaptiveSelect
                  value={form.opportunityType}
                  placeholder="请选择"
                  sheetTitle="建设类型"
                  options={opportunityTypeSelectOptions}
                  className={cn(
                    fieldErrors.opportunityType ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
                  )}
                  onChange={(value) => handleChange("opportunityType", value)}
                />
                {fieldErrors.opportunityType ? <p className="text-xs text-rose-500">{fieldErrors.opportunityType}</p> : null}
              </label>
              {selectedItem ? (
                <>
                  <label className="space-y-2 sm:col-span-2">
                    <span className="text-sm font-medium text-slate-700 dark:text-slate-300">项目最新进展</span>
                    <textarea
                      rows={3}
                      readOnly
                      value={selectedItem.latestProgress || "暂无日报回写进展"}
                      className="w-full cursor-not-allowed rounded-xl border border-slate-200 bg-slate-100 px-4 py-3 text-sm text-slate-500 outline-none dark:border-slate-800 dark:bg-slate-800/70 dark:text-slate-400"
                    />
                    <p className="text-xs text-slate-400 dark:text-slate-500">该字段由日报自动回写，基础信息编辑时不可修改。</p>
                  </label>
                  <label className="space-y-2 sm:col-span-2">
                    <span className="text-sm font-medium text-slate-700 dark:text-slate-300">后续规划</span>
                    <textarea
                      rows={3}
                      readOnly
                      value={selectedItem.nextPlan || "暂无日报回写规划"}
                      className="w-full cursor-not-allowed rounded-xl border border-slate-200 bg-slate-100 px-4 py-3 text-sm text-slate-500 outline-none dark:border-slate-800 dark:bg-slate-800/70 dark:text-slate-400"
                    />
                    <p className="text-xs text-slate-400 dark:text-slate-500">该字段由日报自动回写，基础信息编辑时不可修改。</p>
                  </label>
                </>
              ) : null}
              <label className="space-y-2 sm:col-span-2">
                <span className="text-sm font-medium text-slate-700 dark:text-slate-300">备注说明</span>
                <textarea rows={4} value={form.description || ""} onChange={(e) => handleChange("description", e.target.value)} className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50" />
              </label>
            </div>
            {error ? <div className="crm-alert crm-alert-error mt-4">{error}</div> : null}
          </ModalShell>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {quickCreateOpen ? (
          <ModalShell
            title={quickCreateType === "sales" ? "新增负责人并回填商机" : "新增渠道并回填商机"}
            subtitle={quickCreateType === "sales" ? "保存成功后会自动选中当前负责人，并回填到商机表单。" : "保存成功后会自动选中当前渠道，并回填到商机表单。"}
            onClose={resetQuickCreateState}
            footer={(
              <div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-end">
                <button onClick={resetQuickCreateState} className="crm-btn crm-btn-secondary">取消</button>
                <button onClick={() => void handleQuickCreateSubmit()} disabled={quickCreateSubmitting} className="crm-btn crm-btn-primary disabled:cursor-not-allowed disabled:opacity-60">
                  {quickCreateSubmitting ? "提交中..." : quickCreateType === "sales" ? "确认新增负责人并回填" : "确认新增渠道并回填"}
                </button>
              </div>
            )}
          >
            {quickCreateType === "sales" ? (
              <SharedQuickSalesForm
                form={quickSalesForm}
                fieldErrors={quickSalesFieldErrors}
                officeOptions={quickOfficeOptions}
                industryOptions={quickIndustryOptions}
                duplicateMessage={quickSalesDuplicateMessage}
                requiredMark={<RequiredMark />}
                onChange={handleQuickSalesChange}
              />
            ) : (
              <SharedQuickChannelForm
                form={quickChannelForm}
                fieldErrors={quickChannelFieldErrors}
                invalidContactRows={quickInvalidChannelContactRows}
                provinceOptions={quickProvinceOptions}
                cityOptions={quickCityOptions}
                industryOptions={quickIndustryOptions}
                certificationLevelOptions={quickCertificationLevelOptions}
                channelAttributeOptions={quickChannelAttributeOptions}
                internalAttributeOptions={quickInternalAttributeOptions}
                channelOtherOptionValue={quickChannelOtherOptionValue}
                duplicateMessage={quickChannelDuplicateMessage}
                requiredMark={<RequiredMark />}
                onChange={handleQuickChannelChange}
                onContactChange={handleQuickChannelContactChange}
                onAddContact={addQuickChannelContact}
                onRemoveContact={removeQuickChannelContact}
                onProvinceChange={handleQuickChannelProvinceChange}
              />
            )}
            {quickCreateError ? <div className="crm-alert crm-alert-error mt-4">{quickCreateError}</div> : null}
          </ModalShell>
        ) : null}
      </AnimatePresence>

      <AnimatePresence>
        {pushConfirmOpen && selectedItem ? (
          <>
            <motion.div
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              onClick={() => setPushConfirmOpen(false)}
              className="fixed inset-0 z-[90] bg-slate-900/40 backdrop-blur-sm dark:bg-slate-950/70"
            />
            <motion.div
              initial={{ opacity: 0, y: 24 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 24 }}
              className="fixed inset-x-0 bottom-0 z-[100] px-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] pt-3 sm:inset-0 sm:flex sm:items-center sm:justify-center sm:p-6"
            >
              <div className="mx-auto w-full max-w-md rounded-3xl border border-slate-200 bg-white shadow-2xl dark:border-slate-800 dark:bg-slate-900">
                <div className="border-b border-slate-100 px-5 py-4 dark:border-slate-800 sm:px-6">
                  <div className="mx-auto mb-3 h-1.5 w-10 rounded-full bg-slate-200 dark:bg-slate-700 sm:hidden" />
                  <div className="flex items-start gap-3">
                    <div className="crm-tone-warning mt-0.5 flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl">
                      <AlertTriangle className="crm-icon-lg" />
                    </div>
                    <div className="min-w-0">
                      <h3 className="text-base font-semibold text-slate-900 dark:text-white">确认推送 OMS</h3>
                      <p className="mt-1 text-sm leading-6 text-slate-500 dark:text-slate-400">
                        请选择售前后同步到 OMS，系统会携带当前商机编码供 OMS 判断。
                      </p>
                    </div>
                  </div>
                </div>
                <div className="px-5 py-4 text-sm text-slate-600 dark:text-slate-300 sm:px-6">
                  <div className="crm-form-section">
                    <p className="text-xs text-slate-400 dark:text-slate-500">当前商机</p>
                    <p className="mt-1 font-medium text-slate-900 dark:text-white">{selectedItem.name || selectedItem.code || `#${selectedItem.id}`}</p>
                  </div>
                  <div className="crm-form-section mt-4">
                    <p className="mb-2 text-xs text-slate-400 dark:text-slate-500">售前人员</p>
                    <SearchableSelect
                      value={pushPreSalesId}
                      options={omsPreSalesSearchOptions}
                      placeholder={loadingOmsPreSales ? "售前人员加载中..." : "请选择售前人员"}
                      searchPlaceholder="搜索售前姓名或登录账号"
                      emptyText={loadingOmsPreSales ? "正在加载售前人员..." : "未找到匹配的售前人员"}
                      onChange={(value) => {
                        const matched = omsPreSalesOptions.find((item) => item.userId === value);
                        setPushPreSalesId(value);
                        setPushPreSalesName(matched?.userName || matched?.loginName || "");
                        setError("");
                      }}
                    />
                    <p className="mt-2 text-xs text-slate-400 dark:text-slate-500">
                      {loadingOmsPreSales ? "正在从 OMS 拉取售前人员列表。" : "推送前必须选择售前，系统会把售前ID和姓名回写到 CRM 商机表。"}
                    </p>
                  </div>
                  {error ? <div className="crm-alert crm-alert-error mt-4">{error}</div> : null}
                </div>
                <div className="flex flex-col-reverse gap-3 px-5 pb-[calc(1rem+env(safe-area-inset-bottom))] pt-1 sm:flex-row sm:justify-end sm:px-6 sm:pb-5">
                  <button
                    type="button"
                    onClick={() => setPushConfirmOpen(false)}
                    className="crm-btn crm-btn-secondary min-h-0 rounded-2xl px-4 py-3"
                  >
                    取消
                  </button>
                  <button
                    type="button"
                    onClick={() => void handleConfirmPushToOms()}
                    disabled={pushingOms}
                    className="crm-btn crm-btn-primary min-h-0 rounded-2xl px-4 py-3 disabled:cursor-not-allowed disabled:opacity-60"
                  >
                    {pushingOms ? "推送中..." : "确认推送"}
                  </button>
                </div>
              </div>
            </motion.div>
          </>
        ) : null}
      </AnimatePresence>

      <AnimatePresence>
        {selectedItem && (
          <>
            <motion.div
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              onClick={() => setSelectedItem(null)}
              className={`fixed inset-0 z-40 bg-slate-900/20 backdrop-blur-sm transition-opacity dark:bg-slate-900/60 ${hasForegroundModal ? "pointer-events-none opacity-30" : ""}`}
            />
            <motion.div
              initial={{ x: "100%", y: 0 }}
              animate={{ x: 0, y: 0 }}
              exit={{ x: "100%", y: 0 }}
              transition={{ type: "spring", damping: 25, stiffness: 200 }}
              className={`fixed inset-x-0 bottom-0 z-50 flex h-[90dvh] w-full flex-col overflow-hidden rounded-t-3xl border border-slate-200 bg-white shadow-2xl transition-opacity dark:border-slate-800 dark:bg-slate-900 sm:inset-y-0 sm:right-0 sm:left-auto sm:h-full sm:w-[min(840px,92vw)] sm:max-w-none sm:rounded-none sm:rounded-l-3xl sm:border-l lg:w-[min(960px,90vw)] ${hasForegroundModal ? "pointer-events-none opacity-20" : ""}`}
            >
              <div className="flex items-center justify-between border-b border-slate-100 px-5 py-4 dark:border-slate-800 sm:px-6">
                <div className="flex items-center gap-3">
                  <div className="h-1.5 w-10 rounded-full bg-slate-200 sm:hidden dark:bg-slate-700" />
                  <h2 className="text-base font-semibold text-slate-900 dark:text-white sm:text-lg">商机详情</h2>
                </div>
                  <button onClick={() => setSelectedItem(null)} className="rounded-full p-2 text-slate-400 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800">
                    <X className="crm-icon-lg" />
                </button>
              </div>

              <div className="min-h-0 flex-1 overflow-y-auto px-4 py-4 sm:px-6 sm:py-5">
                <div className="crm-modal-stack">
                <div>
                  <div className="mb-2 flex items-center gap-2">
                    <span className="text-xs font-medium text-slate-400 dark:text-slate-500">{selectedItem.code || `#${selectedItem.id}`}</span>
                  </div>
                  <h3 className="line-clamp-1 text-lg font-bold leading-tight text-slate-900 dark:text-white sm:break-anywhere sm:line-clamp-none sm:text-xl">{selectedItem.name || "未命名商机"}</h3>
                  <div className="mt-3 flex flex-wrap gap-2">
                    <span className="crm-pill crm-pill-neutral">{selectedItem.stage || "初步沟通"}</span>
                    <span className={`rounded-full px-2.5 py-1 text-xs font-medium ${getConfidenceColor(selectedItem.confidence)}`}>项目把握度 {getConfidenceLabel(selectedItem.confidence, effectiveConfidenceOptions)}</span>
                  </div>
                </div>

                <div className="crm-section-stack">
                  <h4 className="flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white">
                    <FileText className="crm-icon-md text-violet-500" />
                    基本信息
                  </h4>
                  <div className="crm-detail-grid text-sm md:grid-cols-2">
                    <DetailItem label="项目地" value={selectedItem.projectLocation || "无"} />
                    <DetailItem label="最终用户" value={selectedItem.client || "无"} icon={<Building className="h-3 w-3" />} />
                    <DetailItem label="运作方" value={selectedItem.operatorName || "无"} />
                    {hasSelectedChannelExpansion ? (
                      <DetailItem label="渠道名称" value={selectedChannelExpansionName} icon={<Building className="h-3 w-3" />} />
                    ) : null}
                    {hasSelectedSalesExpansion ? (
                      <DetailItem label="新华三负责人" value={selectedSalesExpansionName} icon={<User className="h-3 w-3" />} />
                    ) : null}
                    <DetailItem label="售前" value={selectedPreSalesName} icon={<User className="h-3 w-3" />} />
                    <DetailItem label="预计金额（元）" value={<span className="text-rose-600 dark:text-rose-400">¥{formatAmount(selectedItem.amount)}</span>} icon={<DollarSign className="h-3 w-3" />} />
                    <DetailItem label="预计下单时间" value={selectedItem.date || "待定"} icon={<Calendar className="h-3 w-3" />} />
                    <DetailItem label="项目把握度" value={getConfidenceLabel(selectedItem.confidence, effectiveConfidenceOptions)} />
                    <DetailItem label="项目阶段" value={selectedItem.stage || "无"} icon={<Activity className="h-3 w-3" />} />
                    <DetailItem
                      label="是否已推送OMS"
                      value={(
                        <span
                          className={cn(
                            "inline-flex rounded-full px-2.5 py-1 text-xs font-semibold",
                            selectedItem.pushedToOms
                              ? "bg-emerald-50 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300"
                              : "bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300",
                          )}
                        >
                          {selectedItem.pushedToOms ? "已推送" : "未推送"}
                        </span>
                      )}
                    />
                    <DetailItem label="竞争对手" value={selectedItem.competitorName || "无"} />
                    <DetailItem label="建设类型" value={selectedItem.type || "新建"} icon={<Tag className="h-3 w-3" />} />
                    <DetailItem label="项目最新进展" value={selectedItem.latestProgress || "暂无日报回写进展"} className="md:col-span-2" />
                    <DetailItem label="后续规划" value={selectedItem.nextPlan || "暂无日报回写规划"} className="md:col-span-2" />
                    <DetailItem label="备注说明" value={selectedItem.notes || "无"} className="md:col-span-2" />
                  </div>
                </div>

                <div className="crm-section-stack">
                  <div className="flex rounded-2xl border border-slate-200 bg-slate-50 p-1 dark:border-slate-800 dark:bg-slate-800/40">
                    <button
                      type="button"
                      onClick={() => setDetailTab("sales")}
                      className={`flex-1 rounded-xl px-2 py-2 text-xs font-medium transition-colors sm:px-4 sm:text-sm ${
                        detailTab === "sales"
                          ? "bg-white text-violet-600 shadow-sm dark:bg-slate-900 dark:text-violet-400"
                          : "text-slate-500 dark:text-slate-400"
                      }`}
                    >
                      销售拓展人员
                    </button>
                    <button
                      type="button"
                      onClick={() => setDetailTab("channel")}
                      className={`flex-1 rounded-xl px-2 py-2 text-xs font-medium transition-colors sm:px-4 sm:text-sm ${
                        detailTab === "channel"
                          ? "bg-white text-violet-600 shadow-sm dark:bg-slate-900 dark:text-violet-400"
                          : "text-slate-500 dark:text-slate-400"
                      }`}
                    >
                      拓展渠道
                    </button>
                    <button
                      type="button"
                      onClick={() => setDetailTab("followups")}
                      className={`flex-1 rounded-xl px-2 py-2 text-xs font-medium transition-colors sm:px-4 sm:text-sm ${
                        detailTab === "followups"
                          ? "bg-white text-violet-600 shadow-sm dark:bg-slate-900 dark:text-violet-400"
                          : "text-slate-500 dark:text-slate-400"
                      }`}
                    >
                      跟进记录
                    </button>
                  </div>

                  {detailTab === "sales" ? (
                    selectedSalesExpansion || selectedSalesExpansionName ? (
                      <div className="crm-detail-grid text-sm sm:grid-cols-3">
                        <DetailItem label="姓名" value={selectedSalesExpansion?.name || selectedSalesExpansionName || "无"} />
                        <DetailItem label="合作意向" value={selectedSalesExpansion?.intent || "无"} />
                        <DetailItem label="是否在职" value={selectedSalesExpansion ? (selectedSalesExpansion.active ? "是" : "否") : "待同步"} />
                      </div>
                    ) : (
                      <div className="crm-empty-panel">
                        暂无关联销售拓展人员
                      </div>
                    )
                  ) : null}

                  {detailTab === "channel" ? (
                    selectedChannelExpansion || selectedChannelExpansionName ? (
                      <div className="crm-detail-grid text-sm sm:grid-cols-3">
                        <DetailItem label="渠道名称" value={selectedChannelExpansion?.name || selectedChannelExpansionName || "无"} />
                        <DetailItem label="合作意向" value={selectedChannelExpansion?.intent || "无"} />
                        <DetailItem label="建立联系时间" value={selectedChannelExpansion?.establishedDate || "无"} />
                      </div>
                    ) : (
                      <div className="crm-empty-panel">
                        暂无关联拓展渠道
                      </div>
                    )
                  ) : null}

                  {detailTab === "followups" ? (
                    <div className="crm-section-stack">
                      <p className="crm-field-note">工作台日报里关联到该商机的工作内容，会自动回写到这里。</p>
                      <div className="crm-section-stack">
                        <h4 className="flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white">
                          <Clock className="crm-icon-md text-violet-500" />
                          交流记录
                        </h4>
                        {followUpRecords.length > 0 ? (
                          <div className="relative space-y-6 border-l-2 border-slate-100 pl-4 dark:border-slate-800">
                            {followUpRecords.map((record) => {
                              const summary = getOpportunityFollowUpSummary(record);
                              return (
                                <div key={record.id} className="relative">
                                  <div className="absolute -left-[21px] mt-1.5 h-2.5 w-2.5 rounded-full bg-violet-500 ring-4 ring-white dark:ring-slate-900" />
                                  <div className="crm-form-section">
                                    <div className="rounded-xl border border-amber-200 bg-amber-50 p-3 text-sm dark:border-amber-500/20 dark:bg-amber-500/10">
                                      <p className="mb-1 text-xs text-amber-700 dark:text-amber-300">沟通内容</p>
                                      <p className="whitespace-pre-wrap font-medium leading-6 text-slate-900 dark:text-white">{summary.communicationContent}</p>
                                    </div>
                                    <p className="mt-2 text-xs text-slate-400">跟进人: {record.user || "无"}<span className="ml-3">{record.date || "无"}</span></p>
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        ) : (
                          <div className="crm-empty-panel">
                            暂无交流记录
                          </div>
                        )}
                      </div>
                    </div>
                  ) : null}
                </div>
              </div>
              </div>
              <div className="w-full shrink-0 border-t border-slate-200/80 bg-slate-50/95 backdrop-blur dark:border-slate-800/80 dark:bg-slate-900/90">
                <div className="px-4 pb-[calc(1rem+env(safe-area-inset-bottom))] pt-4 sm:p-4">
                  {error ? <div className="crm-alert crm-alert-error mb-3">{error}</div> : null}
                  {!canEditSelectedItem ? (
                    <p className="mb-3 text-xs text-slate-400 dark:text-slate-500">当前商机非本人负责，仅支持查看详情，不能编辑或推送。</p>
                  ) : null}
                  <div className="grid grid-cols-2 gap-3">
                    <button
                      type="button"
                      onClick={handleOpenEdit}
                      disabled={!canEditSelectedItem}
                      title={canEditSelectedItem ? "编辑商机" : "仅本人可操作"}
                      className="crm-btn crm-btn-secondary inline-flex h-11 items-center justify-center disabled:cursor-not-allowed disabled:opacity-60"
                    >
                      {canEditSelectedItem ? "编辑商机" : "仅本人可操作"}
                    </button>
                    <button
                      type="button"
                      onClick={() => void handleOpenPushConfirm()}
                      disabled={!canPushSelectedItem || pushingOms}
                      title={
                        !canPushSelectedItem
                          ? "仅本人可操作"
                          : selectedItem.pushedToOms
                            ? "重新推送 OMS"
                            : "推送 OMS"
                      }
                      className={cn(
                        "crm-btn inline-flex h-11 items-center justify-center disabled:cursor-not-allowed disabled:opacity-60",
                        !canPushSelectedItem
                          ? "cursor-not-allowed rounded-2xl border border-slate-200 bg-slate-50 text-slate-400 dark:border-slate-800 dark:bg-slate-800/40 dark:text-slate-500"
                          : "crm-btn-primary",
                      )}
                    >
                      {!canPushSelectedItem ? "仅本人可操作" : pushingOms ? "推送中..." : selectedItem.pushedToOms ? "重新推送 OMS" : "推送 OMS"}
                    </button>
                  </div>
                </div>
              </div>
            </motion.div>
          </>
        )}
      </AnimatePresence>
    </div>
  );
}

function getOpportunityFollowUpSummary(record: {
  content?: string;
  latestProgress?: string;
  communicationContent?: string;
  nextAction?: string;
}) {
  const content = record.content || "";
  const parsedLatestProgress = extractOpportunityFollowUpField(content, "项目最新进展");
  const parsedNextPlan = extractOpportunityFollowUpField(content, "后续规划");
  const communicationContent = buildOpportunityCommunicationContent(
    pickOpportunityFollowUpValue(record.latestProgress, parsedLatestProgress),
    pickOpportunityFollowUpValue(record.nextAction, parsedNextPlan),
    pickOpportunityFollowUpValue(
      record.communicationContent,
      extractLegacyOpportunityCommunicationContent(extractOpportunityFollowUpField(content, "交流记录")),
    ),
  );

  return {
    communicationContent: normalizeOpportunityFollowUpDisplayValue(communicationContent),
  };
}

function extractOpportunityFollowUpField(content: string, label: string) {
  const normalized = content.replace(/\r/g, "");
  const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const match = normalized.match(new RegExp(`${escapedLabel}：([^\\n]+)`));
  return match?.[1]?.trim();
}

function extractLegacyOpportunityCommunicationContent(rawValue?: string) {
  const normalized = rawValue?.trim();
  if (!normalized || normalized === "无") {
    return undefined;
  }

  const match = normalized.match(/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2})\s+(.+)$/);
  if (!match) {
    return normalized;
  }

  return match[2].trim();
}

function pickOpportunityFollowUpValue(...values: Array<string | undefined>) {
  return values.find((value) => {
    const normalized = value?.trim();
    return normalized && normalized !== "无";
  });
}

function buildOpportunityCommunicationContent(
  latestProgress?: string,
  nextPlan?: string,
  legacyCommunicationContent?: string,
) {
  const parts = [
    latestProgress?.trim() ? `项目最新进展：${latestProgress.trim()}` : "",
    nextPlan?.trim() ? `后续规划：${nextPlan.trim()}` : "",
  ].filter(Boolean);
  if (parts.length > 0) {
    return parts.join("\n");
  }
  return legacyCommunicationContent?.trim();
}

function normalizeOpportunityFollowUpDisplayValue(value?: string) {
  const normalized = value?.trim();
  return normalized ? normalized : "无";
}
