"""
ShelfAI file operations.

Handles reading/writing shelf files with optional YAML frontmatter,
and provides utility functions for file manipulation.
"""

from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

try:
    import frontmatter  # type: ignore
except ImportError:  # pragma: no cover
    frontmatter = None

import yaml


@dataclass
class ShelfFile:
    """
    A parsed shelf file with optional frontmatter metadata and content body.

    Attributes:
        path: Relative path from shelf root (e.g. "skills/code_review.md")
        content: The markdown content body (without frontmatter)
        metadata: Dict of frontmatter key-value pairs
    """

    path: str
    content: str = ""
    metadata: dict = field(default_factory=dict)

    @property
    def title(self) -> str:
        """Title from frontmatter, or derived from filename."""
        if "title" in self.metadata:
            return self.metadata["title"]
        # Derive from filename: "code_review.md" -> "Code Review"
        stem = Path(self.path).stem
        return stem.replace("_", " ").replace("-", " ").title()

    @property
    def file_type(self) -> str:
        """Type from frontmatter, or derived from parent directory."""
        if "type" in self.metadata:
            return self.metadata["type"]
        parts = Path(self.path).parts
        if len(parts) > 1:
            return parts[0]
        return "unknown"

    @property
    def tags(self) -> list[str]:
        """Tags from frontmatter, or empty list."""
        return self.metadata.get("tags", [])

    @property
    def category(self) -> str:
        """Category derived from the top-level directory."""
        parts = Path(self.path).parts
        if len(parts) > 1:
            return parts[0].title()
        return "Uncategorized"


def read_shelf_file(shelf_root: Path, relative_path: str) -> ShelfFile:
    """
    Read a shelf file and parse its frontmatter.

    Args:
        shelf_root: Absolute path to the shelf directory
        relative_path: Path relative to the shelf root

    Returns:
        Parsed ShelfFile

    Raises:
        FileNotFoundError: If the file doesn't exist
    """
    full_path = shelf_root / relative_path

    if not full_path.exists():
        raise FileNotFoundError(f"Shelf file not found: {full_path}")

    if frontmatter is not None:
        post = frontmatter.load(str(full_path))
        return ShelfFile(
            path=relative_path,
            content=post.content,
            metadata=dict(post.metadata),
        )

    raw = full_path.read_text(encoding="utf-8")
    metadata = {}
    content = raw
    if raw.startswith("---\n"):
        end = raw.find("\n---\n", 4)
        if end != -1:
            meta_text = raw[4:end]
            metadata = yaml.safe_load(meta_text) or {}
            content = raw[end + 5 :]

    return ShelfFile(
        path=relative_path,
        content=content,
        metadata=dict(metadata),
    )


def write_shelf_file(shelf_root: Path, shelf_file: ShelfFile):
    """
    Write a shelf file with frontmatter.

    Creates parent directories if they don't exist.

    Args:
        shelf_root: Absolute path to the shelf directory
        shelf_file: The ShelfFile to write
    """
    full_path = shelf_root / shelf_file.path
    full_path.parent.mkdir(parents=True, exist_ok=True)

    if frontmatter is not None:
        post = frontmatter.Post(shelf_file.content, **shelf_file.metadata)
        with open(full_path, "w", encoding="utf-8") as f:
            f.write(frontmatter.dumps(post))
        return

    body = shelf_file.content
    if shelf_file.metadata:
        meta = yaml.safe_dump(shelf_file.metadata, sort_keys=False).strip()
        body = f"---\n{meta}\n---\n\n{body}"
    full_path.write_text(body, encoding="utf-8")


def append_to_file(shelf_root: Path, relative_path: str, text: str):
    """
    Append text to a shelf file. Creates the file if it doesn't exist.

    Args:
        shelf_root: Absolute path to the shelf directory
        relative_path: Path relative to the shelf root
        text: Text to append
    """
    full_path = shelf_root / relative_path
    full_path.parent.mkdir(parents=True, exist_ok=True)

    with open(full_path, "a", encoding="utf-8") as f:
        f.write(text)


def list_shelf_files(shelf_root: Path, categories: Optional[list[str]] = None) -> list[str]:
    """
    List all markdown files in the shelf, as paths relative to shelf root.

    Excludes index.md and config files.

    Args:
        shelf_root: Absolute path to the shelf directory
        categories: Optional list of categories to filter by. If None, returns all.

    Returns:
        List of relative file paths (e.g. ["skills/code_review.md", "knowledge/api.md"])
    """
    files: list[str] = []
    exclude = {"index.md", "shelf.config.yaml", ".shelfai"}
    normalized_categories = None
    if categories:
        normalized_categories = [
            c.strip("/").replace("\\", "/")
            for c in categories
            if c and c.strip()
        ]

    for item in sorted(shelf_root.rglob("*.md")):
        relative = item.relative_to(shelf_root)
        relative_str = relative.as_posix()

        # Skip excluded files, hidden directories/files, and generated review artifacts
        if relative.name in exclude:
            continue
        if any(part.startswith(".") for part in relative.parts):
            continue
        if relative.parts and relative.parts[0] == "generated":
            continue

        if normalized_categories:
            if not any(
                relative_str == category or relative_str.startswith(f"{category}/")
                for category in normalized_categories
            ):
                continue

        files.append(relative_str)

    return files


def estimate_tokens(text: str) -> int:
    """
    Rough token count estimation.

    Uses the ~4 chars per token heuristic. Not exact, but good enough
    for cost estimation and display purposes.

    Args:
        text: Text to estimate token count for

    Returns:
        Estimated token count
    """
    return len(text) // 4
