"""
ShelfAI Shelf — the main interface for shelf operations.

The Shelf class provides high-level methods for interacting with
the shelf directory structure, index, and configuration.
"""

from pathlib import Path
from typing import Optional

from shelfai.core.config import ShelfConfig
from shelfai.core.index import Index
from shelfai.core.safety import validate_path
from shelfai.core.fileops import (
    ShelfFile,
    read_shelf_file,
    write_shelf_file,
    append_to_file,
    list_shelf_files,
    estimate_tokens,
)


class Shelf:
    """
    High-level interface for a ShelfAI shelf.

    A shelf is a directory containing:
        - index.md: Master index with abstracts
        - shelf.config.yaml: Configuration
        - Category directories (skills/, knowledge/, memory/, resources/)

    Usage:
        shelf = Shelf("./shelf")
        shelf.read_index()
        files = shelf.list_files()
        content = shelf.read_file("skills/code_review.md")
    """

    def __init__(self, path: str):
        """
        Initialize a Shelf instance.

        Args:
            path: Path to the shelf root directory
        """
        self.path = Path(path).resolve()
        self._index: Optional[Index] = None
        self._config: Optional[ShelfConfig] = None

    def exists(self) -> bool:
        """Check if the shelf directory exists and has been initialized."""
        return (self.path / "index.md").exists()

    @property
    def index(self) -> Index:
        """Lazy-loaded index."""
        if self._index is None:
            index_path = self.path / "index.md"
            self._index = Index(index_path)
        return self._index

    def load_config(self) -> ShelfConfig:
        """
        Load the shelf configuration.

        Returns:
            ShelfConfig instance

        Raises:
            FileNotFoundError: If shelf.config.yaml doesn't exist
        """
        if self._config is None:
            config_path = self.path / "shelf.config.yaml"
            if config_path.exists():
                self._config = ShelfConfig.load(config_path)
            else:
                self._config = ShelfConfig.default()
        return self._config

    def read_index(self) -> str:
        """
        Read the raw index.md content.

        Returns:
            Full text of index.md
        """
        index_path = self.path / "index.md"
        if not index_path.exists():
            return ""
        return index_path.read_text(encoding="utf-8")

    def read_file(self, relative_path: str, default: Optional[str] = None) -> str:
        """
        Read a shelf file's content (without frontmatter).

        Args:
            relative_path: Path relative to shelf root
            default: Value to return if file doesn't exist (None raises FileNotFoundError)

        Returns:
            File content as string

        Raises:
            FileNotFoundError: If file doesn't exist and no default provided
        """
        validate_path(self.path, relative_path)
        try:
            shelf_file = read_shelf_file(self.path, relative_path)
            return shelf_file.content
        except FileNotFoundError:
            if default is not None:
                return default
            raise

    def read_shelf_file(self, relative_path: str) -> ShelfFile:
        """
        Read a shelf file with parsed frontmatter.

        Args:
            relative_path: Path relative to shelf root

        Returns:
            ShelfFile with metadata and content
        """
        validate_path(self.path, relative_path)
        return read_shelf_file(self.path, relative_path)

    def write_file(self, relative_path: str, content: str, metadata: Optional[dict] = None):
        """
        Write a file to the shelf.

        Creates parent directories if needed. Validates that the path
        stays within the shelf root to prevent path traversal.

        Args:
            relative_path: Path relative to shelf root
            content: File content
            metadata: Optional frontmatter metadata dict

        Raises:
            ValueError: If the path escapes the shelf root
        """
        validate_path(self.path, relative_path)

        shelf_file = ShelfFile(
            path=relative_path,
            content=content,
            metadata=metadata or {},
        )
        write_shelf_file(self.path, shelf_file)

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

        Args:
            relative_path: Path relative to shelf root
            text: Text to append

        Raises:
            ValueError: If the path escapes the shelf root
        """
        validate_path(self.path, relative_path)
        append_to_file(self.path, relative_path, text)

    def list_files(self, categories: Optional[list[str]] = None) -> list[str]:
        """
        List all content files in the shelf.

        Excludes index.md and config files.

        Args:
            categories: Optional category filter

        Returns:
            List of relative file paths
        """
        return list_shelf_files(self.path, categories)

    def update_index_abstract(self, file_path: str, new_abstract: str):
        """
        Update an abstract in the index.

        Args:
            file_path: Relative path of the file
            new_abstract: New abstract text
        """
        self.index.update(file_path, new_abstract)

    def add_index_entry(self, file_path: str, abstract: str, category: Optional[str] = None):
        """
        Add a new entry to the index.

        Category is auto-detected from the file path if not specified.

        Args:
            file_path: Relative path of the file
            abstract: One-line abstract
            category: Category section name (auto-detected if None)
        """
        if category is None:
            parts = Path(file_path).parts
            category = parts[0].title() if len(parts) > 1 else "Uncategorized"
        self.index.add(file_path, abstract, category)

    def status(self) -> dict:
        """
        Get shelf health and statistics.

        Returns:
            Dict with keys: total_files, indexed_count, unindexed, stale,
            categories, estimated_tokens
        """
        files = self.list_files()
        index = self.index

        unindexed = index.unindexed_files(files)
        stale = index.stale_entries(files)

        # Count files per category
        category_counts = {}
        for f in files:
            parts = Path(f).parts
            cat = parts[0] if len(parts) > 1 else "root"
            category_counts[cat] = category_counts.get(cat, 0) + 1

        # Estimate total tokens
        total_text = self.read_index()
        for f in files:
            try:
                total_text += self.read_file(f, default="")
            except Exception:
                pass

        return {
            "total_files": len(files),
            "indexed_count": index.count,
            "unindexed": unindexed,
            "stale": [e.file_path for e in stale],
            "categories": category_counts,
            "estimated_tokens": estimate_tokens(total_text),
            "index_tokens": estimate_tokens(self.read_index()),
        }

    def export(
        self,
        categories: Optional[list[str]] = None,
        index_only: bool = False,
        max_tokens: Optional[int] = None,
    ) -> str:
        """
        Export the shelf as a single string.

        Useful for pasting directly into an LLM context window.

        Args:
            categories: Optional category filter
            index_only: If True, export only the index
            max_tokens: Optional token budget (truncates if exceeded)

        Returns:
            Combined shelf content as a single string
        """
        parts = []

        # Always include the index
        parts.append(self.read_index())

        if not index_only:
            parts.append("\n---\n")
            for file_path in self.list_files(categories):
                try:
                    content = self.read_file(file_path)
                    parts.append(f"\n## File: {file_path}\n\n{content}\n")
                except Exception:
                    parts.append(f"\n## File: {file_path}\n\n(error reading file)\n")

        result = "\n".join(parts)

        # Truncate if over token budget
        if max_tokens:
            estimated = estimate_tokens(result)
            if estimated > max_tokens:
                # Rough truncation: cut to approximate character count
                char_limit = max_tokens * 4
                result = result[:char_limit] + "\n\n... (truncated to fit token budget)"

        return result

    def __repr__(self) -> str:
        status = "initialized" if self.exists() else "not initialized"
        return f"Shelf('{self.path}', {status})"
