#!/usr/bin/env python3
"""Apply a JSON batch of translations to a catalog, validating as it goes.

    venv/bin/python scripts/po-merge.py it chunk-translated.json

Input: JSON list of {"i": <index>, "s": "<translation>"} — or, for plurals,
{"i": <index>, "s": ["<form1>", "<form2>", ...]}.

REJECTS the whole batch (exit 1, nothing written) when a translation drops or
invents a %(name)s / %s / {brace} placeholder, or changes the set of HTML tags.
A counter that eats its placeholder is a 500 in production, so this is a gate,
not a warning.
"""
import json
import re
import sys
from babel.messages.pofile import read_po, write_po

PLACEHOLDER = re.compile(r"%\([^)]*\)[sd]|%[sd]|\{[a-zA-Z_][a-zA-Z0-9_]*\}")
TAG = re.compile(r"<\s*/?\s*([a-zA-Z][a-zA-Z0-9]*)")


def sig(text):
    return sorted(PLACEHOLDER.findall(text)), sorted(TAG.findall(text))


lang, batch_path = sys.argv[1], sys.argv[2]
path = f"supporthub/app/translations/{lang}/LC_MESSAGES/messages.po"

with open(path, encoding="utf-8") as fh:
    catalog = read_po(fh)
messages = list(catalog)

with open(batch_path, encoding="utf-8") as fh:
    batch = json.load(fh)

errors, applied = [], 0
for item in batch:
    i, s = item["i"], item["s"]
    if i >= len(messages):
        errors.append(f"[{i}] index out of range")
        continue
    msg = messages[i]
    ids = list(msg.id) if isinstance(msg.id, (list, tuple)) else [msg.id]
    forms = s if isinstance(s, list) else [s]
    if isinstance(msg.id, (list, tuple)):
        if len(forms) < 2:
            errors.append(f"[{i}] plural needs >=2 forms, got {len(forms)}: {ids[0]!r}")
            continue
    elif len(forms) != 1:
        errors.append(f"[{i}] singular takes one string: {ids[0]!r}")
        continue
    if not all(f.strip() for f in forms):
        errors.append(f"[{i}] empty translation: {ids[0]!r}")
        continue
    # Placeholders/tags from ALL source forms are allowed in ANY target form:
    # plural rules differ per language, so form counts do not line up.
    src_ph, src_tag = set(), set()
    for src in ids:
        p, t = sig(src)
        src_ph |= set(p)
        src_tag |= set(t)
    for f in forms:
        p, t = sig(f)
        if set(p) - src_ph:
            errors.append(f"[{i}] invented placeholder {sorted(set(p) - src_ph)}: {ids[0]!r}")
        if src_tag and set(t) != src_tag:
            errors.append(f"[{i}] tag mismatch {sorted(set(t))} vs {sorted(src_tag)}: {ids[0]!r}")
    # Every singular placeholder must survive.
    if len(ids) == 1:
        p, _ = sig(ids[0])
        missing = set(p) - set(sig(forms[0])[0])
        if missing:
            errors.append(f"[{i}] lost placeholder {sorted(missing)}: {ids[0]!r}")
    if errors:
        continue
    msg.string = tuple(forms) if isinstance(msg.id, (list, tuple)) else forms[0]
    msg.flags.discard("fuzzy")
    applied += 1

if errors:
    print(f"REJECTED — {len(errors)} problem(s), nothing written:", file=sys.stderr)
    for e in errors[:40]:
        print("  " + e, file=sys.stderr)
    sys.exit(1)

with open(path, "wb") as fh:
    write_po(fh, catalog, width=None, sort_output=False)
print(f"applied {applied} translation(s) to {lang}")
