from __future__ import annotations

import copy
import json
from pathlib import Path

from runner.experiment_config import (
    ConnectionScaffold,
    LayerScaffold,
    RegionScaffold,
    parse_focus_config,
    parse_connections as parse_shared_connections,
    parse_input_bindings as parse_shared_input_bindings,
    parse_layer_scaffold as parse_shared_layer_scaffold,
    parse_port_names as parse_shared_port_names,
    parse_region_scaffold as parse_shared_region_scaffold,
)

from .interactive_profiles import InteractiveProfile, load_builtin_profile

VALID_NORMALIZE_MODES = {"normalized", "raw"}
VALID_OUTPUT_MODES = {"compact", "verbose", "jsonl"}


def load_interactive_profile(profile_name: str | None, config_path: str | None) -> InteractiveProfile:
    base_profile = load_builtin_profile(profile_name or "single_scalar")
    if not config_path:
        return base_profile

    config_payload = load_interactive_config_file(config_path)
    return merge_profile_with_config(base_profile, config_payload)


def load_interactive_config_file(config_path: str) -> dict:
    config_file = Path(config_path)
    if not config_file.exists():
        raise ValueError(f"Interactive config file not found: {config_path}")
    with config_file.open("r", encoding="utf-8") as config_handle:
        return json.load(config_handle)


def merge_profile_with_config(base_profile: InteractiveProfile, config_payload: dict) -> InteractiveProfile:
    merged_profile = copy.deepcopy(base_profile)

    config_name = config_payload.get("name")
    if config_name:
        merged_profile.name = str(config_name)

    interactive_payload = config_payload.get("interactive", {})
    normalize_mode = interactive_payload.get("normalize_inputs")
    if normalize_mode is not None:
        merged_profile.default_normalize_inputs = validate_normalize_mode(str(normalize_mode))

    output_mode = interactive_payload.get("output")
    if output_mode is not None:
        merged_profile.default_output = validate_output_mode(str(output_mode))

    trace_enabled = interactive_payload.get("trace")
    if trace_enabled is not None:
        merged_profile.default_trace = bool(trace_enabled)

    input_space_payload = config_payload.get("input_space", {})
    rank_value = input_space_payload.get("rank")
    if rank_value is not None:
        merged_profile.rank = int(rank_value)

    ports_payload = input_space_payload.get("ports")
    if ports_payload:
        merged_profile.input_ports = parse_port_names(ports_payload)

    network_payload = config_payload.get("network", {})
    regions_payload = network_payload.get("regions")
    if regions_payload:
        merged_profile.regions = [
            parse_region_scaffold(region_payload, merged_profile.input_ports)
            for region_payload in regions_payload
        ]

    merged_profile.focus_config = parse_focus_config(config_payload.get("focus", {}))

    return merged_profile


def validate_normalize_mode(normalize_mode: str) -> str:
    normalized_mode = str(normalize_mode or "normalized").strip().lower()
    if normalized_mode not in VALID_NORMALIZE_MODES:
        raise ValueError(f"Unsupported normalize mode: {normalize_mode}")
    return normalized_mode


def validate_output_mode(output_mode: str) -> str:
    normalized_mode = str(output_mode or "compact").strip().lower()
    if normalized_mode not in VALID_OUTPUT_MODES:
        raise ValueError(f"Unsupported interactive output mode: {output_mode}")
    return normalized_mode


def parse_port_names(ports_payload: list[dict] | list[str]) -> list[str]:
    return parse_shared_port_names(ports_payload)


def parse_region_scaffold(region_payload: dict, input_ports: list[str]) -> RegionScaffold:
    return parse_shared_region_scaffold(region_payload, input_ports)


def parse_layer_scaffold(layer_payload: dict) -> LayerScaffold:
    return parse_shared_layer_scaffold(layer_payload)


def parse_input_bindings(
    bindings_payload: dict | None,
    input_ports: list[str],
    layer_count: int,
) -> dict[str, list[int]]:
    return parse_shared_input_bindings(bindings_payload, input_ports, layer_count)


def parse_connections(connections_payload: list[dict] | None, layer_count: int) -> list[ConnectionScaffold]:
    return parse_shared_connections(connections_payload, layer_count)
