// 七日全流程自动化验收：任务/线索/悲伤行为/黄昏/梦境/结局 + 控制台零报错
// 运行：node tools/e2e_flow.mjs
import { chromium } from "playwright";
import { createServer } from "vite";
import { mkdirSync } from "fs";

const SHOT_DIR = process.env.SHOT_DIR || "/tmp/mtc-flow";
mkdirSync(SHOT_DIR, { recursive: true });

const server = await createServer({ server: { port: 5198 } });
await server.listen();
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
const errors = [];
page.on("console", (m) => { if (m.type() === "error") errors.push(m.text()); });
page.on("pageerror", (e) => errors.push("PAGEERROR: " + e.message));

const sleep = (ms) => page.waitForTimeout(ms);
// 无头环境下 press() 的 down/up 同帧到达会吞掉 JustDown，改为跨帧按键
const pressE = async () => { await page.keyboard.down("e"); await sleep(90); await page.keyboard.up("e"); };
const shot = (n) => page.screenshot({ path: `${SHOT_DIR}/${n}.png` });
const D = (expr) => page.evaluate(`(() => { const d = window.__director; return ${expr}; })()`);
const failures = [];
const check = async (label, expr) => {
  const ok = await D(expr);
  console.log(`${ok ? "PASS" : "FAIL"}  ${label}`);
  if (!ok) {
    failures.push(label);
    const diag = await page.evaluate(() => {
      const d = window.__director;
      const sc = window.__game.scene.getScenes(true).find((s) => s.player);
      return `day=${d.day} scene=${d.currentScene} wipes=${d.wipes} clues=[${[...d.clues]}] cutscene=${d.cutscene} dialog=${window.__game.scene.getScene("ui").dialogOpen} player=${sc ? Math.round(sc.player.x) + "," + Math.round(sc.player.y) : "-"} trans=${sc ? sc.transitioning : "-"}`;
    });
    console.log("      diag:", diag);
  }
};

// 把玩家瞬移到当前场景某像素点
const tp = (x, y) => page.evaluate(([x, y]) => {
  const g = window.__game;
  const sc = g.scene.getScenes(true).find((s) => s.player);
  sc.player.setPosition(x, y);
  sc.player.body.reset(x, y);
}, [x, y]);
const curScene = () => page.evaluate(() => window.__director.currentScene);
// 连按 E 直到对话结束
const talk = async (times = 14) => {
  for (let i = 0; i < times; i++) {
    await pressE();
    await sleep(160);
    const open = await page.evaluate(() => window.__game.scene.getScene("ui").dialogOpen);
    if (!open) break;
  }
};
const interactAt = async (x, y, presses = 14) => {
  await tp(x, y); await sleep(350);
  await pressE(); await sleep(300);
  await talk(presses);
  await sleep(200);
};
// 走进门（瞬移到门内并等场景切换）
const enterDoor = async (x, y, expectScene) => {
  await tp(x, y);
  for (let i = 0; i < 30; i++) { await sleep(200); if ((await curScene()) === expectScene) break; }
  const ok = (await curScene()) === expectScene;
  console.log(`${ok ? "PASS" : "FAIL"}  进入 ${expectScene}`);
  if (!ok) {
    failures.push("进入 " + expectScene);
    const diag = await page.evaluate(() => {
      const d = window.__director;
      const sc = window.__game.scene.getScenes(true).find((s) => s.player);
      return `scene=${d.currentScene} cutscene=${d.cutscene} dialog=${window.__game.scene.getScene("ui").dialogOpen} player=${sc ? Math.round(sc.player.x) + "," + Math.round(sc.player.y) : "-"} trans=${sc ? sc.transitioning : "-"}`;
    });
    console.log("      diag:", diag);
  }
  await sleep(400);
};
const sleepToNextDay = async () => {
  await enterDoor(72, 68, "house"); // 回小屋（从广场小屋门）
  await interactAt(30, 70, 6);      // 床
  await sleep(14500);               // 梦境过场
  await sleep(1200);
};

await page.goto("http://localhost:5198/");
await sleep(2500);
await page.fill("#name-input", "验收员");
await page.press("#name-input", "Enter");
await sleep(1800);

