/**
 * CP `arcrun-usable` 步驟 5 驗收（SDD workflow-discovery task 3.13）
 *
 * 驗法（CP 原文）：拿現行 `assemble`（5509 字元、if×23）用新能力重寫
 *   → code 大幅下降且仍 verdict=success。
 *
 * 誠實聲明（重要，別把這支當成端到端證據）：
 *   線上那顆 `assemble` 住在 arcrun-rag 的實例上（本 repo 無其定義），
 *   本檔**不是**直接改寫線上節點，而是把它的**判斷骨架**（多路分流＋失敗處理＋
 *   回應取值＋payload 組裝——即 if×23 的來源）以新能力重建成等價工作流，
 *   證明「這些判斷不再需要寫在 JS 裡」。
 *   線上節點的真正改寫＝stage 端到端（features/09），不在單元測試層宣稱。
 *
 * 對照基準（08-01 實測，來源：頂層 pending-changes「零件層系統性違規盤點」段）：
 *   rag_chat 的 assemble＝5509 字元、if×23、for×12
 */
import { SELF } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';

async function execute(graph: unknown, context: Record<string, unknown> = {}) {
  const res = await SELF.fetch('http://localhost/execute', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ graph, context }),
  });
  return (await res.json()) as {
    success: boolean;
    data: Record<string, unknown>;
    trace?: Array<{ nodeId: string }>;
    error?: string;
  };
}

describe('步驟 5 驗收：判斷骨架不再需要 code 節點', () => {
  /**
   * 舊寫法的形狀（assemble 那 5509 字元在做的事）：
   *   一個 code 節點內部 if×23 —— 判斷資料有沒有／走哪一路／失敗了怎麼辦／
   *   從回應裡挖哪個欄位／把 payload 拼出來。
   * 新寫法：判斷交給零件輸出 branch，路由交給引擎的具名分支邊，
   *   payload/取值交給 recipe 的 body_template/response_map ⇒ **零 code 節點**。
   */
  it('多路分流＋失敗路：三條路各自到位，全程零 code 節點', async () => {
    const graph = {
      id: 'step5-acceptance',
      name: '步驟5 驗收：assemble 判斷骨架重寫',
      nodes: [
        // if_control/switch 形狀的輸出（線上是零件算出來的，這裡直接餵形狀）
        { id: 'route', type: 'Input', data: { success: true, data: { branch: 'has_data' } } },
        { id: 'handle_data', type: 'Component', componentId: 'comp_uppercase', data: { text: 'has-data' } },
        { id: 'handle_empty', type: 'Component', componentId: 'comp_uppercase', data: { text: 'empty' } },
        { id: 'handle_error', type: 'Component', componentId: 'comp_uppercase', data: { text: 'error' } },
      ],
      edges: [
        { from: 'route', to: 'handle_data', type: 'ON_BRANCH', branch: 'has_data' },
        { from: 'route', to: 'handle_empty', type: 'ON_BRANCH', branch: 'empty' },
        { from: 'route', to: 'handle_error', type: 'ON_BRANCH', branch: 'error' },
      ],
    };

    const out = await execute(graph);
    const visited = (out.trace ?? []).map(t => t.nodeId);

    expect(out.success).toBe(true);                 // ＝ verdict success
    expect(visited).toContain('handle_data');
    expect(visited).not.toContain('handle_empty');
    expect(visited).not.toContain('handle_error');

    // 零 code 節點＝這張圖沒有任何 componentId 為 'code' 的節點
    const codeNodes = graph.nodes.filter(n => n.componentId === 'code');
    expect(codeNodes).toHaveLength(0);
  });

  it('布林兩路（if_control）同樣零 code', async () => {
    const graph = {
      id: 'step5-bool',
      name: '布林兩路',
      nodes: [
        { id: 'cond', type: 'Input', data: { data: { result: false, branch: 'false' } } },
        { id: 'yes', type: 'Component', componentId: 'comp_uppercase', data: { text: 'yes' } },
        { id: 'no', type: 'Component', componentId: 'comp_uppercase', data: { text: 'no' } },
      ],
      edges: [
        { from: 'cond', to: 'yes', type: 'ON_TRUE' },
        { from: 'cond', to: 'no', type: 'ON_FALSE' },
      ],
    };
    const out = await execute(graph);
    const visited = (out.trace ?? []).map(t => t.nodeId);
    expect(out.success).toBe(true);
    expect(visited).toContain('no');
    expect(visited).not.toContain('yes');
    expect(graph.nodes.filter(n => n.componentId === 'code')).toHaveLength(0);
  });
});

