import plistlib
from pathlib import Path

# Quick MacRoman → Unicode map for title cleanup (typographic chars only)
_MAC_ROMAN_TITLE_FIXES = {
    0xD0: "\u2013",  # en dash
    0xD1: "\u2014",  # em dash
    0xD2: "\u201c",  # left double quote
    0xD3: "\u201d",  # right double quote
    0xD4: "\u2018",  # left single quote
    0xD5: "\u2019",  # right single quote
    0xDB: "\u2026",  # ellipsis
}


def _clean_mac_roman(text: str) -> str:
    """Remap common Mac OS Roman typographic characters in titles."""
    for byte_val, replacement in _MAC_ROMAN_TITLE_FIXES.items():
        text = text.replace(chr(byte_val), replacement)
    return text


_LOCAL_ROOT = Path(__file__).resolve().parent.parent / "modules"
_SYSTEM_ROOT = (
    Path.home() / "Library" / "Application Support" / "Accordance" / "Modules"
)

if _LOCAL_ROOT.exists():
    ACCORDANCE_ROOT = _LOCAL_ROOT
    TEXTS_DIR = _LOCAL_ROOT / "Texts"
    TOOLS_DIR = _LOCAL_ROOT / "Tools"
else:
    ACCORDANCE_ROOT = _SYSTEM_ROOT
    TEXTS_DIR = _SYSTEM_ROOT / "Texts"
    TOOLS_DIR = _SYSTEM_ROOT / "Tools"


def _parse_plist(bundle_path: Path) -> dict:
    for name in ("Info.plist", "ExtraInfo.plist"):
        plist_path = bundle_path / name
        if plist_path.exists():
            with open(plist_path, "rb") as f:
                return plistlib.load(f)
    return {}


def _find_data_file(bundle_path: Path, stem: str) -> Path | None:
    candidate = bundle_path / stem
    if candidate.is_file():
        return candidate
    for entry in bundle_path.iterdir():
        if entry.is_file() and entry.suffix == "":
            return entry
    return None


def discover_modules() -> dict:
    """Scan the Accordance Modules directory and return a registry of installed modules.

    Returns:
        Dict keyed by module abbreviation, each value:
            abbr: str           e.g. "NABRE"
            name: str           e.g. "New American Bible, Revised Edition"
            type: int           moduletype from Info.plist (1=text, 3=lexicon, 4=hebrew-tool, 5=commentary)
            path: str           full path to binary data file
            unicode: bool       useunicodetext flag
            language: str       language code string
            tags: list[str]     category tags
    """
    registry = {}

    for modules_dir, ext, kind in [
        (TEXTS_DIR, ".atext", "Text"),
        (TOOLS_DIR, ".atool", "Tool"),
        (TOOLS_DIR, ".acc2", "LegacyTool"),
    ]:
        if not modules_dir.exists():
            continue
        for entry in sorted(modules_dir.iterdir()):
            if entry.suffix.lower() != ext:
                continue

            # ── Legacy flat-file .acc2 modules ────────────────────────
            if ext == ".acc2" and entry.is_file():
                with open(entry, "rb") as f:
                    title_len = f.read(1)[0]
                    title = f.read(title_len).decode("latin-1", errors="replace")
                    title = _clean_mac_roman(title)
                    f.seek(0x100)
                    abbrev_len = f.read(1)[0]
                    abbrev = f.read(abbrev_len).decode("ascii", errors="replace")

                registry[abbrev] = {
                    "abbr": abbrev,
                    "name": title,
                    "human_name": title,
                    "type": 15,
                    "kind": "LegacyTool",
                    "path": str(entry),
                    "bundle_path": str(entry),
                    "unicode": False,
                    "language": "en",
                    "tags": ["legacy"],
                }
                continue

            plist = _parse_plist(entry)
            if not plist:
                continue

            abbr = plist.get("com.oaktree.module.textabbr", "")
            name = plist.get("com.oaktree.module.fullmodulename", "")
            module_type = plist.get("com.oaktree.module.moduletype", 0)
            unicode = bool(plist.get("com.oaktree.module.useunicodetext", 0))
            tags = plist.get("com.oaktree.module.tagsarray", [])
            human_name = plist.get("com.oaktree.module.humanreadablename", name)

            language_map = {0: "en", 1: "en", 2: "el", 3: "he", 4: "arc"}
            lang_code = plist.get("com.oaktree.module.language", 0)
            language = language_map.get(lang_code, f"unknown-{lang_code}")

            stem = entry.stem
            data_file = _find_data_file(entry, stem)
            if data_file is None:
                continue

            registry[abbr] = {
                "abbr": abbr,
                "name": name,
                "human_name": human_name,
                "type": module_type,
                "kind": kind,
                "path": str(data_file),
                "bundle_path": str(entry),
                "unicode": unicode,
                "language": language,
                "tags": tags,
            }

    return registry


def get_text_modules() -> dict:
    """Return only type-1 (Bible text) modules."""
    all_modules = discover_modules()
    return {k: v for k, v in all_modules.items() if v["type"] == 1}


def get_tool_modules() -> dict:
    """Return type-3,4,5 (lexicon/commentary) modules."""
    all_modules = discover_modules()
    return {k: v for k, v in all_modules.items() if v["type"] in (3, 4, 5)}
