"""Skill discovery from filesystem.

This module provides functionality to discover skills from the
filesystem by scanning directories for manifest files.
"""

from __future__ import annotations

import os
from collections.abc import Iterator
from pathlib import Path

from oclawma.skills.base import SkillLoadError
from oclawma.skills.manifest import ManifestParser, SkillManifest


class SkillDiscovery:
    """Discover skills from the filesystem.

    This class scans directories for skill manifest files and
    returns information about available skills without loading them.

    Example:
        >>> discovery = SkillDiscovery()
        >>> skills = discovery.discover("/path/to/skills")
        >>> for manifest in skills:
        ...     print(f"Found skill: {manifest.name}")
    """

    MANIFEST_NAMES = [
        "skill.yaml",
        "skill.yml",
        "manifest.yaml",
        "manifest.yml",
    ]

    def __init__(self) -> None:
        """Initialize the discovery service."""
        self._parser = ManifestParser()
        self._discovered: dict[str, Path] = {}

    def discover(self, path: Path | str, recursive: bool = True) -> Iterator[SkillManifest]:
        """Discover skills in a directory.

        Args:
            path: Directory to scan
            recursive: Whether to scan subdirectories

        Yields:
            SkillManifest for each discovered skill
        """
        path = Path(path)

        if not path.exists():
            return

        if not path.is_dir():
            # Check if it's a single manifest file
            if path.suffix in (".yaml", ".yml"):
                yield from self._load_manifest(path)
            return

        if recursive:
            for manifest_path in self._find_manifests_recursive(path):
                yield from self._load_manifest(manifest_path)
        else:
            for manifest_path in self._find_manifests(path):
                yield from self._load_manifest(manifest_path)

    def discover_single(self, path: Path | str) -> SkillManifest | None:
        """Discover a single skill from a manifest file or directory.

        Args:
            path: Path to manifest file or skill directory

        Returns:
            SkillManifest or None if not found/invalid
        """
        path = Path(path)

        if path.is_file() and path.suffix in (".yaml", ".yml"):
            try:
                return self._parser.parse(path)
            except SkillLoadError:
                return None

        if path.is_dir():
            for manifest_name in self.MANIFEST_NAMES:
                manifest_path = path / manifest_name
                if manifest_path.exists():
                    try:
                        return self._parser.parse(manifest_path)
                    except SkillLoadError:
                        continue

        return None

    def scan(self, path: Path | str, recursive: bool = True) -> dict[str, Path]:
        """Scan for skills and return a mapping of names to paths.

        Args:
            path: Directory to scan
            recursive: Whether to scan subdirectories

        Returns:
            Dictionary mapping skill names to manifest paths
        """
        results = {}

        for manifest in self.discover(path, recursive):
            results[manifest.name] = self._parser._cache.get(
                Path(path).resolve() / f"{manifest.name}.yaml"
            )
            # Actually get the real path
            for discovered in self.discover(path, recursive):
                # Re-find the path for this manifest
                manifest_path = self._find_manifest_path(path, discovered.name)
                if manifest_path:
                    results[discovered.name] = manifest_path

        return results

    def _find_manifests(self, directory: Path) -> Iterator[Path]:
        """Find manifest files in a single directory."""
        for name in self.MANIFEST_NAMES:
            manifest_path = directory / name
            if manifest_path.exists():
                yield manifest_path

    def _find_manifests_recursive(self, directory: Path) -> Iterator[Path]:
        """Find manifest files recursively."""
        for root, _, files in os.walk(directory):
            root_path = Path(root)
            for name in self.MANIFEST_NAMES:
                if name in files:
                    yield root_path / name

    def _load_manifest(self, path: Path) -> Iterator[SkillManifest]:
        """Load a manifest file."""
        try:
            manifest = self._parser.parse(path)
            self._discovered[manifest.name] = path
            yield manifest
        except SkillLoadError:
            # Skip invalid manifests
            pass

    def _find_manifest_path(self, base_path: Path | str, skill_name: str) -> Path | None:
        """Find the manifest path for a skill name."""
        base_path = Path(base_path)

        for root, _, files in os.walk(base_path):
            root_path = Path(root)
            for name in self.MANIFEST_NAMES:
                if name in files:
                    manifest_path = root_path / name
                    try:
                        manifest = self._parser.parse(manifest_path)
                        if manifest.name == skill_name:
                            return manifest_path
                    except SkillLoadError:
                        continue

        return None

    def get_manifest_path(self, skill_name: str) -> Path | None:
        """Get the path to a discovered skill's manifest.

        Args:
            skill_name: Name of the skill

        Returns:
            Path to manifest or None if not discovered
        """
        return self._discovered.get(skill_name)

    def clear_cache(self) -> None:
        """Clear the discovery cache."""
        self._discovered.clear()
        self._parser.clear_cache()

    @classmethod
    def find_skill_directories(cls, base_path: Path | str) -> list[Path]:
        """Find all directories that contain skills.

        Args:
            base_path: Base directory to search

        Returns:
            List of directories containing skill manifests
        """
        skill_dirs = []
        base_path = Path(base_path)

        for root, _, files in os.walk(base_path):
            root_path = Path(root)
            for name in cls.MANIFEST_NAMES:
                if name in files:
                    skill_dirs.append(root_path)
                    break

        return skill_dirs

    @classmethod
    def is_skill_directory(cls, path: Path | str) -> bool:
        """Check if a directory contains a skill.

        Args:
            path: Directory to check

        Returns:
            True if the directory contains a skill manifest
        """
        path = Path(path)

        if not path.is_dir():
            return False

        return any((path / name).exists() for name in cls.MANIFEST_NAMES)
