#!/usr/bin/env node
// Regenerate src/models/iso_codes.rs from the ISO source data in data/.
//
// Usage: node tools/gen-locales.mjs
//
// Sources:
//   data/iso-3166-1.json  - ISO 3166-1 alpha-2 country codes
//   data/iso-639-1.csv    - ISO 639-1 two-letter language codes

import { promises as fs } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..');

const countriesJson = JSON.parse(
    await fs.readFile(join(repoRoot, 'data/iso-3166-1.json'), 'utf8'),
);
const countries = Object.entries(countriesJson)
    .map(([code, v]) => [code.toUpperCase(), v.name])
    .sort(([a], [b]) => a.localeCompare(b));

const languagesCsv = await fs.readFile(join(repoRoot, 'data/iso-639-1.csv'), 'utf8');
const languages = languagesCsv
    .split(/\r?\n/)
    .slice(1)
    .map(line => line.trim())
    .filter(Boolean)
    .map(line => {
        const m = line.match(/^"([^"]+)"\s*,\s*"([^"]*)"/);
        if (!m) throw new Error(`unparsable CSV line: ${line}`);
        return [m[1].toLowerCase(), m[2]];
    })
    .sort(([a], [b]) => a.localeCompare(b));

const pascal = code => code[0].toUpperCase() + code.slice(1).toLowerCase();
const escape = s => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');

function emitEnum(out, name, entries, isUpper, doc) {
    out.push(`/// ${doc}`);
    out.push('///');
    out.push('/// Auto-generated by `tools/gen-locales.mjs` — do not edit by hand.');
    out.push('#[allow(non_camel_case_types)]');
    out.push('#[derive(Debug, Clone, PartialEq, Eq)]');
    out.push(`pub enum ${name} {`);
    for (const [code] of entries) out.push(`    ${pascal(code)},`);
    out.push('}');
    out.push('');

    out.push(`impl ${name} {`);
    out.push('    /// Returns the canonical ISO code string.');
    out.push('    pub fn as_str(&self) -> &\'static str {');
    out.push('        match self {');
    for (const [code] of entries) {
        const display = isUpper ? code.toUpperCase() : code.toLowerCase();
        out.push(`            ${name}::${pascal(code)} => "${display}",`);
    }
    out.push('        }');
    out.push('    }');
    out.push('');
    out.push('    /// Returns the English name from the ISO source data.');
    out.push('    pub fn english_name(&self) -> &\'static str {');
    out.push('        match self {');
    for (const [code, label] of entries) {
        out.push(`            ${name}::${pascal(code)} => "${escape(label)}",`);
    }
    out.push('        }');
    out.push('    }');
    out.push('');
    out.push('    /// Iterates over every variant in code order.');
    out.push(`    pub fn all() -> &'static [${name}] {`);
    out.push(`        &[${entries.map(([code]) => `${name}::${pascal(code)}`).join(', ')}]`);
    out.push('    }');
    out.push('}');
    out.push('');

    out.push(`impl FromStr for ${name} {`);
    out.push('    type Err = String;');
    out.push('    fn from_str(s: &str) -> Result<Self, Self::Err> {');
    out.push(`        match s.${isUpper ? 'to_ascii_uppercase' : 'to_ascii_lowercase'}().as_str() {`);
    for (const [code] of entries) {
        const display = isUpper ? code.toUpperCase() : code.toLowerCase();
        out.push(`            "${display}" => Ok(${name}::${pascal(code)}),`);
    }
    out.push(`            _ => Err(format!("unknown ${name.toLowerCase()}: {s}")),`);
    out.push('        }');
    out.push('    }');
    out.push('}');
    out.push('');

    out.push(`impl serde::Serialize for ${name} {`);
    out.push('    fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {');
    out.push('        ser.serialize_str(self.as_str())');
    out.push('    }');
    out.push('}');
    out.push('');
    out.push(`impl<'de> serde::Deserialize<'de> for ${name} {`);
    out.push("    fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {");
    out.push("        let s = <std::borrow::Cow<'_, str>>::deserialize(de)?;");
    out.push(`        <${name} as FromStr>::from_str(&s).map_err(serde::de::Error::custom)`);
    out.push('    }');
    out.push('}');
    out.push('');
}

const out = [];
out.push('// Auto-generated by `tools/gen-locales.mjs`.');
out.push('// Source data lives in `data/iso-3166-1.json` (ISO 3166-1 alpha-2) and');
out.push('// `data/iso-639-1.csv` (ISO 639-1). Re-run the generator after updating either.');
out.push('//');
out.push(`// Countries: ${countries.length}    Languages: ${languages.length}`);
out.push('');
out.push('use std::str::FromStr;');
out.push('');

emitEnum(out, 'Market', countries, true, 'A Microsoft Store market, identified by an ISO 3166-1 alpha-2 country code.');
emitEnum(out, 'Lang', languages, false, 'A Microsoft Store UI language, identified by an ISO 639-1 alpha-2 code.');

const target = join(repoRoot, 'src/models/iso_codes.rs');
await fs.writeFile(target, out.join('\n'));
console.log(`Wrote ${target}: ${countries.length} markets, ${languages.length} languages`);
