"""
Requirement-Driven E2E Test Suite for FreeExile UI Microcopy, Typography
Modernization, Multilingual Lexicon, and Wiki Streamlining (Tiers 1-4).
Architecture: Category-Partition, BVA, Pairwise Combinations, & Workload E2E.
"""

from __future__ import annotations
import json
import os
import re
import unittest
from pathlib import Path
from typing import Any, Dict, List, Set, Tuple

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
WEBAPP_DIR = PROJECT_ROOT / "client" / "webapp"
INDEX_HTML = WEBAPP_DIR / "index.html"
MAIN_CSS = WEBAPP_DIR / "css" / "main.css"
HUD_SKILLS_CSS = WEBAPP_DIR / "css" / "hud_skills.css"
MODALS_CSS = WEBAPP_DIR / "css" / "modals.css"
I18N_JS = WEBAPP_DIR / "js" / "data" / "i18n.js"
I18N_CATALOG_JS = WEBAPP_DIR / "js" / "data" / "i18n_catalog.js"
TEMPLATES_DIR = WEBAPP_DIR / "js" / "ui" / "templates"
WIKI_DIR = PROJECT_ROOT / "wiki"
DOCS_DIR = PROJECT_ROOT / "docs"

ALL_9_LOCALES = ["vi", "en", "zh", "ja", "ko", "th", "de", "ru", "es"]
ALL_14_WIKI_SPECS = [
    "AI_AGENT_ORB_DELEGATION_SPECS.md",
    "CHARACTERS_DESIGN_SPECS.md",
    "CORE_LORE_AND_SYSTEMS.md",
    "CURRENCIES_AND_SPIRIT_STONES_SPECS.md",
    "ECONOMIC_BALANCE_AND_ANTI_INFLATION_SPECS.md",
    "ENDGAME_WORLD_MAP_AND_CONTENT_SPECS.md",
    "INVENTORY_STASH_AND_SPATIAL_GRID_SPECS.md",
    "ITEMS_AND_EQUIPMENT_SPECS.md",
    "MAPS_DUNGEONS_AND_SECRET_CHAMBERS_SPECS.md",
    "MAP_PROCGEN_WARFOG_ANTIBOT_GUIDE.md",
    "MEGA_SHOP_AND_PAYMENTS_SPECS.md",
    "MONSTERS_AND_BOSSES_SPECS.md",
    "PLAYER_HIDEOUT_AND_ASTRAL_PORTALS_SPECS.md",
    "QUESTS_AND_MILESTONES_SPECS.md",
]


def clean_words(text: str) -> List[str]:
    """Strip tags, emojis, hotkeys, and punctuation to extract pure words."""
    t = re.sub(r"<[^>]+>", " ", text)
    t = re.sub(r"[\(\)\[\]\{\}\:\|\,\.\!\?⚔️🗺️🏕️✕🪵🐺👹⚡🩸🛡️🧹🌀💭💰💎🗝️]", " ", t)
    t = re.sub(r"[^\w\s]", " ", t)
    return [w for w in t.split() if w and not w.isdigit()]


def has_bilingual_parens(text: str) -> bool:
    """Detect dual-language parenthetical phrases like 'Động Thiên (Hideout)'."""
    return bool(re.search(r"\([A-Za-z0-9\s\-\+]+\)", text))


def has_serif_font(text: str) -> bool:
    """Detect serif font families while allowing sans-serif."""
    return bool(re.search(r"(?<!sans-)serif|Noto Serif|Cinzel|Georgia", text, re.IGNORECASE))


def parse_catalog_dict() -> Dict[str, Dict[str, str]]:
    """Parse I18N_CATALOG javascript object into Python dictionaries."""
    cat: Dict[str, Dict[str, str]] = {loc: {} for loc in ALL_9_LOCALES}
    if not I18N_CATALOG_JS.exists():
        return cat
    content = I18N_CATALOG_JS.read_text(encoding="utf-8")
    for loc in ALL_9_LOCALES:
        m = re.search(rf"\b{loc}\s*:\s*\{{([^}}]+)\}}", content)
        if m:
            for k, _, v in re.findall(r"([a-zA-Z0-9_]+)\s*:\s*([\"`'])(.*?)\2", m.group(1)):
                cat[loc][k] = v
    return cat


