// Development helper for turning an AllTheThings Zones.lua map block into
// compact, reviewable ID lists. It does not run in WoW or ship ATT data.
const fs = require("fs");

const [input, mapIDText, nextMapIDText, format] = process.argv.slice(2);
if (!input || !mapIDText || !nextMapIDText) {
    throw new Error("usage: node extract-att-zone.js <Zones.lua> <mapID> <nextTopLevelMapID>");
}

const source = fs.readFileSync(input, "utf8");
const startToken = `\nm(${mapIDText},{`;
const endToken = `\nm(${nextMapIDText},`;
const start = source.indexOf(startToken);
const end = source.indexOf(endToken, start + startToken.length);
if (start < 0 || end < 0) throw new Error("map block boundaries not found");
const zone = source.slice(start + 1, end);

function extractCall(text, startIndex) {
    let depth = 0;
    let quote = null;
    let escaped = false;
    for (let index = text.indexOf("(", startIndex); index < text.length; index++) {
        const char = text[index];
        if (quote) {
            if (escaped) escaped = false;
            else if (char === "\\") escaped = true;
            else if (char === quote) quote = null;
            continue;
        }
        if (char === '"' || char === "'") { quote = char; continue; }
        if (char === "(") depth++;
        else if (char === ")" && --depth === 0) return text.slice(startIndex, index + 1);
    }
    throw new Error(`unterminated call at ${startIndex}`);
}

function calls(text, name, firstArgumentPattern = "\\d+") {
    const expression = new RegExp(`(?:^|\\n)${name}\\((${firstArgumentPattern}),`, "g");
    const found = [];
    for (let match; (match = expression.exec(text));) {
        const startIndex = match.index + (match[0][0] === "\n" ? 1 : 0);
        found.push({ id: Number(match[1]), text: extractCall(text, startIndex) });
    }
    return found;
}

function header(call) {
    const group = call.indexOf(",g={");
    return group < 0 ? call : call.slice(0, group);
}

function numberList(text, field) {
    const match = text.match(new RegExp(`(?:^|,)${field}=\\{([0-9,]+)\\}`));
    return match ? match[1].split(",").map(Number) : undefined;
}

function questEntry(call) {
    const top = header(call.text);
    if (/[,{}]u=2(?:[,}])/.test(top) || /(?:isBreadcrumb|isDaily|isWeekly|repeatable)=1/.test(top)) return;
    const entry = { id: call.id };
    entry.classes = numberList(top, "c");
    entry.races = numberList(top, "races");
    entry.alternatives = numberList(top, "altQuests");
    const faction = top.match(/(?:^|,)r=([12])(?:[,}])/);
    if (faction) entry.faction = Number(faction[1]);
    return entry;
}

const quests = calls(zone, "q").map(questEntry).filter(Boolean);
const questByID = new Map();
for (const quest of quests) if (!questByID.has(quest.id)) questByID.set(quest.id, quest);

const transmogs = new Map();
for (const sourceCall of calls(zone, "s")) {
    const top = header(sourceCall.text);
    if (/[,{}]u=2(?:[,}])/.test(top)) continue;
    const item = sourceCall.text.match(/^s\(\d+,(\d+),/);
    if (item) transmogs.set(sourceCall.id, Number(item[1]));
}

const rares = new Map();
for (const rareGroup of calls(zone, "h", "-46")) {
    for (const npc of calls(rareGroup.text, "n")) {
        for (const sourceCall of calls(npc.text, "s")) {
            const top = header(sourceCall.text);
            const item = sourceCall.text.match(/^s\(\d+,(\d+),/);
            if (item && !/[,{}]u=2(?:[,}])/.test(top)) {
                rares.set(`${npc.id}:${item[1]}`, { npcID: npc.id, itemID: Number(item[1]), sourceID: sourceCall.id });
            }
        }
        for (const itemCall of calls(npc.text, "i")) {
            const top = header(itemCall.text);
            if (!/[,{}]u=2(?:[,}])/.test(top)) rares.set(`${npc.id}:${itemCall.id}`, { npcID: npc.id, itemID: itemCall.id });
        }
    }
}

const result = {
    mapID: Number(mapIDText),
    quests: [...questByID.values()],
    transmogs: [...transmogs].map(([sourceID, itemID]) => ({ sourceID, itemID })),
    rares: [...rares.values()],
};

if (format === "lua") {
    const list = values => `{ ${values.join(", ")} }`;
    const quest = value => {
        if (!value.classes && !value.races && !value.alternatives && !value.faction) return String(value.id);
        const fields = [`id = ${value.id}`];
        if (value.classes) fields.push(`classes = ${list(value.classes)}`);
        if (value.races) fields.push(`races = ${list(value.races)}`);
        if (value.alternatives) fields.push(`alternatives = ${list(value.alternatives)}`);
        if (value.faction) fields.push(`faction = ${value.faction}`);
        return `{ ${fields.join(", ")} }`;
    };
    console.log("quests = {");
    for (const value of result.quests) console.log(`    ${quest(value)},`);
    console.log("},\ntransmogs = {");
    for (const value of result.transmogs) console.log(`    ${value.sourceID}, -- item ${value.itemID}`);
    console.log("},\nrares = {");
    for (const value of result.rares) {
        console.log(`    { npcID = ${value.npcID}, itemID = ${value.itemID}${value.sourceID ? `, sourceID = ${value.sourceID}` : ""} },`);
    }
    console.log("},");
} else {
    console.log(JSON.stringify(result, null, 2));
}