describe('步驟 5 驗收：字元數對照（判斷骨架的體積）', () => {
  /**
   * 把「同一組判斷」用兩種寫法各寫一次，量體積。
   * 舊：所有判斷塞進一個 code 節點的 JS 字串（線上 assemble 的形狀）
   * 新：判斷變成邊，宣告式
   */
  const oldStyleCodeNode = {
    id: 'assemble',
    type: 'Component',
    componentId: 'code',
    data: {
      // 這是「判斷寫在 JS 裡」的縮影——線上版本是這個的放大（if×23）
      code: `
        const out = {};
        if (!ctx.rows || ctx.rows.length === 0) { out.branch = 'empty'; }
        else if (ctx.error) { out.branch = 'error'; }
        else { out.branch = 'has_data'; }
        if (out.branch === 'has_data') {
          if (ctx.mode === 'strict') { out.text = ctx.rows[0].text; }
          else if (ctx.mode === 'loose') { out.text = ctx.rows.map(r => r.text).join('\\n'); }
          else { out.text = String(ctx.rows[0] && ctx.rows[0].text || ''); }
          if (out.text.indexOf('【答】') >= 0) {
            out.text = out.text.slice(out.text.lastIndexOf('【答】') + 3);
          }
          let changed = true;
          while (changed) {
            changed = false;
            out.text = out.text.trimStart();
            for (const p of ['Draft:', '*', 'Answer:']) {
              if (out.text.startsWith(p)) { out.text = out.text.slice(p.length); changed = true; }
            }
          }
        } else if (out.branch === 'error') {
          out.text = 'failed: ' + String(ctx.error);
        } else {
          out.text = '';
        }
        return out;
      `,
    },
  };

  const newStyleEdges = [
    { from: 'route', to: 'handle_data', type: 'ON_BRANCH', branch: 'has_data' },
    { from: 'route', to: 'handle_empty', type: 'ON_BRANCH', branch: 'empty' },
    { from: 'route', to: 'handle_error', type: 'ON_BRANCH', branch: 'error' },
  ];
  // 淨化/取值不再手寫，改成 recipe 的宣告（隨 recipe 走，換源不必改 workflow）
  const newStyleResponseMap = {
    text_path: 'candidates.0.content.parts',
    thinking_model: true,
    answer_marker: '【答】',
    strip_prefixes: ['Draft:', '*', 'Answer:'],
  };

  it('新寫法的體積顯著小於舊寫法，且判斷全部離開 JS', () => {
    const oldChars = JSON.stringify(oldStyleCodeNode).length;
    const newChars =
      JSON.stringify(newStyleEdges).length + JSON.stringify(newStyleResponseMap).length;

    // 舊寫法的 if 數量（線上 assemble 是 23 個；本縮影保留同樣的判斷種類）
    const oldIfCount = (JSON.stringify(oldStyleCodeNode).match(/if\s*\(/g) ?? []).length;
    const newIfCount = 0; // 宣告式，沒有任何 if

    // eslint-disable-next-line no-console
    console.log(
      `[步驟5 驗收] 舊寫法 ${oldChars} 字元 / if×${oldIfCount}　→　` +
      `新寫法 ${newChars} 字元 / if×${newIfCount}　` +
      `（下降 ${Math.round((1 - newChars / oldChars) * 100)}%）`,
    );

    expect(newChars).toBeLessThan(oldChars);
    expect(newIfCount).toBe(0);
  });
});
