// 難読化された 1.6.3 のソースから、語彙表と補助関数の「潰された名前」を割り出す。
//
// 1.6.3 でコードが minify され、変数名は 1〜3 文字になった。
//   MECH_ADD → Le,  WORLD_ADD → Oe,  mechLine → Ue,  sNum → H,  fmtFig → B …
// 一方 **オブジェクトのキーは残っている**（critDmg, keeneye, procs …）。
// data から引くので潰せないためで、ここを足場にすれば所在が分かる。
//
// 名前を決め打ちにすると次の版でまた黙って外れるので、毎回ここで割り出す。
export function mapMinified(src) {
  const out = { ok: true, notes: [] };

  // --- オブジェクトの範囲を、開き波括弧から対応する閉じまで取る ---------
  const objAt = i => {
    let d = 0, j = i;
    for (; j < src.length; j++) {
      const c = src[j];
      if (c === '"' || c === "'" || c === '`') { const q = c; j++; while (j < src.length) { if (src[j] === '\\') { j += 2; continue; } if (src[j] === q) break; j++; } continue; }
      if (c === '{') d++;
      else if (c === '}') { d--; if (d === 0) return { start: i, end: j + 1, body: src.slice(i, j + 1) }; }
    }
    return null;
  };

  // --- 表を探す: `名前={critDmg:` / `名前={morning:` の形 ----------------
  const findTables = (firstKey) => {
    const res = [];
    const re = new RegExp('([A-Za-z_$][\\w$]{0,3})\\s*=\\s*\\{' + firstKey + ':', 'g');
    let m;
    while ((m = re.exec(src))) {
      const o = objAt(m.index + m[0].length - firstKey.length - 2);
      if (o) res.push({ name: m[1], ...o });
    }
    return res;
  };

  const adds = findTables('critDmg');
  const mults = findTables('morning');
  if (adds.length !== 2 || mults.length !== 2) {
    out.ok = false;
    out.notes.push(`表が見つかりません（ADD ${adds.length} / MULT ${mults.length}、どちらも 2 のはず）`);
    return out;
  }
  // 平明な声は " more" を使い、世界の声は " further" を使う
  const pick = (arr, plainMark) => {
    const plain = arr.find(t => t.body.includes(plainMark));
    const world = arr.find(t => t !== plain);
    return { plain, world };
  };
  const A = pick(adds, '" more"'), M = pick(mults, '" more"');
  if (!A.plain || !A.world || !M.plain || !M.world) {
    out.ok = false; out.notes.push('平明な声と世界の声を見分けられません');
    return out;
  }
  out.MECH_ADD = A.plain; out.WORLD_ADD = A.world;
  out.MECH_MULT = M.plain; out.WORLD_MULT = M.world;

  // --- 補助関数の潰された名前 ------------------------------------------
  // critDmg:(e,t)=>H(e)+"%"...   の H が sNum
  let m = A.plain.body.match(/critDmg:\(([\w$]+),([\w$]+)\)=>([\w$]+)\(\1\)\+"%"/);
  if (!m) { out.ok = false; out.notes.push('sNum を割り出せません'); return out; }
  out.v = m[1]; out.f = m[2]; out.sNum = m[3];

  // wound:(e,t)=>(e<0?"+":"-")+B(Math.abs(e))   の B が fmtFig
  m = A.plain.body.match(/wound:\([\w$]+,[\w$]+\)=>\([\w$]+<0\?"\+":"-"\)\+([\w$]+)\(Math\.abs/);
  if (!m) { out.ok = false; out.notes.push('fmtFig を割り出せません'); return out; }
  out.fmtFig = m[1];

  // xp:(e,t)=>(e>1?"+":"-")+P(e)+"%"   の P が mPct
  m = M.plain.body.match(/xp:\([\w$]+,[\w$]+\)=>\([\w$]+>1\?"\+":"-"\)\+([\w$]+)\(/);
  if (!m) { out.ok = false; out.notes.push('mPct を割り出せません'); return out; }
  out.mPct = m[1];

  // restBonus:(e,t)=>S(e)+"%"   の S が sPct
  m = A.plain.body.match(/restBonus:\([\w$]+,[\w$]+\)=>([\w$]+)\(/);
  if (!m) { out.ok = false; out.notes.push('sPct を割り出せません'); return out; }
  out.sPct = m[1];

  // --- mechLine（表を引く関数）------------------------------------------
  // function Ue(e,t,a){ … 0===e.indexOf("procs:") … }
  m = src.match(/function ([\w$]+)\(([\w$]+),([\w$]+),([\w$]+)\)\{[^}]{0,400}?indexOf\("procs:"\)/);
  if (!m) { out.ok = false; out.notes.push('mechLine を割り出せません'); return out; }
  out.mechLine = m[1];
  out.mechLineStart = m.index;
  // 本体の終わりを探す
  {
    let d = 0, j = src.indexOf('{', m.index);
    for (; j < src.length; j++) {
      const c = src[j];
      if (c === '"' || c === "'") { const q = c; j++; while (j < src.length) { if (src[j] === '\\') { j += 2; continue; } if (src[j] === q) break; j++; } continue; }
      if (c === '{') d++; else if (c === '}') { d--; if (d === 0) { out.mechLineEnd = j + 1; break; } }
    }
  }

  // --- 学派の名前表と procs 関数 ----------------------------------------
  const schools = [];
  for (const mm of src.matchAll(/([A-Za-z_$][\w$]{0,3})\s*=\s*\{sight:"([^"]*)"/g)) {
    const o = objAt(src.indexOf('{', mm.index));
    if (o) schools.push({ name: mm[1], first: mm[2], ...o });
  }
  out.schools = schools;

  // (R[e]||e)+" abilities trigger " の形 = MECH_PROCS
  m = src.match(/([\w$]+)\s*=\s*\(([\w$]+),([\w$]+),([\w$]+)\)=>\(([\w$]+)\[\2\]\|\|\2\)\+" abilities trigger "[^;]*?;/);
  if (m) { out.MECH_PROCS = { name: m[1], start: m.index, end: m.index + m[0].length, schoolTable: m[5] }; }
  // ("fury"===e?" fires ":" fire ") を含むのが WORLD_PROCS
  m = src.match(/([\w$]+)\s*=\s*\(([\w$]+),([\w$]+),([\w$]+)\)=>[^;]*?"fury"===\2\?" fires ":" fire "[^;]*?;/);
  if (m) { out.WORLD_PROCS = { name: m[1], start: m.index, end: m.index + m[0].length }; }

  return out;
}
