import fs from "fs";

import path from "path";

import { XMLParser } from "fast-xml-parser";



/** @typedef {{

    LCX: {

        $_TgtCul: string;

        Item: {

            Item: {

                Item: {

                    $_ItemId: string;

                    Str: {

                        Val: string;

                        Tgt: {

                            Val: string;

                        };

                    };

                }[];

            };

        };

    }

}} ParsedLCL */

void 0;



async function main() {

    const args = process.argv.slice(2);

    if (args.length !== 3) {

        console.log("Usage:");

        console.log("\tnode generateLocalizedDiagnosticMessages.js <lcl source directory> <output directory> <generated diagnostics map file>");

        return;

    }



    const inputPath = args[0];

    const outputPath = args[1];

    const diagnosticsMapFilePath = args[2];



    // generate the lcg file for enu

    await generateLCGFile();



    // generate other langs

    const files = await fs.promises.readdir(inputPath);

    await Promise.all(files.map(visitDirectory));



    return;



    /**

     * @param {string} name

     */

    async function visitDirectory(name) {

        const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl");

        const contents = await fs.promises.readFile(inputFilePath);

        /** @type {ParsedLCL} */

        // eslint-disable-next-line local/object-literal-surrounding-space

        const result = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "$_"}).parse(contents);

        if (!result || !result.LCX || !result.LCX.$_TgtCul) {

            console.error("Unexpected XML file structure. Expected to find result.LCX.$_TgtCul.");

            process.exit(1);

        }

        const outputDirectoryName = getPreferredLocaleName(result.LCX.$_TgtCul).toLowerCase();

        if (!outputDirectoryName) {

            console.error(`Invalid output locale name for '${result.LCX.$_TgtCul}'.`);

            process.exit(1);

        }

        await writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result));

    }



    /**

     * A locale name is based on the language tagging conventions of RFC 4646 (Windows Vista

     * and later), and is represented by LOCALE_SNAME.

     * Generally, the pattern <language>-<REGION> is used. Here, language is a lowercase ISO 639

     * language code. The codes from ISO 639-1 are used when available. Otherwise, codes from

     * ISO 639-2/T are used. REGION specifies an uppercase ISO 3166-1 country/region identifier.

     * For example, the locale name for English (United States) is "en-US" and the locale name

     * for Divehi (Maldives) is "dv-MV".

     *

     * If the locale is a neutral locale (no region), the LOCALE_SNAME value follows the

     * pattern <language>. If it is a neutral locale for which the script is significant, the

     * pattern is <language>-<Script>.

     *

     * More at https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx

     *

     * Most of the languages we support are neutral locales, so we want to use the language name.

     * There are three exceptions, zh-CN, zh-TW and pt-BR.

     *

     * @param {string} localeName

     */

    function getPreferredLocaleName(localeName) {

        switch (localeName) {

            case "zh-CN":

            case "zh-TW":

            case "pt-BR":

                return localeName;

            default:

                return localeName.split("-")[0];

        }

    }



    /**

     * @param {ParsedLCL} o

     */

    function xmlObjectToString(o) {

        /** @type {any} */

        const out = {};

        for (const item of o.LCX.Item.Item.Item) {

            let ItemId = item.$_ItemId;

            let val = item.Str.Tgt ? item.Str.Tgt.Val : item.Str.Val;



            if (typeof ItemId !== "string" || typeof val !== "string") {

                console.error("Unexpected XML file structure");

                process.exit(1);

            }



            if (ItemId.charAt(0) === ";") {

                ItemId = ItemId.slice(1); // remove leading semicolon

            }



            val = val.replace(/]5D;/, "]"); // unescape `]`

            out[ItemId] = val;

        }

        return JSON.stringify(out, undefined, 2);

    }



    /**

     * @param {string} fileName

     * @param {string} contents

     */

    async function writeFile(fileName, contents) {

        await fs.promises.mkdir(path.dirname(fileName), { recursive: true });

        await fs.promises.writeFile(fileName, contents);

    }



    async function generateLCGFile() {

        const contents = await fs.promises.readFile(diagnosticsMapFilePath, "utf-8");

        await writeFile(

            path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"),

            getLCGFileXML(

                Object.entries(JSON.parse(contents))

                    .sort((a, b) => a[0] > b[0] ? 1 : -1)  // lcg sorted by property keys

                    .reduce((s, [key, value]) => s + getItemXML(key, value), "")

            ),

        );

        return;



        /**

         * @param {string} key

         * @param {string} value

         */

        function getItemXML(key, value) {

            // escape entrt value

            value = value.replace(/]/g, "]5D;");



            return `

            <Item ItemId=";${key}" ItemType="0" PsrId="306" Leaf="true">

              <Str Cat="Text">

                <Val><![CDATA[${value}]]></Val>

              </Str>

              <Disp Icon="Str" />

            </Item>`;

        }



        /**

         * @param {string} items

         */

        function getLCGFileXML(items) {

            return `<?xml version="1.0" encoding="utf-8"?>

<LCX SchemaVersion="6.0" Name="diagnosticMessages.generated.json" PsrId="306" FileType="1" SrcCul="en-US" xmlns="http://schemas.microsoft.com/locstudio/2006/6/lcx">

  <OwnedComments>

    <Cmt Name="Dev" />

    <Cmt Name="LcxAdmin" />

    <Cmt Name="Rccx" />

  </OwnedComments>

  <Item ItemId=";String Table" ItemType="0" PsrId="306" Leaf="false">

    <Disp Icon="Expand" Expand="true" Disp="true" LocTbl="false" />

    <Item ItemId=";Strings" ItemType="0" PsrId="306" Leaf="false">

      <Disp Icon="Str" Disp="true" LocTbl="false" />${items}

    </Item>

  </Item>

</LCX>`;

        }

    }

}



await main();