<template>
    <div class="cs-page">
        <header class="cs-masthead">
            <div class="cs-masthead__inner">
                <div class="cs-masthead__main">
                    <span class="cs-masthead__eyebrow">
                        <span class="cs-masthead__dot"></span>
                        财务结算 · 业绩台账
                    </span>
                    <h1 class="cs-masthead__title">提成结算业绩</h1>
                    <p class="cs-masthead__desc">
                        订单池默认与<strong>处方订单列表</strong>同源：<strong>创建时间 start_time～end_time</strong> +
                        <strong>fulfillment_status</strong>（默认完成态 3），账号<strong>列表可见性／数据域</strong>一并生效。
                        选结算月<strong>仍可</strong>套用「物流签收<strong>与</strong>尾款支付<strong>均须在</strong><strong>结算月 7 日 24 点</strong>前」拆分「本期提成 / 顺延下期」
                        （仅有签收在范围内、尾款晚于截止仍会顺延）；
                        展示部门／渠道下拉与列表不同，勾选后会与列表<strong>计数不同</strong>。
                    </p>
                </div>
                <dl v-if="meta.settlement_month || meta.cutoff_end" class="cs-masthead__stats">
                    <div class="cs-stat">
                        <dt class="cs-stat__label">结算月</dt>
                        <dd class="cs-stat__value">{{ meta.settlement_month || settlementMonth }}</dd>
                    </div>
                    <div v-if="meta.order_month || meta.order_start_time" class="cs-stat">
                        <dt class="cs-stat__label">订单时间窗</dt>
                        <dd class="cs-stat__value cs-stat__value--xs">{{ orderWindowLabel }}</dd>
                    </div>
                    <div v-if="meta.cutoff_end" class="cs-stat">
                        <dt class="cs-stat__label">截止时刻</dt>
                        <dd class="cs-stat__value cs-stat__value--xs">{{ meta.cutoff_end }}</dd>
                    </div>
                </dl>
            </div>
        </header>

        <el-card class="cs-panel filter-card" shadow="never">
            <div class="filter-surface">
                <div class="filter-row filter-row--top">
                    <div class="filter-field">
                        <span class="lbl">结算月</span>
                        <el-date-picker
                            v-model="settlementMonth"
                            type="month"
                            value-format="YYYY-MM"
                            placeholder="选择结算月"
                            class="filter-month"
                        />
                    </div>
                    <div class="filter-field filter-field--rangewrap">
                        <span class="lbl">订单创建</span>
                        <el-date-picker
                            v-model="orderTimeRange"
                            type="datetimerange"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            range-separator="至"
                            start-placeholder="列表 start_time"
                            end-placeholder="列表 end_time"
                            class="filter-datetimerange"
                        />
                        <el-checkbox v-model="requireSystemAutoOnly" class="filter-sa-check">
                            仅系统代开处方
                        </el-checkbox>
                    </div>
                    <div class="filter-field">
                        <span class="lbl">渠道来源</span>
                        <el-select
                            v-model="selectedChannel"
                            filterable
                            clearable
                            placeholder="全部"
                            class="filter-select filter-select--channel"
                            :loading="channelOptionsLoading"
                            @change="loadOverview"
                        >
                            <el-option label="全部" value="" />
                            <el-option-group
                                v-for="g in channelGroups"
                                :key="g.group_name"
                                :label="g.group_name"
                            >
                                <el-option
                                    v-for="ch in g.channels"
                                    :key="ch.channel_code"
                                    :label="ch.channel_name"
                                    :value="ch.channel_code"
                                />
                            </el-option-group>
                        </el-select>
                    </div>
                    <div class="filter-field filter-field--wide">
                        <span class="lbl">展示部门</span>
                        <div
                            class="filter-select filter-select--dept filter-dept-tree-wrap"
                            v-loading="deptOptionsLoading"
                        >
                            <el-tree-select
                                v-model="selectedDeptIds"
                                :data="deptTreeOptions"
                                multiple
                                collapse-tags
                                collapse-tags-tooltip
                                filterable
                                check-strictly
                                :render-after-expand="false"
                                :default-expand-all="true"
                                placeholder="默认：所有「中心」部门"
                                class="filter-dept-tree-select"
                                node-key="id"
                                :props="deptTreeSelectProps"
                            />
                        </div>
                    </div>
                    <div class="filter-actions">
                        <el-button
                            type="primary"
                            class="filter-btn filter-btn--query"
                            :loading="loading"
                            @click="loadOverview"
                        >
                            <el-icon class="filter-btn__icon"><Search /></el-icon>
                            查询
                        </el-button>
                        <el-button class="filter-btn filter-btn--reset" :disabled="loading" @click="handleReset">
                            <el-icon class="filter-btn__icon"><RefreshRight /></el-icon>
                            重置
                        </el-button>
                    </div>
                </div>
            </div>
        </el-card>

        <el-alert type="info" :closable="false" show-icon class="cs-alert">
            <template #title>
                <span class="cs-alert__title">统计口径</span>
            </template>
            <div class="cs-alert__body">
                <p v-if="meta.rule_note">{{ meta.rule_note }}</p>
                <p v-else>
                    请选择结算月并设置<strong>订单创建时间范围</strong>（与处方订单列表 URL 参数
                    <code>start_time</code>/<code>end_time</code>/<code>fulfillment_status</code>
                    一致）；切换结算月会自动带出<strong>上一自然月整段</strong>作为默认时段，可按业务修改（例如 2026-04-02～04-30）。
                    「仅系统代开处方」勾选后与列表在未限制开方类型时的条数会不一致。
                </p>
                <p v-if="meta.cutoff_end" class="cs-alert__meta">
                    当前结算月：<strong>{{ meta.settlement_month }}</strong>
                    · 订单时间：<strong>{{ orderWindowLabel }}</strong>
                    · 履约状态：<strong>{{ meta.fulfillment_status ?? 3 }}</strong>
                    · 截止时刻：<strong>{{ meta.cutoff_end }}</strong>
                    <template v-if="meta.channel_name">
                        · 渠道：<strong>{{ meta.channel_name }}</strong>
                    </template>
                    <template v-if="meta.settlement_month">
                        · 处方池：<strong>{{
                            meta.require_system_auto_prescription ? '仅系统代开' : '含手动与系统'
                        }}</strong>
                        <template v-if="meta.explicit_order_time_window">
                            · 列表对齐：<strong>是</strong></template>
                    </template>
                </p>
            </div>
        </el-alert>

        <el-card class="cs-panel cs-reconcile-card" shadow="never">
            <div class="cs-reconcile">
                <div class="cs-reconcile__head">
                    <div class="cs-reconcile__tags">
                        <el-tag v-if="settlementConfirmedLocked" type="success" effect="dark">已确定业绩</el-tag>
                        <el-tag v-else-if="confirmInfo?.status === 2" type="warning" effect="plain">已撤回确定</el-tag>
                        <el-tag v-else type="info" effect="plain">待确定业绩</el-tag>
                        <span
                            v-if="settlementConfirmedLocked && confirmInfo?.confirmed_at_text"
                            class="cs-reconcile__meta"
                        >
                            {{ confirmInfo.confirmed_admin_name || '—' }} · {{ confirmInfo.confirmed_at_text }}
                        </span>
                        <span
                            v-else-if="confirmInfo?.status === 2 && confirmInfo.revoked_at_text"
                            class="cs-reconcile__meta"
                        >
                            撤回：{{ confirmInfo.revoked_admin_name || '—' }} · {{ confirmInfo.revoked_at_text }}
                        </span>
                    </div>
                    <div class="cs-reconcile__toolbar">
                        <el-button type="primary" plain @click="openReconcileDialog">业绩明细核对</el-button>
                        <el-button :disabled="settlementConfirmedLocked || reconcileSaving" @click="handleSaveReconcile">
                            保存核对备注
                        </el-button>
                        <el-button
                            type="warning"
                            :disabled="settlementConfirmedLocked || confirmSubmitting"
                            @click="handleConfirmFinalize"
                        >
                            确定本期业绩
                        </el-button>
                        <el-button
                            v-if="settlementConfirmedLocked"
                            type="danger"
                            plain
                            :loading="confirmRevoking"
                            @click="handleConfirmRevoke"
                        >
                            撤回确定
                        </el-button>
                    </div>
                </div>
                <el-input
                    v-model="reconcileNote"
                    type="textarea"
                    :rows="2"
                    maxlength="500"
                    show-word-limit
                    placeholder="核对备注（可与财务线下对齐后填写；确定业绩前可反复保存）"
                    :disabled="settlementConfirmedLocked"
                    class="cs-reconcile__note"
                />
                <p v-if="Number(totalAgg.carry_in_order_count) > 0" class="cs-reconcile__carry">
                    上期顺延汇入本期：<strong>{{ totalAgg.carry_in_order_count }}</strong> 单（已并入当前汇总）
                </p>
            </div>
        </el-card>

        <el-card class="cs-panel cs-summary-card" shadow="never">
            <el-tabs v-model="summaryTab" class="cs-summary-tabs">
                <el-tab-pane label="部门汇总" name="dept">
                    <div class="cs-table-head">
                        <div class="cs-table-head__row">
                            <span class="cs-table-head__title">部门汇总</span>
                            <div v-if="drilledFromDept" class="cs-drill-state">
                                <el-tag size="small" type="info" effect="plain" class="cs-drill-state__tag">
                                    当前下钻：{{ drilledFromDept.path || drilledFromDept.name }}
                                </el-tag>
                                <el-button link type="primary" class="cs-drill-state__back" @click="handleDrillUp">
                                    <el-icon><ArrowUp /></el-icon>
                                    {{ drillUpLabel }}
                                </el-button>
                            </div>
                        </div>
                        <span class="cs-table-head__hint">
                            金额单位：元；与业绩看板相同的部门摊列规则（业绩归属医助 → 展示「中心」）。
                            <strong>点击部门</strong>：若有子部门则下钻一层；末级部门弹出「已完成业绩订单」明细。
                        </span>
                    </div>
                    <el-table
                        v-loading="loading"
                        :data="rows"
                        border
                        stripe
                        size="small"
                        class="cs-table cs-table--clickable"
                        show-summary
                        :summary-method="getSummaries"
                    >
                        <el-table-column prop="dept_name" label="部门" min-width="220" fixed>
                            <template #default="{ row }">
                                <el-link
                                    v-if="row.dept_id > 0"
                                    type="primary"
                                    :underline="false"
                                    class="cs-dept-link"
                                    @click="handleDeptRowClick(row)"
                                >
                                    <span class="cs-dept-link__text">{{ row.dept_name }}</span>
                                    <el-icon class="cs-dept-link__icon">
                                        <ArrowRight v-if="deptHasChildren(row.dept_id)" />
                                        <Tickets v-else />
                                    </el-icon>
                                </el-link>
                                <span v-else class="cs-muted">{{ row.dept_name }}</span>
                            </template>
                        </el-table-column>
                        <el-table-column prop="completed_order_count" label="已完成单数" min-width="104" align="right" />
                        <el-table-column prop="completed_order_amount" label="已完成金额" min-width="112" align="right">
                            <template #default="{ row }">{{ formatMoney(row.completed_order_amount) }}</template>
                        </el-table-column>
                        <el-table-column prop="current_period_count" label="本期提成单数" min-width="118" align="right" />
                        <el-table-column prop="current_period_amount" label="本期提成金额" min-width="118" align="right">
                            <template #default="{ row }">{{ formatMoney(row.current_period_amount) }}</template>
                        </el-table-column>
                        <el-table-column prop="deferred_period_count" label="顺延下期单数" min-width="118" align="right" />
                        <el-table-column prop="deferred_period_amount" label="顺延下期金额" min-width="118" align="right">
                            <template #default="{ row }">{{ formatMoney(row.deferred_period_amount) }}</template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>
                <el-tab-pane label="按医助下发" name="assistant">
                    <div class="cs-table-head">
                        <span class="cs-table-head__title">业绩归属医助 × 挂号渠道</span>
                        <span class="cs-table-head__hint">
                            按诊单业绩归属医助 <strong>×</strong> 挂号渠道（doctor_appointment.channels）拆分到行；
                            <strong>「复诊2」「复诊3」「自媒体1」等保持各自独立</strong>，未关联挂号统一归到「未匹配挂号渠道」。
                            同一医助多行<strong>自动合并「医助 / 归属中心」单元格</strong>，「顺延汇入」为上期结转进入本期订单池的单数。
                            <strong>展示部门</strong>有勾选时，本表仅列出<strong>可归入该部门上下文</strong>的业绩医助（与人事部门落在所选部门范围内的账号一致）。
                            <strong>点击挂号渠道</strong>：查看该行对应的业务订单明细（与上方部门筛选一致）。
                        </span>
                    </div>
                    <el-table
                        v-loading="loading"
                        :data="assistantChannelRows"
                        border
                        stripe
                        size="small"
                        class="cs-table cs-table--grouped"
                        show-summary
                        :summary-method="getPersonSummaries"
                        :span-method="assistantSpanMethod"
                    >
                        <el-table-column prop="assistant_name" label="医助" min-width="120" fixed show-overflow-tooltip />
                        <el-table-column prop="primary_dept_name" label="归属中心" min-width="120" show-overflow-tooltip />
                        <el-table-column prop="appt_channel_name" label="挂号渠道" min-width="120" show-overflow-tooltip>
                            <template #default="{ row }">
                                <el-tag
                                    size="small"
                                    class="cs-channel-tag cs-channel-tag--clickable"
                                    :class="channelTagClass(row.appt_channel_name)"
                                    effect="plain"
                                    @click.stop="openOrdersForAssistantChannel(row)"
                                >
                                    {{ row.appt_channel_name }}
                                </el-tag>
                            </template>
                        </el-table-column>
                        <el-table-column prop="carry_in_order_count" label="顺延汇入单数" min-width="112" align="right" />
                        <el-table-column prop="completed_order_count" label="已完成单数" min-width="104" align="right" />
                        <el-table-column prop="completed_order_amount" label="已完成金额" min-width="112" align="right">
                            <template #default="{ row }">{{ formatMoney(row.completed_order_amount) }}</template>
                        </el-table-column>
                        <el-table-column prop="current_period_count" label="本期提成单数" min-width="118" align="right" />
                        <el-table-column prop="current_period_amount" label="本期提成金额" min-width="118" align="right">
                            <template #default="{ row }">{{ formatMoney(row.current_period_amount) }}</template>
                        </el-table-column>
                        <el-table-column prop="deferred_period_count" label="顺延下期单数" min-width="118" align="right" />
                        <el-table-column prop="deferred_period_amount" label="顺延下期金额" min-width="118" align="right">
                            <template #default="{ row }">{{ formatMoney(row.deferred_period_amount) }}</template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>
                <el-tab-pane label="按医生下发" name="doctor">
                    <div class="cs-table-head">
                        <span class="cs-table-head__title">处方开方医生 × 挂号渠道</span>
                        <span class="cs-table-head__hint">
                            按处方创建账号（开方人） <strong>×</strong> 挂号渠道拆分；
                            可与医助维度交叉核对；同一医生多行自动合并「医生 / 归属中心」单元格。
                            <strong>展示部门</strong>有勾选时，与医助表相同，仅统计<strong>该部门上下文内</strong>订单对应的开方医生。
                            <strong>点击挂号渠道</strong>：查看该行对应的业务订单明细。
                        </span>
                    </div>
                    <el-table
                        v-loading="loading"
                        :data="doctorChannelRows"
                        border
                        stripe
                        size="small"
                        class="cs-table cs-table--grouped"
                        show-summary
                        :summary-method="getPersonSummaries"
                        :span-method="doctorSpanMethod"
                    >
                        <el-table-column prop="doctor_name" label="医生" min-width="120" fixed show-overflow-tooltip />
                        <el-table-column prop="primary_dept_name" label="归属中心" min-width="120" show-overflow-tooltip />
                        <el-table-column prop="appt_channel_name" label="挂号渠道" min-width="120" show-overflow-tooltip>
                            <template #default="{ row }">
                                <el-tag
                                    size="small"
                                    class="cs-channel-tag cs-channel-tag--clickable"
                                    :class="channelTagClass(row.appt_channel_name)"
                                    effect="plain"
                                    @click.stop="openOrdersForDoctorChannel(row)"
                                >
                                    {{ row.appt_channel_name }}
                                </el-tag>
                            </template>
                        </el-table-column>
                        <el-table-column prop="carry_in_order_count" label="顺延汇入单数" min-width="112" align="right" />
                        <el-table-column prop="completed_order_count" label="已完成单数" min-width="104" align="right" />
                        <el-table-column prop="completed_order_amount" label="已完成金额" min-width="112" align="right">
                            <template #default="{ row }">{{ formatMoney(row.completed_order_amount) }}</template>
                        </el-table-column>
                        <el-table-column prop="current_period_count" label="本期提成单数" min-width="118" align="right" />
                        <el-table-column prop="current_period_amount" label="本期提成金额" min-width="118" align="right">
                            <template #default="{ row }">{{ formatMoney(row.current_period_amount) }}</template>
                        </el-table-column>
                        <el-table-column prop="deferred_period_count" label="顺延下期单数" min-width="118" align="right" />
                        <el-table-column prop="deferred_period_amount" label="顺延下期金额" min-width="118" align="right">
                            <template #default="{ row }">{{ formatMoney(row.deferred_period_amount) }}</template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>
            </el-tabs>
        </el-card>

        <el-dialog
            v-model="reconcileDialogVisible"
            title="业绩明细核对"
            width="1280px"
            destroy-on-close
            class="cs-dialog"
            @open="onReconcileDialogOpen"
        >
            <div class="cs-dialog__bar">
                <el-radio-group v-model="linesBucket" size="small" @change="loadOrderLines(1)">
                    <el-radio-button label="all">全部</el-radio-button>
                    <el-radio-button label="current">本期提成</el-radio-button>
                    <el-radio-button label="deferred">顺延下期</el-radio-button>
                </el-radio-group>
            </div>
            <el-table v-loading="linesLoading" :data="linesRows" border stripe size="small" max-height="440">
                <el-table-column prop="order_no" label="业务订单号" min-width="128" show-overflow-tooltip />
                <el-table-column label="患者" min-width="112" show-overflow-tooltip>
                    <template #default="{ row }">
                        <div class="cs-patient-cell">
                            <span class="cs-patient-cell__name">{{ row.patient_name || '—' }}</span>
                            <span v-if="row.patient_phone || row.patient_gender_text" class="cs-patient-cell__meta cs-muted">
                                <template v-if="row.patient_gender_text">{{ row.patient_gender_text }}</template>
                                <template v-if="row.patient_gender_text && row.patient_phone"> · </template>
                                <template v-if="row.patient_phone">{{ row.patient_phone }}</template>
                            </span>
                        </div>
                    </template>
                </el-table-column>
                <el-table-column label="快递单号" min-width="132" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.tracking_number || '—' }}</template>
                </el-table-column>
                <el-table-column label="挂号日期" min-width="124" show-overflow-tooltip>
                    <template #default="{ row }">
                        <span>{{ row.appointment_date_text || '—' }}</span>
                        <el-tag
                            v-if="row.appointment_status && Number(row.appointment_status) !== 3"
                            class="cs-tag-appt-muted"
                            type="info"
                            size="small"
                            effect="plain"
                            >未完结挂号</el-tag
                        >
                    </template>
                </el-table-column>
                <el-table-column label="挂号渠道" min-width="124" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.appt_channel_name || '—' }}</template>
                </el-table-column>
                <el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
                <el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
                <el-table-column prop="primary_dept_name" label="归属部门" min-width="100" show-overflow-tooltip />
                <el-table-column prop="amount" label="金额" width="108" align="right">
                    <template #default="{ row }">{{ formatMoney(row.amount) }}</template>
                </el-table-column>
                <el-table-column label="分段" width="88" align="center">
                    <template #default="{ row }">
                        <el-tag v-if="row.bucket === 'current'" type="success" size="small">本期</el-tag>
                        <el-tag v-else type="warning" size="small">顺延</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="结转" width="72" align="center">
                    <template #default="{ row }">
                        <span v-if="row.is_carry_in">是</span>
                        <span v-else class="cs-muted">—</span>
                    </template>
                </el-table-column>
                <el-table-column label="顺延说明" min-width="200" show-overflow-tooltip>
                    <template #default="{ row }">
                        <span v-if="row.bucket === 'deferred' && row.bucket_defer_text" class="cs-muted">{{
                            row.bucket_defer_text
                        }}</span>
                        <span v-else class="cs-muted">—</span>
                    </template>
                </el-table-column>
                <el-table-column label="物流签收" min-width="152" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.sign_time_text || '—' }}</template>
                </el-table-column>
                <el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.pay_time_text || '—' }}</template>
                </el-table-column>
                <el-table-column prop="create_time_text" label="订单创建" width="152" />
            </el-table>
            <div class="cs-dialog__pager">
                <el-pagination
                    layout="total, prev, pager, next"
                    :total="linesTotal"
                    :page-size="linesPageSize"
                    :current-page="linesPage"
                    @current-change="loadOrderLines"
                />
            </div>
        </el-dialog>

        <el-dialog
            v-model="leafOrdersVisible"
            :title="leafOrdersDialogTitle"
            width="1280px"
            destroy-on-close
            class="cs-dialog"
        >
            <div class="cs-dialog__bar cs-dialog__bar--split">
                <el-radio-group v-model="leafOrdersBucket" size="small" @change="loadLeafOrders(1)">
                    <el-radio-button label="all">全部已完成</el-radio-button>
                    <el-radio-button label="current">本期提成</el-radio-button>
                    <el-radio-button label="deferred">顺延下期</el-radio-button>
                </el-radio-group>
                <span class="cs-dialog__hint">
                    结算月：<strong>{{ meta.settlement_month || settlementMonth }}</strong>
                    <template v-if="orderWindowLabel && orderWindowLabel !== '—'"
                        > · 订单创建：<strong>{{ orderWindowLabel }}</strong></template
                    >
                    <template v-if="meta.channel_name"> · 渠道：<strong>{{ meta.channel_name }}</strong></template>
                </span>
            </div>
            <el-table v-loading="leafOrdersLoading" :data="leafOrdersRows" border stripe size="small" max-height="440">
                <el-table-column prop="order_no" label="业务订单号" min-width="128" show-overflow-tooltip />
                <el-table-column label="患者" min-width="112" show-overflow-tooltip>
                    <template #default="{ row }">
                        <div class="cs-patient-cell">
                            <span class="cs-patient-cell__name">{{ row.patient_name || '—' }}</span>
                            <span v-if="row.patient_phone || row.patient_gender_text" class="cs-patient-cell__meta cs-muted">
                                <template v-if="row.patient_gender_text">{{ row.patient_gender_text }}</template>
                                <template v-if="row.patient_gender_text && row.patient_phone"> · </template>
                                <template v-if="row.patient_phone">{{ row.patient_phone }}</template>
                            </span>
                        </div>
                    </template>
                </el-table-column>
                <el-table-column label="快递单号" min-width="132" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.tracking_number || '—' }}</template>
                </el-table-column>
                <el-table-column label="挂号日期" min-width="124" show-overflow-tooltip>
                    <template #default="{ row }">
                        <span>{{ row.appointment_date_text || '—' }}</span>
                        <el-tag
                            v-if="row.appointment_status && Number(row.appointment_status) !== 3"
                            class="cs-tag-appt-muted"
                            type="info"
                            size="small"
                            effect="plain"
                            >未完结挂号</el-tag
                        >
                    </template>
                </el-table-column>
                <el-table-column label="挂号渠道" min-width="124" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.appt_channel_name || '—' }}</template>
                </el-table-column>
                <el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
                <el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
                <el-table-column prop="primary_dept_name" label="归属部门" min-width="120" show-overflow-tooltip />
                <el-table-column prop="amount" label="金额" width="108" align="right">
                    <template #default="{ row }">{{ formatMoney(row.amount) }}</template>
                </el-table-column>
                <el-table-column label="分段" width="88" align="center">
                    <template #default="{ row }">
                        <el-tag v-if="row.bucket === 'current'" type="success" size="small">本期</el-tag>
                        <el-tag v-else type="warning" size="small">顺延</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="结转" width="72" align="center">
                    <template #default="{ row }">
                        <span v-if="row.is_carry_in">是</span>
                        <span v-else class="cs-muted">—</span>
                    </template>
                </el-table-column>
                <el-table-column label="顺延说明" min-width="200" show-overflow-tooltip>
                    <template #default="{ row }">
                        <span v-if="row.bucket === 'deferred' && row.bucket_defer_text" class="cs-muted">{{
                            row.bucket_defer_text
                        }}</span>
                        <span v-else class="cs-muted">—</span>
                    </template>
                </el-table-column>
                <el-table-column label="物流签收" min-width="152" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.sign_time_text || '—' }}</template>
                </el-table-column>
                <el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip>
                    <template #default="{ row }">{{ row.pay_time_text || '—' }}</template>
                </el-table-column>
                <el-table-column prop="create_time_text" label="订单创建" width="152" />
            </el-table>
            <div class="cs-dialog__pager">
                <el-pagination
                    layout="total, prev, pager, next"
                    :total="leafOrdersTotal"
                    :page-size="leafOrdersPageSize"
                    :current-page="leafOrdersPage"
                    @current-change="loadLeafOrders"
                />
            </div>
        </el-dialog>
    </div>
