/* AI_CHANGELOG
| 日期 | Prompt | 变更 |
|------|--------|------|
| 2026-03-23 | 角色路由+页面权限守卫 | onShow 添加 checkPageAccess 权限守卫 |
| 2026-03-27 | board-finance-integration T3.1-T3.3 | _loadGiftRows→_loadData 绑定全部 6 板块；筛选联动调 _loadData；areaOptions 9 项 |
| 2026-03-27 | board-finance-integration T3.1 fix | _loadData 移除 formatMoney，所有金额/百分比字段传原始数字，由 WXS 负责格式化，修复双重格式化导致 NaN |
| 2026-07-22 | 财务看板多问题修复 | discountRate/balanceRate ×100 修复百分比计算（后端返回小数，WXS 期望百分比数字） |
| 2026-03-28 | 环比修复 | _loadData 绑定所有板块的 is_down/is_flat；mapExpenseItems/mapCoachTable 读取后端真实涨跌方向 |
*/
// 财务看板页 — 忠于 H5 原型结构

import { checkPageAccess, getVisibleBoardTabs } from '../../utils/auth-guard'
import { getRandomAiColor } from '../../utils/ai-color'
import { fetchBoardFinance, fetchAICache } from '../../services/api'
import { getBusinessClock } from '../../utils/runtime-clock'

/**
 * 判断当前选择的时间筛选是否为"本月前 5 天"(用于显示"(预估)"提示)。
 *
 * Wave 1 W1-T1 沙箱接入(2026-05-04):
 *   - 优先使用业务时钟 businessDate(YYYY-MM-DD),沙箱模式下回放虚拟日期
 *   - businessDate 为空时降级到本地 new Date()(初次进入页面、业务时钟未就绪)
 *   - 后端 board_service 已通过 runtime_context 自处理时间窗口,本函数仅控制 UI 提示
 */
function isCurrentMonthFilter(selectedTime: string, businessDate?: string): boolean {
  if (selectedTime !== 'month') return false
  if (businessDate) {
    const day = parseInt(businessDate.slice(8, 10), 10)
    return !Number.isNaN(day) && day <= 5
  }
  return new Date().getDate() <= 5
}

// 2026-04-22 小程序轻量 Markdown 内联解析：支持 **加粗** / *倾斜* / _倾斜_ / ***加粗倾斜***
//   - 不支持块级（标题、列表、代码块），AI 洞察文案为单段纯文本，足够覆盖
//   - 输出片段数组，供 WXML 通过 <text> 循环渲染不同样式
interface MarkdownSeg {
  text: string
  bold?: boolean
  italic?: boolean
}

function parseMarkdownInline(text: string): MarkdownSeg[] {
  if (!text) return []
  const segs: MarkdownSeg[] = []
  // 先匹配 ***x***（加粗倾斜）、再匹配 **x**（加粗）、再匹配 *x* / _x_（倾斜）、__x__（加粗）
  const re = /\*\*\*([^*]+)\*\*\*|\*\*([^*]+)\*\*|__([^_]+)__|\*([^*\s][^*]*)\*|_([^_\s][^_]*)_/g
  let last = 0
  let m: RegExpExecArray | null
  while ((m = re.exec(text)) !== null) {
    if (m.index > last) {
      segs.push({ text: text.slice(last, m.index) })
    }
    if (m[1] !== undefined) segs.push({ text: m[1], bold: true, italic: true })
    else if (m[2] !== undefined) segs.push({ text: m[2], bold: true })
    else if (m[3] !== undefined) segs.push({ text: m[3], bold: true })
    else if (m[4] !== undefined) segs.push({ text: m[4], italic: true })
    else if (m[5] !== undefined) segs.push({ text: m[5], italic: true })
    last = m.index + m[0].length
  }
  if (last < text.length) {
    segs.push({ text: text.slice(last) })
  }
  return segs.length ? segs : [{ text }]
}

interface TocItem {
  emoji: string
  title: string
  sectionId: string
}