// ===== Day 1 =====
await check("D1 开局幸福 30", "d.day===1 && d.happiness===30 && d.awareness===0");
await interactAt(30, 40, 4); // 日历（D1 无线索）
await check("D1 日历尚无 C1", "!d.clues.has('C1')");
await enterDoor(88, 122, "plaza"); // 出小屋
await interactAt(188, 126); // 公告板
await check("D1 公告已读", "d.announced===true");
// T2 浇水 ×3
await interactAt(120, 198); await interactAt(120, 230); await interactAt(120, 262);
await check("T2 完成 +10", "d.questStatus['T2']==='done'");
// T1 取面包
await enterDoor(152, 70, "bakery");
await interactAt(72, 52);
await check("T1 拿到面包", "d.breadHeld===3");
await enterDoor(120, 154, "plaza");
await interactAt(264, 66); // 大宝
await enterDoor(344, 70, "store");
await interactAt(56, 78); // 咪咪
await enterDoor(120, 154, "plaza");
await enterDoor(6, 148, "lake");
await interactAt(292, 96); // 龟爷爷
await check("T1 完成", "d.questStatus['T1']==='done'");
await check("T3 完成（问好×3）", "d.questStatus['T3']==='done'");
await check("D1 黄昏已触发", "d.dusk===true");
await shot("d1_dusk");
await enterDoor(378, 112, "plaza"); // 湖畔右缘回广场
await sleepToNextDay();

// ===== Day 2 =====
await check("进入 Day 2", "d.day===2");
await interactAt(30, 40, 6); // 日历 C1
await check("C1 获得（觉知+8）", "d.clues.has('C1') && d.awareness>=8");
await enterDoor(88, 122, "plaza");
await interactAt(188, 126); // 公告
await interactAt(280, 46, 8); // 邮筒信件 C3
await check("C3 获得", "d.clues.has('C3')");
await interactAt(264, 66); // 大宝取信
await check("T4 拿到信", "d.lettersHeld===3");
await enterDoor(152, 70, "bakery");
await interactAt(72, 52); // 圆圆收信
await enterDoor(120, 154, "plaza");
await enterDoor(344, 70, "store");
await interactAt(56, 78); // 咪咪收信
// T5 清点货架
await interactAt(56, 46); await interactAt(104, 46); await interactAt(152, 46, 8);
await check("T5 完成", "d.questStatus['T5']==='done'");
await interactAt(198, 92, 8); // C5 货架
await check("C5 获得", "d.clues.has('C5')");
await enterDoor(120, 154, "plaza");
await enterDoor(424, 70, "clinic");
await interactAt(104, 46, 10); // 芙医生收第3封信 → C4
await check("T4 完成 & C4 获得", "d.questStatus['T4']==='done' && d.clues.has('C4')");
await enterDoor(88, 122, "plaza");
await sleepToNextDay();

// ===== Day 3 =====
await check("进入 Day 3", "d.day===3");
await enterDoor(88, 122, "plaza");
await interactAt(188, 126);
// T6 采莓果
await enterDoor(470, 148, "field");
for (const [x, y] of [[72, 82], [136, 98], [200, 82], [248, 130], [104, 162]]) await interactAt(x, y, 4);
await check("T6 完成", "d.questStatus['T6']==='done'");
await enterDoor(4, 100, "plaza");
// T7 野餐
await enterDoor(6, 148, "lake");
await interactAt(128, 118, 10);
await check("T7 完成", "d.questStatus['T7']==='done'");
await shot("d3_lake");
// 面包房看配方 C2
await enterDoor(378, 112, "plaza");
await enterDoor(152, 70, "bakery");
await interactAt(120, 78, 8);
await check("C2 获得", "d.clues.has('C2')");
await enterDoor(120, 154, "plaza");
await sleepToNextDay();

// ===== Day 4（完美的重复） =====
await check("进入 Day 4", "d.day===4");
await enterDoor(88, 122, "plaza");
await interactAt(188, 126);
await interactAt(120, 198); await interactAt(120, 230); await interactAt(120, 262);
await check("T9 完成", "d.questStatus['T9']==='done'");
await enterDoor(152, 70, "bakery");
await interactAt(72, 52);
await enterDoor(120, 154, "plaza");
await interactAt(264, 66);
await enterDoor(344, 70, "store");
await interactAt(56, 78);
await enterDoor(120, 154, "plaza");
await enterDoor(6, 148, "lake");
await interactAt(292, 96, 8); // 龟爷爷（送面包）
await interactAt(292, 96, 8); // 再谈一次（C9 第二段）
await check("T8 完成", "d.questStatus['T8']==='done'");
await check("T10 完成", "d.questStatus['T10']==='done'");
await check("C9 链已记 D2/D4", "d.guiChain.has(4)");
await enterDoor(378, 112, "plaza");
await sleepToNextDay();

