// 无头对拍: 参数化引擎 (一期 2026-08-25) — 网页 UI 与桌面 selftest 读同一份 parametric-vectors.json
// 用法: node tools\e2e-parametric.js [siteUrl] [vectorsPath]
// 断言: 标准档位编码/下载可用; 非标档位编码+徽标+下载置灰+最近标准档位提示; 非法组合无型号
const fs = require('fs');
let chromium;
try { chromium = require('playwright-core').chromium; }
catch (e) { chromium = require('C:/Users/ruigu/AppData/Local/Temp/nds3dtest/node_modules/playwright-core').chromium; }
const edge = 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe';

(async () => {
  const url = process.argv[2] || 'http://localhost:8080/';
  const vecPath = process.argv[3] ||
    'w:/团队文件-TeamCenter/airtac_0602_2026_v11sp5_world_version/airtac_0602_2026_v11sp5_world_version/OnebotCatalog/onebot-data/catalog/parametric-vectors.json';
  const vec = JSON.parse(fs.readFileSync(vecPath, 'utf8'));
  const browser = await chromium.launch({ executablePath: edge, headless: true });
  const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
  const errs = [];
  page.on('pageerror', e => errs.push(e.message));
  await page.goto(url, { waitUntil: 'domcontentloaded' });
  await page.waitForSelector('.tree .series', { timeout: 20000 });   // 等目录树渲染 (服务器冷启动首请求慢)
  await page.waitForTimeout(800);

  let pass = 0, fail = 0;
  const out = [];
  const check = (name, ok) => { out.push((ok ? 'PASS ' : 'FAIL ') + name); if (ok) pass++; else fail++; };

  // 选中 KC 系列 (首个参数化系列)
  const names = await page.$$eval('.tree .series', els => els.map(e => e.textContent.trim()));
  const kcIdx = names.findIndex(n => n.indexOf('KC') === 0);
  if (kcIdx < 0) { console.log('FAIL: 树中未找到 KC 系列'); await browser.close(); process.exit(1); }
  const seriesEls = await page.$$('.tree .series');
  await seriesEls[kcIdx].click();
  await page.waitForTimeout(500);

  for (let ci = 0; ci < vec.cases.length; ci++) {
    const c = vec.cases[ci];
    const tag = 'case' + (ci + 1) + ' ' + c.series;
    const keys = Object.keys(c.params);
    const setParam = async (p, v) => {
      const sel = await page.$('#sel-' + p);
      if (!sel) { check(tag + ' 参数控件缺失 ' + p, false); return; }
      const isSelect = (await sel.evaluate(el => el.tagName)) === 'SELECT';
      console.log('  [' + tag + '] 设置 ' + p + '=' + JSON.stringify(v) + ' (' + (isSelect ? 'select' : 'input') + ')');
      if (isSelect) {
        const val = String(v) === '' ? '__EMPTY__' : String(v);
        await sel.selectOption(val);
      } else {
        await sel.fill(String(v));
        await sel.dispatchEvent('change');
      }
      await page.waitForTimeout(200);
    };
    // 错误用例: 引擎层拒绝已由桌面向量对拍覆盖; 网页端断言 UI 联动拦截行为
    if (c.expectError === 'rule') {
      // 末参数 = 违背规则的那个: 先设置其余参数, 该选项应已被联动剔除
      for (let k = 0; k < keys.length - 1; k++) await setParam(keys[k], c.params[keys[k]]);
      await page.waitForTimeout(300);
      const lastP = keys[keys.length - 1];
      const lastV = String(c.params[lastP]);
      const opts = await page.$$eval('#sel-' + lastP + ' option', os => os.map(o => o.value));
      check(tag + ' 规则联动剔除非法选项 ' + lastP + '=' + lastV + ' (实际选项: ' + opts.join(',') + ')',
        opts.indexOf(lastV) < 0);
      continue;
    }
    if (c.expectError === 'domain') {
      for (let k = 0; k < keys.length - 1; k++) await setParam(keys[k], c.params[keys[k]]);
      await page.waitForTimeout(300);
      const lastP = keys[keys.length - 1];
      await setParam(lastP, c.params[lastP]);   // 输入越界值 → 处理函数拒绝
      await page.waitForTimeout(300);
      const title = await page.$eval('#modelTitle', el => el.textContent);
      const status = await page.$eval('#status', el => el.textContent);
      check(tag + ' 域外输入被拒绝 (标题: ' + title.trim() + ' | 状态: ' + status.trim() + ')',
        title.indexOf('还缺') >= 0 || status.indexOf('超出范围') >= 0);
      continue;
    }
    // 依次设置各参数 (枚举用 select; range 用输入框 + change)
    for (const p of keys) await setParam(p, c.params[p]);
    await page.waitForTimeout(400);
    const title = await page.$eval('#modelTitle', el => el.textContent);
    if (c.expectError) {
      const noModel = title.indexOf('KC') < 0 || title.indexOf('还缺') >= 0 || title.indexOf('无对应型号') >= 0;
      check(tag + ' 非法组合不生成型号 (标题: ' + title.trim() + ')', noModel);
      continue;
    }
    const hasCode = title.indexOf(c.expectCode) >= 0;
    check(tag + ' 型号编码 ' + c.expectCode + ' (标题: ' + title.trim() + ')', hasCode);
    if (c.expectStandard) {
      const dlOk = await page.$eval('#btnDownload', el => !el.classList.contains('hidden') && !el.disabled);
      check(tag + ' 标准档位下载可用', dlOk);
    } else {
      const badge = await page.$eval('#modelTitle', el => !!el.querySelector('.nonstd-badge'));
      // 生成服务未配置 → 置灰(一期过渡态); 已配置 → 在线生成按钮可用 (两种都是正确状态)
      const dl = await page.$eval('#btnDownload', el => ({ hidden: el.classList.contains('hidden'), disabled: el.disabled, text: el.textContent }));
      const dlOk = !dl.hidden && ((dl.disabled && dl.text.indexOf('下载 STEP') >= 0) ||
        (!dl.disabled && dl.text.indexOf('在线生成') >= 0));
      check(tag + ' 非标徽标显示 + 下载状态正确 (按钮: ' + dl.text + (dl.disabled ? '/disabled' : '/enabled') + ')', badge && dlOk);
      if (c.expectNearest) {
        const hint = await page.$eval('#viewer3dHint', el => el.textContent);
        check(tag + ' 最近标准档位提示 ' + c.expectNearest + ' (提示: ' + hint.trim() + ')',
          hint.indexOf(c.expectNearest) >= 0);
      }
    }
  }

  // 非标加入购物车 → 角标 + 抽屉标注 (与桌面语义一致)
  await page.fill('#sel-stroke', '87');
  await page.dispatchEvent('#sel-stroke', 'change');
  await page.waitForTimeout(400);
  await page.click('#btnAddCart');
  await page.waitForTimeout(300);
  await page.click('#btnCart');
  await page.waitForTimeout(300);
  const sub = await page.$eval('#cartList .cart-item .ci-sub', el => el.textContent);
  check('购物车非标条目标注 (内容: ' + sub.trim() + ')', sub.indexOf('非标') >= 0);
  await page.click('#cartClose');
  await page.waitForTimeout(200);

  // 标准档位回归: 换回 stroke=75 → 下载恢复可用
  await page.fill('#sel-stroke', '75');
  await page.dispatchEvent('#sel-stroke', 'change');
  await page.waitForTimeout(400);
  const dlRe = await page.$eval('#btnDownload', el => !el.disabled && el.getAttribute('href').indexOf('.step') >= 0);
  check('标准档位回归: 下载恢复可用 (href=' + (await page.$eval('#btnDownload', el => el.getAttribute('href'))) + ')', dlRe);

  console.log(out.join('\n'));
  console.log('结果: ' + pass + ' 通过, ' + fail + ' 失败 | 页面错误: ' + (errs.length ? errs.join(' | ') : '无'));
  await browser.close();
  process.exit(fail ? 1 : 0);
})().catch(e => { console.error('FAIL:', e.message); process.exit(1); });