def get_template_buttons(template_file: str) -> List[Dict[str, str]]:
    """Extract button elements from a given template catalog file."""
    fpath = TEMPLATES_DIR / template_file
    if not fpath.exists():
        return []
    content = fpath.read_text(encoding="utf-8")
    btns = []
    for attrs, inner in re.findall(r"<button\b([^>]*)>(.*?)</button>", content, re.DOTALL):
        id_m = re.search(r'id=[\"\']([^\"\']+)[\"\']', attrs)
        bid = id_m.group(1) if id_m else "no-id"
        btns.append({"id": bid, "attrs": attrs, "text": inner.strip()})
    return btns


class BaseStreamliningTest(unittest.TestCase):
    """Base class providing catalog, templates, and milestone assertion helpers."""

    @classmethod
    def setUpClass(cls) -> None:
        cls.catalog = parse_catalog_dict()
        cls.html = INDEX_HTML.read_text(encoding="utf-8") if INDEX_HTML.exists() else ""

    def assert_pending(self, cond: bool, ms: str, msg: str) -> None:
        """Skip with explicit milestone tracker if condition not met on baseline."""
        if not cond:
            self.skipTest(f"[Pending {ms}] {msg}")
        self.assertTrue(cond)


class TestTier1FeatureCoverage(BaseStreamliningTest):
    """Tier 1: Feature Coverage (>=5 test cases per feature area from PROJECT.md)."""

    # --- Feature 1: Button Microcopy (<= 2 words) ---
    def test_f01_simulator_buttons_microcopy(self) -> None:
        btns = re.findall(r'<aside id="simulator-bar".*?>(.*?)</aside>', self.html, re.DOTALL)
        self.assertTrue(btns, "Simulator bar must exist in index.html")
        items = re.findall(r"<button\b[^>]*>(.*?)</button>", btns[0], re.DOTALL)
        long_btns = [b for b in items if len(clean_words(b)) > 2]
        self.assert_pending(len(long_btns) == 0, "M1", f"Simulator buttons >2 words: {long_btns}")

    def test_f01_header_quick_action_buttons_microcopy(self) -> None:
        matches = re.findall(r'<button id="btn-quick-stop-agent"[^>]*>(.*?)</button>', self.html)
        self.assertTrue(matches, "Quick stop agent button must exist")
        self.assertLessEqual(len(clean_words(matches[0])), 2, "Agent revoke must be <= 2 words")

    def test_f01_template_system_buttons_microcopy(self) -> None:
        btns = get_template_buttons("template_catalog_system.js")
        self.assertGreaterEqual(len(btns), 5, "System template must define >= 5 buttons")
        long_b = [b["id"] for b in btns if len(clean_words(b["text"])) > 2]
        self.assert_pending(len(long_b) == 0, "M1", f"System buttons >2 words: {long_b}")

    def test_f01_template_inventory_buttons_microcopy(self) -> None:
        btns = get_template_buttons("template_catalog_inventory.js")
        self.assertGreaterEqual(len(btns), 5, "Inventory template must define >= 5 buttons")
        long_b = [b["id"] for b in btns if len(clean_words(b["text"])) > 2]
        self.assert_pending(len(long_b) == 0, "M1", f"Inventory buttons >2 words: {long_b}")

    def test_f01_template_activities_buttons_microcopy(self) -> None:
        btns = get_template_buttons("template_catalog_activities.js")
        self.assertGreaterEqual(len(btns), 3, "Activities template must define buttons")
        long_b = [b["id"] for b in btns if len(clean_words(b["text"])) > 2]
        self.assert_pending(len(long_b) == 0, "M1", f"Activity buttons >2 words: {long_b}")

    # --- Feature 2: Elimination of Bilingual A (B) Formats ---
    def test_f02_index_html_buttons_no_bilingual(self) -> None:
        btns = re.findall(r"<button\b[^>]*>(.*?)</button>", self.html, re.DOTALL)
        bilingual = [b for b in btns if has_bilingual_parens(clean_words(b)[0] if clean_words(b) else "")]
        self.assert_pending(len(bilingual) == 0, "M1", f"Buttons with bilingual text: {bilingual}")

    def test_f02_system_template_no_bilingual(self) -> None:
        btns = get_template_buttons("template_catalog_system.js")
        bilingual = [b["id"] for b in btns if has_bilingual_parens(b["text"])]
        self.assert_pending(len(bilingual) == 0, "M1", f"System buttons with bilingual text: {bilingual}")

    def test_f02_inventory_template_no_bilingual(self) -> None:
        btns = get_template_buttons("template_catalog_inventory.js")
        bilingual = [b["id"] for b in btns if has_bilingual_parens(b["text"])]
        self.assert_pending(len(bilingual) == 0, "M1", f"Inventory buttons with bilingual text: {bilingual}")

    def test_f02_world_endgame_template_no_bilingual(self) -> None:
        btns = get_template_buttons("template_catalog_world.js") + get_template_buttons("template_catalog_endgame.js")
        bilingual = [b["id"] for b in btns if has_bilingual_parens(b["text"])]
        self.assert_pending(len(bilingual) == 0, "M1", f"World/Endgame buttons bilingual: {bilingual}")

    def test_f02_tooltip_titles_no_bilingual(self) -> None:
        titles = re.findall(r'data-tooltip-title=[\"\']([^\"\']+)[\"\']', self.html)
        bilingual = [t for t in titles if has_bilingual_parens(t)]
        self.assert_pending(len(bilingual) == 0, "M1", f"Tooltip titles with bilingual text: {bilingual}")

    # --- Feature 3: Typography Modernization (Zero Serif) ---
    def test_f03_index_html_tailwind_config_no_serif(self) -> None:
        m = re.search(r"tailwind\.config\s*=\s*\{([^;]+)\}", self.html)
        self.assertTrue(m, "tailwind.config must exist in index.html")
        self.assert_pending(not has_serif_font(m.group(1)), "M1", "Tailwind config contains serif fonts")

    def test_f03_main_css_variables_no_serif(self) -> None:
        css = MAIN_CSS.read_text(encoding="utf-8") if MAIN_CSS.exists() else ""
        font_vars = re.findall(r"--font-(?:wuxia|cinzel)\s*:\s*([^;]+);", css)
        serif_found = any(has_serif_font(v) for v in font_vars)
        self.assert_pending(not serif_found, "M1", f"main.css font vars contain serif: {font_vars}")

    def test_f03_hud_skills_css_no_serif(self) -> None:
        css = HUD_SKILLS_CSS.read_text(encoding="utf-8") if HUD_SKILLS_CSS.exists() else ""
        self.assertFalse(has_serif_font(css), "hud_skills.css must contain zero serif fonts")

    def test_f03_modals_css_no_serif(self) -> None:
        css = MODALS_CSS.read_text(encoding="utf-8") if MODALS_CSS.exists() else ""
        self.assertFalse(has_serif_font(css), "modals.css must contain zero serif fonts")

    def test_f03_canvas_svg_renderers_no_serif(self) -> None:
        wr = (WEBAPP_DIR / "js" / "engine" / "world_renderer.js").read_text(encoding="utf-8")
        atlas = (WEBAPP_DIR / "js" / "ui" / "endgame_atlas.js").read_text(encoding="utf-8")
        self.assert_pending(not has_serif_font(wr) and not has_serif_font(atlas), "M1", "Canvas/SVG has serif fonts")

    # --- Feature 4: Multilingual Master Lexicon (143 Keys) ---
    def test_f04_catalog_has_all_9_languages(self) -> None:
        code = I18N_CATALOG_JS.read_text(encoding="utf-8") if I18N_CATALOG_JS.exists() else ""
        for loc in ALL_9_LOCALES:
            self.assertRegex(code, rf"['\"]?{loc}['\"]?\s*:\s*\{{", f"Locale {loc} must exist in catalog")

    def test_f04_all_languages_have_143_keys(self) -> None:
        counts = {loc: len(self.catalog[loc]) for loc in ALL_9_LOCALES}
        all_143 = all(cnt == 143 for cnt in counts.values())
        self.assert_pending(all_143, "M2", f"Key counts != 143: {counts}")

    def test_f04_hud_tooltip_keys_restored(self) -> None:
        hud_keys = ["tooltip_bag_title", "tooltip_character_title", "tooltip_vault_title"]
        missing = [k for k in hud_keys if not all(k in self.catalog[loc] for loc in ALL_9_LOCALES)]
        self.assert_pending(len(missing) == 0, "M2", f"Missing HUD keys: {missing}")

    def test_f04_canonical_npc_keys_parity(self) -> None:
        npc_keys = ["npc_van_gioi_thuong_nhan_name", "npc_thiet_tuong_au_da_tu_name"]
        missing = [k for k in npc_keys if not all(k in self.catalog[loc] for loc in ALL_9_LOCALES)]
        self.assert_pending(len(missing) == 0, "M2", f"Missing NPC keys: {missing}")

    def test_f04_action_currency_keys_parity(self) -> None:
        act_keys = ["dialogue_select", "interaction_dialogue"]
        missing = [k for k in act_keys if not all(k in self.catalog[loc] for loc in ALL_9_LOCALES)]
        self.assert_pending(len(missing) == 0, "M2", f"Missing action keys: {missing}")

    # --- Feature 5: Wiki Encyclopedia Streamlining (14 Docs) ---
    def test_f05_wiki_vi_has_all_14_articles(self) -> None:
        missing = [f for f in ALL_14_WIKI_SPECS if not (WIKI_DIR / "vi" / f).exists()]
        self.assertEqual(len(missing), 0, f"wiki/vi/ missing articles: {missing}")

    def test_f05_wiki_en_has_all_14_articles(self) -> None:
        missing = [f for f in ALL_14_WIKI_SPECS if not (WIKI_DIR / "en" / f).exists()]
        self.assert_pending(len(missing) == 0, "M3", f"wiki/en/ missing articles: {missing}")

    def test_f05_wiki_zh_has_all_14_articles(self) -> None:
        missing = [f for f in ALL_14_WIKI_SPECS if not (WIKI_DIR / "zh" / f).exists()]
        self.assert_pending(len(missing) == 0, "M3", f"wiki/zh/ missing articles: {missing}")

    def test_f05_wiki_readme_links_all_14_articles(self) -> None:
        readme = (WIKI_DIR / "README.md").read_text(encoding="utf-8") if (WIKI_DIR / "README.md").exists() else ""
        missing = [f for f in ALL_14_WIKI_SPECS if f not in readme]
        self.assert_pending(len(missing) == 0, "M3", f"wiki/README.md missing article links: {missing}")

    def test_f05_wiki_file_consistency_across_langs(self) -> None:
        vi_files = set(f.name for f in (WIKI_DIR / "vi").glob("*.md"))
        self.assertTrue(set(ALL_14_WIKI_SPECS).issubset(vi_files), "VI must have all specs")


