"""
PyFile class for nblite.

Represents a Python module file, potentially autogenerated from notebooks.
"""

from __future__ import annotations

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

__all__ = ["PyFile", "PyFileCell"]


# Pattern to match autogenerated header
AUTOGEN_HEADER_PATTERN = re.compile(
    r"^#\s*AUTOGENERATED!\s*DO NOT EDIT!\s*File to edit:\s*(.+)$",
    re.MULTILINE,
)

# Pattern to match percent-style cell markers: # %% path/to/file.ipynb INDEX
CELL_MARKER_PATTERN = re.compile(
    r"^#\s*%%\s+(\S+)\s+(\d+)\s*$",
    re.MULTILINE,
)


@dataclass
class PyFileCell:
    """
    A cell extracted from a percent-style Python file.

    Attributes:
        source: Cell source code
        source_path: Path to source notebook
        source_cell_index: Cell index in source notebook
    """

    source: str
    source_path: str
    source_cell_index: int


@dataclass
class PyFile:
    """
    Represents a Python module file.

    This class handles both regular Python files and autogenerated files
    from nblite exports. It can parse percent-style cell markers to extract
    the original notebook cells.

    Attributes:
        path: File path
        content: File content
        package_root: Root path of the Python package
    """

    path: Path
    content: str
    package_root: Path | None = None

    _source_notebook: Path | None = field(default=None, repr=False, init=False)
    _is_autogenerated: bool | None = field(default=None, repr=False, init=False)
    _cells: list[PyFileCell] | None = field(default=None, repr=False, init=False)

    @classmethod
    def from_file(cls, path: Path | str, package_root: Path | None = None) -> PyFile:
        """
        Load a Python file.

        Args:
            path: Path to Python file
            package_root: Root path of the Python package

        Returns:
            PyFile instance
        """
        path = Path(path)
        content = path.read_text()
        return cls(path=path, content=content, package_root=package_root)

    @property
    def is_autogenerated(self) -> bool:
        """Check if this file is autogenerated by nblite."""
        if self._is_autogenerated is None:
            self._parse_header()
        return self._is_autogenerated  # type: ignore

    @property
    def source_notebook(self) -> Path | None:
        """
        Path to the source notebook (for autogenerated files).

        Returns:
            Path to source notebook, or None if not autogenerated
        """
        if self._is_autogenerated is None:
            self._parse_header()
        return self._source_notebook

    def _parse_header(self) -> None:
        """Parse the autogenerated header from file content."""
        match = AUTOGEN_HEADER_PATTERN.search(self.content)
        if match:
            self._is_autogenerated = True
            source_path = match.group(1).strip()
            self._source_notebook = Path(source_path)
        else:
            self._is_autogenerated = False
            self._source_notebook = None

    @property
    def cells(self) -> list[PyFileCell]:
        """
        Parse cells from percent-style file.

        Returns:
            List of PyFileCell objects, or empty list if no cell markers found
        """
        if self._cells is None:
            self._parse_cells()
        return self._cells  # type: ignore

    def _parse_cells(self) -> None:
        """Parse cells from percent-style markers."""
        self._cells = []

        # Find all cell markers
        matches = list(CELL_MARKER_PATTERN.finditer(self.content))
        if not matches:
            return

        for i, match in enumerate(matches):
            source_path = match.group(1)
            cell_index = int(match.group(2))

            # Get content from this marker to the next (or end)
            start = match.end()
            if i + 1 < len(matches):
                end = matches[i + 1].start()
            else:
                end = len(self.content)

            # Extract and clean content
            cell_content = self.content[start:end].strip()

            self._cells.append(
                PyFileCell(
                    source=cell_content,
                    source_path=source_path,
                    source_cell_index=cell_index,
                )
            )

    @property
    def module_path(self) -> str | None:
        """
        Calculate the module path for this file.

        Requires package_root to be set.

        Returns:
            Module path string (e.g., "mypackage.utils.helpers"), or None
        """
        if self.package_root is None:
            return None

        try:
            relative = self.path.relative_to(self.package_root.parent)
        except ValueError:
            return None

        # Convert path to module path
        parts = list(relative.parts)

        # Remove .py extension from last part
        if parts and parts[-1].endswith(".py"):
            parts[-1] = parts[-1][:-3]

        # Handle __init__.py
        if parts and parts[-1] == "__init__":
            parts = parts[:-1]

        return ".".join(parts) if parts else None

    def __repr__(self) -> str:
        return f"PyFile(path={self.path!r}, autogenerated={self.is_autogenerated})"