// ===== Day 5（自由日 + 古井/雾墙开闸） =====
await check("进入 Day 5", "d.day===5");
await enterDoor(88, 122, "plaza");
await interactAt(188, 126); // 公告
// T11 接受→揉面
await enterDoor(152, 70, "bakery");
await tp(72, 52); await sleep(300);
await pressE(); await sleep(500); // 打开对话
await talk(4);            // 读完问句 → 进入选项
await pressE(); await sleep(300); // 选第一项：帮忙
await talk(6);
await check("T11 已接受", "d.t11Accepted===true");
await interactAt(168, 102, 8); // 揉面台
await check("T11 完成", "d.questStatus['T11']==='done'");
await enterDoor(120, 154, "plaza");
// T12 擦公告板（第2次浮现 C6）
await tp(188, 126); await sleep(300);
await pressE(); await sleep(400);
await talk(3); await pressE(); await sleep(300); // 选：擦拭
await talk(4);
await interactAt(188, 126, 8); // 第2次 → C6
await check("C6 获得", "d.clues.has('C6')");
await interactAt(188, 126, 8); // 第3次 → 完成
await check("T12 完成", "d.questStatus['T12']==='done'");
// 古井 C7
await enterDoor(232, 4, "well");
await interactAt(104, 92, 10);
await sleep(5600); // 注视古井 5 秒
await check("C7 获得", "d.clues.has('C7')");
await check("注视古井 +3 觉知", "d.stared.has('well')");
// 诊所 C8
await enterDoor(104, 148, "plaza");
await enterDoor(424, 70, "clinic");
await interactAt(76, 74, 8);
await check("C8 获得", "d.clues.has('C8')");
await enterDoor(88, 122, "plaza");
await sleepToNextDay();

// ===== Day 6（加速 + T14 + 雨） =====
await check("进入 Day 6", "d.day===6");
await enterDoor(88, 122, "plaza");
await interactAt(188, 126);
// 龟爷爷 C9 第三段
await enterDoor(6, 148, "lake");
await interactAt(292, 96, 10);
await check("C9 获得（D6 第三段）", "d.clues.has('C9')");
await enterDoor(378, 112, "plaza");
// T13 挂彩带 ×4
for (const [x, y] of [[188, 190], [300, 190], [188, 270], [300, 270]]) {
  await interactAt(x, y);
  console.log("   ribbons:", await D("[...d.ribbons].join('|')"));
}
await check("T13 完成", "d.questStatus['T13']==='done'");
// T14 保持微笑 60 秒（把计时快进以免真等 60s）
await interactAt(240, 210, 4); // 启动
await sleep(1500);
await page.evaluate(() => {
  const sc = window.__game.scene.getScenes(true).find((s) => s.player);
  sc.t14Start -= 61000; // 快进
});
await sleep(1500);
await talk(6);
await check("T14 完成", "d.questStatus['T14']==='done'");
await check("D6 黄昏（脚本雨）", "d.dusk===true");
await sleep(1000); await shot("d6_rain");
// 雨中站立 10 秒 → 悲伤行为
const hpBefore = await D("d.happiness");
await page.evaluate(() => {
  const sc = window.__game.scene.getScenes(true).find((s) => s.player);
  sc.rainStillAt -= 11000;
});
await sleep(800);
const hpAfter = await D("d.happiness");
console.log(`${hpAfter <= hpBefore ? "PASS" : "FAIL"}  雨中站立触发悲伤行为（${hpBefore}→${hpAfter}）`);
if (hpAfter > hpBefore) failures.push("rain sadact");
await sleepToNextDay();

// ===== Day 7（分歧日：E3 苏醒线） =====
await check("进入 Day 7", "d.day===7");
await check("线索 ≥6", "d.clues.size>=6");
await D("d.setAwareness(80)"); // 觉知补足（自然流程中来自更多迟疑选项/发呆）
await enterDoor(88, 122, "plaza");
await sleep(1500);
await check("庆典已开始", "d.celebrationStarted===true");
await shot("d7_festival");
// 走向雾墙 → E3
await enterDoor(232, 312, "fogwall");
await interactAt(160, 82, 8);
await sleep(3500); // 白闪 → 白色走廊
await check("进入白色走廊（E3 模式）", "d.e3Mode===true && d.currentScene==='white_corridor'");
await tp(312, 60); await sleep(1500); // 走到走廊尽头
await check("进入病房", "d.currentScene==='white_ward'");
await interactAt(80, 72, 6); // 病历
await sleep(16000);
await shot("d7_e3_final");

console.log("CONSOLE ERRORS:", errors.length);
errors.slice(0, 20).forEach((e) => console.log("  -", e));
console.log("FAILURES:", failures.length);
await browser.close();
await server.close();
process.exit(errors.length || failures.length ? 1 : 0);