</template>

<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { ArrowRight, ArrowUp, RefreshRight, Search, Tickets } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
    commissionSettlementChannelOptions,
    commissionSettlementConfirmFinalize,
    commissionSettlementConfirmRevoke,
    commissionSettlementDeptOptions,
    commissionSettlementOrderLines,
    commissionSettlementOverview,
    commissionSettlementSaveReconcile,
} from '@/api/stats'

interface DeptOption {
    id: number
    name: string
    pid: number
    path: string
}

interface DeptTreeNode extends DeptOption {
    children?: DeptTreeNode[]
}

const deptTreeSelectProps = {
    value: 'id',
    label: 'name',
    children: 'children',
}

function buildDeptTreeFromFlat(flat: DeptOption[]): DeptTreeNode[] {
    if (!flat.length) return []
    const map = new Map<number, DeptTreeNode>()
    for (const d of flat) {
        map.set(d.id, { ...d, children: [] })
    }
    const roots: DeptTreeNode[] = []
    for (const d of flat) {
        const node = map.get(d.id)!
        const pid = Number(d.pid)
        if (pid > 0 && map.has(pid)) {
            map.get(pid)!.children!.push(node)
        } else {
            roots.push(node)
        }
    }
    const stripEmptyChildren = (n: DeptTreeNode) => {
        if (n.children?.length) {
            n.children.forEach(stripEmptyChildren)
        } else {
            delete n.children
        }
    }
    roots.forEach(stripEmptyChildren)
    const sortLevel = (nodes: DeptTreeNode[]) => {
        nodes.sort((a, b) => {
            const ac = a.name.includes('中心') ? -1 : 1
            const bc = b.name.includes('中心') ? -1 : 1
            if (ac !== bc) return ac - bc
            return a.id - b.id
        })
        for (const n of nodes) {
            if (n.children?.length) sortLevel(n.children)
        }
    }
    sortLevel(roots)
    return roots
}

