// 残っている未翻訳リテラルを、どの定数/関数の中にあるかで束ねて報告する。
import fs from 'fs';

const src = fs.readFileSync('app/game/index.html', 'utf8');
const tm = JSON.parse(fs.readFileSync('tm.json', 'utf8'));
const ov = JSON.parse(fs.readFileSync('overrides.json', 'utf8'));
for (const k in ov) if (!k.startsWith('_')) tm[k] = ov[k];

const ranges = [];
{
  const re = /<script>([\s\S]*?)<\/script>/g;
  let m;
  while ((m = re.exec(src))) ranges.push([m.index + '<script>'.length, m.index + '<script>'.length + m[1].length]);
}
function scan(text, base) {
  const out = []; let i = 0; const n = text.length;
  while (i < n) {
    const c = text[i];
    if (c === '/' && text[i + 1] === '/') { const e = text.indexOf('\n', i); i = e < 0 ? n : e; continue; }
    if (c === '/' && text[i + 1] === '*') { const e = text.indexOf('*/', i + 2); i = e < 0 ? n : e + 2; continue; }
    if (c === '"' || c === "'" || c === '`') {
      const q = c; let j = i + 1, cooked = '', bad = false;
      while (j < n) {
        const d = text[j];
        if (d === '\\') { cooked += text[j + 1]; j += 2; continue; }
        if (d === q) break;
        if (q !== '`' && d === '\n') { bad = true; break; }
        cooked += d; j++;
      }
      if (bad || j >= n) { i++; continue; }
      out.push({ start: base + i, quote: q, cooked });
      i = j + 1; continue;
    }
    i++;
  }
  return out;
}
let strings = [];
for (const [a, b] of ranges) strings = strings.concat(scan(src.slice(a, b), a));

// 行番号
const lineStarts = [0];
for (let i = 0; i < src.length; i++) if (src[i] === '\n') lineStarts.push(i + 1);
const lineOf = pos => {
  let lo = 0, hi = lineStarts.length - 1;
  while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (lineStarts[mid] <= pos) lo = mid; else hi = mid - 1; }
  return lo + 1;
};
// 直前のトップレベル宣言名
const decls = [];
{
  const re = /^(?:const|let|function)\s+([A-Za-z_$][\w$]*)/gm;
  let m;
  while ((m = re.exec(src))) decls.push([m.index, m[1]]);
}
const ownerOf = pos => {
  let lo = 0, hi = decls.length - 1, best = '(top)';
  while (lo <= hi) { const mid = (lo + hi) >> 1; if (decls[mid][0] <= pos) { best = decls[mid][1]; lo = mid + 1; } else hi = mid - 1; }
  return best;
};

const dotProps = new Set([...src.matchAll(/\.([A-Za-z_$][\w$]*)/g)].map(m => m[1]));
const hasLetters = s => /[A-Za-z]{2,}/.test(s);
const svgish = s => /<(path|circle|ellipse|polyline|polygon|rect|line )\b/.test(s) ||
  /^[\sMLCQZAHVmlcqzahv0-9.,\-]+$/.test(s);
const codeish = s => /^[#.][-\w]+$/.test(s) || /^lowmoor\./.test(s) || /^https?:/.test(s) ||
  /^[\w-]+$/.test(s) && s.length < 3;

const groups = new Map();
let total = 0;
for (const st of strings) {
  const s = st.cooked;
  if (tm[s] !== undefined) continue;
  if (!hasLetters(s) || svgish(s) || codeish(s)) continue;
  if (s.startsWith('data:')) continue;
  const owner = ownerOf(st.start);
  const kind = s.includes('${') ? 'template'
    : (/^[A-Za-z_$][\w$]*$/.test(s) && dotProps.has(s)) ? 'ident'
      : 'plain';
  const key = owner + ' | ' + kind;
  if (!groups.has(key)) groups.set(key, { n: 0, chars: 0, line: lineOf(st.start), ex: [] });
  const g = groups.get(key);
  g.n++; g.chars += s.length; total += s.length;
  if (g.ex.length < 3) g.ex.push(s.length > 90 ? s.slice(0, 90) + '…' : s);
  groups.set(key, g);
}

const rows = [...groups.entries()].sort((a, b) => b[1].chars - a[1].chars);
console.log('untranslated literals total chars:', total, 'groups:', rows.length);
console.log('');
for (const [k, g] of rows.slice(0, 45)) {
  console.log(String(g.chars).padStart(7), String(g.n).padStart(5) + 'x', 'L' + String(g.line).padStart(6), k);
  for (const e of g.ex) console.log('        · ' + JSON.stringify(e));
}

// 種別合計
const byKind = {};
for (const [k, g] of rows) { const kind = k.split(' | ')[1]; byKind[kind] = (byKind[kind] || 0) + g.chars; }
console.log('\nby kind:', byKind);
