#!/usr/bin/env python3
"""Convert openpencil/apps/web/src/i18n/locales/*.ts files into
Rust match-arm tables under crates/op-i18n/src/i18n/<locale>.rs.

Each TS file exports a single object literal of the form:
    'key.name': 'value',
    'key.with.escape': 'a\\nb',
    'key.with.unicode': '…',

We emit a `pub fn lookup(key: &str) -> Option<&'static str>` that
returns the value for known keys and None otherwise.

Multi-line values aren't used in the TS source so a per-line regex
is sufficient.
"""

import re
import sys
from pathlib import Path

TS_DIR = Path("apps/web/src/i18n/locales")
RS_DIR = Path("crates/op-i18n/src/i18n")

# Map TS file basename → Rust file basename (sanitised).
LOCALES = {
    "en": "en",
    "zh": "zh_cn",
    "zh-tw": "zh_tw",
    "ja": "ja",
    "ko": "ko",
    "fr": "fr",
    "es": "es",
    "de": "de",
    "pt": "pt",
    "ru": "ru",
    "hi": "hi",
    "tr": "tr",
    "th": "th",
    "vi": "vi",
    "id": "id",
}

# Match `'key': 'value'` / `'key': "value"` on one logical line.
# Spans newline because some entries split key + value across lines:
#   'long.key.name':
#       'value here',
KEY_RE = re.compile(
    r"""^\s*'([^']+)':\s*(?:
        '((?:[^'\\]|\\.)*)'
      | "((?:[^"\\]|\\.)*)"
    ),?\s*$""",
    re.VERBOSE | re.MULTILINE,
)
KEY_AND_VALUE_RE = re.compile(
    r"""'([^']+)':\s*(?:
        '((?:[^'\\]|\\.)*)'
      | "((?:[^"\\]|\\.)*)"
    )\s*,?""",
    re.VERBOSE,
)


def parse_ts(path: Path) -> list[tuple[str, str]]:
    text = path.read_text(encoding="utf-8")
    rows: list[tuple[str, str]] = []
    for m in KEY_AND_VALUE_RE.finditer(text):
        key = m.group(1)
        value = m.group(2) if m.group(2) is not None else m.group(3)
        # TS uses \u escapes + \n / \t — Python read_text already
        # decoded the file as UTF-8, but the JS-style \uXXXX
        # escapes are still literal. Decode them.
        value = (
            value
            .replace("\\\\", "\x00")  # placeholder
            .replace("\\'", "'")
            .replace("\\\"", "\"")
            .replace("\\n", "\n")
            .replace("\\t", "\t")
            .replace("\\r", "\r")
        )
        # Decode \uXXXX
        def _u(m):
            return chr(int(m.group(1), 16))
        value = re.sub(r"\\u([0-9A-Fa-f]{4})", _u, value)
        value = value.replace("\x00", "\\")
        rows.append((key, value))
    return rows


def rust_escape(s: str) -> str:
    out = []
    for c in s:
        if c == "\\":
            out.append("\\\\")
        elif c == "\"":
            out.append("\\\"")
        elif c == "\n":
            out.append("\\n")
        elif c == "\t":
            out.append("\\t")
        elif c == "\r":
            out.append("\\r")
        elif ord(c) < 0x20:
            out.append(f"\\u{{{ord(c):04x}}}")
        else:
            out.append(c)
    return "".join(out)


def emit_rust(rows: list[tuple[str, str]], rs_module: str, locale_label: str) -> str:
    lines = [
        "//! Generated from `apps/web/src/i18n/locales/{}.ts` —".format(locale_label),
        "//! do NOT hand-edit. Re-run `tools/convert-locales.py`.",
        "",
        "pub fn lookup(key: &str) -> Option<&'static str> {",
        "    Some(match key {",
    ]
    seen: set[str] = set()
    for key, value in rows:
        if key in seen:
            continue
        seen.add(key)
        lines.append(f"        \"{rust_escape(key)}\" => \"{rust_escape(value)}\",")
    lines.append("        _ => return None,")
    lines.append("    })")
    lines.append("}")
    lines.append("")
    return "\n".join(lines)


def main() -> int:
    RS_DIR.mkdir(parents=True, exist_ok=True)
    for ts_name, rs_name in LOCALES.items():
        ts_path = TS_DIR / f"{ts_name}.ts"
        if not ts_path.exists():
            print(f"missing: {ts_path}", file=sys.stderr)
            continue
        rows = parse_ts(ts_path)
        rs_path = RS_DIR / f"{rs_name}.rs"
        rs_path.write_text(emit_rust(rows, rs_name, ts_name), encoding="utf-8")
        print(f"{ts_name} → {rs_path} ({len(rows)} keys)")
    return 0


if __name__ == "__main__":
    sys.exit(main())
