from pathlib import Path

import yaml

CONFIG_FILENAME = "c2m.yaml"
DEFAULT_TOKEN_LIMIT = 32_768
DEFAULT_OUTPUT_PATH = ".c2m/outputs"
DEFAULT_FLAT_OUTPUT = False
DEFAULT_SINGLE_FILE = False
DEFAULT_IGNORE_PATTERNS: list[str] = [
    "**/package-lock.json",
    "**/yarn.lock",
    "**/composer.lock",
]


class ConfigError(Exception):
    pass


def _validate_token_limit(value: object) -> int:
    if not isinstance(value, int):
        raise ConfigError(f"token_limit must be an integer, got {type(value).__name__}")
    if value <= 0:
        raise ConfigError(f"token_limit must be positive, got {value}")
    return value


def _validate_output_path(value: object) -> str:
    if not isinstance(value, str):
        raise ConfigError(f"output_path must be a string, got {type(value).__name__}")
    if not value.strip():
        raise ConfigError("output_path must not be empty")
    return value.strip()


def _validate_single_file(value: object) -> bool:
    if not isinstance(value, bool):
        raise ConfigError(f"single_file must be a boolean, got {type(value).__name__}")
    return value


def _validate_flat_output(value: object) -> bool:
    if not isinstance(value, bool):
        raise ConfigError(f"flat_output must be a boolean, got {type(value).__name__}")
    return value


def _validate_ignore_patterns(value: object) -> list[str]:
    if not isinstance(value, list):
        raise ConfigError(
            f"ignore_patterns must be a list, got {type(value).__name__}"
        )
    result: list[str] = []
    for i, item in enumerate(value):
        if not isinstance(item, str):
            raise ConfigError(
                f"ignore_patterns[{i}] must be a string, got {type(item).__name__}"
            )
        result.append(item.strip())
    return result


class ConfigManager:
    def __init__(
        self,
        token_limit: int = DEFAULT_TOKEN_LIMIT,
        output_path: str = DEFAULT_OUTPUT_PATH,
        ignore_patterns: list[str] | None = None,
        flat_output: bool = DEFAULT_FLAT_OUTPUT,
        single_file: bool = DEFAULT_SINGLE_FILE,
    ) -> None:
        self.token_limit = token_limit
        self.output_path = output_path
        self.ignore_patterns = ignore_patterns or DEFAULT_IGNORE_PATTERNS.copy()
        self.flat_output = flat_output
        self.single_file = single_file

    @classmethod
    def load(cls, root: Path | str) -> "ConfigManager":
        root_path = Path(root)
        config_path = root_path / CONFIG_FILENAME

        if not config_path.exists():
            return cls()

        try:
            raw = yaml.safe_load(config_path.read_text())
        except yaml.YAMLError as e:
            raise ConfigError(f"Invalid YAML in {CONFIG_FILENAME}: {e}") from e

        if raw is None:
            raw = {}

        if not isinstance(raw, dict):
            raise ConfigError(
                f"Config must be a YAML mapping, got {type(raw).__name__}"
            )

        token_limit = DEFAULT_TOKEN_LIMIT
        if "token_limit" in raw:
            token_limit = _validate_token_limit(raw["token_limit"])

        output_path = DEFAULT_OUTPUT_PATH
        if "output_path" in raw:
            output_path = _validate_output_path(raw["output_path"])

        ignore_patterns = DEFAULT_IGNORE_PATTERNS.copy()
        if "ignore_patterns" in raw:
            ignore_patterns = ignore_patterns + _validate_ignore_patterns(
                raw["ignore_patterns"]
            )

        flat_output = DEFAULT_FLAT_OUTPUT
        if "flat_output" in raw:
            flat_output = _validate_flat_output(raw["flat_output"])

        single_file = DEFAULT_SINGLE_FILE
        if "single_file" in raw:
            single_file = _validate_single_file(raw["single_file"])

        return cls(
            token_limit=token_limit,
            output_path=output_path,
            ignore_patterns=ignore_patterns,
            flat_output=flat_output,
            single_file=single_file,
        )