const tipContents: Record<string, { title: string; content: string }> = {
  occurrence: { title: '发生额/正价', content: '所有消费项目按标价计算的总金额，不扣除任何优惠。\n\n即"如果没有任何折扣，客户应付多少"。' },
  discount: { title: '总优惠', content: '包含会员折扣、赠送卡抵扣、团购差价等所有优惠金额。\n\n优惠越高，实际收入越低。' },
  confirmed: { title: '成交/确认收入', content: '发生额减去总优惠后的实际入账金额。\n\n成交收入 = 发生额 - 总优惠' },
  cashIn: { title: '实收/现金流入', content: '实际到账的现金，包含消费直接支付和储值充值。\n\n往期为已结算金额，本期为截至当前的发生额。' },
  cashOut: { title: '现金支出', content: '包含人工、房租、水电、进货等所有经营支出。' },
  balance: { title: '现金结余', content: '现金流入减去现金支出。\n\n现金结余 = 现金流入 - 现金支出' },
  rechargeActual: { title: '储值卡充值实收', content: '会员储值卡首充和续费的实际到账金额。\n\n不含赠送金额。' },
  firstCharge: { title: '首充', content: '新会员首次充值的金额。' },
  renewCharge: { title: '续费', content: '老会员续费充值的金额。' },
  consume: { title: '消耗', content: '会员使用储值卡消费的金额。' },
  cardBalance: { title: '储值卡总余额', content: '所有储值卡的剩余可用余额。' },
  allCardBalance: { title: '全类别会员卡余额合计', content: '储值卡 + 赠送卡（酒水卡、台费卡、抵用券）的总余额。\n\n仅供经营参考，非财务属性。' },
}