interface ChannelOption {
    channel_code: string
    channel_name: string
    source_tag_id: string
    source_group_name: string
    customer_count: number
}

interface ChannelGroup {
    group_name: string
    channels: ChannelOption[]
}

interface CsRow {
    dept_id: number
    dept_name: string
    completed_order_count: number
    completed_order_amount: number
    current_period_count: number
    current_period_amount: number
    deferred_period_count: number
    deferred_period_amount: number
}

interface CsAssistRow {
    assistant_id: number
    assistant_name: string
    primary_dept_id: number
    primary_dept_name: string
    carry_in_order_count: number
    completed_order_count: number
    completed_order_amount: number
    current_period_count: number
    current_period_amount: number
    deferred_period_count: number
    deferred_period_amount: number
}

interface CsDoctorRow {
    doctor_id: number
    doctor_name: string
    primary_dept_id: number
    primary_dept_name: string
    carry_in_order_count: number
    completed_order_count: number
    completed_order_amount: number
    current_period_count: number
    current_period_amount: number
    deferred_period_count: number
    deferred_period_amount: number
}

type LeafOrderDrillDept = { mode: 'dept'; dept_id: number; dept_name: string }
type LeafOrderDrillChannel = {
    mode: 'channel'
    title: string
    assistant_id?: number
    doctor_id?: number
    appt_channel_value: number
}
type LeafOrderDrill = LeafOrderDrillDept | LeafOrderDrillChannel

