from pathlib import Path
from typing import Optional, Dict, List
import yaml
from pydantic import BaseModel, ValidationError

class Task(BaseModel):
    """
    Represents a single task to be executed.

    A task defines a command to be run, along with its metadata, such as
        dependencies and expected inputs/outputs.

    Attributes:
        command (str): The shell command to execute for the task.
        description (Optional[str]): A human-readable description of what the task does.
        inputs (Optional[List[str]]): A list of input files or resources the task depends on.
        depends_on (Optional[List[str]]): A list of names of other tasks that must be
                                         completed before this one can run.
        outputs (Optional[List[str]]): A list of output files or resources generated by the task.
    """
    command: str
    description: Optional[str] = None
    inputs: Optional[List[str]] = None
    depends_on: Optional[List[str]] = None
    outputs: Optional[List[str]] = None

class Config(BaseModel):
    """
    Defines the top-level structure of the configuration file.

    This model acts as the root of the configuration schema, containing all
    the tasks defined for the project.

    Attributes:
        tasks (Dict[str, Task]): A dictionary mapping unique task names to their
                                 corresponding Task object definitions.
    """
    tasks: Dict[str, Task]

def find_config() -> Optional[Path]:
    """
    Search for wandler config files in current and parent directories.

    Returns:
        Path: Path to config file if found, otherwise None.
    """
    config_files = ('wandler.yml', '.wandler.yml', 'wandler.yaml', '.wandler.yaml',
                    'Wandler.yaml', '.Wandler.yaml', 'Wandler.yml', '.Wandler.yml')
    for path in (Path.cwd(), *Path.cwd().parents):
        for filename in config_files:
            file_path = path / filename
            if file_path.exists():
                return file_path
    return None

def load_config(config_file_path: Path):
    """
    Load and parse a configuration file.

    Parameters:
        config_file_path (Path): Path to the configuration file.

    Returns:
        dict or list: Parsed configuration data from the file.

    Raises:
        yaml.YAMLError: If there is an error parsing the YAML file.
        OSError: If there is an issue reading the file.
    """
    try:
        return yaml.safe_load(config_file_path.read_text())
    except yaml.YAMLError as ye:
        raise
    except OSError as oe:
        raise

def validate_config(config_file_data):
    """
    Validates raw configuration data against the Config model.

    This function uses Pydantic's validation capabilities to ensure the
    provided data structure and types conform to the `Config` schema.

    Args:
        config_file_data (Dict): The raw configuration data, typically
            loaded from a YAML or JSON file.

    Returns:
        Config: An instance of the Config model containing the validated data.

    Raises:
        ValidationError: If the configuration data does not match the
            structure or types defined in the Config model.
    """
    try:
        return Config.model_validate(config_file_data)
    except ValidationError as ve:
        raise