"""Model-architecture identifiers and checkpoint validation helpers."""

from __future__ import annotations

import re
from collections.abc import Mapping


TRANSFORMER_FFN_ARCHITECTURE = "transformer_ffn_v1"
TRAJ_MIXER_ARCHITECTURE = "traj_mixer_v5"
DEFAULT_MODEL_ARCHITECTURE = TRANSFORMER_FFN_ARCHITECTURE
SUPPORTED_MODEL_ARCHITECTURES = (
    TRANSFORMER_FFN_ARCHITECTURE,
    TRAJ_MIXER_ARCHITECTURE,
)


_FFN_STATE_KEY = re.compile(
    r"(?:^|\.)blocks\.\d+\.mlp\.w[123]\.(?:weight|bias)$"
)
_TRAJ_MIXER_STATE_KEY = re.compile(
    r"(?:^|\.)blocks\.\d+\.mlp\.(?:"
    r"norm\.(?:weight|bias)|"
    r"intra_gate_proj|"
    r"intra_value_proj|"
    r"intra_output_proj|"
    r"intra_gate_logits|"
    r"gate_proj|"
    r"value_proj|"
    r"output_proj"
    r")$"
)


def _validate_model_architecture(model_architecture: object) -> str:
    if not isinstance(model_architecture, str):
        raise ValueError(
            "model_architecture must be one of "
            f"{SUPPORTED_MODEL_ARCHITECTURES}, got {model_architecture!r}"
        )
    if model_architecture not in SUPPORTED_MODEL_ARCHITECTURES:
        raise ValueError(
            f"Unsupported model_architecture={model_architecture!r}; "
            f"expected one of {SUPPORTED_MODEL_ARCHITECTURES}."
        )
    return model_architecture


def detect_model_architecture_from_state_dict(
    state_dict: Mapping[str, object],
) -> str:
    """Infer the architecture from block parameter names.

    Detection deliberately accepts any ``blocks.<index>`` prefix rather than
    assuming that block zero is present.
    """

    if not isinstance(state_dict, Mapping):
        raise TypeError(
            "state_dict must be a mapping, got "
            f"{type(state_dict).__name__}"
        )

    has_ffn = False
    has_traj_mixer = False
    for raw_key in state_dict:
        key = str(raw_key)
        has_ffn = has_ffn or _FFN_STATE_KEY.search(key) is not None
        has_traj_mixer = (
            has_traj_mixer
            or _TRAJ_MIXER_STATE_KEY.search(key) is not None
        )
        if has_ffn and has_traj_mixer:
            raise ValueError(
                "Checkpoint contains both Transformer FFN and TrajMixer "
                "block parameters; its model architecture is ambiguous."
            )

    if has_ffn:
        return TRANSFORMER_FFN_ARCHITECTURE
    if has_traj_mixer:
        return TRAJ_MIXER_ARCHITECTURE
    raise ValueError(
        "Could not detect model architecture from checkpoint parameters. "
        "Expected a blocks.<index>.mlp FFN or TrajMixer parameter."
    )


def resolve_model_architecture(
    config_or_marker: Mapping[str, object] | str | None = None,
    state_dict: Mapping[str, object] | None = None,
) -> str:
    """Resolve and cross-check a configured and checkpoint architecture.

    Every saved run must provide an explicit architecture marker. Checkpoint
    parameter names are used only to verify that the marker describes the
    weights being loaded.
    """

    if isinstance(config_or_marker, Mapping):
        configured = config_or_marker.get("model_architecture")
    elif isinstance(config_or_marker, str) or config_or_marker is None:
        configured = config_or_marker
    else:
        raise TypeError(
            "config_or_marker must be a config mapping, string, or None, got "
            f"{type(config_or_marker).__name__}"
        )

    resolved_config = (
        _validate_model_architecture(configured)
        if configured is not None
        else None
    )
    detected = (
        detect_model_architecture_from_state_dict(state_dict)
        if state_dict is not None
        else None
    )

    if resolved_config is None:
        raise ValueError(
            "model_architecture is required; expected one of "
            f"{SUPPORTED_MODEL_ARCHITECTURES}."
        )
    if detected is not None and resolved_config != detected:
        raise ValueError(
            "Configured model architecture conflicts with checkpoint: "
            f"config={resolved_config!r}, checkpoint={detected!r}."
        )
    return resolved_config
