// tools/split.js — one-off: cut index.html's script into ES modules by line ranges. Analysis mode by default; WRITE=1 writes.
// STEP=a.js,b.js extracts only those modules and leaves the rest as an inline module in index.html (the intermediate commits).
const fs = require("fs");
const src = fs.readFileSync("index.html", "utf8").split("\n");
const S = 402, E = 2199; // at E
const cuts = JSON.parse(fs.readFileSync("tools/split-map.json", "utf8")); // [[startLine, "file"], ...] sorted
const body = src.slice(S, E - 1); // lines S+1 .. E-1 (1-based)
const at = (n) => n - (S + 1); // index into body
const chunks = [];
for (let i = 0; i < cuts.length; i++) {
const [a, f] = cuts[i], b = i + 1 < cuts.length ? cuts[i + 1][0] : E;
chunks.push({ file: f, from: a, to: b - 1, lines: body.slice(at(a), at(b)) });
}
const STEP = process.env.STEP ? new Set(process.env.STEP.split(",")) : null;
for (const c of chunks) if (STEP && !STEP.has(c.file)) c.file = "index.html";
const mods = {};
for (const c of chunks) (mods[c.file] ||= { file: c.file, lines: [], decl: new Map(), lets: new Set() }).lines.push(...c.lines, "");
const declRe = /^(?:async\s+)?function\s+([A-Za-z_$][\w$]*)|^(const|let)\s+(.+?)(?:\s*=|;|$)/;
for (const m of Object.values(mods)) {
m.lines.forEach((ln, i) => {
let r = declRe.exec(ln); if (!r) return;
if (r[1]) m.decl.set(r[1], i);
else {
// const a = 1, b = 2; const { x } = y; const [a, b] = z
const head = ln.slice(r[2].length).trim();
const names = [...head.matchAll(/(?:^|,\s*)([A-Za-z_$][\w$]*)\s*=/g)].map((x) => x[1]);
if (/^\s*[\[{]/.test(head)) for (const n of head.slice(0, head.indexOf("=")).match(/[A-Za-z_$][\w$]*/g) || []) names.push(n);
if (/=>|function/.test(head)) names.length = 1;
for (const n of names) { m.decl.set(n, i); if (r[2] === "let") m.lets.add(n); }
}
});
}
if (mods["render.js"]) mods["render.js"].decl.set("setFetcher", -1); // added by tools/split-post.py after the cut
const owner = {}; for (const m of Object.values(mods)) for (const n of m.decl.keys()) { if (owner[n]) console.error("DUP", n, owner[n], m.file); owner[n] = m.file; }
const strip = (s) => s.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|`(?:\\.|[^`\\])*`|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'/g, (t) => t[0] === "`" ? (t.match(/\$\{[^}]*\}/g) || []).map((x) => x.slice(2, -1)).join(" ") : " ");
const GLOBALS = fs.readFileSync("tools/split-globals.txt", "utf8");
const report = {};
for (const m of Object.values(mods)) {
const text = strip(m.lines.join("\n") + (m.file === "app.js" || m.file === "index.html" ? GLOBALS : ""));
const used = new Set(text.replace(/(? [f, [...s]])), assignsForeign: m.assignsForeign };
}
for (const m of Object.values(mods)) report[m.file].exports = [...m.decl.keys()].filter((n) => Object.values(mods).some((o) => o !== m && o.imports[m.file] && o.imports[m.file].has(n)));
// window / brain.js / harness needs
const brain = strip(fs.readFileSync("brain.js", "utf8"));
const bUsed = [...new Set(brain.match(/(? owner[n]);
console.log(JSON.stringify({ report, brainUses: bUsed.map((n) => n + "@" + owner[n]) }, null, 1));
if (!process.env.WRITE) process.exit(0);
const rel = (from, to) => { const d = from.includes("/") ? "../" : "./"; return d + to; };
let inline = "";
for (const m of Object.values(mods)) {
const ex = new Set(report[m.file].exports);
const out = m.lines.map((ln, i) => { for (const [n, li] of m.decl) if (li === i && ex.has(n) && /^(const|let|async function|function)\b/.test(ln)) return "export " + ln; return ln; });
const head = Object.entries(m.imports).map(([f, s]) => `import { ${[...s].sort().join(", ")} } from "${rel(m.file, f)}";`);
const text = (head.length ? head.join("\n") + "\n" : "") + out.join("\n").replace(/\n+$/, "\n");
if (m.file === "index.html") { inline = text; continue; }
fs.mkdirSync(require("path").dirname(m.file), { recursive: true });
fs.writeFileSync(m.file, text + (m.file === "app.js" ? GLOBALS : ""));
}
const tag = inline ? [``] : [``];
const html = src.slice(0, S - 1).concat(tag, src.slice(E));
fs.writeFileSync("index.html", html.join("\n"));
console.error("written");