from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Dict, List

import yaml

from .model import Book, parse_book


def load_book(path: Path) -> Book:
    with open(path, "r", encoding="utf-8") as f:
        data = yaml.safe_load(f)
    if not isinstance(data, dict):
        raise ValueError("Book YAML must be a mapping/object at the top level.")
    return parse_book(data)


def parse_vars_kv(items: List[str]) -> Dict[str, str]:
    out: Dict[str, str] = {}
    for item in items:
        if "=" not in item:
            raise ValueError(f"Invalid input {item!r}. Expected KEY=VALUE.")
        k, v = item.split("=", 1)
        k = k.strip()
        if not k:
            raise ValueError(f"Invalid input {item!r}. KEY cannot be empty.")
        out[k] = v
    return out


def resolve_inputs(
    input_specs: Dict[str, Dict[str, object]], *, input_cli: List[str]
) -> Dict[str, object]:
    values: Dict[str, object] = {}
    cli_map = parse_vars_kv(input_cli)

    for name, spec in input_specs.items():
        typ = str(spec.get("type", "") or "")
        raw = cli_map[name] if name in cli_map else spec.get("default")
        if raw is None:
            raise ValueError(f"Missing required input: {name}")
        if typ == "string":
            val = str(raw)
        elif typ == "boolean":
            if isinstance(raw, bool):
                val = raw
            else:
                s = str(raw).strip().lower()
                if s in ("1", "true", "yes", "on"):
                    val = True
                elif s in ("0", "false", "no", "off"):
                    val = False
                else:
                    raise ValueError(f"Input {name} must be boolean")
        elif typ == "integer":
            try:
                val = int(raw)  # type: ignore[arg-type]
            except Exception as e:
                raise ValueError(f"Input {name} must be integer") from e
        elif typ == "number":
            try:
                val = float(raw)  # type: ignore[arg-type]
            except Exception as e:
                raise ValueError(f"Input {name} must be number") from e
        elif typ == "array":
            if isinstance(raw, list):
                val = raw
            elif isinstance(raw, str):
                txt = raw.strip()
                if txt.startswith("["):
                    try:
                        parsed = json.loads(txt)
                    except Exception as e:
                        raise ValueError(f"Input {name} array JSON parse failed") from e
                    if not isinstance(parsed, list):
                        raise ValueError(f"Input {name} must be array")
                    val = parsed
                elif txt:
                    val = [p.strip() for p in txt.split(",") if p.strip()]
                else:
                    val = []
            else:
                raise ValueError(f"Input {name} must be array")
            min_items = spec.get("minItems")
            if isinstance(min_items, int) and len(val) < min_items:
                raise ValueError(
                    f"Input {name} must contain at least {min_items} item(s)"
                )
        else:
            raise ValueError(f"Unsupported input type for {name}: {typ}")

        regex = spec.get("regex")
        if isinstance(regex, str) and regex and isinstance(val, str):
            if re.match(regex, val) is None:
                raise ValueError(f"Input {name} does not match regex")
        values[name] = val
    return values