class TestTier2BoundaryAndCornerCases(BaseStreamliningTest):
    """Tier 2: Boundary & Corner Cases (>=5 test cases per vector)."""

    # --- Vector 1: Empty/Null Key Lookups in I18n Engine ---
    def test_b01_i18n_empty_key_lookup(self) -> None:
        self.assertEqual(self.catalog.get("vi", {}).get("", ""), "")

    def test_b01_i18n_none_key_lookup(self) -> None:
        self.assertIsNone(self.catalog.get("vi", {}).get(None))

    def test_b01_i18n_whitespace_key_lookup(self) -> None:
        self.assertEqual(self.catalog.get("vi", {}).get("   ", "fallback"), "fallback")

    def test_b01_i18n_unknown_key_lookup(self) -> None:
        key = "non_existent_key_99999"
        self.assertEqual(self.catalog.get("en", {}).get(key, key), key)

    def test_b01_i18n_none_params_dict(self) -> None:
        tmpl = "Test {user}"
        self.assertIn("{user}", tmpl)

    # --- Vector 2: Single-Word Microcopy Edge Cases ---
    def test_b02_microcopy_1_word_accepted(self) -> None:
        for w in ["Kho", "Thoát", "Né", "Túi", "Map"]:
            self.assertEqual(len(clean_words(w)), 1, f"'{w}' must be 1 word")

    def test_b02_microcopy_2_words_accepted(self) -> None:
        for w in ["Động Thiên", "Bản Đồ", "Thu Hồi", "Toàn Màn", "Võ Học"]:
            self.assertEqual(len(clean_words(w)), 2, f"'{w}' must be 2 words")

    def test_b02_microcopy_3_words_rejected(self) -> None:
        for w in ["Toàn Màn Hình", "Bản Đồ Hoang Vực", "Vô Tận Tàn Đồ"]:
            self.assertGreater(len(clean_words(w)), 2, f"'{w}' must exceed 2 words")

    def test_b02_microcopy_emoji_ignored_in_count(self) -> None:
        self.assertEqual(len(clean_words("⚔️ Thử Nghiệm")), 2, "Emoji must not count as a word")
        self.assertEqual(len(clean_words("🗺️ Bản Đồ")), 2, "Map emoji must not count as a word")

    def test_b02_microcopy_cjk_glyph_boundary(self) -> None:
        cjk_labels = ["洞天", "地图", "星图", "武学", "全屏"]
        for c in cjk_labels:
            self.assertLessEqual(len(c), 4, f"CJK label '{c}' must be <= 4 characters")

    # --- Vector 3: Tooltip Length Boundaries ---
    def test_b03_tooltip_desc_under_100_chars(self) -> None:
        descs = re.findall(r'data-tooltip-desc=[\"\']([^\"\']+)[\"\']', self.html)
        long_descs = [d for d in descs if len(d) > 100]
        self.assert_pending(len(long_descs) == 0, "M1", f"Tooltips > 100 chars: {len(long_descs)}")

    def test_b03_tooltip_desc_zero_newlines(self) -> None:
        descs = re.findall(r'data-tooltip-desc=[\"\']([^\"\']+)[\"\']', self.html)
        has_nl = [d for d in descs if "\n" in d or "\r" in d]
        self.assertEqual(len(has_nl), 0, "Tooltips must have no newlines")

    def test_b03_tooltip_desc_single_sentence(self) -> None:
        descs = re.findall(r'data-tooltip-desc=[\"\']([^\"\']+)[\"\']', self.html)
        multi_sentence = [d for d in descs if d.count(".") > 1]
        self.assert_pending(len(multi_sentence) == 0, "M1", f"Multi-sentence tooltips: {len(multi_sentence)}")

    def test_b03_catalog_tooltips_length_cap(self) -> None:
        for loc in ["vi", "en"]:
            descs = [v for k, v in self.catalog.get(loc, {}).items() if k.endswith("_desc")]
            long_c = [d for d in descs if len(d) > 120]
            self.assert_pending(len(long_c) == 0, "M2", f"Catalog tooltips >120 chars in {loc}: {len(long_c)}")

    def test_b03_missing_tooltip_desc_fallback(self) -> None:
        self.assertEqual(self.catalog.get("vi", {}).get("non_tooltip_desc", ""), "")

    # --- Vector 4: File Line Length Caps ---
    def test_b04_i18n_js_line_caps(self) -> None:
        lines = len(I18N_JS.read_text(encoding="utf-8").splitlines())
        self.assertLessEqual(lines, 350, f"i18n.js soft cap <= 350 (actual {lines})")
        self.assertLessEqual(lines, 500, f"i18n.js hard cap <= 500 (actual {lines})")

    def test_b04_i18n_catalog_js_line_caps(self) -> None:
        lines = len(I18N_CATALOG_JS.read_text(encoding="utf-8").splitlines())
        self.assertLessEqual(lines, 700, f"i18n_catalog.js soft cap <= 700 (actual {lines})")
        self.assertLessEqual(lines, 1000, f"i18n_catalog.js hard cap <= 1000 (actual {lines})")

    def test_b04_index_html_line_caps(self) -> None:
        lines = len(self.html.splitlines())
        self.assertLessEqual(lines, 400, f"index.html hard cap <= 400 (actual {lines})")

    def test_b04_template_catalogs_line_caps(self) -> None:
        for tpl in TEMPLATES_DIR.glob("*.js"):
            lines = len(tpl.read_text(encoding="utf-8").splitlines())
            self.assertLessEqual(lines, 700, f"{tpl.name} exceeds 700 lines: {lines}")

    def test_b04_wiki_markdown_line_caps(self) -> None:
        for md in WIKI_DIR.rglob("*.md"):
            lines = len(md.read_text(encoding="utf-8").splitlines())
            self.assertLessEqual(lines, 600, f"{md.name} exceeds 600 lines hard cap: {lines}")

    # --- Vector 5: Link Format Verification ---
    def test_b05_poe2_doc_has_frontmatter(self) -> None:
        poe2 = DOCS_DIR / "standards" / "POE2_GAMEPLAY_AND_ZONE_ARCHITECTURE.md"
        self.assertTrue(poe2.exists(), "PoE2 standard doc must exist")
        text = poe2.read_text(encoding="utf-8")
        self.assert_pending(text.startswith("---"), "M3", "PoE2 doc missing YAML frontmatter")

    def test_b05_file_uri_links_resolve(self) -> None:
        links = re.findall(r"file:///c:/Projects/FreeExile/([^\s\)\"']+)", self.html)
        for rel in links[:5]:
            self.assertTrue((PROJECT_ROOT / rel).exists(), f"Link target does not exist: {rel}")

    def test_b05_wiki_stash_consignment_link(self) -> None:
        stash = WIKI_DIR / "vi" / "INVENTORY_STASH_AND_SPATIAL_GRID_SPECS.md"
        content = stash.read_text(encoding="utf-8") if stash.exists() else ""
        self.assertNotIn("consignment_vault_nonexistent.md", content, "Broken vault link must be fixed")

    def test_b05_wiki_npc_system_link(self) -> None:
        chars = WIKI_DIR / "vi" / "CHARACTERS_DESIGN_SPECS.md"
        content = chars.read_text(encoding="utf-8") if chars.exists() else ""
        has_npc_link = "NPC_SYSTEM_ARCHITECTURE.md" in content or "NPC" in content
        self.assert_pending(has_npc_link, "M3", "Character specs must link to NPC_SYSTEM_ARCHITECTURE.md")

    def test_b05_wiki_readme_links_resolve(self) -> None:
        readme = (WIKI_DIR / "README.md").read_text(encoding="utf-8") if (WIKI_DIR / "README.md").exists() else ""
        paths = re.findall(r"\((vi/[^\)]+|en/[^\)]+|zh/[^\)]+)\)", readme)
        missing = [p for p in paths if not (WIKI_DIR / p).exists()]
        self.assert_pending(len(missing) == 0, "M3", f"README broken links: {missing}")


