"""Strict visible-copy case guard for English and Indonesian UI text.

This guard is intentionally stronger than the previous title-only check.
It checks single-line user-visible strings in English and Indonesian language files,
including page titles, card titles, buttons, tabs, empty states, placeholders,
menu labels, confirmations, hints, descriptions, and status copy.  Multiline templates,
release-note paragraphs, and raw content fields are excluded.

Run from project root:
  python tools/ui_title_case_check.py --write-report
  python tools/ui_title_case_check.py --fix --write-report
"""
from __future__ import annotations

import argparse
import json
import re
from datetime import datetime
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parent.parent
LANG_FILES = {
    "en_US": ROOT / "lang" / "en_US.json",
    "id_ID": ROOT / "lang" / "id_ID.json",
}
REPORT_PATH = ROOT / "data" / "ui_title_case_latest.json"

# Preserve known acronyms, shortcuts, product names, and technical forms.
PRESERVE_EXACT = {
    "AI", "API", "AppData", "CSV", "CRM", "CN", "EN", "EXE", "FastAPI", "Firebase",
    "HTTP", "HTTPS", "ID", "JSON", "NexaOffice", "PDF", "PUID", "PyInstaller",
    "SmartScreen", "Supabase", "Tk", "Tkinter", "URL", "UI", "UPX", "WPS", "Windows",
    "Defender", "Avast", "Ctrl", "Shift", "Enter", "Esc", "Tab", "MB", "PNG", "ICO",
    "IME", "OCR", "SaaS", "Q1", "Q2", "Q3", "Q4", "AM", "PM", "YYYY", "MM", "DD", "HH", "SS",
}
PRESERVE_LOWER = {"localhost", "root", "dist"}
STOPWORDS_BY_LANG = {
    "id_ID": {"di", "dan", "untuk", "dari"},
    "en_US": {"and", "for", "from"},
}

EXCLUDE_KEY_PARTS = (
    "template", "raw",
)

# Release note list items can be complete marketing sentences. Keep them out of
# the strict UI case check unless they are explicit titles.
EXCLUDE_KEY_PREFIXES = ("update_",)


def _flatten(payload: dict[str, Any], prefix: str = "") -> dict[str, str]:
    out: dict[str, str] = {}
    for key, value in payload.items():
        name = f"{prefix}.{key}" if prefix else key
        if isinstance(value, str):
            out[name] = value
        elif isinstance(value, dict):
            out.update(_flatten(value, name))
    return out


def _is_visible_copy_candidate(key: str, value: str) -> bool:
    if not isinstance(value, str):
        return False
    text = value.strip()
    if not text or "\n" in text:
        return False
    if not re.search(r"[A-Za-zÀ-ÖØ-öø-ÿ]", text):
        return False
    lower_key = key.lower()
    if lower_key.startswith(EXCLUDE_KEY_PREFIXES) and not lower_key.endswith("_title"):
        return False
    if any(part in lower_key for part in EXCLUDE_KEY_PARTS):
        return False
    # Keep deployment filenames readable; they must stay exact for admins.
    if any(name in text for name in ("nexaoffice_server.json", "system_server_config.json")):
        return False
    if re.match(r"^https?://", text, flags=re.I):
        return False
    return True


def _format_word(token: str) -> str:
    if token.startswith("{") and token.endswith("}"):
        return token
    if not re.search(r"[A-Za-zÀ-ÖØ-öø-ÿ]", token):
        return token
    size_match = re.fullmatch(r"(\d+)(kb|mb|gb|tb)", token, flags=re.I)
    if size_match:
        return size_match.group(1) + size_match.group(2).upper()
    if token in PRESERVE_EXACT or re.fullmatch(r"[A-Z]{2,}\d*", token):
        return token
    if token.lower() in PRESERVE_LOWER:
        return token.lower()
    # Preserve unknown mixed technical words that already contain internal caps.
    if re.search(r"[A-Z].*[A-Z]", token) and not token.isupper():
        return token
    lower = token.lower()
    return lower[:1].upper() + lower[1:]


def to_strict_title_case(text: str, language: str = "en_US") -> str:
    """Capitalize visible words while allowing sentence-internal stopwords to stay lowercase."""
    pattern = re.compile(r"\{[^}]+\}|[A-Za-zÀ-ÖØ-öø-ÿ0-9]+(?:'[A-Za-zÀ-ÖØ-öø-ÿ]+)?")
    stopwords = STOPWORDS_BY_LANG.get(language, set())
    out: list[str] = []
    pos = 0
    word_index = 0
    for match in pattern.finditer(text):
        if match.start() > pos:
            out.append(text[pos:match.start()])
        token = match.group(0)
        formatted = _format_word(token)
        if word_index > 0 and token.lower() in stopwords:
            formatted = token.lower()
        out.append(formatted)
        pos = match.end()
        word_index += 1
    if pos < len(text):
        out.append(text[pos:])
    return "".join(out)


def build_report(fix: bool = False) -> dict[str, Any]:
    language_reports = []
    total_checked = 0
    total_changed = 0
    for lang, path in LANG_FILES.items():
        payload = json.loads(path.read_text(encoding="utf-8"))
        flat = _flatten(payload)
        problems = []
        for key, value in flat.items():
            if not _is_visible_copy_candidate(key, value):
                continue
            expected = to_strict_title_case(value, lang)
            total_checked += 1
            if value != expected:
                problems.append({"key": key, "expected": expected, "actual": value})
                if fix and "." not in key:
                    payload[key] = expected
                    total_changed += 1
        if fix and problems:
            path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        language_reports.append({
            "language": lang,
            "checked_labels": sum(1 for key, value in flat.items() if _is_visible_copy_candidate(key, value)),
            "changed_labels": len(problems) if fix else 0,
            "status": "OK" if (fix or not problems) else "FAILED",
            "problems": [] if fix else problems,
        })
    status = "OK" if all(item["status"] == "OK" for item in language_reports) else "FAILED"
    return {
        "checked_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "languages": language_reports,
        "total_checked": total_checked,
        "total_changed_when_fixing": total_changed,
        "status": status,
        "notes": [
            "Checks single-line visible English and Indonesian UI copy, not only page titles.",
            "Every visible word is capitalized, while placeholders, acronyms, shortcuts, and product names are preserved.",
            "Multiline templates, release-note paragraphs, and raw content values are excluded to keep large text readable.",
        ],
    }


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--fix", action="store_true", help="Rewrite single-line English/Indonesian UI copy to strict title case.")
    parser.add_argument("--write-report", action="store_true")
    args = parser.parse_args()
    report = build_report(fix=args.fix)
    if args.fix:
        report = build_report(fix=False)
    if args.write_report:
        REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
        REPORT_PATH.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    print("NexaOffice Global UI Copy Case Check")
    print(f"- Languages: {', '.join(LANG_FILES)}")
    print(f"- Checked labels: {report['total_checked']}")
    print(f"- Status: {report['status']}")
    for item in report["languages"]:
        if item["problems"]:
            print(f"  {item['language']} problems: {len(item['problems'])}")
            for problem in item["problems"][:40]:
                print(f"    {problem['key']}: expected={problem['expected']!r}, actual={problem['actual']!r}")
    if args.write_report:
        print(f"- Report: {REPORT_PATH.relative_to(ROOT)}")
    return 0 if report["status"] == "OK" else 1


if __name__ == "__main__":
    raise SystemExit(main())