Page({
  data: {
    pageState: 'normal' as 'loading' | 'empty' | 'error' | 'normal',
    aiColorClass: '',
    boardTabs: [] as Array<{ key: string; label: string; active: boolean }>,

    selectedTime: 'month',
    selectedTimeText: '本月',
    timeOptions: [
      { value: 'month', text: '本月' },
      { value: 'lastMonth', text: '上月' },
      { value: 'week', text: '本周' },
      { value: 'lastWeek', text: '上周' },
      { value: 'quarter3', text: '前3个月 不含本月' },
      { value: 'quarter', text: '本季度' },
      { value: 'lastQuarter', text: '上季度' },
      { value: 'half6', text: '最近6个月不含本月' },
    ],

    selectedArea: 'all',
    selectedAreaText: '全部区域',
    areaOptions: [
      { value: 'all', text: '全部区域' },
      { value: 'hall', text: '大厅' },
      { value: 'hallA', text: 'A区' },
      { value: 'hallB', text: 'B区' },
      { value: 'hallC', text: 'C区' },
      { value: 'vip', text: '台球包厢' },
      { value: 'snooker', text: '斯诺克' },
      { value: 'mahjong', text: '麻将房' },
      { value: 'ktv', text: '团建房' },
    ],

    compareEnabled: false,
    isCurrentMonth: true,
    // Wave 1 W1-T1 沙箱接入:业务时钟(YYYY-MM-DD),沙箱模式下回放虚拟日期。空字符串表示尚未拉取,降级用本地 new Date()
    businessDate: '',
    // 2026-04-22 改版：拆成 title/body 两段，title 在 wxml 渲染为 ai-insight-dim（灰色标题），body 为正文
    aiInsights: [] as Array<{ title: string; body: string }>,
    // 2026-04-22 seq11/12 置顶：从 aiInsights 派生，供"本期总结"卡片渲染
    aiInsightSummary: {
      evaluation: null as null | { title: string; body: string },
      tracking: null as null | { title: string; body: string },
    },
    aiInsightDetails: [] as Array<{ title: string; body: string }>,
    summaryLightType: '' as '' | 'green' | 'yellow' | 'red',
    summaryLightLabel: '',
    aiInsightsModalVisible: false,   // 查看全部弹窗可见性

    tocVisible: false,
    tocItems: [
      { emoji: '📈', title: '经营一览', sectionId: 'section-overview' },
      { emoji: '💳', title: '预收资产', sectionId: 'section-recharge' },
      { emoji: '💰', title: '应计收入确认', sectionId: 'section-revenue' },
      { emoji: '🧾', title: '现金流入', sectionId: 'section-cashflow' },
      { emoji: '📤', title: '现金流出', sectionId: 'section-expense' },
      { emoji: '🎱', title: '助教分析', sectionId: 'section-coach' },
    ] as TocItem[],
    currentSectionIndex: 0,

    stickyHeaderVisible: false,
    stickyHeaderEmoji: '',
    stickyHeaderTitle: '',
    stickyHeaderDesc: '',

    tipVisible: false,
    tipTitle: '',
    tipContent: '',

    /** 经营一览 */
    overview: {
      occurrence: 0 as any, occurrenceCompare: '', occurrenceDown: false, occurrenceFlat: false,
      discount: 0 as any, discountCompare: '', discountDown: false, discountFlat: false,
      discountRate: 0 as any, discountRateCompare: '', discountRateDown: false, discountRateFlat: false,
      confirmedRevenue: 0 as any, confirmedCompare: '', confirmedDown: false, confirmedFlat: false,
      cashIn: 0 as any, cashInCompare: '', cashInDown: false, cashInFlat: false,
      cashOut: 0 as any, cashOutCompare: '', cashOutDown: false, cashOutFlat: false,
      cashBalance: 0 as any, cashBalanceCompare: '', cashBalanceDown: false, cashBalanceFlat: false,
      balanceRate: 0 as any, balanceRateCompare: '', balanceRateDown: false, balanceRateFlat: false,
    },

    /** 预收资产 */
    recharge: {
      actualIncome: 0 as any, actualCompare: '', actualDown: false,
      firstCharge: 0 as any, firstChargeCompare: '', firstChargeDown: false,
      renewCharge: 0 as any, renewChargeCompare: '', renewChargeDown: false,
      consumed: 0 as any, consumedCompare: '', consumedDown: false,
      cardBalance: 0 as any, cardBalanceCompare: '', cardBalanceDown: false,
      allCardBalance: 0 as any, allCardBalanceCompare: '', allCardBalanceDown: false,
      giftRows: [] as Array<{
        label: string; total: any; totalCompare: string;
        wine: any; wineCompare: string;
        table: any; tableCompare: string;
        coupon: any; couponCompare: string;
      }>,
    },

    /** 应计收入确认 */
    revenue: {
      structureRows: [] as any[],
      priceItems: [] as any[],
      totalOccurrence: 0 as any,
      totalOccurrenceCompare: '',
      discountItems: [] as any[],
      discountTotal: 0,
      confirmedTotal: 0 as any,
      confirmedTotalCompare: '',
      channelItems: [] as any[],
    },

    /** 现金流入 */
    cashflow: {
      consumeItems: [] as any[],
      rechargeItems: [] as any[],
      total: 0 as any,
      totalCompare: '',
    },

    /** 现金流出 */
    expense: {
      operationItems: [] as any[],
      fixedItems: [] as any[],
      coachItems: [] as any[],
      platformItems: [] as any[],
      total: 0 as any,
      totalCompare: '',
      totalDown: false,
      totalFlat: false,
    },

    /** 助教分析 */
    coachAnalysis: {
      basic: {
        totalPay: 0 as any, totalPayCompare: '', totalPayDown: false,
        totalShare: 0 as any, totalShareCompare: '', totalShareDown: false,
        avgHourly: 0 as any, avgHourlyCompare: '', avgHourlyFlat: false,
        rows: [] as any[],
      },
      incentive: {
        totalPay: 0 as any, totalPayCompare: '', totalPayDown: false,
        totalShare: 0 as any, totalShareCompare: '', totalShareDown: false,
        avgHourly: 0 as any, avgHourlyCompare: '', avgHourlyFlat: false,
        rows: [] as any[],
      },
    },
  },

  onLoad() {
    const aiColor = getRandomAiColor()
    this.setData({ aiColorClass: aiColor.className })
  },

  onShow() {
    checkPageAccess('pages/board-finance/board-finance').then((allowed) => {
      if (!allowed) return
      const TAB_LABELS: Record<string, string> = { finance: '财务', customer: '客户', coach: '助教' }
      const visibleKeys = getVisibleBoardTabs()
      this.setData({
        boardTabs: visibleKeys.map(k => ({ key: k, label: TAB_LABELS[k] || k, active: k === 'finance' })),
      })
      const tabBar = this.getTabBar?.()
      if (tabBar) tabBar.setData({ active: 'board' })
      // Wave 1 W1-T1:刷新业务时钟,沙箱模式下回放虚拟日期 → 修正"本月前 5 天预估"提示
      getBusinessClock().then((clock) => {
        const businessDate = clock.business_date || ''
        this.setData({
          businessDate,
          isCurrentMonth: isCurrentMonthFilter(this.data.selectedTime, businessDate),
        })
      }).catch(() => {
        // 降级:保留默认 isCurrentMonth(本地时钟)
      })
      this._loadData()
    })
  },

  onReady() {
    this._cacheSectionPositions()
  },

  onPageScroll(e: { scrollTop: number }) {
    const now = Date.now()
    if (now - this._lastScrollTime < 100) return
    this._lastScrollTime = now

    const scrollTop = e.scrollTop
    const isScrollingDown = scrollTop > this._lastScrollTop
    this._lastScrollTop = scrollTop

    if (this._sectionTops.length === 0) return

    const offset = 100
    let currentIdx = 0
    for (let i = this._sectionTops.length - 1; i >= 0; i--) {
      if (scrollTop + offset >= this._sectionTops[i]) {
        currentIdx = i
        break
      }
    }

    if (scrollTop < 80) {
      if (this.data.stickyHeaderVisible) {
        this.setData({ stickyHeaderVisible: false })
      }
      return
    }

    const toc = this.data.tocItems[currentIdx]

    if (isScrollingDown && !this.data.stickyHeaderVisible) {
      this.setData({
        stickyHeaderVisible: true,
        stickyHeaderEmoji: toc?.emoji || '',
        stickyHeaderTitle: toc?.title || '',
        stickyHeaderDesc: toc ? (this._getSectionDesc(currentIdx) || '') : '',
        currentSectionIndex: currentIdx,
      })
    } else if (!isScrollingDown && this.data.stickyHeaderVisible) {
      this.setData({ stickyHeaderVisible: false })
    } else if (this.data.stickyHeaderVisible && currentIdx !== this.data.currentSectionIndex) {
      this.setData({
        stickyHeaderEmoji: toc?.emoji || '',
        stickyHeaderTitle: toc?.title || '',
        stickyHeaderDesc: toc ? (this._getSectionDesc(currentIdx) || '') : '',
        currentSectionIndex: currentIdx,
      })
    }
  },

  _sectionTops: [] as number[],
  _lastScrollTop: 0,
  _lastScrollTime: 0,

  _sectionDescs: [
    '快速了解收入与现金流的整体健康度',
    '会员卡充值与余额 掌握资金沉淀',
    '从发生额到入账收入的全流程',
    '实际到账的资金来源明细',
    '清晰呈现各类开销与结构',
    '全部助教服务收入与分成的平均值',
  ] as string[],

  _getSectionDesc(index: number): string {
    return this._sectionDescs[index] || ''
  },

  _cacheSectionPositions() {
    const sectionIds = this.data.tocItems.map(item => item.sectionId)
    const query = wx.createSelectorQuery().in(this)
    sectionIds.forEach(id => {
      query.select(`#${id}`).boundingClientRect()
    })
    query.exec((results: Array<WechatMiniprogram.BoundingClientRectCallbackResult | null>) => {
      if (!results) return
      this._sectionTops = results.map(r => (r ? r.top : 0))
    })
  },

  // CHANGE 2026-03-28 | 环比修复 | 绑定所有板块 is_down/is_flat，修复箭头方向硬编码问题
  async _loadData() {
    wx.showLoading({ title: '加载中' })
    this.setData({ pageState: 'loading' })
    try {
      const data = await fetchBoardFinance({
        time: this.data.selectedTime,
        area: this.data.selectedArea,
        compare: this.data.compareEnabled ? 1 : 0,
      })

      // 1. 经营一览
      const ov = data.overview || {} as any
      this.setData({
        'overview.occurrence': ov.occurrence ?? 0,
        'overview.discount': ov.discount ?? 0,
        'overview.discountRate': (ov.discountRate ?? 0) * 100,
        'overview.confirmedRevenue': ov.confirmedRevenue ?? 0,
        'overview.cashIn': ov.cashIn ?? 0,
        'overview.cashOut': ov.cashOut ?? 0,
        'overview.cashBalance': ov.cashBalance ?? 0,
        'overview.balanceRate': (ov.balanceRate ?? 0) * 100,
        'overview.occurrenceCompare': ov.occurrenceCompare || '',
        'overview.occurrenceDown': ov.occurrenceDown ?? false,
        'overview.occurrenceFlat': ov.occurrenceFlat ?? false,
        'overview.discountCompare': ov.discountCompare || '',
        'overview.discountDown': ov.discountDown ?? false,
        'overview.discountFlat': ov.discountFlat ?? false,
        'overview.discountRateCompare': ov.discountRateCompare || '',
        'overview.discountRateDown': ov.discountRateDown ?? false,
        'overview.discountRateFlat': ov.discountRateFlat ?? false,
        'overview.confirmedCompare': ov.confirmedRevenueCompare || '',
        'overview.confirmedDown': ov.confirmedRevenueDown ?? false,
        'overview.confirmedFlat': ov.confirmedRevenueFlat ?? false,
        'overview.cashInCompare': ov.cashInCompare || '',
        'overview.cashInDown': ov.cashInDown ?? false,
        'overview.cashInFlat': ov.cashInFlat ?? false,
        'overview.cashOutCompare': ov.cashOutCompare || '',
        'overview.cashOutDown': ov.cashOutDown ?? false,
        'overview.cashOutFlat': ov.cashOutFlat ?? false,
        'overview.cashBalanceCompare': ov.cashBalanceCompare || '',
        'overview.cashBalanceDown': ov.cashBalanceDown ?? false,
        'overview.cashBalanceFlat': ov.cashBalanceFlat ?? false,
        'overview.balanceRateCompare': ov.balanceRateCompare || '',
        'overview.balanceRateDown': ov.balanceRateDown ?? false,
        'overview.balanceRateFlat': ov.balanceRateFlat ?? false,
      })

      // 2. 预收资产（area=all 时后端才返回）
      if (data.recharge) {
        const rc = data.recharge as any
        const giftRows = (rc.giftRows || []).map((row: any) => ({
          label: row.label || '',
          total: row.total?.value ?? 0,
          totalCompare: row.total?.compare || '',
          wine: row.liquor?.value ?? 0,
          wineCompare: row.liquor?.compare || '',
          table: row.tableFee?.value ?? 0,
          tableCompare: row.tableFee?.compare || '',
          coupon: row.voucher?.value ?? 0,
          couponCompare: row.voucher?.compare || '',
        }))
        this.setData({
          'recharge.actualIncome': rc.actualIncome ?? 0,
          'recharge.firstCharge': rc.firstCharge ?? 0,
          'recharge.renewCharge': rc.renewCharge ?? 0,
          'recharge.consumed': rc.consumed ?? 0,
          'recharge.cardBalance': rc.cardBalance ?? 0,
          'recharge.allCardBalance': rc.allCardBalance ?? 0,
          'recharge.giftRows': giftRows,
          'recharge.actualCompare': rc.actualIncomeCompare || '',
          'recharge.actualDown': rc.actualIncomeDown ?? false,
          'recharge.firstChargeCompare': rc.firstChargeCompare || '',
          'recharge.firstChargeDown': rc.firstChargeDown ?? false,
          'recharge.renewChargeCompare': rc.renewChargeCompare || '',
          'recharge.renewChargeDown': rc.renewChargeDown ?? false,
          'recharge.consumedCompare': rc.consumedCompare || '',
          'recharge.consumedDown': rc.consumedDown ?? false,
          'recharge.cardBalanceCompare': rc.cardBalanceCompare || '',
          'recharge.cardBalanceDown': rc.cardBalanceDown ?? false,
          'recharge.allCardBalanceCompare': rc.allCardBalanceCompare || '',
          'recharge.allCardBalanceDown': rc.allCardBalanceDown ?? false,
        })
      }

      // 3. 应计收入确认
      const rv = data.revenue || {} as any
      const structureRows = (rv.structureRows || []).map((r: any) => ({
        id: r.id || '', name: r.name || '', desc: r.desc || '',
        isSub: r.isSub || false,
        amount: r.amount ?? 0, discount: r.discount ?? 0,
        booked: r.booked ?? 0, bookedCompare: r.bookedCompare || '',
      }))
      const priceItems = (rv.priceItems || []).map((r: any) => ({ name: r.label || '', value: r.amount ?? 0, compare: r.compare || '' }))
      const discountItems = (rv.discountItems || []).map((r: any) => ({ name: r.label || '', desc: r.desc || '', value: r.amount ?? 0, compare: r.compare || '' }))
      const channelItems = (rv.channelItems || []).map((r: any) => ({ name: r.label || '', desc: r.desc || '', value: r.amount ?? 0, compare: r.compare || '' }))
      this.setData({
        'revenue.structureRows': structureRows,
        'revenue.priceItems': priceItems,
        'revenue.totalOccurrence': rv.totalOccurrence ?? 0,
        // CHANGE 2026-03-28 | 环比字段映射
        'revenue.totalOccurrenceCompare': rv.totalOccurrenceCompare || '',
        'revenue.discountItems': discountItems,
        'revenue.discountTotal': rv.discountTotal ?? 0,
        'revenue.confirmedTotal': rv.confirmedTotal ?? 0,
        'revenue.confirmedTotalCompare': rv.confirmedTotalCompare || '',
        'revenue.channelItems': channelItems,
      })

      // 4. 现金流入
      const cf = data.cashflow || {} as any
      const consumeItems = (cf.consumeItems || []).map((r: any) => ({
        name: r.label || '', desc: r.desc || '', value: r.amount ?? 0, compare: r.compare || '', isDown: r.down ?? false,
      }))
      const rechargeItems = (cf.rechargeItems || []).map((r: any) => ({
        name: r.label || '', desc: r.desc || '', value: r.amount ?? 0, compare: r.compare || '',
      }))
      this.setData({
        'cashflow.consumeItems': consumeItems,
        'cashflow.rechargeItems': rechargeItems,
        'cashflow.total': cf.total ?? 0,
        // CHANGE 2026-03-28 | 环比字段映射
        'cashflow.totalCompare': cf.totalCompare || '',
      })

      // 5. 现金流出
      const ex = data.expense || {} as any
      const mapExpenseItems = (items: any[]) => (items || []).map((r: any) => ({
        name: r.label || '', value: r.amount ?? 0,
        compare: r.compare || '',
        isDown: r.down ?? false,
        isFlat: r.flat ?? false,
      }))
      this.setData({
        'expense.operationItems': mapExpenseItems(ex.operationItems),
        'expense.fixedItems': mapExpenseItems(ex.fixedItems),
        'expense.coachItems': mapExpenseItems(ex.coachItems),
        'expense.platformItems': mapExpenseItems(ex.platformItems),
        'expense.total': ex.total ?? 0,
        'expense.totalCompare': ex.totalCompare || '',
        'expense.totalDown': ex.totalDown ?? false,
        'expense.totalFlat': ex.totalFlat ?? false,
      })

      // 6. 助教分析
      const ca = data.coachAnalysis || {} as any
      const mapCoachTable = (table: any) => {
        if (!table) return { totalPay: 0, totalPayCompare: '', totalPayDown: false, totalShare: 0, totalShareCompare: '', totalShareDown: false, avgHourly: 0, avgHourlyCompare: '', avgHourlyFlat: false, rows: [] }
        return {
          totalPay: table.totalPay ?? 0,
          totalPayCompare: table.totalPayCompare || '',
          totalPayDown: table.totalPayDown ?? false,
          totalShare: table.totalShare ?? 0,
          totalShareCompare: table.totalShareCompare || '',
          totalShareDown: table.totalShareDown ?? false,
          avgHourly: table.avgHourly ?? 0,
          avgHourlyCompare: table.avgHourlyCompare || '',
          avgHourlyFlat: table.avgHourlyFlat ?? false,
          rows: (table.rows || []).map((r: any) => ({
            level: r.level || '',
            pay: r.pay ?? 0,
            payCompare: r.payCompare || '',
            payDown: r.payDown ?? false,
            share: r.share ?? 0,
            shareCompare: r.shareCompare || '',
            shareDown: r.shareDown ?? false,
            hourly: r.hourly ?? 0,
            hourlyCompare: r.hourlyCompare || '',
            hourlyFlat: r.hourlyFlat ?? false,
          })),
        }
      }
      this.setData({
        coachAnalysis: {
          basic: mapCoachTable(ca.basic),
          incentive: mapCoachTable(ca.incentive),
        },
      })

      // 后端不返回 aiInsights，仅在有值时覆盖（避免重载时清空旧缓存洞察）
      const dataToSet: Record<string, any> = { pageState: 'normal' }
      if (Array.isArray(data.aiInsights) && data.aiInsights.length > 0) {
        dataToSet.aiInsights = data.aiInsights
      }
      this.setData(dataToSet)

      // Phase 2.5：独立加载 AI 财务洞察缓存（不阻塞主流程）
      // 2026-04-21：缓存按 time__area 粒度预热（cron 每日 10:00 × 72 组合），按当前筛选读缓存
      this._loadAIInsights(this.data.selectedTime, this.data.selectedArea).catch(() => {})

    } catch (err) {
      console.error('[board-finance] 数据加载失败', err)
      this.setData({ pageState: 'error' })
      wx.showToast({ title: '数据加载失败', icon: 'none', duration: 2000 })
    } finally {
      wx.hideLoading()
    }
  },

  async _loadAIInsights(selectedTime: string, selectedArea: string) {
    // 2026-04-21：缓存粒度 time__area；后端 dispatcher._app2_target_id 同款拼装规则
    // 2026-04-23：按 area 切 cache_type（全域 app2_finance / 区域 app2a_finance_area）
    const TIME_MAP: Record<string, string> = {
      month: 'this_month', lastMonth: 'last_month',
      week: 'this_week', lastWeek: 'last_week',
      quarter: 'this_quarter', lastQuarter: 'last_quarter',
      quarter3: 'last_3_months', half6: 'last_6_months',
    }
    const timeKey = TIME_MAP[selectedTime]
    if (!timeKey) return
    const areaKey = selectedArea || 'all'
    const targetId = `${timeKey}__${areaKey}`
    const cacheType = areaKey === 'all' ? 'app2_finance' : 'app2a_finance_area'
    // 切换筛选前先清空当前展示的洞察，避免 cache miss 时仍显示上个区域的陈旧数据
    this.setData({
      aiInsights: [],
      aiInsightSummary: { evaluation: null, tracking: null },
      aiInsightDetails: [],
      summaryLightType: '',
      summaryLightLabel: '',
    })
    const cache = await fetchAICache(cacheType, targetId)
    if (!cache?.result_json) return
    const rj = cache.result_json as any
    // 2026-04-22：忠于 demo 两段式渲染（dim 标题 + 白色正文），拆分 title/body 存到 data
    // 百炼返回字段 content（兜底 body）；seq 字段用于 _extractSummary 精确定位 seq11/12
    const insights = Array.isArray(rj.insights)
      ? rj.insights.map((item: any, idx: number) => {
          const title = (item.title || '').trim()
          const body = (item.content || item.body || '').trim()
          const seq = Number(item.seq) || (idx + 1)
          return {
            seq,
            title,
            body,
            titleSegs: parseMarkdownInline(title),
            bodySegs: parseMarkdownInline(body),
          }
        }).filter((i: { title: string; body: string }) => i.title.length > 0 || i.body.length > 0)
      : []
    if (insights.length === 0) return
    // 2026-04-22 seq11/12 置顶：识别"本期总结"（健康度评级 + 跟踪指标）
    //   - 优先按 seq 字段精确匹配（seq=11 健康度 · seq=12 跟踪指标）
    //   - 失败时回退到"末两条启发式"，兼容旧缓存或输出顺序错位
    const summary = this._extractSummary(insights)
    this.setData({
      aiInsights: insights,
      aiInsightSummary: summary.summary,
      aiInsightDetails: summary.details,
      summaryLightType: summary.lightType,
      summaryLightLabel: summary.lightLabel,
    })
  },

  /**
   * 识别 seq11(健康度) + seq12(跟踪指标)，并解析三色灯类型。
   * 规则：
   *   1. 优先按 item.seq === 11 / 12 精确匹配
   *   2. 找不到时回退"末两条启发式"（数组长度 ≥ 4）
   *   3. 数组过短直接降级为全量明细无总结
   */
  _extractSummary(insights: Array<{ seq?: number; title: string; body: string }>) {
    const empty = {
      summary: { evaluation: null, tracking: null },
      details: insights,
      lightType: '' as '' | 'green' | 'yellow' | 'red',
      lightLabel: '',
    }
    if (insights.length < 4) return empty

    // 优先按 seq 精确匹配
    let evaluation = insights.find(i => i.seq === 11) || null
    let tracking = insights.find(i => i.seq === 12) || null
    let details: typeof insights

    if (evaluation && tracking) {
      // seq 精确匹配成功 → 过滤掉 seq 11/12
      details = insights.filter(i => i.seq !== 11 && i.seq !== 12)
    } else {
      // 回退：末两条启发式
      evaluation = insights[insights.length - 2]
      tracking = insights[insights.length - 1]
      details = insights.slice(0, insights.length - 2)
    }
    // 三色灯识别：优先匹配 emoji，其次匹配文案
    const text = `${evaluation.title} ${evaluation.body}`
    let lightType: '' | 'green' | 'yellow' | 'red' = ''
    let lightLabel = ''
    if (/🔴|红灯|警告/.test(text)) {
      lightType = 'red'
      lightLabel = '🔴 红灯警告'
    } else if (/🟡|黄灯|观察/.test(text)) {
      lightType = 'yellow'
      lightLabel = '🟡 黄灯观察'
    } else if (/🟢|绿灯|健康/.test(text)) {
      lightType = 'green'
      lightLabel = '🟢 绿灯健康'
    }
    return {
      summary: { evaluation, tracking },
      details,
      lightType,
      lightLabel,
    }
  },

  openAllInsights() {
    this.setData({ aiInsightsModalVisible: true })
  },

  closeAllInsights() {
    this.setData({ aiInsightsModalVisible: false })
  },

  /** 弹窗遮罩点击穿透阻断（避免滚动到 page 底） */
  _noop() {},

  onPullDownRefresh() {
    this._loadData()
    setTimeout(() => wx.stopPullDownRefresh(), 500)
  },

  // CHANGE 2026-03-29 | P1：redirectTo 替代 navigateTo
  onTabChange(e: WechatMiniprogram.TouchEvent) {
    const tab = e.currentTarget.dataset.tab as string
    if (tab === 'finance') return
    const routes: Record<string, { url: string; isTab: boolean }> = {
      finance: { url: '/pages/board-finance/board-finance', isTab: true },
      customer: { url: '/pages/board-customer/board-customer', isTab: false },
      coach: { url: '/pages/board-coach/board-coach', isTab: false },
    }
    const route = routes[tab]
    if (!route) return
    if (route.isTab) { wx.switchTab({ url: route.url }) } else { wx.redirectTo({ url: route.url }) }
  },

  onTimeChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) {
    const value = e.detail.value
    const option = this.data.timeOptions.find(o => o.value === value)
    this.setData({ selectedTime: value, selectedTimeText: option?.text || '本月', isCurrentMonth: isCurrentMonthFilter(value, this.data.businessDate) })
    this._loadData()
  },

  onAreaChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) {
    const value = e.detail.value
    const option = this.data.areaOptions.find(o => o.value === value)
    this.setData({ selectedArea: value, selectedAreaText: option?.text || '全部区域' })
    this._loadData()
    setTimeout(() => this._cacheSectionPositions(), 300)
  },

  toggleCompare() {
    this.setData({ compareEnabled: !this.data.compareEnabled })
    this._loadData()
  },

  toggleToc() { this.setData({ tocVisible: !this.data.tocVisible }) },
  closeToc() { this.setData({ tocVisible: false }) },

  onTocItemTap(e: WechatMiniprogram.TouchEvent) {
    const index = e.currentTarget.dataset.index as number
    const sectionId = this.data.tocItems[index]?.sectionId
    if (sectionId) {
      this.setData({ tocVisible: false, currentSectionIndex: index })
      wx.createSelectorQuery().in(this).select(`#${sectionId}`).boundingClientRect((rect) => {
        if (rect) wx.pageScrollTo({ scrollTop: rect.top + (this._lastScrollTop || 0) - 140, duration: 300 })
      }).exec()
    }
  },

  onHelpTap(e: WechatMiniprogram.TouchEvent) {
    const key = e.currentTarget.dataset.key as string
    const tip = tipContents[key]
    if (tip) this.setData({ tipVisible: true, tipTitle: tip.title, tipContent: tip.content })
  },

  closeTip() { this.setData({ tipVisible: false }) },

  onRetry() {
    this.setData({ pageState: 'normal' })
    this._loadData()
  },
})
