/**
 * 條件邊 ON_TRUE / ON_FALSE / ON_BRANCH —— CP `arcrun-usable` 步驟 5 缺口①
 * SDD: workflow-discovery tasks 3.11
 *
 * 為什麼要有這組測試（別刪）：
 *   `if_control` 零件回 `{success, data:{result, branch}}`，但引擎過去只有
 *   ON_SUCCESS / IF / FOREACH ⇒ 就算照規矩用 if_control，也只拿到布林值，
 *   還是得寫 code 判斷該走哪條路 ⇒ 這正是「全變成 code」的根（Arcrun#5）。
 *
 * 本檔先寫測試再改引擎（引擎核心風險最高，紅線要求）。
 * 既有邊行為的零變化迴歸另見 executor.test.ts（PIPE/IF/ON_SUCCESS 原樣通過）。
 */
import { SELF } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';

/** 送一張圖進 /execute，回 parsed JSON */
async function run(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 {
    status: res.status,
    body: (await res.json()) as {
      success: boolean;
      data: Record<string, unknown>;
      trace?: Array<{ nodeId: string }>;
      error?: string;
    },
  };
}

/**
 * 用 Input 節點直接餵出 if_control 形狀的 output（{data:{result,branch}}），
 * 避免測試依賴真的 WASM 零件（單元層只驗「引擎怎麼走邊」）。
 */
function branchGraph(branch: 'true' | 'false', edges: Array<Record<string, unknown>>) {
  return {
    id: `g-branch-${branch}`,
    name: '條件邊測試',
    nodes: [
      // 模擬 if_control 的輸出形狀
      { id: 'cond', type: 'Input', data: { success: true, data: { result: branch === 'true', branch } } },
      { id: 'yes', type: 'Component', componentId: 'comp_uppercase', data: { text: 'yes' } },
      { id: 'no', type: 'Component', componentId: 'comp_uppercase', data: { text: 'no' } },
    ],
    edges,
  };
}

