# suma_browser/config.py
from __future__ import annotations

import asyncio
import os
from types import MappingProxyType
from typing import Any, Optional
import copy
import yaml

# Default config path (override with APP_CONFIG_FILE)
_DEFAULT_CONFIG_PATH = os.environ.get(
    "APP_CONFIG_FILE",
    os.path.join(os.getcwd(), "config/app-config.yaml"),
)

# Module state
_init_lock = asyncio.Lock()
_data_frozen: Any = None   # read-only view (mappingproxy / tuples / frozensets)
_data_plain: Any = None    # plain builtins for pretty-print / logging

# ---------------- utils ----------------
def _freeze(obj: Any) -> Any:
    """Deep-freeze containers to read-only equivalents."""
    if isinstance(obj, dict):
        # freeze children first, then wrap dict in mappingproxy
        return MappingProxyType({k: _freeze(v) for k, v in obj.items()})
    if isinstance(obj, list):
        return tuple(_freeze(v) for v in obj)
    if isinstance(obj, set):
        return frozenset(_freeze(v) for v in obj)
    if isinstance(obj, tuple):
        return tuple(_freeze(v) for v in obj)
    return obj

# ---------------- API ----------------
async def init_config(path: Optional[str] = None, *, force: bool = False) -> None:
    """
    Load YAML exactly as-is and cache both a frozen (read-only) and a plain copy.
    Idempotent; pass force=True to reload.
    """
    global _data_frozen, _data_plain

    if _data_frozen is not None and not force:
        return

    async with _init_lock:
        if _data_frozen is not None and not force:
            return

        cfg_path = path or _DEFAULT_CONFIG_PATH
        if not os.path.exists(cfg_path):
            raise FileNotFoundError(f"Config file not found: {cfg_path}")

        with open(cfg_path, "r", encoding="utf-8") as f:
            raw = yaml.safe_load(f)  # whatever the YAML root is

        # store plain as loaded; store frozen for normal use
        _data_plain = raw
        _data_frozen = _freeze(raw)

def config() -> Any:
    """
    Return the read-only (frozen) config. Call init_config() first.
    """
    if _data_frozen is None:
        raise RuntimeError("init_config() has not been awaited yet.")
    return _data_frozen

def as_plain() -> Any:
    """
    Return a deep copy of the plain config for safe external use/pretty printing.
    """
    if _data_plain is None:
        raise RuntimeError("init_config() has not been awaited yet.")
    return copy.deepcopy(_data_plain)

def pretty() -> str:
    """
    YAML pretty string without mappingproxy noise.
    """
    if _data_plain is None:
        raise RuntimeError("init_config() has not been awaited yet.")
    return yaml.safe_dump(_data_plain, sort_keys=False, allow_unicode=True)