interface CsAssistChannelRow extends CsAssistRow {
    appt_channel_value: number
    appt_channel_name: string
}

interface CsDoctorChannelRow extends CsDoctorRow {
    appt_channel_value: number
    appt_channel_name: string
}

type CsPersonMetricRow = CsAssistRow | CsDoctorRow | CsAssistChannelRow | CsDoctorChannelRow

function defaultSettlementMonth(): string {
    const d = new Date()
    const y = d.getFullYear()
    const m = String(d.getMonth() + 1).padStart(2, '0')
    return `${y}-${m}`
}

function pad2(n: number): string {
    return String(n).padStart(2, '0')
}

function formatDateTime(d: Date): string {
    return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
}

/** 订单池默认：结算月「上一自然月」整段（与未传 start/end 的后端行为一致） */
function defaultOrderTimeRangeForSettlementMonth(sm: string): [string, string] {
    const m = /^(\d{4})-(\d{2})$/.exec(sm)
    const y = m ? parseInt(m[1], 10) : new Date().getFullYear()
    const monthNum = m ? parseInt(m[2], 10) : new Date().getMonth() + 1
    const start = new Date(y, monthNum - 2, 1, 0, 0, 0)
    const end = new Date(y, monthNum - 1, 0, 23, 59, 59)
    return [formatDateTime(start), formatDateTime(end)]
}

const settlementMonth = ref<string>(defaultSettlementMonth())
const orderTimeRange = ref<[string, string] | null>(defaultOrderTimeRangeForSettlementMonth(settlementMonth.value))
const requireSystemAutoOnly = ref(false)
const loading = ref(false)
const deptOptions = ref<DeptOption[]>([])
const deptOptionsLoading = ref(false)
const deptTreeOptions = computed(() => buildDeptTreeFromFlat(deptOptions.value))
const selectedDeptIds = ref<number[]>([])
const channelGroups = ref<ChannelGroup[]>([])
const channelOptionsLoading = ref(false)
const selectedChannel = ref<string>('')

const rows = ref<CsRow[]>([])
const assistantRows = ref<CsAssistRow[]>([])
const doctorRows = ref<CsDoctorRow[]>([])
const assistantChannelRows = ref<CsAssistChannelRow[]>([])
const doctorChannelRows = ref<CsDoctorChannelRow[]>([])
const summaryTab = ref<'dept' | 'assistant' | 'doctor'>('dept')
const totalAgg = ref<{
    completed_order_count?: number
    completed_order_amount?: number
    current_period_count?: number
    current_period_amount?: number
    deferred_period_count?: number
    deferred_period_amount?: number
    carry_in_order_count?: number
}>({})
const meta = reactive({
    settlement_month: '',
    order_month: '',
    order_start_time: '',
    order_end_time: '',
    fulfillment_status: 3,
    explicit_order_time_window: false,
    cutoff_end: '',
    rule_note: '',
    channel_name: '',
    require_system_auto_prescription: false,
})

const confirmInfo = ref<{
    status?: number
    reconcile_note?: string
    confirmed_admin_name?: string
    confirmed_at_text?: string
    revoked_admin_name?: string
    revoked_at_text?: string
    is_confirmed_locked?: boolean
} | null>(null)
const reconcileNote = ref('')
const reconcileSaving = ref(false)
const confirmSubmitting = ref(false)
const confirmRevoking = ref(false)

/** 仅 status=1 时锁定，禁止保存备注 / 重复确定；status=2 已撤回可再操作 */
const settlementConfirmedLocked = computed(() => Number(confirmInfo.value?.status) === 1)

const reconcileDialogVisible = ref(false)
const linesLoading = ref(false)
const linesBucket = ref<string>('all')
const linesRows = ref<Record<string, any>[]>([])
const linesTotal = ref(0)
const linesPage = ref(1)
const linesPageSize = ref(20)

const leafOrdersVisible = ref(false)
const leafOrderDrill = ref<LeafOrderDrill | null>(null)
const leafOrdersBucket = ref<string>('all')
const leafOrdersRows = ref<Record<string, any>[]>([])
const leafOrdersTotal = ref(0)
const leafOrdersPage = ref(1)
const leafOrdersPageSize = ref(20)
const leafOrdersLoading = ref(false)

const leafOrdersDialogTitle = computed(() => {
    const d = leafOrderDrill.value
    if (!d) return '已完成业绩订单'
    if (d.mode === 'dept') return `已完成业绩订单 · ${d.dept_name}`
    return d.title
})

const orderWindowLabel = computed(() => {
    if (meta.order_start_time && meta.order_end_time) {
        return `${meta.order_start_time}～${meta.order_end_time}`
    }
    return meta.order_month || '—'
})

const deptChildrenMap = computed(() => {
    const map = new Map<number, number[]>()
    for (const d of deptOptions.value) {
        const pid = Number(d.pid)
        const arr = map.get(pid) ?? []
        arr.push(d.id)
        map.set(pid, arr)
    }
    return map
})

const deptByIdMap = computed(() => {
    const map = new Map<number, DeptOption>()
    for (const d of deptOptions.value) {
        map.set(d.id, d)
    }
    return map
})

const drilledFromDept = computed<DeptOption | null>(() => {
    if (selectedDeptIds.value.length !== 1) return null
    const id = selectedDeptIds.value[0]
    const node = deptByIdMap.value.get(id)
    return node ?? null
})