describe('條件邊：ON_TRUE / ON_FALSE（缺口① Arcrun#5 根治）', () => {
  it('branch=true → 只走 ON_TRUE 那條，ON_FALSE 那條不執行', async () => {
    const { body } = await run(
      branchGraph('true', [
        { from: 'cond', to: 'yes', type: 'ON_TRUE' },
        { from: 'cond', to: 'no', type: 'ON_FALSE' },
      ]),
    );
    expect(body.success).toBe(true);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('yes');
    expect(visited).not.toContain('no');
  });

  it('branch=false → 只走 ON_FALSE 那條，ON_TRUE 那條不執行', async () => {
    const { body } = await run(
      branchGraph('false', [
        { from: 'cond', to: 'yes', type: 'ON_TRUE' },
        { from: 'cond', to: 'no', type: 'ON_FALSE' },
      ]),
    );
    expect(body.success).toBe(true);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('no');
    expect(visited).not.toContain('yes');
  });

  it('result 是布林但沒有 branch 欄位 → 仍judged得出（相容 {result:true} 形狀）', async () => {
    const graph = {
      id: 'g-bool-only',
      name: '只有 result',
      nodes: [
        { id: 'cond', type: 'Input', data: { result: true } },
        { 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 { body } = await run(graph);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('yes');
    expect(visited).not.toContain('no');
  });

  it('條件邊的下游拿得到上游 context（propagateCtx 一致）', async () => {
    const graph = {
      id: 'g-ctx',
      name: 'context 傳遞',
      nodes: [
        { id: 'cond', type: 'Input', data: { data: { result: true, branch: 'true' }, carried: 'keep-me' } },
        { id: 'yes', type: 'Component', componentId: 'comp_passthrough' },
      ],
      edges: [{ from: 'cond', to: 'yes', type: 'ON_TRUE' }],
    };
    const { body } = await run(graph);
    expect(body.success).toBe(true);
    expect(body.data.carried).toBe('keep-me');
  });

  it('兩條 ON_TRUE 並存 → 都走（同分支多下游是合法 fan-out）', async () => {
    const graph = {
      id: 'g-fanout',
      name: '同分支多下游',
      nodes: [
        { id: 'cond', type: 'Input', data: { data: { result: true, branch: 'true' } } },
        { id: 'a', type: 'Component', componentId: 'comp_uppercase', data: { text: 'a' } },
        { id: 'b', type: 'Component', componentId: 'comp_uppercase', data: { text: 'b' } },
      ],
      edges: [
        { from: 'cond', to: 'a', type: 'ON_TRUE' },
        { from: 'cond', to: 'b', type: 'ON_TRUE' },
      ],
    };
    const { body } = await run(graph);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('a');
    expect(visited).toContain('b');
  });
});

describe('條件邊：ON_BRANCH（switch 具名分支）', () => {
  /** switch 零件回 {success, data:{branch:"branch_a"}} */
  function switchGraph(branch: string) {
    return {
      id: 'g-switch',
      name: 'switch 具名分支',
      nodes: [
        { id: 'sw', type: 'Input', data: { success: true, data: { branch } } },
        { id: 'a', type: 'Component', componentId: 'comp_uppercase', data: { text: 'a' } },
        { id: 'z', type: 'Component', componentId: 'comp_uppercase', data: { text: 'z' } },
      ],
      edges: [
        { from: 'sw', to: 'a', type: 'ON_BRANCH', branch: 'branch_a' },
        { from: 'sw', to: 'z', type: 'ON_BRANCH', branch: 'fallback' },
      ],
    };
  }

  it('branch=branch_a → 只走標 branch_a 的邊', async () => {
    const { body } = await run(switchGraph('branch_a'));
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('a');
    expect(visited).not.toContain('z');
  });

  it('branch=fallback → 只走標 fallback 的邊', async () => {
    const { body } = await run(switchGraph('fallback'));
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('z');
    expect(visited).not.toContain('a');
  });

  it('沒有任何邊匹配 → 誠實地不走（不亂挑一條，也不報錯）', async () => {
    const { body } = await run(switchGraph('no_such_branch'));
    expect(body.success).toBe(true);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).not.toContain('a');
    expect(visited).not.toContain('z');
  });
});

describe('通用具名分支涵蓋三型零件（leo 08-01：switch 比 if 更嚴重）', () => {
  /**
   * 三顆流程控制零件的 output_schema 都收斂到同一個形狀 `data.branch: string`：
   *   if_control → "true" | "false"（布林兩路）
   *   switch     → case 的 branch 名 | default_branch（N 路）
   *   try_catch  → "try" | "catch"（成功/失敗兩路）
   * ⇒ 引擎只需要「依標籤選邊」這一個機制，不是為每顆零件開特例。
   *   ON_TRUE / ON_FALSE 只是 if 布林路的語法糖，底層與 ON_BRANCH 同一條路。
   */
  async function branchTo(branch: string, edges: Array<Record<string, unknown>>) {
    return run({
      id: `g-generic-${branch}`,
      name: '通用具名分支',
      nodes: [
        { id: 'ctrl', type: 'Input', data: { success: true, data: { branch } } },
        { id: 'p1', type: 'Component', componentId: 'comp_uppercase', data: { text: 'p1' } },
        { id: 'p2', type: 'Component', componentId: 'comp_uppercase', data: { text: 'p2' } },
        { id: 'p3', type: 'Component', componentId: 'comp_uppercase', data: { text: 'p3' } },
      ],
      edges,
    });
  }

  const threeWay = [
    { from: 'ctrl', to: 'p1', type: 'ON_BRANCH', branch: 'branch_active' },
    { from: 'ctrl', to: 'p2', type: 'ON_BRANCH', branch: 'branch_inactive' },
    { from: 'ctrl', to: 'p3', type: 'ON_BRANCH', branch: 'branch_default' },
  ];

  it('switch 多路：branch_active → 只走第一條，其餘兩條不走', async () => {
    const { body } = await branchTo('branch_active', threeWay);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('p1');
    expect(visited).not.toContain('p2');
    expect(visited).not.toContain('p3');
  });

  it('switch 多路：branch_inactive → 只走第二條', async () => {
    const { body } = await branchTo('branch_inactive', threeWay);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('p2');
    expect(visited).not.toContain('p1');
    expect(visited).not.toContain('p3');
  });

  it('switch default：無匹配 case 時零件回 default_branch → 走 default 那條', async () => {
    // 注意：挑 default 是 switch 零件內部的事（它回 default_branch 名）；
    // 引擎這層看到的一律是「一個標籤」，故 default 不需要引擎特別處理。
    const { body } = await branchTo('branch_default', threeWay);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('p3');
    expect(visited).not.toContain('p1');
    expect(visited).not.toContain('p2');
  });

  it('try_catch 成功路：branch=try → 走 try 邊，不走 catch 邊', async () => {
    const { body } = await branchTo('try', [
      { from: 'ctrl', to: 'p1', type: 'ON_BRANCH', branch: 'try' },
      { from: 'ctrl', to: 'p2', type: 'ON_BRANCH', branch: 'catch' },
    ]);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('p1');
    expect(visited).not.toContain('p2');
  });

  it('try_catch 失敗路：branch=catch → 走 catch 邊，不走 try 邊', async () => {
    const { body } = await branchTo('catch', [
      { from: 'ctrl', to: 'p1', type: 'ON_BRANCH', branch: 'try' },
      { from: 'ctrl', to: 'p2', type: 'ON_BRANCH', branch: 'catch' },
    ]);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).toContain('p2');
    expect(visited).not.toContain('p1');
  });

  it('ON_TRUE 與 ON_BRANCH branch="true" 等價（語法糖，底層同一條路）', async () => {
    const sugar = await branchTo('true', [{ from: 'ctrl', to: 'p1', type: 'ON_TRUE' }]);
    const raw = await branchTo('true', [{ from: 'ctrl', to: 'p1', type: 'ON_BRANCH', branch: 'true' }]);
    const v1 = (sugar.body.trace ?? []).map(t => t.nodeId);
    const v2 = (raw.body.trace ?? []).map(t => t.nodeId);
    expect(v1).toEqual(v2);
    expect(v1).toContain('p1');
  });
});

describe('零變化保證：新邊型不影響既有邊', () => {
  it('ON_TRUE 邊存在時，同圖的 PIPE 邊照常走', async () => {
    const graph = {
      id: 'g-mixed',
      name: '混合邊',
      nodes: [
        { id: 'cond', type: 'Input', data: { data: { result: false, branch: 'false' }, count: 0 } },
        { id: 'yes', type: 'Component', componentId: 'comp_uppercase', data: { text: 'yes' } },
        { id: 'always', type: 'Component', componentId: 'comp_counter' },
      ],
      edges: [
        { from: 'cond', to: 'yes', type: 'ON_TRUE' },
        { from: 'cond', to: 'always', type: 'PIPE' },
      ],
    };
    const { body } = await run(graph);
    const visited = (body.trace ?? []).map(t => t.nodeId);
    expect(visited).not.toContain('yes');   // 條件邊擋掉
    expect(visited).toContain('always');    // PIPE 不受影響
  });

  it('/validate 接受 ON_TRUE / ON_FALSE / ON_BRANCH（schema 已放行）', async () => {
    const res = await SELF.fetch('http://localhost/validate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        id: 'g-validate',
        name: 'schema 驗證',
        nodes: [
          { id: 'a', type: 'Input' },
          { id: 'b', type: 'Output' },
          { id: 'c', type: 'Output' },
        ],
        edges: [
          { from: 'a', to: 'b', type: 'ON_TRUE' },
          { from: 'a', to: 'c', type: 'ON_FALSE' },
        ],
      }),
    });
    const data = (await res.json()) as { valid: boolean };
    expect(res.status).toBe(200);
    expect(data.valid).toBe(true);
  });
});
