// Lowmoor 日本語化パイプライン: 抽出フェーズ
// index.html の JS 文字列リテラルを、原文そのままのオフセット付きで抽出し分類する。
import fs from 'fs';
import { scanJsStrings } from './jsscan.mjs';

const SRC = 'app/game/index.html';
const src = fs.readFileSync(SRC, 'utf8');

// --- <script> ブロックの範囲を取る ------------------------------------
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]);
}

// --- スキャナ: 文字列リテラルを raw のまま位置つきで拾う ----------------
// 返すのは {start,end,quote,raw,cooked}。start/end は引用符を含む全体。
// 走査は jsscan.mjs に任せる（正規表現リテラルとテンプレートの入れ子に対応）。
// ここに写しを置いていたが、1.6.3 の難読化コードで正規表現を飛ばせず、
// 位置がずれてコードの断片を文字列として拾っていた。
const scanStrings = (text, base) => scanJsStrings(text, base);

let strings = [];
for (const [a, b] of ranges) strings = strings.concat(scanStrings(src.slice(a, b), a));

// --- 分類 -------------------------------------------------------------
const isSvgPath = s => /<(path|circle|ellipse|polyline|polygon|rect|line )\b/.test(s) || /^[\sMLCQZAHVmlcqzahv0-9.,\-]+$/.test(s);
const isCodeish = s =>
  /^[a-z][a-zA-Z0-9]*$/.test(s) ||                     // 識別子
  /^[#.][-\w]+$/.test(s) ||                            // セレクタ
  /^[\w-]+:[^ ]/.test(s) && !/\s/.test(s) ||           // css宣言片
  /^lowmoor\./.test(s) ||                              // localStorage キー
  /^https?:/.test(s) ||
  /^[-\w]+$/.test(s) && s.length < 4;

const hasLetters = s => /[A-Za-z]{2,}/.test(s);
const words = s => (s.match(/[A-Za-z][A-Za-z'’-]*/g) || []).length;

// 「完結した文」= 大文字か引用符で始まり、終止符で終わる
const isSentence = s => /^["“(]?[A-Z0-9]/.test(s.trim()) && /[.!?…]["”)]?$/.test(s.trim());

const buckets = { svg: [], code: [], sentence: [], fragment: [], label: [] };
for (const st of strings) {
  const s = st.cooked;
  if (!hasLetters(s)) { buckets.code.push(st); continue; }
  if (isSvgPath(s)) { buckets.svg.push(st); continue; }
  if (isCodeish(s)) { buckets.code.push(st); continue; }
  const w = words(s);
  if (w >= 3 && isSentence(s)) buckets.sentence.push(st);
  else if (w <= 4 && !/[.!?]/.test(s)) buckets.label.push(st);
  else buckets.fragment.push(st);
}

const sum = a => a.reduce((x, st) => x + st.cooked.length, 0);
const uniq = a => new Set(a.map(st => st.cooked)).size;
const uniqChars = a => [...new Set(a.map(st => st.cooked))].reduce((x, s) => x + s.length, 0);

console.log('literals scanned:', strings.length);
for (const k of Object.keys(buckets)) {
  console.log(
    k.padEnd(9),
    String(buckets[k].length).padStart(6), 'occ',
    String(uniq(buckets[k])).padStart(6), 'uniq',
    String(uniqChars(buckets[k])).padStart(8), 'uniq-chars'
  );
}

// 出力: 翻訳対象の候補を種別ごとに
const dump = (name, arr) => {
  const u = [...new Set(arr.map(st => st.cooked))].sort((a, b) => b.length - a.length);
  fs.writeFileSync(name, JSON.stringify(u, null, 1));
  return u.length;
};
dump('out-sentence.json', buckets.sentence);
dump('out-fragment.json', buckets.fragment);
dump('out-label.json', buckets.label);

/* 翻訳に回す分を、ここで一緒に書き出す。
 *
 * この絞り込みは以前その場かぎりのコマンドでやっていて、スクリプトに
 * 入っていなかった。1.6.0 の更新で -safe 系が前の版のまま使われ、新しく
 * 増えた断片とラベルがまるごと未訳で残った。抽出の一部なのでここに置く。 */
const uniqOf = arr => [...new Set(arr.map(st => st.cooked))];

// 断片: 文の途中で切れているものは、単独で訳すと語順が壊れる。
const fragUnsafe = s =>
  /^[,;:.)\]}]/.test(s) || /^\s/.test(s) || /\s$/.test(s) ||
  /\b(and|or|of|the|a|an|to|in|for|with|at|by|is|are|was|were|that|which|as|from|upon|per)\s*$/i.test(s) ||
  /^(and|or|of|the|to|in|for|with|at|by|as|from|upon|per|but|so|than|then|it|its|his|her|their)\s/i.test(s) ||
  (/<\/?[a-z]/.test(s) && !/^[A-Z]/.test(s));
dump('out-fragment-safe.json', buckets.fragment.filter(st => !fragUnsafe(st.cooked)));

// ラベル: マークアップ片と、固有名詞だけのものを外す。
const labelOk = s => {
  if (/[<>$=]/.test(s)) return false;
  if (/^\s|\s$/.test(s)) return false;
  if (!/[a-z]{2}/.test(s)) return false;          // 小文字語がない＝固有名詞のみ
  return (s.match(/[A-Za-z][A-Za-z'-]*/g) || []).length >= 2;
};
dump('out-label-safe.json', buckets.label.filter(st => labelOk(st.cooked)));

// HTMLタグ・プレースホルダを含む割合
const withTag = buckets.sentence.filter(st => /<[a-z]/.test(st.cooked)).length;
const withPh = buckets.sentence.filter(st => /\{[a-z]+\}/.test(st.cooked)).length;
console.log('sentences containing HTML tags:', withTag, ' placeholders {x}:', withPh);
dump('out-code.json', buckets.code);
