// 全メニュー・全タブを巡回して、画面に残っている英語を集める。
// 1語だけの語（common / heavy など）も拾う点が findenglish.mjs と違う。
import fs from 'fs';
import { spawn } from 'child_process';

const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe';
const PORT = 9512, profile = 'C:/Users/ADMINI~1/AppData/Local/Temp/lm-sweep';
fs.rmSync(profile, { recursive: true, force: true });
const chrome = spawn(CHROME, ['--headless=new', '--disable-gpu', '--no-sandbox', '--no-first-run',
  '--remote-debugging-port=' + PORT, '--user-data-dir=' + profile,
  '--window-size=1500,1000', 'about:blank'], { stdio: 'ignore' });
const sleep = ms => new Promise(r => setTimeout(r, ms));

let list = null;
for (let i = 0; i < 40; i++) {
  try { list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json(); if (list.length) break; } catch { }
  await sleep(300);
}
const ws = new WebSocket(list.find(t => t.type === 'page').webSocketDebuggerUrl);
await new Promise(r => ws.addEventListener('open', r));
let id = 0; const pend = new Map();
ws.addEventListener('message', e => {
  const m = JSON.parse(e.data);
  if (m.id && pend.has(m.id)) { pend.get(m.id)(m.result); pend.delete(m.id); }
});
const send = (m, p = {}) => new Promise(res => { const i = ++id; pend.set(i, res); ws.send(JSON.stringify({ id: i, method: m, params: p })); });
const ev = async x => (await send('Runtime.evaluate', { expression: x, returnByValue: true, awaitPromise: true })).result?.value;

await send('Page.enable'); await send('Runtime.enable');
const file = process.argv[2] || 'build/index-app.html';
await send('Page.navigate', { url: 'file:///' + process.cwd().replace(/\\/g, '/') + '/' + file });
await sleep(4500);

// 装備・獣・依頼が溜まるまで進める
for (let k = 0; k < 8; k++) { await ev('(function(){try{fastForward(7200);}catch(e){}})()'); await sleep(400); }
await sleep(1500);

// 収集器をページ側に置く
await ev(`
window.__hits = new Map();
window.__collect = function(scr){
  const push = (t, where) => {
    t = t.replace(/\\s+/g,' ').trim();
    if (!t) return;
    if (!/[A-Za-z]{2}/.test(t)) return;
    // 日本語が少しでも入っていれば済み、としていたが、それだと
    //   「道を見守る監視者、そして…: ten, and your Wisdom, and every lens…」
    // のような **途中まで訳された文** が丸ごと見えなくなる。実際これで
    // 看破・足場の説明文が英語のまま残っているのを取り逃がした。
    // 英語の連なりが実質的に残っているかどうかで見る。
    const jaChars = (t.match(/[ぁ-んァ-ヴ一-龯]/g) || []).length;
    if (jaChars) {
      const enRun = (t.match(/[A-Za-z][A-Za-z'’,.\\-]*(?:\\s+[A-Za-z][A-Za-z'’,.\\-]*){2,}/g) || [])
        .filter(x => x.length > 14);
      if (!enRun.length) return;                       // 英単語が散っているだけ
      for (const x of enRun) {
        const k = 'MIX:' + x.toLowerCase();
        if (!window.__hits.has(k)) window.__hits.set(k, {t: '【混在】' + x, where, screens:new Set(), n:0});
        const h = window.__hits.get(k); h.n++; h.screens.add(scr);
      }
      return;
    }
    if (/^[0-9.,:%+\\-×✓✗ ]+$/.test(t)) return;
    if (/^(HP|MP|XP|AC|STR|DEX|CON|INT|WIS|CHA|LV|GP|SP|CP|EXP|DPS|ID|OK|II|III|IV|VI|IX|XI)$/i.test(t)) return;
    const k = t.toLowerCase();
    if (!window.__hits.has(k)) window.__hits.set(k, {t, where, screens:new Set(), n:0});
    const h = window.__hits.get(k); h.n++; h.screens.add(scr);
  };
  const walk = n => {
    if (n.nodeType === 3) { push(n.textContent, (n.parentElement&&(n.parentElement.id||n.parentElement.className))||''); return; }
    if (n.nodeType !== 1) return;
    if (n.tagName === 'SCRIPT' || n.tagName === 'STYLE') return;
    const cs = getComputedStyle(n);
    if (cs.display === 'none' || cs.visibility === 'hidden') return;
    for (const c of n.childNodes) walk(c);
  };
  walk(document.body);
  for (const el of document.querySelectorAll('[data-help],[title],[aria-label],[placeholder]')) {
    for (const a of ['data-help','title','aria-label','placeholder']) {
      const v = el.getAttribute(a); if (v) push(v, a);
    }
  }
};
window.__clickable = function(){
  const out = [];
  document.querySelectorAll('button,[role=tab],.tab,.pill,.chip,.seg,.subtab,[data-tab]').forEach((b,i)=>{
    const cs = getComputedStyle(b);
    if (cs.display==='none'||cs.visibility==='hidden') return;
    if (!b.offsetParent && cs.position!=='fixed') return;
    out.push(i);
  });
  return out;
};
`);

// メニューを列挙
const menus = await ev(`[...document.querySelectorAll('[id^="m-"]')].map(b=>b.id)`);
console.log('メニュー', menus.length, '件:', menus.join(' '));

const screens = [];
for (const m of menus) {
  await ev(`(function(){const b=document.getElementById(${JSON.stringify(m)}); if(b) b.click();})()`);
  await sleep(900);
  await ev(`window.__collect(${JSON.stringify(m)})`);
  screens.push(m);
  // その画面の中のタブ／小見出しボタンを順に押す
  const n = await ev(`(function(){
     const bs=[...document.querySelectorAll('#panel button, #main button, .panel button, [role=tab], .tab, .seg > *')]
        .filter(b=>{const c=getComputedStyle(b); return c.display!=='none'&&c.visibility!=='hidden'&&b.offsetParent;});
     window.__tabs=bs; return bs.length;})()`);
  const lim = Math.min(n || 0, 14);
  for (let i = 0; i < lim; i++) {
    await ev(`(function(){try{const b=window.__tabs[${i}]; if(b&&document.contains(b)) b.click();}catch(e){}})()`);
    await sleep(320);
    await ev(`window.__collect(${JSON.stringify(m + '#' + i)})`);
  }
  await sleep(200);
}

const hits = await ev(`[...window.__hits.values()].map(h=>({t:h.t, where:h.where, n:h.n, screens:[...h.screens].slice(0,4)}))`);
console.log('\n巡回した画面:', screens.length);
console.log('英語のまま残っている文字列:', hits.length, '件');

hits.sort((a, b) => b.n - a.n || b.t.length - a.t.length);
fs.writeFileSync('screen-en-all.json', JSON.stringify(hits, null, 1));

const short = hits.filter(h => h.t.split(/\s+/).length <= 2);
const long = hits.filter(h => h.t.split(/\s+/).length > 2);
console.log(`\n--- 短い語（2語以下） ${short.length} 件 ---`);
short.slice(0, 90).forEach(h => console.log(`  ${String(h.n).padStart(4)}回  ${h.t.slice(0, 46).padEnd(46)} [${String(h.where).slice(0, 22)}]`));
console.log(`\n--- 文・句（3語以上） ${long.length} 件 ---`);
long.slice(0, 60).forEach(h => console.log(`  ${String(h.n).padStart(4)}回  ${h.t.slice(0, 96)}`));

ws.close(); chrome.kill();
