/**
 * 侧边栏面板组件
 * 插件的主操作界面，固定在浏览器右上角
 * 包含：职位信息卡片、自动填写按钮、简历管理、填写进度等功能区域
 */

import { useState, useEffect, useRef, useCallback } from "react"
import { getCookieValue } from "~utils/cookie"
import { get as storageGet, set as storageSet, remove as storageRemove } from "~utils/storage"
import { getCustomizeResume } from "~api/aiApi"
import { getMemberStatus, preloadRegionTree } from "~api/dataApi"
import { handleAutoFillCommon } from "~handlers/handleAutoFillCommon"
import { handleAutoFillBeisen } from "~handlers/handleAutoFillBeisen"
import { handleAutoFillMoka } from "~handlers/handleAutoFillMoka"
import { handleAutoFillFeishu } from "~handlers/handleAutoFillFeishu"
import { handleAutoFillHotjob } from "~handlers/handleAutoFillHotjob"
import { scanPageFields, extractNonResumeFields, printFieldStats, collectPickerOptionTexts, getPickerDisplayValue } from "~lib/fillStats"
import type { TitleStat } from "~lib/fillStats"
import type { MatchedFormField, ResumeData, JobInfo } from "~lib/types"
import { createChannelBridge } from "~lib/channelBridge"
import { buildResumeExcludeTexts } from "~lib/resumeDataHelper"
import logoImg from "data-base64:~/../assets/logo-offerpai.png"
import { config as appConfig } from "~config"
import "./SidebarPanel.scss"

/** 投递链接列表数据类型（跨域通信用） */
interface OfferpaieDeliveryLinkListData {
  linkList: string[]
}

/** 创建通信桥接实例（用于投递链接列表的跨域读写） */
const panelBridge = createChannelBridge({ domain: appConfig.bridgeDomain, isPlugin: true })

/** 侧边栏面板的 Props */
interface SidebarPanelProps {
  /** 当前标签页的来源链接 */
  sourceUrl: string
  /** 岗位信息（由 sidebar 层查询后传入） */
  jobInfo: JobInfo | null
  /** 关闭面板的回调函数 */
  onClose: () => void
}