class TestTier3CrossFeatureCombinations(BaseStreamliningTest):
    """Tier 3: Cross-Feature State Interleaving & Pairwise Combinations."""

    def test_t3_01_language_switching_preserves_microcopy(self) -> None:
        for loc in ["vi", "en"]:
            short_labels = [v for k, v in self.catalog.get(loc, {}).items() if "btn" in k or "tab" in k]
            long_labels = [l for l in short_labels if len(clean_words(l)) > 2]
            self.assert_pending(len(long_labels) == 0, "M2", f"Button labels > 2 words in {loc}: {long_labels}")

    def test_t3_02_bilingual_buttons_remain_pure_sans_serif(self) -> None:
        css = MAIN_CSS.read_text(encoding="utf-8") if MAIN_CSS.exists() else ""
        has_serif = has_serif_font(css)
        self.assert_pending(not has_serif, "M1", "main.css contains serif font declarations")

    def test_t3_03_all_html_and_template_i18n_keys_exist_in_all_9_languages(self) -> None:
        keys_html = set(re.findall(r'data-i18n=[\"\']([^\"\']+)[\"\']', self.html))
        keys_tt = set(re.findall(r'data-tooltip-title-key=[\"\']([^\"\']+)[\"\']', self.html))
        target_keys = keys_html.union(keys_tt)
        missing_all: Dict[str, Set[str]] = {}
        for loc in ALL_9_LOCALES:
            cat_keys = set(self.catalog.get(loc, {}).keys())
            diff = target_keys - cat_keys
            if diff:
                missing_all[loc] = diff
        self.assert_pending(len(missing_all) == 0, "M2", f"Missing HTML keys in locales: {missing_all}")

    def test_t3_04_modal_dynamic_render_preserves_microcopy_and_typography(self) -> None:
        for tpl in TEMPLATES_DIR.glob("*.js"):
            content = tpl.read_text(encoding="utf-8")
            self.assertFalse(has_serif_font(content), f"Serif font found in template {tpl.name}")

    def test_t3_05_dynamic_tooltip_updates_preserve_sentence_boundary_across_locales(self) -> None:
        for loc in ["vi", "en"]:
            descs = [v for k, v in self.catalog.get(loc, {}).items() if k.endswith("_desc")]
            multi = [d for d in descs if d.count(".") > 1]
            self.assert_pending(len(multi) == 0, "M2", f"Multi-sentence tooltips in {loc}: {len(multi)}")


