"""Load and merge ``configs/*.yaml`` into a validated DiagnosticConfig.

``base.yaml`` holds the industry-independent competency model; every other
YAML in the directory is an industry overlay declaring ``extends: base``
plus its value drivers and unlock costs. Merging is shallow and explicit:
the overlay contributes industry identity, drivers, and costs; the base
contributes everything else. Overlays may not redefine the competency
model — one instrument, many industries.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

import yaml

from .models import DiagnosticConfig

#: Overlay keys an industry file may set. Anything else (competencies,
#: capping_heuristic, …) belongs in base.yaml and is rejected loudly.
_OVERLAY_KEYS = {"extends", "industry", "display_name", "value_drivers", "unlock_costs"}


def configs_dir(start: Path | None = None) -> Path:
    """The study's ``configs/`` directory, found from ``start`` (or CWD).

    Walks up so it works from the study root, ``notebooks/``, or ``tests/``.
    """
    here = (start or Path.cwd()).resolve()
    for candidate in (here, *here.parents):
        d = candidate / "configs"
        if (d / "base.yaml").exists():
            return d
    raise FileNotFoundError("configs/base.yaml not found above " + str(here))


def list_industries(directory: Path | None = None) -> list[str]:
    """Industry config names (file stems), base excluded, sorted."""
    d = directory or configs_dir()
    return sorted(p.stem for p in d.glob("*.yaml") if p.stem != "base")


def _read_yaml(path: Path) -> dict[str, Any]:
    with open(path, encoding="utf-8") as fh:
        data = yaml.safe_load(fh)
    if not isinstance(data, dict):
        raise ValueError(f"{path.name}: expected a mapping at top level")
    return data


def load_config(industry: str, directory: Path | None = None) -> DiagnosticConfig:
    """Load ``base.yaml`` + the named industry overlay, validated."""
    d = directory or configs_dir()
    base = _read_yaml(d / "base.yaml")
    overlay = _read_yaml(d / f"{industry}.yaml")

    if overlay.get("extends") != "base":
        raise ValueError(f"{industry}.yaml must declare 'extends: base'")
    stray = set(overlay) - _OVERLAY_KEYS
    if stray:
        raise ValueError(
            f"{industry}.yaml sets base-only keys {sorted(stray)} — "
            "the competency model lives in base.yaml")

    merged: dict[str, Any] = {
        "version": base["version"],
        "dimensions": base["dimensions"],
        "competencies": base["competencies"],
        "capping_heuristic": base["capping_heuristic"],
        "foundational_competencies": base["foundational_competencies"],
        "industry": overlay["industry"],
        "display_name": overlay.get("display_name", overlay["industry"]),
        "value_drivers": overlay.get("value_drivers") or [],
        "unlock_costs": overlay.get("unlock_costs") or {},
    }
    return DiagnosticConfig.model_validate(merged)