const drillUpLabel = computed<string>(() => {
    const cur = drilledFromDept.value
    if (!cur) return '返回顶层'
    const parentId = Number(cur.pid)
    const parent = parentId > 0 ? deptByIdMap.value.get(parentId) : null
    if (!parent || Number(parent.pid) <= 0) return '返回顶层'
    return `返回上级 · ${parent.name}`
})

function deptHasChildren(deptId: number): boolean {
    if (!deptId || deptId <= 0) return false
    const arr = deptChildrenMap.value.get(deptId)
    return !!(arr && arr.length > 0)
}

function handleDeptRowClick(row: CsRow) {
    if (!row || !row.dept_id || row.dept_id <= 0) return
    if (deptHasChildren(row.dept_id)) {
        selectedDeptIds.value = [row.dept_id]
        loadOverview()
        return
    }
    openOrderLinesForDept(row.dept_id, row.dept_name)
}

function handleDrillUp() {
    const cur = drilledFromDept.value
    if (!cur) {
        selectedDeptIds.value = []
        loadOverview()
        return
    }
    const parentId = Number(cur.pid)
    const parent = parentId > 0 ? deptByIdMap.value.get(parentId) : null
    /** 父级为根（pid<=0）或在视图里没有实际意义时直接回到「默认中心视图」，避免一步跳到「全部部门」 */
    if (!parent || Number(parent.pid) <= 0) {
        selectedDeptIds.value = []
    } else {
        selectedDeptIds.value = [parentId]
    }
    loadOverview()
}

function openOrderLinesForDept(deptId: number, deptName: string) {
    if (!settlementMonth.value) {
        ElMessage.warning('请选择结算月')
        return
    }
    leafOrderDrill.value = { mode: 'dept', dept_id: deptId, dept_name: deptName }
    leafOrdersBucket.value = 'all'
    leafOrdersPage.value = 1
    leafOrdersRows.value = []
    leafOrdersTotal.value = 0
    leafOrdersVisible.value = true
    loadLeafOrders(1)
}

function openOrdersForAssistantChannel(row: CsAssistChannelRow) {
    if (!settlementMonth.value) {
        ElMessage.warning('请选择结算月')
        return
    }
    leafOrderDrill.value = {
        mode: 'channel',
        title: `业务订单 · ${row.appt_channel_name} · 医助 ${row.assistant_name}`,
        assistant_id: row.assistant_id,
        appt_channel_value: Number(row.appt_channel_value ?? 0),
    }
    leafOrdersBucket.value = 'all'
    leafOrdersPage.value = 1
    leafOrdersRows.value = []
    leafOrdersTotal.value = 0
    leafOrdersVisible.value = true
    loadLeafOrders(1)
}

function openOrdersForDoctorChannel(row: CsDoctorChannelRow) {
    if (!settlementMonth.value) {
        ElMessage.warning('请选择结算月')
        return
    }
    leafOrderDrill.value = {
        mode: 'channel',
        title: `业务订单 · ${row.appt_channel_name} · 医生 ${row.doctor_name}`,
        doctor_id: row.doctor_id,
        appt_channel_value: Number(row.appt_channel_value ?? 0),
    }
    leafOrdersBucket.value = 'all'
    leafOrdersPage.value = 1
    leafOrdersRows.value = []
    leafOrdersTotal.value = 0
    leafOrdersVisible.value = true
    loadLeafOrders(1)
}

async function loadLeafOrders(page: number) {
    if (!settlementMonth.value || !leafOrderDrill.value) return
    leafOrdersLoading.value = true
    leafOrdersPage.value = page
    try {
        const d = leafOrderDrill.value
        const p: Record<string, any> = {
            settlement_month: settlementMonth.value,
            page,
            page_size: leafOrdersPageSize.value,
        }
        if (d.mode === 'dept') {
            Object.assign(p, buildQueryParams())
            p.table_row_dept_id = d.dept_id
        } else {
            Object.assign(p, buildQueryParams())
            p.appt_channel_value = d.appt_channel_value
            if (d.assistant_id != null) {
                p.assistant_id = d.assistant_id
            }
            if (d.doctor_id != null) {
                p.doctor_id = d.doctor_id
            }
        }
        if (leafOrdersBucket.value && leafOrdersBucket.value !== 'all') {
            p.bucket = leafOrdersBucket.value
        }
        const res: any = await commissionSettlementOrderLines(p as any)
        leafOrdersRows.value = res?.list || []
        leafOrdersTotal.value = Number(res?.count ?? 0)
    } catch (e: any) {
        ElMessage.error(e?.msg || e?.message || '加载明细失败')
        leafOrdersRows.value = []
        leafOrdersTotal.value = 0
    } finally {
        leafOrdersLoading.value = false
    }
}

