'use client';

import type {
  TemperatureValue,
  ListPlansQuery,
  ListPlansResponse,
  PlanActionAck,
  PlanCountsResponse,
  ReleaseReason,
} from '@pac/types';
import { api } from '@/lib/api-client';
import type { PlanDetailData } from '@/components/plan-detail/plan-detail-types';

export interface PoolMatrixRow {
  key: string;
  zh: string;
  hint?: string;
  /**
   * 六档人数(**去重患者数**,不是 plan 条数)。
   * ⚠️ 用 map 不平铺 —— 档位是会变的(2026-08 从 3 档扩到 6 档),平铺每加一档就要改一圈类型 + 组件。
   */
  counts: Record<TemperatureValue, number>;
  /** 温度算不出来(锚点缺失)。⛔ 服务端刻意没把它并进任何一个冷档 —— 那是假分布 */
  unknown: number;
  /** 含 unknown —— 它就是「点这一行能拿到多少人」 */
  total: number;
}

export interface PoolMatrix {
  clinicId: string;
  rows: PoolMatrixRow[];
  unknownTotal: number;
  /** 有待重算的人时才非空;成品句子,直接展示 */
  note: string;
}

export const plansApi = {
  list: (q: Partial<ListPlansQuery>) =>
    api.get<ListPlansResponse>('/pac/v1/plans', {
      query: {
        view: q.view,
        scenario: q.scenario,
        status: q.status,
        targetClinicId: q.targetClinicId,
        // 诊所多选:逗号串上线(后端 preprocess 拆数组);空则不带
        targetClinicIds: q.targetClinicIds?.length ? q.targetClinicIds.join(',') : undefined,
        assigneeUserId: q.assigneeUserId,
        keyword: q.keyword,
        // 画像标签筛选("key:value" 逗号串,维度见 PERSONA_TAG_FILTER_DIMS)
        personaTags: q.personaTags,
        // 初选矩阵两轴 —— temperature 必须与 potentialTreatment 同时给(服务端会拒单给温度的)
        potentialTreatment: q.potentialTreatment,
        temperature: q.temperature,
        // 只看真实号码:布尔上线成 'true'(后端 preprocess 还原);undefined 不带
        phoneVerified: q.phoneVerified === undefined ? undefined : String(q.phoneVerified),
        sort: q.sort,
        page: q.page ?? 1,
        pageSize: q.pageSize ?? 20,
      },
    }),

  /**
   * 初选矩阵(8 潜在治疗 × 3 窗口温度)。
   * ⚠️ 数字是**去重患者数**,不是 plan 条数 —— 列表按 plan 分页,两者本就不该相等。
   */
  matrix: (clinicId?: string) =>
    api.get<PoolMatrix>('/pac/v1/plans/matrix', { query: { clinicId } }),

  /** 「上次医生 / 偏好医生」筛选的候选名单(后端缓存 6h;前端在它上面做客户端模糊匹配)*/
  doctors: () => api.get<{ doctors: string[] }>('/pac/v1/plans/doctors'),

  /** KPI / Tab badge 聚合计数 — 一次拉齐(替代之前 5 个并发 list 请求)*/
  counts: () => api.get<PlanCountsResponse>('/pac/v1/plans/counts'),

  /** 聚合详情:plan + patient + profile + persona + chains + facts(给 PlanDetailApp 用)*/
  getAggregate: (planId: string) =>
    api.get<PlanDetailData>(`/pac/v1/plans/${encodeURIComponent(planId)}/full`),

  /** PV/UV 埋点:上报一次 plan 详情页浏览。**尽力而为**,失败不影响页面(调用方吞异常)*/
  reportView: (planId: string) =>
    api.post<{ ok: boolean }>(`/pac/v1/plans/${encodeURIComponent(planId)}/view`, {}),

  /** 回访历史一句话摘要(有则取、无则当场生成;无回访记录 status='empty')*/
  getRecallSummary: (planId: string) =>
    api.get<{ summary: string | null; status: 'ready' | 'empty'; source?: string }>(
      `/pac/v1/plans/${encodeURIComponent(planId)}/recall-summary`,
    ),

  /** 画像标签一句话重点(有则取、无则当场生成;无 persona/feature status='empty')*/
  getPersonaSummary: (planId: string) =>
    api.get<{ summary: string | null; status: 'ready' | 'empty'; source?: string }>(
      `/pac/v1/plans/${encodeURIComponent(planId)}/persona-summary`,
    ),

  /**
   * 本次召回**四点**简报(2026-08-05 从一句话拆开)。
   * 有则取、无则当场生成;无召回原因 status='empty'。
   * ⚠️ 老缓存行是纯文本 → 服务端解析成 `{who:'—',history:'—',problem:老文本,hook:'—'}`,
   *   所以前端**永远拿到四段**,⛔ 不必再判"是不是老格式"。
   */
  getRecallBrief: (planId: string) =>
    api.get<{
      summary: { who: string; history: string; problem: string; hook: string } | null;
      status: 'ready' | 'empty';
      source?: string;
    }>(
      `/pac/v1/plans/${encodeURIComponent(planId)}/recall-brief`,
    ),

  /** 认领 / 指派 — POST /plans/{id}/assign { assigneeUserId } */
  assign: (planId: string, assigneeUserId: string) =>
    api.post<PlanActionAck>(`/pac/v1/plans/${encodeURIComponent(planId)}/assign`, {
      assigneeUserId,
    }),

  /**
   * 退回 / 回收 — POST /plans/{id}/recycle(mine → pool,plan 仍 active)。
   *
   * ⚠️ 入参从自由文本 `reason` 换成了结构化 `releaseReason`(2026-08):
   * 旧字段服务端**从来没读过**(controller 写作 `@Body() _dto`),客服填了等于没填。
   * 结构化才能聚合出"退回原因分布",那是主管调整分配策略的输入。
   * `other` 必须带 releaseNote,服务端会拦(不是前端校验就够了)。
   */
  recycle: (planId: string, releaseReason?: ReleaseReason, releaseNote?: string) =>
    api.post<PlanActionAck>(`/pac/v1/plans/${encodeURIComponent(planId)}/recycle`, {
      releaseReason,
      releaseNote,
    }),

  /** 拉患者明文手机号(reveal,需 PATIENT_VIEW 权限) — GET /patients/{id}/phone-reveal */
  revealPhone: (patientId: string) =>
    api.get<{ phone: string | null }>(
      `/pac/v1/patients/${encodeURIComponent(patientId)}/phone-reveal`,
    ),

  /**
   * 生成 / 重新生成**企微**话术(一整块,可直接复制发送)。
   *
   * ⚠️ 同步返回,**不走 SSE** —— 企微稿是一整块几百字,几秒出;没有"段"可以逐段推。
   * ⚠️ `source==='failed'` 是**正常返回**不是异常:企微不做模板兜底(套话复制发给患者
   *   比没有更糟),调用方要据此显示失败态并让客服自己写。
   */
  regenerateWecomScript: (planId: string, model?: string) =>
    api.post<{
      planId: string;
      planScriptId: string | null;
      agentInvocationId: string;
      source: 'agent' | 'failed';
      costYuan: number;
      content: string;
      failReason?: string;
    }>(
      `/pac/v1/plans/${encodeURIComponent(planId)}/wecom-script:regenerate` +
        (model ? `?model=${encodeURIComponent(model)}` : ''),
      {},
    ),

  /**
   * 上报「复制了参考话术」(埋点)。
   *
   * ⚠️ **尽力而为的旁路**:调用方不要 await、失败也不要提示 ——
   *   复制这个动作本身已经完成了(剪贴板先写),为一条埋点打断它是本末倒置。
   */
  reportScriptCopy: (planId: string, channel: 'wecom' | 'phone') =>
    api.post<{ ok: boolean; reason?: string }>(
      `/pac/v1/plans/${encodeURIComponent(planId)}/script-copy`,
      { channel },
    ),

  /** W4 末:话术 thumbs up/down 反馈("本段是否好用")
   *  - POST /pac/v1/plans/:id/script-feedback { feedback: 'up'|'down' }
   *  - 写入 agent_invocations.user_feedback;同 invocation 反复点会覆盖以最新为准 */
  submitScriptFeedback: (planId: string, feedback: 'up' | 'down') =>
    api.post<{ ok: boolean; invocationId?: string; reason?: string }>(
      `/pac/v1/plans/${encodeURIComponent(planId)}/script-feedback`,
      { feedback },
    ),

  /** 召回反馈(plan 级)— 详情页标题栏拇指 + 文字("这条召回准不准")
   *  - POST /pac/v1/plans/:id/recall-feedback { feedback: 'up'|'down', note? }
   *  - 写入 followup_plans.recall_feedback(+note/at/by);反复点覆盖以最新为准 */
  submitRecallFeedback: (planId: string, feedback: 'up' | 'down', note?: string) =>
    api.post<{ ok: boolean; feedback?: 'up' | 'down'; reason?: string }>(
      `/pac/v1/plans/${encodeURIComponent(planId)}/recall-feedback`,
      { feedback, note },
    ),

  /** W4 末:单患者按需刷新(从 DW 直连重拉该 patient 全量,触发 persona+plan recompute)
   *  - POST /sync/patient/:id/refresh
   *  - 跟 daily incremental cursor 完全隔离(resource='patient_refresh')
   *  - 返回新 plansCreated:0 = plan 已消失(scenario 不再命中),前端应跳列表 */
  refreshPatient: (patientId: string) =>
    api.post<{
      syncLogId: string;
      status: string;
      transactionsWritten?: number;
      factsEmitted?: number;
      plansCreated?: number;
    }>(`/pac/v1/sync/patient/${encodeURIComponent(patientId)}/refresh`),
};