export function SidebarPanel({ sourceUrl, jobInfo, onClose }: SidebarPanelProps) {
  /** 是否已登录（有 Token） */
  const [isLoggedIn, setIsLoggedIn] = useState<boolean | null>(null)
  /** 是否为会员（null=未检测，true=会员，false=非会员） */
  const [isMember, setIsMember] = useState<boolean | null>(null)
  /** 是否正在执行自动填写 */
  const [filling, setFilling] = useState(false)
  /** 页面语言类型：中文 / 英文 */
  const [pageLang, setPageLang] = useState<"zh" | "en">("zh")
  /** 是否为职位申请表单页面 */
  const [isFormPage, setIsFormPage] = useState(false)
  /** 匹配到的表单字段列表 */
  const [formFields, setFormFields] = useState<MatchedFormField[]>([])
  /** 当前使用的简历数据 */
  const [resumeData, setResumeData] = useState<ResumeData | null>(null)
  /** 自动填写流程是否已完成（控制模拟提交按钮可用） */
  const [fillCompleted, setFillCompleted] = useState(false)
  /** 页面字段扫描统计结果（用于面板底部展示已填/待填字段列表） */
  const [fieldStats, setFieldStats] = useState<TitleStat[]>([])
  /** 顶部状态文字 */
  const [headerStatus, setHeaderStatus] = useState<string>("准备中, 请登录到投递表单页~")

  /** 是否启用字段更新时自动滚动到该字段，模拟跟踪（常量开关，方便调试关闭） */
  const ENABLE_AUTO_SCROLL_TO_UPDATED = true

  /** 循环扫描定时器引用 */
  const scanIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
  /** 上一次扫描结果快照（用于对比哪些字段刚刚更新） */
  const prevFieldSnapshotRef = useRef<Map<Element, string>>(new Map())
  /** 字段列表容器 ref（用于滚动） */
  const fieldListRef = useRef<HTMLDivElement | null>(null)
  /** 最新一次更新的字段 inputElement（用于定位滚动目标） */
  const [lastUpdatedFieldEl, setLastUpdatedFieldEl] = useState<Element | null>(null)
  /** 是否显示"助手已记住的表单"弹窗 */
  const [showCacheModal, setShowCacheModal] = useState(false)
  /** 弹窗中编辑的表单数据（从 localStorage 读取后在弹窗内编辑） */
  const [cacheModalData, setCacheModalData] = useState<any[] | null>(null)

  /** 面板是否收起（收起时只显示 logo 圆圈） */
  const [collapsed, setCollapsed] = useState(false)
  /** 拖拽位置偏移量（相对于初始位置） */
  const [dragOffset, setDragOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
  /** 是否已经被拖动过（用于判断是否显示拖拽提示气泡） */
  const [hasDragged, setHasDragged] = useState(false)
  /** 拖拽提示：收起状态已提示过 */
  const collapsedTooltipShownRef = useRef(false)
  /** 拖拽提示：展开状态已提示过 */
  const expandedTooltipShownRef = useRef(false)
  /** 是否正在显示拖拽提示气泡 */
  const [showDragTooltip, setShowDragTooltip] = useState(false)
  /** 拖拽提示气泡定时器 */
  const dragTooltipTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  /** 面板容器 ref（用于拖拽） */
  const containerRef = useRef<HTMLDivElement | null>(null)
  /** 拖拽状态 ref */
  const dragStateRef = useRef<{ isDragging: boolean; startX: number; startY: number; startOffsetX: number; startOffsetY: number; longPressTimer: ReturnType<typeof setTimeout> | null; isLongPress: boolean }>({
    isDragging: false, startX: 0, startY: 0, startOffsetX: 0, startOffsetY: 0, longPressTimer: null, isLongPress: false
  })

  /** 选择器选项文字缓存（collectPickerOptionTexts 收集后存入，handleSaveUserInput 用于排除） */
  const pickerOptionTextsRef = useRef<Set<string>>(new Set())

  /** 停止当前循环扫描 */
  const stopScanLoop = () => {
    if (scanIntervalRef.current) {
      clearInterval(scanIntervalRef.current)
      scanIntervalRef.current = null
    }
  }

  /** 对比前后快照，找出刚刚变为已填写的字段 */
  const findUpdatedField = (newStats: TitleStat[]): Element | null => {
    const prevMap = prevFieldSnapshotRef.current
    let updatedEl: Element | null = null
    const newMap = new Map<Element, string>()

    for (const ts of newStats) {
      for (const f of ts.fields) {
        if (!f.inputElement) continue
        const newColor = f.color
        newMap.set(f.inputElement, newColor)
        const prevColor = prevMap.get(f.inputElement)
        // 从非绿变成绿/greenTwo → 刚刚被填写（取最后一个变化的）
        if (prevColor && prevColor !== "green" && prevColor !== "greenTwo" && (newColor === "green" || newColor === "greenTwo")) {
          updatedEl = f.inputElement
        }
      }
    }

    prevFieldSnapshotRef.current = newMap
    return updatedEl
  }

  /** 开启循环扫描（每秒执行一次） */
  const startScanLoop = (params: { siteMode?: "beisen" | "moka" | "feishu" | "hotjob"; sectionResults?: any[]; expandedResults?: any[]; excludeTexts?: Set<string> }) => {
    stopScanLoop()
    const hasFullData = !!(params.sectionResults && params.expandedResults)

    /**
     * 同步构建排除集合：合并 params.excludeTexts + 当前 resumeData 中的所有值
     * 避免将已填入表单的简历值（如"汉族"、"广州"等）误认为表单标签
     */
    const buildExcludeTexts = (): Set<string> => {
      const baseSet = buildResumeExcludeTexts(resumeData)
      // 合并外部传入的静态排除集合（第二次调用时由 fillResult.resumeData 构建）
      if (params.excludeTexts) {
        for (const t of params.excludeTexts) baseSet.add(t)
      }
      return baseSet
    }

    /**
     * 补充选择器展示值：遍历已识别字段，对 value 为空的字段调用 getPickerDisplayValue
     * 从 DOM 中提取展示文字与已收集的弹出层选项比对，匹配成功则更新字段 value
     */
    const fillPickerDisplayValues = (fieldStats: TitleStat[]) => {
      const options = pickerOptionTextsRef.current
      if (options.size === 0) return
      // 北森模式（zhiye.com）的选择器值直接写在输入框里，不需要额外提取展示值
      if (window.location.hostname.includes("zhiye.com")) return
      for (const ts of fieldStats) {
        for (let fIdx = 0; fIdx < ts.fields.length; fIdx++) {
          const f = ts.fields[fIdx]
          // 已有值的跳过
          if (f.value) continue
          if (!f.inputElement) continue
          const inputEl = f.inputElement as HTMLElement
          // input.value 有值的跳过
          if ((inputEl as HTMLInputElement).value?.trim()) continue
          // 找同大标题内下一个字段的标签文字作为边界
          const nextField = ts.fields[fIdx + 1]
          const nextLabelText = nextField?.labelText || null
          // 调用 getPickerDisplayValue 比对
          const displayVal = getPickerDisplayValue(inputEl, f.labelText, nextLabelText, options)
          if (displayVal) {
            f.value = displayVal
            f.filled = true
            f.color = "green"
          }
        }
      }
    }

    // 立即执行一次
    const stats = scanPageFields({ ...params, excludeTexts: buildExcludeTexts() })
    // 补充选择器展示值：对 value 为空的字段，从 DOM 中提取展示文字与选项比对
    fillPickerDisplayValues(stats)
    setFieldStats(stats)
    findUpdatedField(stats) // 初始化快照，不滚动
    if (hasFullData) handleSaveUserInput()
    // 每秒循环
    scanIntervalRef.current = setInterval(() => {
      const s = scanPageFields({ ...params, excludeTexts: buildExcludeTexts() })
      // 补充选择器展示值
      fillPickerDisplayValues(s)
      setFieldStats(s)
      if (ENABLE_AUTO_SCROLL_TO_UPDATED) {
        const updatedEl = findUpdatedField(s)
        if (updatedEl) setLastUpdatedFieldEl(updatedEl)
      }
      if (hasFullData) handleSaveUserInput()
    }, 1000)
  }

  /** 组件卸载时清理定时器 */
  useEffect(() => {
    return () => { stopScanLoop() }
  }, [])

  /** 页面加载 & SPA 路由变化时检测是否有足够的输入框，显示"准备就绪" */
  useEffect(() => {
    const hostDoc = document.getRootNode() === document ? document : (document.getRootNode() as ShadowRoot).ownerDocument

    /** 检测页面是否有足够的表单输入框 */
    const checkInputs = () => {
      const inputs = hostDoc.querySelectorAll("input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='file']), textarea")
      if (inputs.length > 2) {
        setHeaderStatus("准备就绪，请点击开始填写")
        return true
      }
      return false
    }

    /** 路由变化后延迟多次重试检测（等待新页面 DOM 渲染） */
    const timers: ReturnType<typeof setTimeout>[] = []
    const scheduleCheck = () => {
      setHeaderStatus("准备中, 请登录到投递表单页~")
      if (checkInputs()) return
      timers.push(setTimeout(checkInputs, 500))
      timers.push(setTimeout(checkInputs, 1500))
      timers.push(setTimeout(checkInputs, 3000))
      timers.push(setTimeout(checkInputs, 5000))
    }

    // 初次挂载时检测
    scheduleCheck()

    // URL 轮询：每秒检查一次 URL 是否变化（兼容所有 SPA 路由方式）
    let lastUrl = window.location.href
    const urlPollInterval = setInterval(() => {
      const currentUrl = window.location.href
      if (currentUrl !== lastUrl) {
        lastUrl = currentUrl
        console.log("[OfferPie] 检测到 URL 变化:", currentUrl)
        scheduleCheck()
      }
    }, 1000)

    return () => {
      clearInterval(urlPollInterval)
      timers.forEach(clearTimeout)
    }
  }, [])

  /** 字段更新时自动滚动字段列表到对应项 */
  useEffect(() => {
    if (!ENABLE_AUTO_SCROLL_TO_UPDATED || !lastUpdatedFieldEl || !fieldListRef.current) return
    // 计算该字段在渲染中的全局索引（按渲染顺序：按大标题 → 按段 → 按字段）
    let globalIdx = 0
    let targetIdx = -1
    for (const ts of fieldStats) {
      if (ts.fields.length === 0) continue
      if (ts.isExpType && ts.segmentCount > 1) {
        for (let sIdx = 0; sIdx < ts.segmentCount; sIdx++) {
          const segFields = ts.fields.filter((f) => f.segmentIndex === sIdx)
          for (const f of segFields) {
            if (f.inputElement === lastUpdatedFieldEl) { targetIdx = globalIdx }
            globalIdx++
          }
        }
      } else {
        for (const f of ts.fields) {
          if (f.inputElement === lastUpdatedFieldEl) { targetIdx = globalIdx }
          globalIdx++
        }
      }
      if (targetIdx >= 0) break
    }
    if (targetIdx >= 0) {
      const targetItem = fieldListRef.current.querySelector(`[data-field-idx="${targetIdx}"]`)
      if (targetItem) {
        // 滚动到居中位置再往上偏移30px，让下一个字段更接近视口中心（模拟实时跟踪效果）
        const container = fieldListRef.current
        const itemRect = targetItem.getBoundingClientRect()
        const containerRect = container.getBoundingClientRect()
        const offsetTop = itemRect.top - containerRect.top + container.scrollTop
        const targetScroll = offsetTop - container.clientHeight / 2 + itemRect.height / 2 + 50
        container.scrollTo({ top: Math.max(0, targetScroll), behavior: "smooth" })
      }
    }
    setLastUpdatedFieldEl(null)
  }, [lastUpdatedFieldEl])

  /** 页面加载时检查 Token，有岗位信息则查询定制简历，并检查会员状态 */
  useEffect(() => {
    getCookieValue("Token").then((token) => {
      console.log("[OfferPie] SidebarPanel 获取到的 Token:", token)
      setIsLoggedIn(!!token)

      if (!token) {
        setHeaderStatus("请登录后继续使用")
        return
      }

      // 预加载省市区行政区划数据（供摩卡级联选择器使用）
      preloadRegionTree()

      // 查询会员状态
      getMemberStatus().then((memberData) => {
        console.log("[OfferPie] 会员状态:", memberData)
        setIsMember(memberData.isMember)
        if (!memberData.isMember) {
          setHeaderStatus("请开通会员后继续使用")
        }
      }).catch((err) => {
        console.warn("[OfferPie] 查询会员状态失败:", err)
        setIsMember(false)
        setHeaderStatus("请开通会员后继续使用")
      })

      // 有岗位 ID 时，查询定制简历
      if (jobInfo?.id) {
        getCustomizeResume(jobInfo.id).then((resumeRes: any) => {
          console.log("[OfferPie] 定制简历数据:", resumeRes)
          // 接口返回的 resume 字段映射为 main
          const mappedData: ResumeData = {
            main: resumeRes.resume || {},
            education: resumeRes.education || [],
            work: resumeRes.work || [],
            internship: resumeRes.internship || [],
            project: resumeRes.project || [],
            competition: resumeRes.competition || [],
          }
          setResumeData(mappedData)
        }).catch((err) => {
          console.warn("[OfferPie] 查询定制简历失败:", err)
        })
      }
    })
  }, [jobInfo?.id])

  /**
   * 自动填写按钮点击处理
   * 内部根据当前页面域名判断走哪个处理模式：
   *   - handleAutoFillBeisen：北森模式（域名包含 zhiye.com，如 avicsz.zhiye.com）
   *   - handleAutoFillCommon：通用模式（其他所有网站）
   *   - 后续特殊网站会在此处加条件分支（如根据 domain 或 jobInfo 来源判断）
   */
  /** 显示全页面填写遮罩层（黑色半透明 + 提示文字 + 闪烁圆点动画） */
  function showFillingOverlay() {
    // 防止重复创建
    if (document.getElementById("offerpie-filling-overlay")) return

    const overlay = document.createElement("div")
    overlay.id = "offerpie-filling-overlay"
    Object.assign(overlay.style, {
      position: "fixed",
      top: "0",
      left: "0",
      width: "100vw",
      height: "100vh",
      backgroundColor: "rgba(0, 0, 0, 0.05)",
      zIndex: "2147483646",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      pointerEvents: "auto",
    })

    // 提示文字容器
    const content = document.createElement("div")
    Object.assign(content.style, {
      display: "flex",
      alignItems: "center",
      gap: "4px",
      userSelect: "none",
    })

    // 文字
    const text = document.createElement("span")
    text.textContent = "填写中..."
    Object.assign(text.style, {
      color: "rgba(255,255,255, 1)",
      background:"rgba(107, 114, 128, 0.5)",
      height:"40px",
      "line-height":"40px",
      width:"370px",
      "text-align":"center",
      "border-radius":"4px",
      fontSize: "18px",
      fontWeight: "500",
      letterSpacing: "1px",
    })
    content.appendChild(text)

    // 5个按顺序消失闪烁的圆点
    for (let i = 0; i < 5; i++) {
      const dot = document.createElement("span")
      Object.assign(dot.style, {
        display: "inline-block",
        width: "6px",
        height: "6px",
        borderRadius: "50%",
        backgroundColor: "rgba(82, 202, 209, 0.3)",
        marginLeft: i === 0 ? "6px" : "3px",
        animation: `offerpie-dot-blink 1.5s ${i * 0.3}s infinite`,
      })
      content.appendChild(dot)
    }

    overlay.appendChild(content)

    // 注入关键帧动画样式（按顺序消失闪烁）
    if (!document.getElementById("offerpie-dot-style")) {
      const style = document.createElement("style")
      style.id = "offerpie-dot-style"
      style.textContent = `
        @keyframes offerpie-dot-blink {
          0%, 20% { opacity: 1; }
          40%, 100% { opacity: 0; }
        }
      `
      document.head.appendChild(style)
    }

    document.body.appendChild(overlay)
  }

  /** 移除全页面填写遮罩层（先显示完成提示，2秒后再移除） */
  function hideFillingOverlay() {
    const overlay = document.getElementById("offerpie-filling-overlay")
    if (!overlay) return
    // 找到提示文字元素，更新为完成状态
    const text = overlay.querySelector("span") as HTMLElement | null
    if (text) {
      text.textContent = "填写完成，请检查"
      text.style.background = "rgba(118, 213, 132, 0.5)"
    }
    // 隐藏闪烁圆点
    const dots = overlay.querySelectorAll("span:not(:first-child)")
    dots.forEach((dot) => {
      ;(dot as HTMLElement).style.display = "none"
    })
    // 2秒后移除遮罩层
    setTimeout(() => {
      overlay.remove()
    }, 2000)
  }

  /**
   * 自动填写入口
   * 根据当前页面域名分发到不同的处理器：
   * - 北森系统（zhiye.com）→ handleAutoFillBeisen：适配北森 Phoenix UI 组件库的特殊 DOM 结构
   * - 其他招聘网站 → handleAutoFillCommon：通用模式，基于 DOM 差异对比适配各种 UI 框架
   *
   * 两个处理器内部流程一致：
   * 1. extractDomStructure 提取页面 DOM 树结构
   * 2. detectPageLanguage 检测中/英文
   * 3. isJobApplicationForm 判断是否为求职表单页（非表单页直接跳过）
   * 4. 获取简历数据（接口优先，无接口时 fallback 到 mock 数据）
   * 5. locateExperienceSections 定位5大经历区块（教育/工作/实习/项目/竞赛）
   * 6. expandExperienceSections 对比简历段数，点击添加按钮补足经历段
   * 7. 逐段匹配字段 + 填充（走 fillMatchedField 统一入口）
   * 8. matchMainFields 匹配非经历区域字段（姓名/手机/邮箱等）并填充
   * 9. 收集剩余未匹配的空白字段（unmatchedFields），后续交给 AI 生成答案
   *
  /** 重置按钮：恢复到开始填写状态，重新检测页面 */
  const handleReset = () => {
    setFillCompleted(false)
    // 重新检测页面输入框状态
    setHeaderStatus("准备中, 请登录到投递表单页~")
    const hostDoc = document.getRootNode() === document ? document : (document.getRootNode() as ShadowRoot).ownerDocument
    const inputs = hostDoc.querySelectorAll("input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='file']), textarea")
    if (inputs.length > 2) {
      setHeaderStatus("准备就绪，请点击开始填写")
    } else {
      // 延迟重试
      setTimeout(() => {
        const inputs2 = hostDoc.querySelectorAll("input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='file']), textarea")
        if (inputs2.length > 2) setHeaderStatus("准备就绪，请点击开始填写")
      }, 1000)
    }
  }

  /**
   * 自动填写主流程
   * 填写完成后更新组件状态，供后续"保存用户修改"和"AI补填"使用
   */
  const handleAutoFill = async () => {
    setFilling(true)
    setHeaderStatus("正在填写表单，请勿跳转页面")
    // 停止之前的循环扫描（如果有）
    stopScanLoop()
    // 操作中打开遮罩
    showFillingOverlay()
    try {
      // 检测当前页面域名，判断走哪个处理模式
      const currentHost = window.location.hostname
      const isBeisen = currentHost.includes("zhiye.com") // 北森招聘平台域名特征
      const isMoka = currentHost.includes("mokahr.com") // 摩卡招聘平台域名特征
      const isFeishu = currentHost.includes("feishu.cn") // 飞书招聘平台域名特征
      const isHotjob = currentHost.includes("hotjob.cn") // Hotjob招聘平台域名特征
      const scanSiteMode = isBeisen ? "beisen" as const
        : isMoka ? "moka" as const
        : isFeishu ? "feishu" as const
        : isHotjob ? "hotjob" as const
        : undefined

      // 第一次开启循环扫描（不传 sectionResults，阶段A之前就开始展示）
      startScanLoop({ siteMode: scanSiteMode })

      const fillResult = isBeisen
        ? await handleAutoFillBeisen({ resumeData, jobInfo })
        : isMoka
          ? await handleAutoFillMoka({ resumeData, jobInfo })
          : isFeishu
            ? await handleAutoFillFeishu({ resumeData, jobInfo })
            : isHotjob
              ? await handleAutoFillHotjob({ resumeData, jobInfo })
              : await handleAutoFillCommon({ resumeData, jobInfo })

      // 将处理器返回的结果同步到组件状态
      setPageLang(fillResult.lang)          // 页面语言（影响后续标签匹配用中文还是英文）
      setIsFormPage(fillResult.isFormPage)   // 是否为表单页（非表单页不显示填写相关 UI）
      if (fillResult.resumeData) setResumeData(fillResult.resumeData) // 更新简历数据（可能从 mock 切换为接口数据）
      setFormFields(fillResult.formFields)   // 已匹配的字段列表（用于后续高亮/状态展示）
      setFillCompleted(true)                 // 标记填写流程已完成，解锁"保存修改"等按钮
      setHeaderStatus("表单填写完成，请检查后提交")
      hideFillingOverlay()                   // 填写完成，移除遮罩

      // 停止第一次循环扫描，开启第二次循环扫描（传入 sectionResults + expandedResults，分段精确）
      // 构建排除集合：从简历数据中提取所有值，避免将已填值误认为标签
      const scanExcludeTexts = new Set<string>()
      const scanResumeData = fillResult.resumeData || resumeData
      if (scanResumeData) {
        const main = scanResumeData.main
        if (main) {
          for (const val of Object.values(main)) {
            if (typeof val === "string" && val.trim()) scanExcludeTexts.add(val.trim())
            if (Array.isArray(val)) val.forEach((v) => { if (typeof v === "string" && v.trim()) scanExcludeTexts.add(v.trim()) })
          }
        }
        const expKeys: ("education" | "work" | "internship" | "project" | "competition")[] = ["education", "work", "internship", "project", "competition"]
        for (const sec of expKeys) {
          const items = scanResumeData[sec]
          if (!Array.isArray(items)) continue
          for (const item of items) {
            for (const val of Object.values(item as Record<string, any>)) {
              if (typeof val === "string" && val.trim()) scanExcludeTexts.add(val.trim())
            }
          }
        }
      }
      // 收集页面上所有选择器字段的下拉选项文字，合并到排除集合
      // 避免选择器已选值（如"维吾尔族"）被误认为表单标签
      // 北森模式（zhiye.com）选择器值直接写在输入框里，跳过整套弹出层收集逻辑
      if (!isBeisen) {
        const preFieldStats = scanPageFields({
          siteMode: scanSiteMode,
          sectionResults: fillResult.sectionResults,
          expandedResults: fillResult.expandedResults,
          excludeTexts: scanExcludeTexts,
        })
        const pickerOptionTexts = await collectPickerOptionTexts(preFieldStats)
        for (const t of pickerOptionTexts) scanExcludeTexts.add(t)
        // 存入 ref，供 handleSaveUserInput 使用
        pickerOptionTextsRef.current = pickerOptionTexts
      }

      startScanLoop({
        siteMode: scanSiteMode,
        sectionResults: fillResult.sectionResults,
        expandedResults: fillResult.expandedResults,
        excludeTexts: scanExcludeTexts,
      })
      
    } catch (e) {
      console.error("OfferPie: 自动填写异常", e)
      hideFillingOverlay()                   // 异常时也移除遮罩
    }
    setTimeout(() => setFilling(false), 1000)
  }

  /**
   * 模拟提交：通过 fillStats 扫描页面字段，过滤出 B2 阶段字段数据，更新到缓存
   * 【注意】弹窗打开时不执行，避免覆盖用户正在编辑的数据
   */
  const handleSaveUserInput = async () => {
    // 弹窗打开时直接跳过，不更新缓存
    if (showCacheModal) return
    try {
      const cacheData = await storageGet<any>("offerpie_unfilled_form")
      if (!cacheData) { return }

      // 检测当前网站模式
      const currentHost = window.location.hostname
      const siteMode = currentHost.includes("zhiye.com") ? "beisen" as const
        : currentHost.includes("mokahr.com") ? "moka" as const
        : currentHost.includes("feishu.cn") ? "feishu" as const
        : currentHost.includes("hotjob.cn") ? "hotjob" as const
        : undefined

      // 从简历数据和缓存数据中提取所有已填值，构建排除集合
      // 避免 findLabelForInput 把这些值文字误认为是表单字段标签
      const excludeTexts = new Set<string>()
      if (resumeData) {
        // 主表字段值
        const main = resumeData.main
        if (main) {
          for (const val of Object.values(main)) {
            if (typeof val === "string" && val.trim()) excludeTexts.add(val.trim())
            if (Array.isArray(val)) val.forEach((v) => { if (typeof v === "string" && v.trim()) excludeTexts.add(v.trim()) })
          }
        }
        // 5大经历字段值
        const expSections: ("education" | "work" | "internship" | "project" | "competition")[] = ["education", "work", "internship", "project", "competition"]
        for (const sec of expSections) {
          const items = resumeData[sec]
          if (!Array.isArray(items)) continue
          for (const item of items) {
            for (const val of Object.values(item as Record<string, any>)) {
              if (typeof val === "string" && val.trim()) excludeTexts.add(val.trim())
            }
          }
        }
      }
      // 缓存中已记住的表单字段值
      if (cacheData.unfilledFormData) {
        for (const sec of cacheData.unfilledFormData as any[]) {
          const items = sec.formItems
          if (!items) continue
          if (sec.isExperience && Array.isArray(items)) {
            for (const seg of items) {
              if (Array.isArray(seg)) {
                for (const f of seg) { if (f.value && typeof f.value === "string") excludeTexts.add(f.value.trim()) }
              }
            }
          } else if (Array.isArray(items)) {
            for (const f of items) { if (f.value && typeof f.value === "string") excludeTexts.add(f.value.trim()) }
          }
        }
      }
      // 合并选择器选项文字（collectPickerOptionTexts 收集的下拉选项）
      for (const t of pickerOptionTextsRef.current) excludeTexts.add(t)

      // 调用 fillStats 扫描页面所有字段
      const titleStats = scanPageFields({ siteMode, excludeTexts })

      // 补充选择器展示值（与 startScanLoop 逻辑一致）
      const pickerOpts = pickerOptionTextsRef.current
      if (pickerOpts.size > 0) {
        for (const ts of titleStats) {
          for (let fIdx = 0; fIdx < ts.fields.length; fIdx++) {
            const f = ts.fields[fIdx]
            if (f.value) continue
            if (!f.inputElement) continue
            if ((f.inputElement as HTMLInputElement).value?.trim()) continue
            const nextField = ts.fields[fIdx + 1]
            const nextLabelText = nextField?.labelText || null
            const displayVal = getPickerDisplayValue(f.inputElement as HTMLElement, f.labelText, nextLabelText, pickerOpts)
            if (displayVal) {
              f.value = displayVal
              f.filled = true
            }
          }
        }
      }

      /**
       * 过滤日期/时间字段值中的年龄后缀，如 "1997-05 (29岁)" → "1997-05"
       * 匹配中英文括号包裹的"数字+岁/years old"格式
       */
      const stripAgeSuffix = (label: string, value: string): string => {
        if (!value) return value
        const dateKeywords = ["日期", "时间", "年月", "生日", "出生", "date", "birth", "年龄"]
        const isDateField = dateKeywords.some((k) => label.toLowerCase().includes(k.toLowerCase()))
        if (!isDateField) return value
        // 去掉中英文括号包裹的年龄信息：(29岁)（29岁）(29 years old) 等
        return value.replace(/\s*[(\uff08]\s*\d+\s*(岁|years?\s*old)\s*[)\uff09]/gi, "").trim()
      }

      /**
       * 地区类字段空格转斜杠：如 "山西 大同市" → "山西/大同市"
       * 适用于：籍贯、户口、所在地、出生地、工作地等地区相关字段
       * @param label - 字段标签名（用于判断是否为地区字段）
       * @param value - 字段当前值
       * @returns 处理后的值（非地区字段原样返回）
       */
      const formatRegionValue = (label: string, value: string): string => {
        if (!value) return value
        const regionKeywords = ["籍贯", "户口", "所在地", "出生地", "工作地", "居住地", "家庭地址", "户籍", "地区", "城市", "省市", "location", "birthplace", "hometown", "residence"]
        const isRegionField = regionKeywords.some((k) => label.toLowerCase().includes(k.toLowerCase()))
        if (!isRegionField) return value
        // 将空格替换为 /（如 "山西 大同市" → "山西/大同市"）
        return value.replace(/\s+/g, "/")
      }

      // 提取非简历格式字段（B2阶段字段）
      const nonResumeData = extractNonResumeFields(titleStats)

      // 更新缓存中的 unfilledFormData
      // 策略：新数据中有的字段用新值替换，新数据中没有但旧数据中有的字段保留
      const oldSections = cacheData.unfilledFormData as any[] || []
      for (const newSec of nonResumeData) {
        const oldSecIdx = oldSections.findIndex((s: any) => s.title === newSec.title)
        if (oldSecIdx < 0) {
          oldSections.push(newSec)
        } else {
          const oldSec = oldSections[oldSecIdx]
          if (newSec.isExperience && oldSec.isExperience) {
            // 经历类型：按段合并，保留旧字段
            const oldSegments = oldSec.formItems as any[][]
            const newSegments = newSec.formItems as any[][]
            for (let sIdx = 0; sIdx < newSegments.length; sIdx++) {
              if (sIdx >= oldSegments.length) {
                oldSegments.push(newSegments[sIdx])
              } else {
                // 新字段更新值，旧字段中有但新字段中没有的保留
                const oldFields = oldSegments[sIdx]
                const newFields = newSegments[sIdx]
                for (const nf of newFields) {
                  const of_ = oldFields.find((f: any) => f.label === nf.label)
                  if (of_) {
                    if (nf.value) of_.value = formatRegionValue(nf.label || "", stripAgeSuffix(nf.label || "", nf.value))
                  } else {
                    oldFields.push(nf)
                  }
                }
              }
            }
          } else if (!newSec.isExperience && !oldSec.isExperience) {
            // 非经历类型：按字段合并，保留旧字段
            const oldFields = oldSec.formItems as any[]
            const newFields = newSec.formItems as any[]
            for (const nf of newFields) {
              const of_ = oldFields.find((f: any) => f.label === nf.label)
              if (of_) {
                if (nf.value) of_.value = formatRegionValue(nf.label || "", stripAgeSuffix(nf.label || "", nf.value))
              } else {
                oldFields.push(nf)
              }
            }
          } else {
            // 类型变化，直接替换
            oldSections[oldSecIdx] = newSec
          }
        }
      }

      cacheData.unfilledFormData = oldSections
      cacheData.timestamp = Date.now()
      await storageSet("offerpie_unfilled_form", cacheData)

      // 打印所有字段数据
      printFieldStats(titleStats)
    } catch (e) {
      console.error("[OfferPie] 模拟提交失败:", e)
    }
  }

  /** 打开"助手已记住的表单"弹窗，从 chrome.storage 加载数据 */
  const openCacheModal = async () => {
    try {
      const cacheData = await storageGet<any>("offerpie_unfilled_form")
      if (!cacheData) {
        setCacheModalData([])
      } else {
        setCacheModalData(cacheData.unfilledFormData || [])
      }
    } catch {
      setCacheModalData([])
    }
    setShowCacheModal(true)
  }

  /** 关闭弹窗 */
  const closeCacheModal = () => {
    setShowCacheModal(false)
    setCacheModalData(null)
  }

  /** 弹窗内更新某个字段的值 */
  const updateCacheFieldValue = (sectionIdx: number, segIdx: number | null, fieldIdx: number, newValue: string) => {
    if (!cacheModalData) return
    const updated = [...cacheModalData]
    const section = updated[sectionIdx]
    if (segIdx !== null) {
      // 经历类型：formItems 是二维数组
      ;(section.formItems as any[][])[segIdx][fieldIdx].value = newValue
    } else {
      // 普通类型：formItems 是一维数组
      ;(section.formItems as any[])[fieldIdx].value = newValue
    }
    setCacheModalData(updated)
  }

  /** 弹窗内保存数据到 chrome.storage */
  const saveCacheModalData = async () => {
    try {
      const cacheData = await storageGet<any>("offerpie_unfilled_form") || {}
      cacheData.unfilledFormData = cacheModalData || []
      cacheData.timestamp = Date.now()
      await storageSet("offerpie_unfilled_form", cacheData)
      alert("已保存表单数据")
    } catch (e) {
      console.error("[OfferPie] 保存缓存数据失败:", e)
      alert("保存失败，请查看控制台")
    }
  }

  /** 弹窗内清空缓存数据（带确认） */
  const clearCacheFromModal = async () => {
    const confirmed = window.confirm("是否清空已记录的表单数据？清空后下次填简历那些数据需要您一个个手动填。")
    if (!confirmed) return
    // 先停止循环扫描，防止 handleSaveUserInput 又把数据写回去
    stopScanLoop()
    await storageRemove("offerpie_unfilled_form")
    setCacheModalData([])
    alert("已清空缓存数据")
    console.log("[OfferPie] 已清空 offerpie_unfilled_form 缓存")
  }

  // ============ 拖拽逻辑 ============

  /** 即时拖拽：收起状态横条 / 展开状态顶部 header，按住即可拖动（无需长按） */
  const handleInstantDragDown = useCallback((e: React.MouseEvent) => {
    // 如果点击的是箭头按钮，不启动拖拽
    const target = e.target as HTMLElement
    if (target.closest("button, .op-collapsed-arrow, .op-collapse-btn")) return

    e.preventDefault()
    const state = dragStateRef.current
    state.startX = e.clientX
    state.startY = e.clientY
    state.startOffsetX = dragOffset.x
    state.startOffsetY = dragOffset.y
    state.isDragging = true
    state.isLongPress = true // 标记为已激活拖拽
    if (containerRef.current) {
      containerRef.current.style.cursor = "grabbing"
    }

    const handleMouseMove = (ev: MouseEvent) => {
      const dx = ev.clientX - state.startX
      const dy = ev.clientY - state.startY
      setDragOffset({ x: state.startOffsetX + dx, y: state.startOffsetY + dy })
      if (!hasDragged) setHasDragged(true)
    }

    const handleMouseUp = () => {
      state.isDragging = false
      state.isLongPress = false
      if (containerRef.current) {
        containerRef.current.style.cursor = ""
      }
      document.removeEventListener("mousemove", handleMouseMove)
      document.removeEventListener("mouseup", handleMouseUp)
    }

    document.addEventListener("mousemove", handleMouseMove)
    document.addEventListener("mouseup", handleMouseUp)
  }, [dragOffset, hasDragged])

  /** 鼠标按下（非 header 区域）：启动长按计时，300ms 后才可拖拽 */
  const handleMouseDown = useCallback((e: React.MouseEvent) => {
    // 如果点击的是交互元素（按钮、链接、输入框等），不启动拖拽
    const target = e.target as HTMLElement
    if (target.closest("button, a, input, textarea, select, [role='button'], .op-credits-link, .op-field-item, .op-cache-modal-mask, .op-header, .op-collapsed-bar")) return

    e.preventDefault()
    const state = dragStateRef.current
    state.startX = e.clientX
    state.startY = e.clientY
    state.startOffsetX = dragOffset.x
    state.startOffsetY = dragOffset.y
    state.isLongPress = false

    // 300ms 后认为是长按，开始拖拽
    state.longPressTimer = setTimeout(() => {
      state.isLongPress = true
      state.isDragging = true
      if (containerRef.current) {
        containerRef.current.style.cursor = "grabbing"
      }
    }, 300)

    const handleMouseMove = (ev: MouseEvent) => {
      if (!state.isLongPress) {
        // 还没触发长按，如果移动太远就取消（避免误触）
        const dist = Math.abs(ev.clientX - state.startX) + Math.abs(ev.clientY - state.startY)
        if (dist > 5 && state.longPressTimer) {
          clearTimeout(state.longPressTimer)
          state.longPressTimer = null
        }
        return
      }
      // 长按中拖拽
      const dx = ev.clientX - state.startX
      const dy = ev.clientY - state.startY
      setDragOffset({ x: state.startOffsetX + dx, y: state.startOffsetY + dy })
      if (!hasDragged) setHasDragged(true)
    }

    const handleMouseUp = () => {
      if (state.longPressTimer) {
        clearTimeout(state.longPressTimer)
        state.longPressTimer = null
      }
      state.isDragging = false
      if (containerRef.current) {
        containerRef.current.style.cursor = ""
      }
      document.removeEventListener("mousemove", handleMouseMove)
      document.removeEventListener("mouseup", handleMouseUp)
    }

    document.addEventListener("mousemove", handleMouseMove)
    document.addEventListener("mouseup", handleMouseUp)
  }, [dragOffset, hasDragged])

  /** 鼠标移入：显示拖拽提示气泡（收起状态提示1次，展开状态提示1次，拖动过后不再提示） */
  const handleMouseEnter = useCallback(() => {
    if (hasDragged) return
    if (collapsed) {
      if (collapsedTooltipShownRef.current) return
      collapsedTooltipShownRef.current = true
    } else {
      if (expandedTooltipShownRef.current) return
      expandedTooltipShownRef.current = true
    }
    setShowDragTooltip(true)
    if (dragTooltipTimerRef.current) clearTimeout(dragTooltipTimerRef.current)
    dragTooltipTimerRef.current = setTimeout(() => {
      setShowDragTooltip(false)
    }, 3000)
  }, [hasDragged, collapsed])

  /** 组件卸载时清理拖拽提示定时器 */
  useEffect(() => {
    return () => {
      if (dragTooltipTimerRef.current) clearTimeout(dragTooltipTimerRef.current)
    }
  }, [])

  return (
    <div
      className={`op-container ${collapsed ? "op-container--collapsed" : ""}`}
      ref={containerRef}
      style={{ transform: `translate(${dragOffset.x}px, ${dragOffset.y}px)` }}
      onMouseDown={handleMouseDown}
      onMouseEnter={handleMouseEnter}
    >
      {/* 拖拽提示气泡 */}
      {showDragTooltip && (
        <div className="op-drag-tooltip">助手位置可以拖动</div>
      )}

      {/* 收起状态：显示 logo + 提示文字 + 向下箭头 */}
      {collapsed ? (
        <div className="op-collapsed-bar" onMouseDown={handleInstantDragDown}>
          <img src={logoImg} alt="OfferPie" width={28} height={31} className="op-header-logo" />
          <span className="op-collapsed-text">{headerStatus || "Offer派助手"}</span>
          <svg className="op-collapsed-arrow" onClick={() => setCollapsed(false)} width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="6 9 12 15 18 9" />
          </svg>
        </div>
      ) : (
        <>
      {/* 填写中遮罩：覆盖整个面板，阻止用户操作 */}
      {filling && <div className="op-filling-overlay" />} 
      <div className="op-inner">
      {/* 新顶部：logo + 提示文字 + 收起箭头（按住即可拖动） */}
      <div className="op-header" onMouseDown={handleInstantDragDown}>
        <div className="op-header-left">
          <img src={logoImg} alt="OfferPie" width={28} height={31} className="op-header-logo" />
          <span className="op-header-status">{headerStatus || "Offer派助手"}</span>
        </div>
        <button className="op-collapse-btn" onClick={() => setCollapsed(true)}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="9 6 15 12 9 18" />
          </svg>
        </button>
      </div>

      {/* 未登录状态：显示登录提示 */}
      {!isLoggedIn && isLoggedIn !== null && (
        <div className="op-nonmember">
          <div className="op-nonmember-divider" />
          <div className="op-nonmember-title">登录 Offer 派，开启智能投递</div>
          <div className="op-nonmember-desc">登录后将自动同步你的会员状态、简历和求职信息</div>
          <button
            className="op-nonmember-btn"
            onClick={() => { window.open("https://test.offerpai.com.cn/jobs", "_blank") }}
          >
            前往登录
          </button>
          <div
            className="op-nonmember-refresh"
            onClick={() => { window.location.reload() }}
          >
            已完成登录？刷新登录状态
          </div>
        </div>
      )}

      {/* 已登录状态：显示完整功能 */}
      {isLoggedIn && (
        <>
          {/* 非会员状态：显示开通会员提示 */}
          {isMember === false && (
            <div className="op-nonmember">
              <div className="op-nonmember-divider" />
              <div className="op-nonmember-title">智能投递助手为会员专享功能</div>
              <div className="op-nonmember-desc">开通会员后，即可自动识别并填写网申表单，减少重复操作</div>
              <button
                className="op-nonmember-btn"
                onClick={() => { window.open("https://test.offerpai.com.cn/jobs", "_blank") }}
              >
                开通会员，立即使用
              </button>
              <div
                className="op-nonmember-refresh"
                onClick={() => { window.location.reload() }}
              >
                已开通会员？刷新会员状态
              </div>
            </div>
          )}

          {/* 会员状态：显示完整功能 */}
          {isMember === true && (
          <>
          {/* 职位信息卡片 */}
          <div className="op-job-card">
            
            <div className="op-job-info">
              <div className="op-job-title">{jobInfo?.title || "未匹配到岗位信息"}</div>
              <div className="op-job-meta">
                {jobInfo
                  ? [jobInfo.companyName || jobInfo.companyShortName]
                      .filter(Boolean)
                      .join(" · ") || "—"
                  : "当前页面暂未关联岗位"}
              </div>
            </div>
            {jobInfo?.matchScore != null && (
              <div className="op-match-score">
                <svg width="48" height="48" viewBox="0 0 48 48">
                  <circle cx="24" cy="24" r="20" fill="none" stroke="#f0f0f0" strokeWidth="3" />
                  <circle cx="24" cy="24" r="20" fill="none" stroke="#52CAD1" strokeWidth="3"
                    strokeDasharray={`${(jobInfo.matchScore) / 100 * 2 * Math.PI * 20} ${2 * Math.PI * 20}`}
                    strokeLinecap="round" transform="rotate(-90 24 24)" />
                  <text x="24" y="26" textAnchor="middle" fontSize="13" fontWeight="700" fill="#000">
                    {jobInfo.matchScore}%
                  </text>
                </svg>
              </div>
            )}
          </div>

          {/* 自动填写按钮区域 */}
          {fillCompleted ? (
            <div className="op-fill-done-row">
              <button className="op-reset-btn" onClick={handleReset}>
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#52CAD1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <polyline points="23 4 23 10 17 10" />
                  <polyline points="1 20 1 14 7 14" />
                  <path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" />
                </svg>
              </button>
              <div className="op-fill-done-label">填写完成，请检查</div>
            </div>
          ) : (
            <button className={`op-autofill-btn${filling ? " op-autofill-btn--filling" : ""}`} onClick={handleAutoFill} disabled={filling || headerStatus !== "准备就绪，请点击开始填写"}>
              {filling ? "填写中...." : "开始填写"}
            </button>
          )}

          {/* 操作链接 */}
          <div className="op-credits-row">
            <span className="op-credits-link" onClick={async () => {
              const cachedUrl = sessionStorage.getItem("offerpie_source_url") || sourceUrl
              if (!cachedUrl) return

              try {
                // 通过跨域 CSL 模式读取投递链接列表
                const result = await panelBridge.get<OfferpaieDeliveryLinkListData>(appConfig.bridgeDomain, "offerpaiDeliveryLinkList", true)
                const linkList = result?.linkList || []

                // 检查当前投递链接是否已在列表中
                if (!linkList.includes(cachedUrl)) {
                  linkList.push(cachedUrl)
                }

                // 写回跨域通信（让网页端能读到）
                await panelBridge.put<OfferpaieDeliveryLinkListData>(appConfig.bridgeDomain, "offerpaiDeliveryLinkList", { linkList }, true)
                console.log("[OfferPie] 已更新投递链接列表:", linkList)
              } catch (err) {
                console.warn("[OfferPie] 更新投递链接列表失败:", err)
              }

              // 跳转到投递链接
              window.location.href = cachedUrl
            }}>打开投递链接</span>
            <span className="op-credits-link" onClick={openCacheModal}>助手已记住的表单</span>
          </div>

          {/* 简历区域 */}
          <div className="op-section-label">简历</div>
          <div className="op-resume-card">
            <div className="op-resume-avatar">
                {resumeData?.main?.name ? '已优化' : ""}

            </div>
            <div className="op-resume-info">
              <span className="op-resume-name">
                {resumeData?.main?.name || "暂无简历"}.pdf
              </span>
            </div>
          </div>

          {/* 按钮 */}

          {/* 填写进度区域 */}
          <div className="op-progress-row">
            <span className="op-progress-label">填写进度</span>
            <span className="op-progress-value">
              {fieldStats.length > 0
                ? `${fieldStats.reduce((s, t) => s + t.fields.filter((f) => f.color === "green" || f.color === "greenTwo").length, 0)}/${fieldStats.reduce((s, t) => s + t.fields.length, 0)}`
                : "0/0"}
            </span>
          </div>
          <div className="op-progress-bar-bg">
            <div
              className="op-progress-bar-fill"
              style={{
                width: fieldStats.length > 0
                  ? `${Math.round(fieldStats.reduce((s, t) => s + t.fields.filter((f) => f.color === "green" || f.color === "greenTwo").length, 0) / Math.max(fieldStats.reduce((s, t) => s + t.fields.length, 0), 1) * 100)}%`
                  : "0%"
              }}
            />
          </div>

          {/* 已识别字段列表 */}
          {fieldStats.length > 0 && (() => {
            /** 点击字段名时滚动到对应输入框 */
            const scrollToField = (el: Element | null) => {
              if (!el || !(el instanceof HTMLElement)) return
              el.scrollIntoView({ behavior: "smooth", block: "center" })
              // 闪烁高亮提示
              const origOutline = el.style.outline
              el.style.outline = "2px solid #52cad1"
              setTimeout(() => { el.style.outline = origOutline }, 1500)
            }

            return (
              <>
                {/* 已识别字段区域（合并展示所有字段） */}
                <div className="op-field-section">
                  <div className="op-field-section-title">
                    <span className="op-field-section-dot op-field-section-dot--green" />
                    已识别字段
                    <span className="op-field-section-required-count">（剩余必填:{fieldStats.reduce((s, t) => s + t.fields.filter((f) => f.color === "red").length, 0)}）</span>
                  </div>
                  <div className="op-field-list" ref={fieldListRef}>
                    {(() => {
                      let globalIdx = 0
                      return fieldStats.map((ts) => {
                        if (ts.fields.length === 0) return null
                        return (
                          <div key={ts.titleText} className="op-field-group">
                            <div className="op-field-group-title">{ts.titleText}</div>
                            {ts.isExpType && ts.segmentCount > 1
                              ? Array.from({ length: ts.segmentCount }, (_, sIdx) => {
                                  const segFields = ts.fields.filter((f) => f.segmentIndex === sIdx)
                                  if (segFields.length === 0) return null
                                  return (
                                    <div key={sIdx}>
                                      <div className="op-field-group-segment">经历{sIdx + 1}</div>
                                      {segFields.map((f, idx) => {
                                        const isFilled = f.color === "green" || f.color === "greenTwo"
                                        const fieldIdx = globalIdx++
                                        return (
                                          <div
                                            key={`${f.labelText}-${sIdx}-${idx}`}
                                            className="op-field-item"
                                            data-field-idx={fieldIdx}
                                            onClick={() => scrollToField(f.inputElement)}
                                          >
                                            {isFilled ? (
                                              <svg className="op-field-item-icon" width="16" height="16" viewBox="0 0 24 24" fill="none">
                                                <circle cx="12" cy="12" r="11" fill="#52cad1" />
                                                <path d="M7 12.5l3 3 7-7" stroke="#fff" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
                                              </svg>
                                            ) : (
                                              <svg className="op-field-item-icon" width="16" height="16" viewBox="0 0 24 24" fill="none">
                                                <circle cx="12" cy="12" r="10" stroke="#999" strokeWidth="2.5" />
                                              </svg>
                                            )}
                                            <span className="op-field-item-name">{f.labelText}</span>
                                            {f.color === "red" && <span className="op-field-item-required">必填</span>}
                                          </div>
                                        )
                                      })}
                                    </div>
                                  )
                                })
                              : ts.fields.map((f, idx) => {
                                  const isFilled = f.color === "green" || f.color === "greenTwo"
                                  const fieldIdx = globalIdx++
                                  return (
                                    <div
                                      key={`${f.labelText}-${idx}`}
                                      className="op-field-item"
                                      data-field-idx={fieldIdx}
                                      onClick={() => scrollToField(f.inputElement)}
                                    >
                                      {isFilled ? (
                                        <svg className="op-field-item-icon" width="16" height="16" viewBox="0 0 24 24" fill="none">
                                          <circle cx="12" cy="12" r="11" fill="#52cad1" />
                                          <path d="M7 12.5l3 3 7-7" stroke="#fff" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
                                        </svg>
                                      ) : (
                                        <svg className="op-field-item-icon" width="16" height="16" viewBox="0 0 24 24" fill="none">
                                          <circle cx="12" cy="12" r="10" stroke="#999" strokeWidth="2.5" />
                                        </svg>
                                      )}
                                      <span className="op-field-item-name">{f.labelText}</span>
                                      {f.color === "red" && <span className="op-field-item-required">必填</span>}
                                    </div>
                                  )
                                })
                            }
                          </div>
                        )
                      })
                    })()}
                  </div>
                </div>
              </>
            )
          })()}
        </>
          )}
        </>
      )}
      {/* 版本号（未登录或非会员时隐藏） */}
      {isLoggedIn && isMember !== false && (
        <div className="op-version">ver: {appConfig.pluginVersion}</div>
      )}
      </div>

      {/* 助手已记住的表单 弹窗 */}
      {showCacheModal && (() => {
        // 因为 op-container 有 transform，内部的 fixed 定位会相对于容器而非视口
        // 需要计算反向偏移让弹窗遮罩覆盖整个视口
        const containerEl = containerRef.current
        const rect = containerEl?.getBoundingClientRect()
        const maskStyle: React.CSSProperties = rect ? {
          position: "fixed" as const,
          top: -rect.top,
          left: -rect.left,
          width: "100vw",
          height: "100vh",
        } : {}
        return (
        <div className="op-cache-modal-mask" style={maskStyle} onClick={closeCacheModal}>
          <div className="op-cache-modal" onClick={(e) => e.stopPropagation()}>
            <div className="op-cache-modal-header">
              <span className="op-cache-modal-title">助手已记住的表单</span>
              <button className="op-cache-modal-close" onClick={closeCacheModal}>✕</button>
            </div>
            <div className="op-cache-modal-body">
              {(!cacheModalData || cacheModalData.length === 0) ? (
                <div className="op-cache-modal-empty">暂无缓存数据</div>
              ) : (
                <div className="op-cache-modal-body-inner">
                  {/* 左侧竖直 tab 导航 */}
                  <div className="op-cache-modal-tabs">
                    {cacheModalData.map((section: any, sIdx: number) => (
                      <div
                        key={sIdx}
                        className="op-cache-modal-tab"
                        onClick={() => {
                          const contentEl = containerRef.current?.querySelector(".op-cache-modal-content")
                          const targetEl = contentEl?.querySelector(`[data-cache-section-idx="${sIdx}"]`)
                          if (targetEl) targetEl.scrollIntoView({ behavior: "smooth", block: "start" })
                        }}
                      >
                        {section.title}
                      </div>
                    ))}
                  </div>
                  {/* 右侧内容区域 */}
                  <div className="op-cache-modal-content">
                {cacheModalData.map((section: any, sIdx: number) => (
                  <div key={sIdx} className="op-cache-modal-section" data-cache-section-idx={sIdx}>
                    <div className="op-cache-modal-section-title">{section.title}</div>
                    {section.isExperience ? (
                      // 经历类型：formItems 是二维数组，每段经历分开展示
                      (section.formItems as any[][]).map((segFields: any[], segIdx: number) => (
                        <div key={segIdx} className="op-cache-modal-segment">
                          <div className="op-cache-modal-segment-title">经历{segIdx + 1}</div>
                          <div className="op-cache-modal-fields">
                            {segFields.map((field: any, fIdx: number) => {
                              // 标签名包含"内容"二字的字段用 textarea 单独一行
                              const isLongText = field.label && field.label.includes("内容")
                              if (isLongText) {
                                return (
                                  <div key={fIdx} className="op-cache-modal-field op-cache-modal-field--full">
                                    <label className="op-cache-modal-label">{field.label}</label>
                                    <textarea
                                      className="op-cache-modal-textarea"
                                      rows={3}
                                      value={field.value || ""}
                                      onChange={(e) => updateCacheFieldValue(sIdx, segIdx, fIdx, e.target.value)}
                                    />
                                  </div>
                                )
                              }
                              return (
                                <div key={fIdx} className="op-cache-modal-field">
                                  <label className="op-cache-modal-label">{field.label}</label>
                                  <input
                                    className="op-cache-modal-input"
                                    type="text"
                                    value={field.value || ""}
                                    onChange={(e) => updateCacheFieldValue(sIdx, segIdx, fIdx, e.target.value)}
                                  />
                                </div>
                              )
                            })}
                          </div>
                        </div>
                      ))
                    ) : (
                      // 普通类型：formItems 是一维数组
                      <div className="op-cache-modal-fields">
                        {(section.formItems as any[]).map((field: any, fIdx: number) => (
                          <div key={fIdx} className="op-cache-modal-field">
                            <label className="op-cache-modal-label">{field.label}</label>
                            <input
                              className="op-cache-modal-input"
                              type="text"
                              value={field.value || ""}
                              onChange={(e) => updateCacheFieldValue(sIdx, null, fIdx, e.target.value)}
                            />
                          </div>
                        ))}
                      </div>
                    )}
                  </div>
                ))}
                  </div>
                </div>
              )}
            </div>
            <div className="op-cache-modal-footer">
              <button className="op-cache-modal-btn op-cache-modal-btn--clear" onClick={clearCacheFromModal}>清空缓存</button>
              <button className="op-cache-modal-btn op-cache-modal-btn--save" onClick={saveCacheModalData}>确认保存</button>
            </div>
          </div>
        </div>
        )
      })()}
        </>
      )}
    </div>
  )
}