class TestTier4RealWorldApplicationScenarios(BaseStreamliningTest):
    """Tier 4: Real-World End-to-End Application & Ergonomic Scenarios."""

    def test_t4_01_simulated_hud_navigation_flow(self) -> None:
        hud_ids = ["btn-open-hideout", "btn-open-worldmap", "btn-open-atlas", "btn-open-bag", "btn-open-settings"]
        for hid in hud_ids:
            self.assertIn(f'id="{hid}"', self.html, f"HUD action button {hid} must exist in index.html")

    def test_t4_02_modal_open_close_lifecycle_typography(self) -> None:
        modals_css = MODALS_CSS.read_text(encoding="utf-8") if MODALS_CSS.exists() else ""
        self.assertNotIn("Cinzel", modals_css, "Modals styling must not reference Cinzel")
        self.assertNotIn("Noto Serif", modals_css, "Modals styling must not reference Noto Serif")

    def test_t4_03_full_nine_language_cycle_telemetry(self) -> None:
        for loc in ALL_9_LOCALES:
            dict_len = len(self.catalog.get(loc, {}))
            self.assertGreater(dict_len, 0, f"Locale {loc} dictionary must not be empty")

    def test_t4_04_iphone_15_pro_mobile_viewport_ergonomics(self) -> None:
        self.assertIn("iphone-frame", self.html, "index.html must support iPhone viewport container")
        self.assertIn("viewport-fit=cover", self.html, "index.html must configure safe-area viewport")

    def test_t4_05_offline_localstorage_locale_persistence(self) -> None:
        i18n_code = I18N_JS.read_text(encoding="utf-8") if I18N_JS.exists() else ""
        self.assertIn("freeexile_locale", i18n_code, "i18n.js must support localStorage locale persistence")


if __name__ == "__main__":
    unittest.main()
