"""Architect configuration."""

from __future__ import annotations

import shutil
from dataclasses import dataclass, field
from pathlib import Path

from cleave.architect.errors import ArchitectError
from cleave.orchestrator.config import _parse_timeout


@dataclass
class ArchitectConfig:
    """Configuration for an architect run."""

    directive: str
    repo_path: Path
    success_criteria: list[str] = field(default_factory=list)

    # Budget
    max_budget_usd: float = 200.0
    phase_budget_usd: float = 50.0
    child_budget_usd: float = 15.0

    # Timing
    timeout_seconds: int = 86400  # 24h
    phase_timeout_seconds: int = 28800  # 8h

    # Models
    planner_model: str = "opus"
    executor_model: str = "opus"
    executor_planner_model: str = "sonnet"

    # Constraints
    max_phases: int = 10
    max_depth: int = 3
    circuit_breaker_threshold: int = 2

    # Orchestrator passthrough
    max_parallel_children: int = 4
    permission_mode: str = "bypassPermissions"
    allowed_tools: str = "Bash Edit Read Write Glob Grep"
    mcp_config: str = ""

    # Behavior
    dirty: bool = False
    confirm: bool = False
    verbose: bool = False
    db_path: Path | None = None

    def __post_init__(self) -> None:
        if isinstance(self.repo_path, str):
            self.repo_path = Path(self.repo_path)
        if self.db_path is not None and isinstance(self.db_path, str):
            self.db_path = Path(self.db_path)

    def validate(self) -> None:
        """Validate configuration, raising ArchitectError on problems."""
        if not self.directive.strip():
            raise ArchitectError("Directive must not be empty")
        if not self.repo_path.is_dir():
            raise ArchitectError(f"Repo path does not exist: {self.repo_path}")
        if not (self.repo_path / ".git").exists():
            raise ArchitectError(f"Repo path is not a git repository: {self.repo_path}")
        if self.max_budget_usd <= 0:
            raise ArchitectError("max_budget_usd must be positive")
        if self.phase_budget_usd <= 0:
            raise ArchitectError("phase_budget_usd must be positive")
        if self.child_budget_usd <= 0:
            raise ArchitectError("child_budget_usd must be positive")
        if self.phase_budget_usd > self.max_budget_usd:
            raise ArchitectError("phase_budget_usd cannot exceed max_budget_usd")
        if self.timeout_seconds <= 0:
            raise ArchitectError("timeout_seconds must be positive")
        if self.phase_timeout_seconds <= 0:
            raise ArchitectError("phase_timeout_seconds must be positive")
        if self.max_phases < 1 or self.max_phases > 20:
            raise ArchitectError("max_phases must be between 1 and 20")
        if self.max_depth < 1 or self.max_depth > 10:
            raise ArchitectError("max_depth must be between 1 and 10")
        if self.max_parallel_children < 1:
            raise ArchitectError("max_parallel_children must be at least 1")

    def find_claude_cli(self) -> str:
        """Locate the claude CLI binary, raising if not found."""
        path = shutil.which("claude")
        if path is None:
            raise ArchitectError(
                "Could not find 'claude' on PATH. Install Claude Code first."
            )
        return path

    def to_dict(self) -> dict:
        """Serialize config to a plain dict for persistence."""
        return {
            "directive": self.directive,
            "repo_path": str(self.repo_path),
            "success_criteria": self.success_criteria,
            "max_budget_usd": self.max_budget_usd,
            "phase_budget_usd": self.phase_budget_usd,
            "child_budget_usd": self.child_budget_usd,
            "timeout_seconds": self.timeout_seconds,
            "phase_timeout_seconds": self.phase_timeout_seconds,
            "planner_model": self.planner_model,
            "executor_model": self.executor_model,
            "executor_planner_model": self.executor_planner_model,
            "max_phases": self.max_phases,
            "max_depth": self.max_depth,
            "circuit_breaker_threshold": self.circuit_breaker_threshold,
            "max_parallel_children": self.max_parallel_children,
            "permission_mode": self.permission_mode,
            "allowed_tools": self.allowed_tools,
            "mcp_config": self.mcp_config,
            "dirty": self.dirty,
            "confirm": self.confirm,
            "verbose": self.verbose,
            "db_path": str(self.db_path) if self.db_path else None,
        }

    @classmethod
    def from_dict(cls, data: dict) -> ArchitectConfig:
        """Reconstruct config from a serialized dict."""
        data = dict(data)
        data["repo_path"] = Path(data["repo_path"])
        if data.get("db_path"):
            data["db_path"] = Path(data["db_path"])
        known = cls.__dataclass_fields__
        filtered = {k: v for k, v in data.items() if k in known}
        return cls(**filtered)
