// Pure decisions behind js/shared/script_colours.mjs: which parts of a script
// get which colour. No DOM, so tests run it under node:test.
const escape = (text) => text.replace(/&/g, "&").replace(//g, ">");
const span = (kind, text) => `${escape(text)}`;
const TOKENS = /(@\d)|("[^"\n]*"?)/g;
const LABEL = /^\s*(sound|music|camera|look):/i;
function lineMarkup(line, wired) {
let head = "";
let rest = line;
const time = rest.match(/^(\s*)(\d+(?:\.\d+)?s:)/);
const key = rest.match(/^(\s*)(sound|music|camera|look):/i);
if (time) { head = escape(time[1]) + span("time", time[2]); rest = rest.slice(time[0].length); }
else if (key) { head = escape(key[1]) + span("key", `${key[2]}:`); rest = rest.slice(key[0].length); }
let out = head;
let at = 0;
for (const match of rest.matchAll(TOKENS)) {
out += escape(rest.slice(at, match.index));
if (match[1]) {
const number = Number(match[1].slice(1));
out += span(number >= 1 && number <= 4 && wired(number) ? "at" : "at unknown", match[1]);
} else {
out += span("say", match[2]);
}
at = match.index + match[0].length;
}
return out + escape(rest.slice(at));
}
// The coloured copy of a script, as HTML. wired(number) says whether a
// picture is plugged in under that @ number; one that isn't shows amber.
// A blank line between two lines is a cut, unless the script says no cuts or
// what follows is a label line.
export function scriptMarkup(text, wired = () => true) {
const lines = String(text ?? "").split("\n");
const cuts = !/\bno cuts\b/i.test(String(text ?? ""));
const filled = lines.map((line) => line.trim() !== "");
// A sound:, music:, camera: or look: line on its own is for the whole clip, not a new shot.
const label = lines.map((line) => LABEL.test(line));
const html = lines.map((line, index) => {
if (filled[index]) return lineMarkup(line, wired);
const before = filled.slice(0, index).some(Boolean);
const next = filled.indexOf(true, index + 1);
const after = next >= 0 && !label[next];
const firstBlank = index > 0 && filled[index - 1];
return cuts && before && after && firstBlank ? '' : escape(line);
});
// A box that ends on a new line shows one more empty line.
return html.join("\n") + (String(text ?? "").endsWith("\n") ? " " : "");
}