"""
Abstract base class for explode-implode variants.
"""

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, List, Any, Optional
from dataclasses import dataclass

from .enums import ExplodeVariant, ExplodeMode


@dataclass
class ExplodeOptions:
    """Options for explode operations."""

    variant: ExplodeVariant
    mode: ExplodeMode = ExplodeMode.STANDARD
    output_dir: Optional[Path] = None
    max_depth: Optional[int] = None
    preserve_front_matter: bool = True
    section_spacing: int = 2
    dry_run: bool = False
    verbose: bool = False
    create_manifest: bool = True


@dataclass
class ImplodeOptions:
    """Options for implode operations."""

    output_file: Optional[Path] = None
    force_variant: Optional[ExplodeVariant] = None
    preserve_front_matter: bool = True
    section_spacing: int = 2
    dry_run: bool = False
    verbose: bool = False
    overwrite: bool = False


@dataclass
class ExplodeResult:
    """Result of an explode operation."""

    success: bool
    output_directory: Path
    files_created: List[Path]
    manifest_path: Optional[Path]
    warnings: List[str]
    errors: List[str]
    variant_used: ExplodeVariant


@dataclass
class ImplodeResult:
    """Result of an implode operation."""

    success: bool
    output_file: Path
    files_processed: List[Path]
    variant_detected: Optional[ExplodeVariant]
    warnings: List[str]
    errors: List[str]


class BaseVariant(ABC):
    """
    Abstract base class for explode-implode variants.

    Each variant implements a specific strategy for organizing exploded
    markdown content and reconstructing it during implode operations.
    """

    def __init__(self, variant_type: ExplodeVariant):
        """
        Initialize the variant.

        Args:
            variant_type: The type of variant this implements
        """
        self.variant_type = variant_type

    @property
    @abstractmethod
    def name(self) -> str:
        """Human-readable name of the variant."""
        pass

    @property
    @abstractmethod
    def description(self) -> str:
        """Description of the variant's behavior."""
        pass

    @abstractmethod
    def explode(
        self,
        input_file: Path,
        options: ExplodeOptions
    ) -> ExplodeResult:
        """
        Explode a markdown file into a directory structure.

        Args:
            input_file: Path to the markdown file to explode
            options: Options controlling the explode operation

        Returns:
            Result of the explode operation

        Raises:
            FileNotFoundError: If input file doesn't exist
            PermissionError: If unable to create output directory
            ValueError: If input file is not valid markdown
        """
        pass

    @abstractmethod
    def implode(
        self,
        input_directory: Path,
        options: ImplodeOptions
    ) -> ImplodeResult:
        """
        Implode a directory structure back into a markdown file.

        Args:
            input_directory: Path to the directory to implode
            options: Options controlling the implode operation

        Returns:
            Result of the implode operation

        Raises:
            FileNotFoundError: If input directory doesn't exist
            ValueError: If directory structure is invalid for this variant
        """
        pass

    @abstractmethod
    def can_handle_directory(self, directory: Path) -> bool:
        """
        Check if this variant can handle the given directory structure.

        Args:
            directory: Path to the directory to check

        Returns:
            True if this variant can handle the directory
        """
        pass

    @abstractmethod
    def get_detection_patterns(self) -> Dict[str, Any]:
        """
        Get patterns used for auto-detecting this variant.

        Returns:
            Dictionary of detection patterns and weights
        """
        pass

    def validate_input_file(self, input_file: Path) -> List[str]:
        """
        Validate the input markdown file.

        Args:
            input_file: Path to the file to validate

        Returns:
            List of validation errors (empty if valid)
        """
        errors = []

        if not input_file.exists():
            errors.append(f"Input file does not exist: {input_file}")
            return errors

        if not input_file.is_file():
            errors.append(f"Input path is not a file: {input_file}")
            return errors

        if input_file.suffix.lower() not in ['.md', '.markdown']:
            errors.append(f"Input file is not a markdown file: {input_file}")

        try:
            content = input_file.read_text(encoding='utf-8')
            if not content.strip():
                errors.append("Input file is empty")
        except UnicodeDecodeError:
            errors.append("Input file contains invalid UTF-8 encoding")
        except Exception as e:
            errors.append(f"Error reading input file: {e}")

        return errors

    def validate_input_directory(self, input_directory: Path) -> List[str]:
        """
        Validate the input directory structure.

        Args:
            input_directory: Path to the directory to validate

        Returns:
            List of validation errors (empty if valid)
        """
        errors = []

        if not input_directory.exists():
            errors.append(f"Input directory does not exist: {input_directory}")
            return errors

        if not input_directory.is_dir():
            errors.append(f"Input path is not a directory: {input_directory}")
            return errors

        # Check if directory contains any markdown files
        md_files = list(input_directory.glob("**/*.md"))
        if not md_files:
            errors.append("Directory contains no markdown files")

        return errors

    def create_output_directory(self, output_dir: Path, overwrite: bool = False) -> List[str]:
        """
        Create the output directory if it doesn't exist.

        Args:
            output_dir: Path to the directory to create
            overwrite: Whether to overwrite existing directory

        Returns:
            List of errors (empty if successful)
        """
        errors = []

        try:
            if output_dir.exists():
                if not overwrite:
                    errors.append(f"Output directory already exists: {output_dir}")
                    return errors

                if output_dir.is_file():
                    errors.append(f"Output path exists and is a file: {output_dir}")
                    return errors

            output_dir.mkdir(parents=True, exist_ok=overwrite)

        except PermissionError:
            errors.append(f"Permission denied creating directory: {output_dir}")
        except Exception as e:
            errors.append(f"Error creating output directory: {e}")

        return errors