function formatMoney(v: number | null | undefined) {
    if (v === null || v === undefined || !Number.isFinite(Number(v))) return '—'
    return `¥ ${Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}

async function loadDeptOptions() {
    deptOptionsLoading.value = true
    try {
        const res: DeptOption[] = await commissionSettlementDeptOptions()
        deptOptions.value = (res || []).sort((a, b) => {
            const aIsCenter = a.name.includes('中心') ? -1 : 1
            const bIsCenter = b.name.includes('中心') ? -1 : 1
            if (aIsCenter !== bIsCenter) return aIsCenter - bIsCenter
            return a.id - b.id
        })
    } catch (_e) {
        // ignore
    } finally {
        deptOptionsLoading.value = false
    }
}

async function loadChannelOptions() {
    channelOptionsLoading.value = true
    try {
        const res: any = await commissionSettlementChannelOptions()
        channelGroups.value = res?.groups || []
    } catch (_e) {
        // ignore
    } finally {
        channelOptionsLoading.value = false
    }
}

async function loadOverview() {
    const sm = settlementMonth.value
    if (!sm) {
        ElMessage.warning('请选择结算月')
        return
    }
    const ot = orderTimeRange.value
    if (!ot || !ot[0] || !ot[1]) {
        ElMessage.warning('请设置订单创建时间范围')
        return
    }
    loading.value = true
    try {
        const res: any = await commissionSettlementOverview(buildQueryParams() as any)
        rows.value = res?.rows || []
        assistantRows.value = res?.assistant_rows || []
        doctorRows.value = res?.doctor_rows || []
        assistantChannelRows.value = res?.assistant_channel_rows || []
        doctorChannelRows.value = res?.doctor_channel_rows || []
        totalAgg.value = res?.total || {}
        meta.settlement_month = res?.settlement_month || sm
        meta.order_month = res?.order_month || ''
        meta.order_start_time = res?.order_start_time ? String(res.order_start_time) : ''
        meta.order_end_time = res?.order_end_time ? String(res.order_end_time) : ''
        meta.fulfillment_status =
            res?.fulfillment_status != null ? Number(res.fulfillment_status) : 3
        meta.explicit_order_time_window =
            typeof res?.explicit_order_time_window === 'boolean'
                ? res.explicit_order_time_window
                : !!(meta.order_start_time && meta.order_end_time)
        meta.cutoff_end = res?.cutoff_end || ''
        meta.rule_note = res?.rule_note || ''
        meta.channel_name = res?.channel_name || ''
        meta.require_system_auto_prescription =
            typeof res?.require_system_auto_prescription === 'boolean'
                ? res.require_system_auto_prescription
                : false
        confirmInfo.value = res?.confirm ?? null
        reconcileNote.value =
            res?.confirm?.reconcile_note != null ? String(res.confirm.reconcile_note) : ''
    } catch (e: any) {
        ElMessage.error(e?.msg || e?.message || '加载失败')
        rows.value = []
        assistantRows.value = []
        doctorRows.value = []
        assistantChannelRows.value = []
        doctorChannelRows.value = []
        totalAgg.value = {}
        confirmInfo.value = null
    } finally {
        loading.value = false
    }
}

function buildQueryParams(): Record<string, any> {
    const sm = settlementMonth.value
    const p: Record<string, any> = {
        settlement_month: sm,
        fulfillment_status: 3,
    }
    const ot = orderTimeRange.value
    if (ot && ot[0] && ot[1]) {
        p.start_time = ot[0]
        p.end_time = ot[1]
    }
    if (requireSystemAutoOnly.value) {
        p.require_system_auto_prescription = 1
    }
    if (selectedDeptIds.value.length > 0) {
        p.dept_ids = selectedDeptIds.value.join(',')
    }
    if (selectedChannel.value) {
        p.channel_code = selectedChannel.value
    }
    return p
}

function openReconcileDialog() {
    if (!settlementMonth.value) {
        ElMessage.warning('请选择结算月')
        return
    }
    reconcileDialogVisible.value = true
}

function onReconcileDialogOpen() {
    linesBucket.value = 'all'
    loadOrderLines(1)
}

async function loadOrderLines(page: number) {
    if (!settlementMonth.value) return
    linesLoading.value = true
    linesPage.value = page
    try {
        const p: Record<string, any> = {
            ...buildQueryParams(),
            page,
            page_size: linesPageSize.value,
        }
        if (linesBucket.value && linesBucket.value !== 'all') {
            p.bucket = linesBucket.value
        }
        const res: any = await commissionSettlementOrderLines(p as any)
        linesRows.value = res?.list || []
        linesTotal.value = Number(res?.count ?? 0)
    } catch (e: any) {
        ElMessage.error(e?.msg || e?.message || '加载明细失败')
        linesRows.value = []
        linesTotal.value = 0
    } finally {
        linesLoading.value = false
    }
}

async function handleSaveReconcile() {
    if (!settlementMonth.value) {
        ElMessage.warning('请选择结算月')
        return
    }
    if (settlementConfirmedLocked.value) {
        ElMessage.warning('已确定业绩不可修改备注；请先撤回确定')
        return
    }
    reconcileSaving.value = true
    try {
        await commissionSettlementSaveReconcile({
            ...buildQueryParams(),
            reconcile_note: reconcileNote.value,
        })
        ElMessage.success('已保存核对备注')
        await loadOverview()
    } catch (e: any) {
        ElMessage.error(e?.msg || e?.message || '保存失败')
    } finally {
        reconcileSaving.value = false
    }
}

async function handleConfirmFinalize() {
    if (!settlementMonth.value) {
        ElMessage.warning('请选择结算月')
        return
    }
    try {
        await ElMessageBox.confirm(
            '确定后将为「顺延下期」订单写入结转记录，下一结算月在相同渠道与部门筛选下自动并入统计。确定后仍可「撤回确定」以清除结转并重新核对（与财务对齐后再操作）。',
            '确定本期业绩',
            {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning',
            }
        )
    } catch {
        return
    }
    confirmSubmitting.value = true
    try {
        const out: any = await commissionSettlementConfirmFinalize({
            ...buildQueryParams(),
            reconcile_note: reconcileNote.value,
        })
        ElMessage.success(
            `已确定本期业绩；结转 ${Number(out?.deferred_carry_count ?? 0)} 单至 ${String(out?.next_settlement_month ?? '下期')}`
        )
        await loadOverview()
    } catch (e: any) {
        ElMessage.error(e?.msg || e?.message || '提交失败')
    } finally {
        confirmSubmitting.value = false
    }
}

async function handleConfirmRevoke() {
    if (!settlementMonth.value) {
        ElMessage.warning('请选择结算月')
        return
    }
    try {
        await ElMessageBox.confirm(
            '将撤回本次「确定业绩」状态，并删除已写入的顺延结转记录（下期将不再汇入这些单）。确定后继续？',
            '撤回确定业绩',
            {
                confirmButtonText: '撤回',
                cancelButtonText: '取消',
                type: 'warning',
            }
        )
    } catch {
        return
    }
    confirmRevoking.value = true
    try {
        await commissionSettlementConfirmRevoke(buildQueryParams() as any)
        ElMessage.success('已撤回确定；可重新保存备注并再次确定本期业绩')
        await loadOverview()
    } catch (e: any) {
        ElMessage.error(e?.msg || e?.message || '撤回失败')
    } finally {
        confirmRevoking.value = false
    }
}

function handleReset() {
    settlementMonth.value = defaultSettlementMonth()
    orderTimeRange.value = defaultOrderTimeRangeForSettlementMonth(settlementMonth.value)
    requireSystemAutoOnly.value = false
    selectedDeptIds.value = []
    selectedChannel.value = ''
    loadOverview()
}

function getPersonSummaries(param: { columns: Array<{ property?: string }>; data: CsPersonMetricRow[] }) {
    const { columns } = param
    const t = totalAgg.value
    const sums: string[] = []
    columns.forEach((column, index: number) => {
        if (index === 0) {
            sums[index] = '合计'
            return
        }
        const prop = column.property as string | undefined
        if (!t || !prop) {
            sums[index] = ''
            return
        }
        if (prop === 'primary_dept_name' || prop === 'appt_channel_name') {
            sums[index] = ''
            return
        }
        if (prop === 'carry_in_order_count') {
            sums[index] = String(t.carry_in_order_count ?? 0)
            return
        }
        const map: Record<string, string> = {
            completed_order_count: String(t.completed_order_count ?? 0),
            completed_order_amount: formatMoney(t.completed_order_amount),
            current_period_count: String(t.current_period_count ?? 0),
            current_period_amount: formatMoney(t.current_period_amount),
            deferred_period_count: String(t.deferred_period_count ?? 0),
            deferred_period_amount: formatMoney(t.deferred_period_amount),
        }
        sums[index] = map[String(prop)] ?? ''
    })
    return sums
}

/**
 * 合并「医助 / 归属中心」列：同一人的连续行只在首行显示一次。
 * 列索引：0=医助/医生、1=归属中心 合并；其余列（含 2=挂号渠道）保持独立。
 */
function makeGroupedSpanMethod<K extends 'assistant_id' | 'doctor_id'>(
    rowsRef: { value: Array<Record<string, any>> },
    idKey: K,
) {
    return (ctx: { row: any; rowIndex: number; column: any; columnIndex: number }):
        | { rowspan: number; colspan: number }
        | undefined => {
        const { rowIndex, columnIndex } = ctx
        if (columnIndex > 1) return undefined
        const list = rowsRef.value
        if (!list || list.length === 0) return undefined
        const cur = list[rowIndex]
        if (!cur) return undefined
        const curId = cur[idKey]
        const prev = rowIndex > 0 ? list[rowIndex - 1] : null
        if (prev && prev[idKey] === curId) {
            return { rowspan: 0, colspan: 0 }
        }
        let span = 1
        for (let i = rowIndex + 1; i < list.length; i++) {
            if (list[i][idKey] === curId) {
                span++
            } else {
                break
            }
        }
        return { rowspan: span, colspan: 1 }
    }
}

const assistantSpanMethod = makeGroupedSpanMethod(assistantChannelRows as unknown as { value: Array<Record<string, any>> }, 'assistant_id')
const doctorSpanMethod = makeGroupedSpanMethod(doctorChannelRows as unknown as { value: Array<Record<string, any>> }, 'doctor_id')

/** 挂号渠道标签着色：复诊系列 -> teal；自媒体系列 -> indigo；转介绍 -> amber；未匹配 -> 灰 */
function channelTagClass(name: string | undefined | null): string {
    const n = (name || '').trim()
    if (!n) return 'cs-channel-tag--muted'
    if (n.startsWith('复诊')) return 'cs-channel-tag--revisit'
    if (n.startsWith('自媒体')) return 'cs-channel-tag--media'
    if (n.includes('转介绍')) return 'cs-channel-tag--referral'
    if (n.startsWith('未匹配')) return 'cs-channel-tag--muted'
    return 'cs-channel-tag--neutral'
}

function getSummaries(param: { columns: Array<{ property?: string }>; data: CsRow[] }) {
    const { columns } = param
    const t = totalAgg.value
    const sums: string[] = []
    columns.forEach((column, index: number) => {
        if (index === 0) {
            sums[index] = '合计'
            return
        }
        const prop = column.property as keyof CsRow | undefined
        if (!t || !prop) {
            sums[index] = ''
            return
        }
        const map: Record<string, string> = {
            completed_order_count: String(t.completed_order_count ?? 0),
            completed_order_amount: formatMoney(t.completed_order_amount),
            current_period_count: String(t.current_period_count ?? 0),
            current_period_amount: formatMoney(t.current_period_amount),
            deferred_period_count: String(t.deferred_period_count ?? 0),
            deferred_period_amount: formatMoney(t.deferred_period_amount),
        }
        sums[index] = map[String(prop)] ?? ''
    })
    return sums
}

watch(settlementMonth, (v) => {
    if (v) {
        orderTimeRange.value = defaultOrderTimeRangeForSettlementMonth(v)
    }
})

onMounted(async () => {
    await Promise.all([loadDeptOptions(), loadChannelOptions()])
    await loadOverview()
})
</script>

<style lang="scss" scoped>
.cs-page {
    min-width: 0;

    /* Brand / accent */
    --cs-brand: #0d9488;
    --cs-brand-strong: #0f766e;
    --cs-brand-soft: #f0fdfa;
    --cs-brand-ring: rgba(13, 148, 136, 0.18);
    --cs-accent: #7c3aed;

    /* Semantic */
    --cs-success: #10b981;
    --cs-warning: #f59e0b;
    --cs-warning-soft: #fffbeb;
    --cs-danger: #ef4444;

    /* Text */
    --cs-ink: #0f172a;
    --cs-text: #334155;
    --cs-muted: #64748b;
    --cs-faint: #94a3b8;

    /* Surface */
    --cs-line: #e2e8f0;
    --cs-line-soft: #eef2f6;
    --cs-surface: #ffffff;
    --cs-surface-alt: #f8fafc;
    --cs-page: #f5f7fa;

    /* Effects */
    --cs-shadow-sm: 0 1px 2px 0 rgba(15, 23, 42, 0.04);
    --cs-shadow-md: 0 6px 20px -6px rgba(15, 23, 42, 0.12), 0 2px 6px -2px rgba(15, 23, 42, 0.06);
    --cs-radius: 14px;
    --cs-radius-sm: 10px;
    --cs-radius-xs: 6px;

    min-height: 100%;
    padding: 20px 24px 32px;
    color: var(--cs-text);
    background: var(--cs-page);
    font-feature-settings: 'tnum' 1;
}

/* ============ Masthead ============ */
.cs-masthead {
    margin-bottom: 16px;
    &__inner {
        position: relative;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 20px;
        padding: 22px 28px;
        background:
            radial-gradient(120% 160% at 0% 0%, var(--cs-brand-soft) 0%, rgba(255, 255, 255, 0) 55%),
            radial-gradient(80% 120% at 100% 100%, #f5f3ff 0%, rgba(255, 255, 255, 0) 60%),
            #ffffff;
        border: 1px solid var(--cs-line);
        border-radius: var(--cs-radius);
        box-shadow: var(--cs-shadow-sm);
        overflow: hidden;
        &::before {
            content: '';
            position: absolute;
            inset: 0 auto 0 0;
            width: 3px;
            background: linear-gradient(180deg, var(--cs-brand) 0%, var(--cs-accent) 100%);
        }
    }
    &__main {
        min-width: 0;
        flex: 1 1 420px;
    }
    &__eyebrow {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        padding: 4px 10px 4px 8px;
        margin-bottom: 10px;
        font-size: 11px;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--cs-brand-strong);
        background: var(--cs-brand-soft);
        border: 1px solid #99f6e4;
        border-radius: 999px;
    }
    &__dot {
        width: 6px;
        height: 6px;
        border-radius: 50%;
        background: var(--cs-brand);
        box-shadow: 0 0 0 3px var(--cs-brand-ring);
    }
    &__title {
        margin: 0;
        font-size: 22px;
        font-weight: 700;
        color: var(--cs-ink);
        line-height: 1.25;
        letter-spacing: -0.01em;
    }
    &__desc {
        margin: 8px 0 0;
        max-width: 880px;
        font-size: 13px;
        color: var(--cs-muted);
        line-height: 1.65;
    }
    &__stats {
        display: flex;
        flex-wrap: wrap;
        gap: 0;
        margin: 0;
        padding: 4px 0;
        background: rgba(255, 255, 255, 0.7);
        border: 1px solid var(--cs-line);
        border-radius: var(--cs-radius-sm);
        backdrop-filter: blur(4px);
    }
}

.cs-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 18px;
    border-right: 1px dashed var(--cs-line);
    &:last-child { border-right: none; }
    &__label {
        margin: 0;
        font-size: 11px;
        font-weight: 600;
        color: var(--cs-muted);
        letter-spacing: 0.04em;
        text-transform: uppercase;
    }
    &__value {
        margin: 0;
        font-size: 15px;
        font-weight: 700;
        color: var(--cs-ink);
        line-height: 1.2;
        font-variant-numeric: tabular-nums;
        &--xs { font-size: 13px; }
    }
}

/* ============ Generic panel ============ */
.cs-panel {
    margin-bottom: 14px;
    border-radius: var(--cs-radius);
    border: 1px solid var(--cs-line);
    background: var(--cs-surface);
    box-shadow: var(--cs-shadow-sm);
    :deep(.el-card__body) { padding: 0; }
}

/* ============ Filter ============ */
.filter-card { overflow: hidden; }
.filter-surface {
    padding: 16px 20px;
    background: var(--cs-surface);
}
.filter-row--top {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 14px 18px;
}
.filter-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    .lbl {
        font-size: 11px;
        font-weight: 700;
        color: var(--cs-muted);
        letter-spacing: 0.06em;
        text-transform: uppercase;
    }
}
.filter-field--wide {
    flex: 1 1 280px;
    min-width: 240px;
}
.filter-field--rangewrap {
    flex: 1 1 440px;
    min-width: 300px;
}
.filter-month { width: 168px; }
.filter-datetimerange {
    width: 100%;
    max-width: 520px;
}
.filter-sa-check {
    margin-top: 6px;
    font-weight: 500;
    color: var(--cs-text);
}
.cs-tag-appt-muted {
    margin-left: 6px;
    vertical-align: middle;
}
.filter-select--channel { width: 220px; }
.filter-dept-tree-select { width: 100%; min-width: 240px; }
.filter-actions {
    display: flex;
    gap: 8px;
    margin-left: auto;
}
.filter-btn {
    padding: 8px 18px;
    font-weight: 600;
    font-size: 13px;
    border-radius: var(--cs-radius-sm);
    transition: all 0.18s ease;
    &__icon {
        margin-right: 4px;
        vertical-align: -2px;
    }
    &--query {
        background: var(--cs-brand);
        color: #fff;
        border: 1px solid var(--cs-brand);
        box-shadow: 0 1px 2px rgba(15, 118, 110, 0.2);
        &:hover,
        &:focus {
            background: var(--cs-brand-strong);
            border-color: var(--cs-brand-strong);
        }
    }
    &--reset {
        background: var(--cs-surface);
        border: 1px solid var(--cs-line);
        color: var(--cs-text);
        &:hover {
            border-color: var(--cs-brand);
            color: var(--cs-brand-strong);
            background: var(--cs-brand-soft);
        }
    }
}

/* ============ Alert ============ */
.cs-alert {
    margin-bottom: 14px;
    border-radius: var(--cs-radius-sm);
    border: 1px solid var(--cs-line);
    background: var(--cs-surface);
    padding: 12px 16px 12px 16px;
    :deep(.el-alert__icon) {
        color: var(--cs-brand);
        font-size: 18px;
    }
    &__title {
        font-weight: 700;
        font-size: 13px;
        color: var(--cs-ink);
    }
    &__body {
        font-size: 13px;
        line-height: 1.7;
        color: var(--cs-muted);
        p { margin: 0 0 6px; }
        p:last-child { margin-bottom: 0; }
        strong { color: var(--cs-ink); font-weight: 600; }
    }
    &__meta {
        margin-top: 10px !important;
        padding-top: 10px;
        border-top: 1px dashed var(--cs-line);
        font-size: 12px;
        color: var(--cs-text);
        strong {
            color: var(--cs-brand-strong);
            font-weight: 700;
        }
    }
}

/* ============ Reconcile ============ */
.cs-reconcile-card {
    :deep(.el-card__body) { padding: 14px 20px 16px; }
}
.cs-reconcile {
    display: flex;
    flex-direction: column;
    gap: 12px;
    &__head {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
    }
    &__tags {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 10px;
        :deep(.el-tag) {
            border-radius: 999px;
            font-weight: 600;
            padding: 0 10px;
            height: 24px;
            line-height: 22px;
        }
    }
    &__meta {
        font-size: 12px;
        color: var(--cs-muted);
    }
    &__toolbar {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        align-items: center;
    }
    &__note {
        width: 100%;
        max-width: 720px;
    }
    &__carry {
        margin: 0;
        padding: 8px 12px;
        font-size: 12px;
        color: var(--cs-text);
        line-height: 1.5;
        background: var(--cs-brand-soft);
        border-left: 3px solid var(--cs-brand);
        border-radius: var(--cs-radius-xs);
        strong { color: var(--cs-brand-strong); font-weight: 700; }
    }
}

/* ============ Summary tabs ============ */
.cs-summary-card {
    :deep(.el-tabs__header) {
        margin: 0;
        padding: 0 20px;
        border-bottom: 1px solid var(--cs-line);
    }
    :deep(.el-tabs__nav-wrap::after) { height: 0; }
    :deep(.el-tabs__item) {
        height: 48px;
        font-size: 13px;
        font-weight: 600;
        color: var(--cs-muted);
        padding: 0 16px;
        transition: color 0.15s ease;
        &:hover { color: var(--cs-ink); }
        &.is-active { color: var(--cs-brand-strong); }
    }
    :deep(.el-tabs__active-bar) {
        background-color: var(--cs-brand);
        height: 2px;
        border-radius: 2px;
    }
    :deep(.el-tabs__content) { padding: 0; }
}

/* ============ Table head ============ */
.cs-table-head {
    padding: 14px 20px 12px;
    background: var(--cs-surface);
    border-bottom: 1px solid var(--cs-line);
    &__row {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
    }
    &__title {
        position: relative;
        padding-left: 12px;
        font-size: 15px;
        font-weight: 700;
        color: var(--cs-ink);
        letter-spacing: -0.005em;
        &::before {
            content: '';
            position: absolute;
            left: 0;
            top: 50%;
            transform: translateY(-50%);
            width: 3px;
            height: 14px;
            background: var(--cs-brand);
            border-radius: 2px;
        }
    }
    &__hint {
        display: block;
        margin-top: 8px;
        font-size: 12px;
        color: var(--cs-muted);
        line-height: 1.65;
        strong { color: var(--cs-ink); font-weight: 600; }
    }
}

/* ============ Drill state ============ */
.cs-drill-state {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 4px 3px 10px;
    background: var(--cs-brand-soft);
    border: 1px solid #99f6e4;
    border-radius: 999px;
    &__tag {
        max-width: 320px;
        background: transparent !important;
        border: none !important;
        color: var(--cs-brand-strong) !important;
        font-weight: 600 !important;
        font-size: 12px !important;
        padding: 0 !important;
        height: auto !important;
        :deep(.el-tag__content) {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
    }
    &__back {
        display: inline-flex !important;
        align-items: center;
        gap: 3px;
        padding: 4px 10px !important;
        font-size: 12px !important;
        font-weight: 600 !important;
        color: var(--cs-brand-strong) !important;
        background: #ffffff !important;
        border: 1px solid #ccfbf1 !important;
        border-radius: 999px !important;
        &:hover {
            color: #ffffff !important;
            background: var(--cs-brand) !important;
            border-color: var(--cs-brand) !important;
        }
    }
}

/* ============ Table ============ */
.cs-table {
    width: 100%;
    :deep(.el-table__inner-wrapper::before) { display: none; }
    :deep(.el-table) {
        --el-table-border-color: var(--cs-line);
        --el-table-header-bg-color: var(--cs-surface-alt);
    }
    :deep(.el-table__header th.el-table__cell) {
        background: var(--cs-surface-alt) !important;
        font-weight: 600;
        font-size: 11px;
        color: var(--cs-muted);
        letter-spacing: 0.04em;
        text-transform: uppercase;
        border-bottom: 1px solid var(--cs-line) !important;
        padding: 10px 0;
        & > .cell { line-height: 1.4; }
    }
    :deep(.el-table__row > td.el-table__cell) {
        font-size: 13px;
        color: var(--cs-text);
        padding: 10px 0;
        border-bottom: 1px solid var(--cs-line-soft) !important;
    }
    :deep(.el-table__footer-wrapper td.el-table__cell) {
        font-weight: 700;
        font-size: 13px;
        color: var(--cs-ink);
        background: linear-gradient(180deg, #fffbeb 0%, #fef3c7 100%) !important;
        border-top: 1.5px solid var(--cs-warning) !important;
    }
}

.cs-table--clickable {
    :deep(.el-table__body tr) {
        transition: background-color 0.15s ease;
    }
    :deep(.el-table__body tr:hover > td.el-table__cell) {
        background-color: var(--cs-brand-soft) !important;
    }
}

/* 分组表（合并单元格）：合并后的左侧人名/部门列加浅底色让分组更显眼 */
.cs-table--grouped {
    :deep(.el-table__body tr > td.el-table__cell:nth-child(1)),
    :deep(.el-table__body tr > td.el-table__cell:nth-child(2)) {
        background: linear-gradient(180deg, #ffffff 0%, #fafafa 100%);
        font-weight: 600;
        color: var(--cs-ink);
        vertical-align: middle;
    }
    :deep(.el-table__body tr:hover > td.el-table__cell:nth-child(1)),
    :deep(.el-table__body tr:hover > td.el-table__cell:nth-child(2)) {
        background: var(--cs-brand-soft);
    }
    :deep(.el-table__body tr > td.el-table__cell:nth-child(3)) {
        background: var(--cs-surface);
    }
}

/* 挂号渠道彩色徽标 */
.cs-channel-tag {
    border-radius: 999px !important;
    font-weight: 600 !important;
    padding: 0 10px !important;
    height: 22px !important;
    line-height: 20px !important;
    border-width: 1px !important;
    &--revisit {
        background: #ecfeff !important;
        border-color: #67e8f9 !important;
        color: #0e7490 !important;
    }
    &--media {
        background: #eef2ff !important;
        border-color: #c7d2fe !important;
        color: #4338ca !important;
    }
    &--referral {
        background: #fff7ed !important;
        border-color: #fdba74 !important;
        color: #c2410c !important;
    }
    &--muted {
        background: #f1f5f9 !important;
        border-color: #cbd5e1 !important;
        color: #64748b !important;
    }
    &--clickable {
        cursor: pointer;
        &:hover {
            filter: brightness(0.96);
        }
    }
    &--neutral {
        background: #f0fdfa !important;
        border-color: #99f6e4 !important;
        color: var(--cs-brand-strong) !important;
    }
}

/* ============ Dept link ============ */
.cs-dept-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    font-weight: 600;
    color: var(--cs-ink) !important;
    transition: color 0.15s ease;
    &__text {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    &__icon {
        flex: none;
        width: 18px;
        height: 18px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        font-size: 11px;
        color: var(--cs-faint);
        background: var(--cs-surface-alt);
        border-radius: 50%;
        transition: all 0.18s ease;
    }
    &:hover {
        color: var(--cs-brand-strong) !important;
    }
    &:hover &__icon {
        color: #ffffff;
        background: var(--cs-brand);
        transform: translateX(1px);
    }
}

/* ============ Dialog ============ */
.cs-dialog {
    :deep(.el-dialog) {
        border-radius: var(--cs-radius);
        overflow: hidden;
        box-shadow: var(--cs-shadow-md);
        border: 1px solid var(--cs-line);
    }
    :deep(.el-dialog__header) {
        padding: 14px 20px;
        margin: 0;
        border-bottom: 1px solid var(--cs-line);
        background: var(--cs-surface-alt);
    }
    :deep(.el-dialog__title) {
        font-size: 15px;
        font-weight: 700;
        color: var(--cs-ink);
    }
    :deep(.el-dialog__body) {
        padding: 16px 20px;
    }
}
.cs-dialog__bar {
    margin-bottom: 12px;
    &--split {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
    }
}
.cs-dialog__hint {
    font-size: 12px;
    color: var(--cs-muted);
    strong { color: var(--cs-ink); font-weight: 600; }
}
.cs-dialog__pager {
    margin-top: 14px;
    display: flex;
    justify-content: flex-end;
}

.cs-muted { color: var(--cs-muted); }

.cs-patient-cell {
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.35;
}

.cs-patient-cell__name {
    color: var(--cs-ink, #0f172a);
}

.cs-patient-cell__meta {
    font-size: 12px;
}

/* ============ Element Plus overrides (scoped via deep) ============ */
.cs-page {
    :deep(.el-input__wrapper),
    :deep(.el-select__wrapper),
    :deep(.el-date-editor.el-input__wrapper) {
        border-radius: var(--cs-radius-sm);
        box-shadow: inset 0 0 0 1px var(--cs-line);
        transition: box-shadow 0.15s ease;
        &:hover {
            box-shadow: inset 0 0 0 1px var(--cs-brand);
        }
        &.is-focus {
            box-shadow: inset 0 0 0 1px var(--cs-brand), 0 0 0 3px var(--cs-brand-ring) !important;
        }
    }
    :deep(.el-radio-button__inner) {
        border-color: var(--cs-line);
        font-weight: 600;
        font-size: 12px;
    }
    :deep(.el-radio-button.is-active .el-radio-button__inner) {
        background: var(--cs-brand) !important;
        border-color: var(--cs-brand) !important;
        color: #ffffff !important;
        box-shadow: -1px 0 0 0 var(--cs-brand);
    }
    :deep(.el-pagination .btn-prev),
    :deep(.el-pagination .btn-next),
    :deep(.el-pagination .el-pager li) {
        background: transparent;
        font-weight: 600;
    }
    :deep(.el-pagination .el-pager li.is-active) {
        background: var(--cs-brand);
        color: #ffffff;
        border-radius: var(--cs-radius-xs);
    }
}

@media (max-width: 900px) {
    .cs-page { padding: 16px 14px 28px; }
    .cs-masthead__inner { padding: 18px 20px; }
    .cs-masthead__title { font-size: 19px; }
    .filter-actions { width: 100%; margin-left: 0; }
}
</style>
