"""Loom exception hierarchy."""


class LoomError(Exception):
    """Base exception for all Loom errors."""


class TaskNotFoundError(LoomError):
    """Raised when a task ID does not exist."""

    def __init__(self, task_id: str):
        super().__init__(f"Task not found: {task_id}")
        self.task_id = task_id


class TaskStateError(LoomError):
    """Raised when a task state transition is invalid."""

    def __init__(self, task_id: str, current: str, attempted: str):
        super().__init__(
            f"Task {task_id} is '{current}', cannot transition to '{attempted}'"
        )
        self.task_id = task_id
        self.current = current
        self.attempted = attempted


class ProjectNotFoundError(LoomError):
    """Raised when a project ID does not exist."""

    def __init__(self, project_id: str):
        super().__init__(f"Project not found: {project_id}")
        self.project_id = project_id


class DependencyCycleError(LoomError):
    """Raised when adding a dependency would create a cycle."""

    def __init__(self, cycle: list[str]):
        super().__init__(f"Dependency cycle detected: {' -> '.join(cycle)}")
        self.cycle = cycle


class ClaimConflictError(LoomError):
    """Raised when a task cannot be claimed (already claimed or not pending)."""

    def __init__(self, task_id: str):
        super().__init__(f"Task {task_id} cannot be claimed (not pending or already locked)")
        self.task_id = task_id


class SkillError(LoomError):
    """Raised when a skill execution fails."""

    def __init__(self, skill_name: str, reason: str = ""):
        msg = f"Skill '{skill_name}' failed"
        if reason:
            msg += f": {reason}"
        super().__init__(msg)
        self.skill_name = skill_name


class SkillNotFoundError(LoomError):
    """Raised when a requested skill does not exist."""

    def __init__(self, skill_name: str):
        super().__init__(f"Skill not found: {skill_name}")
        self.skill_name = skill_name


class DecompositionError(LoomError):
    """Raised when decomposition output is invalid."""

    def __init__(self, reason: str):
        super().__init__(f"Decomposition failed: {reason}")
        self.reason = reason
