from __future__ import annotations

import inspect
import json
from collections.abc import Mapping
from dataclasses import asdict, is_dataclass
from enum import Enum
from typing import Any

from pydantic import BaseModel


def parse_param_value(raw: str) -> Any:
    text = raw.strip()
    if text == "":
        return ""
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        pass

    lowered = text.lower()
    if lowered == "true":
        return True
    if lowered == "false":
        return False
    if lowered == "null":
        return None

    for caster in (int, float):
        try:
            return caster(text)
        except ValueError:
            continue
    return text


def parse_key_value_pairs(pairs: list[str] | None) -> dict[str, Any]:
    parsed: dict[str, Any] = {}
    for pair in pairs or []:
        if "=" not in pair:
            raise ValueError(f"Invalid --param value '{pair}'. Expected KEY=VALUE.")
        key, raw_value = pair.split("=", 1)
        key = key.strip()
        if not key:
            raise ValueError(f"Invalid --param value '{pair}'. Key cannot be empty.")
        parsed[key] = parse_param_value(raw_value)
    return parsed


def load_json_object(path: str | None) -> dict[str, Any]:
    if not path:
        return {}
    with open(path, "r", encoding="utf-8") as handle:
        data = json.load(handle)
    if not isinstance(data, dict):
        raise ValueError(f"{path} must contain a JSON object.")
    return data


def first_doc_line(obj: Any) -> str:
    doc = inspect.getdoc(obj) or ""
    return doc.splitlines()[0] if doc else ""


def to_plain_data(value: Any) -> Any:
    if isinstance(value, BaseModel):
        return value.model_dump(mode="json")
    if isinstance(value, Enum):
        return value.value
    if is_dataclass(value):
        return asdict(value)
    if isinstance(value, Mapping):
        return {str(key): to_plain_data(item) for key, item in value.items()}
    if isinstance(value, (list, tuple, set)):
        return [to_plain_data(item) for item in value]
    if hasattr(value, "to_dict") and callable(value.to_dict):
        return to_plain_data(value.to_dict())
    if hasattr(value, "__dict__") and not isinstance(value, (str, bytes, bytearray)):
        public = {
            key: val
            for key, val in vars(value).items()
            if not key.startswith("_") and not inspect.isroutine(val)
        }
        if public:
            return to_plain_data(public)
    return value
