"""File browsing and syntax highlighting for the experiment web UI."""

from __future__ import annotations

import dataclasses
from pathlib import Path

from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import TextLexer, get_lexer_for_filename
from pygments.util import ClassNotFound

_IMAGE_EXTENSIONS = frozenset({
    ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".ico", ".svg",
})

_BINARY_EXTENSIONS = frozenset({
    ".pkl", ".pickle", ".pt", ".pth", ".npz", ".npy",
    ".bin", ".dat", ".db", ".sqlite",
    ".zip", ".tar", ".gz", ".bz2", ".xz", ".7z",
    ".whl", ".egg",
    ".so", ".dylib", ".dll", ".exe",
    ".pdf",
}) | _IMAGE_EXTENSIONS

_SNIFF_SIZE = 8192
_DEFAULT_MAX_BYTES = 102_400  # 100 KB

_EXCLUDED_DIR_NAMES = frozenset({"__pycache__"})


@dataclasses.dataclass
class FileEntry:
    relative_path: str
    name: str
    is_dir: bool
    size_bytes: int
    is_binary: bool
    depth: int
    is_image: bool = False


@dataclasses.dataclass
class FileContent:
    content: str
    highlighted_html: str
    language: str
    truncated: bool
    original_size: int
    is_binary: bool
    is_image: bool = False


def is_image_file(path: Path) -> bool:
    """Return True if *path* has a recognized image extension."""
    return path.suffix.lower() in _IMAGE_EXTENSIONS


def is_binary_file(path: Path) -> bool:
    """Check whether a file is binary (by extension then null-byte sniff)."""
    if path.suffix.lower() in _BINARY_EXTENSIONS:
        return True
    try:
        with path.open("rb") as fh:
            chunk = fh.read(_SNIFF_SIZE)
    except OSError:
        return True
    return b"\x00" in chunk


def safe_resolve(exp_path: Path, requested_path: str) -> Path | None:
    """Resolve *requested_path* inside *exp_path*; return None if it escapes."""
    if "\x00" in requested_path:
        return None
    candidate = (exp_path / requested_path).resolve()
    try:
        candidate.relative_to(exp_path.resolve())
    except ValueError:
        return None
    return candidate


def list_experiment_files(exp_path: Path) -> list[FileEntry]:
    """Walk *exp_path* and return a sorted tree of FileEntry objects."""
    if not exp_path.is_dir():
        return []

    entries: list[FileEntry] = []

    def _walk(directory: Path, depth: int) -> None:
        try:
            children = sorted(directory.iterdir(), key=lambda p: p.name)
        except OSError:
            return

        dirs = [
            c for c in children
            if c.is_dir() and c.name not in _EXCLUDED_DIR_NAMES
        ]
        files = [c for c in children if c.is_file()]

        for d in dirs:
            rel = str(d.relative_to(exp_path))
            entries.append(FileEntry(
                relative_path=rel,
                name=d.name,
                is_dir=True,
                size_bytes=0,
                is_binary=False,
                depth=depth,
            ))
            _walk(d, depth + 1)

        for f in files:
            rel = str(f.relative_to(exp_path))
            entries.append(FileEntry(
                relative_path=rel,
                name=f.name,
                is_dir=False,
                size_bytes=f.stat().st_size,
                is_binary=is_binary_file(f),
                is_image=is_image_file(f),
                depth=depth,
            ))

    _walk(exp_path, 0)
    return entries


def highlight_code(content: str, filename: str) -> str:
    """Return Pygments-highlighted HTML for *content*."""
    try:
        lexer = get_lexer_for_filename(filename, stripall=True)
    except ClassNotFound:
        lexer = TextLexer(stripall=True)
    formatter = HtmlFormatter(nowrap=False, cssclass="highlight")
    return highlight(content, lexer, formatter)


def get_pygments_css() -> str:
    """Return the CSS stylesheet for Pygments highlighting."""
    return HtmlFormatter(cssclass="highlight").get_style_defs()


def render_file_content(
    path: Path, max_bytes: int = _DEFAULT_MAX_BYTES
) -> FileContent:
    """Read a file, detect binary, optionally truncate, and syntax-highlight."""
    if is_image_file(path):
        return FileContent(
            content="",
            highlighted_html="",
            language="image",
            truncated=False,
            original_size=path.stat().st_size,
            is_binary=True,
            is_image=True,
        )

    if is_binary_file(path):
        return FileContent(
            content="",
            highlighted_html="",
            language="binary",
            truncated=False,
            original_size=path.stat().st_size,
            is_binary=True,
        )

    raw = path.read_bytes()
    original_size = len(raw)
    truncated = len(raw) > max_bytes
    if truncated:
        raw = raw[:max_bytes]

    content = raw.decode("utf-8", errors="replace")
    highlighted_html = highlight_code(content, path.name)

    # Determine language name from Pygments lexer
    try:
        lexer = get_lexer_for_filename(path.name)
        language = lexer.name
    except ClassNotFound:
        language = "Plain Text"

    return FileContent(
        content=content,
        highlighted_html=highlighted_html,
        language=language,
        truncated=truncated,
        original_size=original_size,
        is_binary=False,
    )
