"""Reference parsing and canonical expansion for @ tokens."""

from __future__ import annotations

import os
import time
from dataclasses import dataclass
from pathlib import Path

from cleveragents.tui.search.fuzzy import rank_candidates

_REFERENCE_TYPES = ("project", "plan", "resource", "actor", "tool", "skill")
_IGNORED_DIRS = {".git", ".venv", "node_modules", "__pycache__", ".mypy_cache"}
_CATALOG_LIMIT = 400
_CATALOG_CACHE_TTL_SECONDS = 5.0
_catalog_cache: dict[str, object] = {"cwd": None, "created_at": 0.0, "catalog": None}


@dataclass(slots=True, frozen=True)
class ReferenceMatch:
    """Represents one resolved reference token."""

    token: str
    canonical: str
    category: str


@dataclass(slots=True)
class ReferenceParseResult:
    """Result of parsing a line for references."""

    expanded_line: str
    matches: list[ReferenceMatch]
    unresolved_tokens: list[str]


def _catalog() -> dict[str, list[str]]:
    cwd = Path.cwd()
    now = time.time()
    cached_cwd = _catalog_cache.get("cwd")
    cached_time = _catalog_cache.get("created_at")
    cached_catalog = _catalog_cache.get("catalog")
    if (
        isinstance(cached_cwd, Path)
        and cached_cwd == cwd
        and isinstance(cached_time, float)
        and now - cached_time < _CATALOG_CACHE_TTL_SECONDS
        and isinstance(cached_catalog, dict)
    ):
        return cached_catalog

    files = []
    for root, dirs, filenames in os.walk(cwd, followlinks=False):
        dirs[:] = [name for name in dirs if name not in _IGNORED_DIRS]
        root_path = Path(root)
        for filename in filenames:
            try:
                files.append((root_path / filename).relative_to(cwd).as_posix())
            except ValueError:
                files.append((root_path / filename).as_posix())
            if len(files) >= _CATALOG_LIMIT:
                break
        if len(files) >= _CATALOG_LIMIT:
            break

    catalog = {
        "resource": sorted(files),
        "project": [value for value in [cwd.name] if value],
        "plan": [],
        "actor": sorted(
            f"local/{file.stem}"
            for file in (cwd / "examples" / "actors").glob("*.y*ml")
        )
        if (cwd / "examples" / "actors").is_dir()
        else [],
        "tool": sorted(
            f"local/{file.stem}" for file in (cwd / "examples" / "tools").glob("*.y*ml")
        )
        if (cwd / "examples" / "tools").is_dir()
        else [],
        "skill": sorted(
            f"local/{entry.name}"
            for entry in (cwd / "examples" / "skills").iterdir()
            if entry.is_dir()
        )
        if (cwd / "examples" / "skills").is_dir()
        else [],
    }
    _catalog_cache["cwd"] = cwd
    _catalog_cache["created_at"] = now
    _catalog_cache["catalog"] = catalog
    return catalog


def _resolve(category: str, query: str, items: dict[str, list[str]]) -> str | None:
    values = items.get(category, [])
    ranked = rank_candidates(query, values, limit=1)
    if not ranked:
        return None
    return ranked[0].value


def parse_references(line: str) -> ReferenceParseResult:
    """Expand @tokens and return canonical forms."""
    items = _catalog()
    parts = line.split()
    expanded: list[str] = []
    matches: list[ReferenceMatch] = []
    unresolved: list[str] = []

    for part in parts:
        if part.startswith("\\@"):
            expanded.append(part[1:])
            continue
        if not part.startswith("@") or part == "@":
            expanded.append(part)
            continue

        raw = part[1:]
        chosen_category = ""
        query = raw
        if ":" in raw:
            category, rest = raw.split(":", 1)
            if category in _REFERENCE_TYPES:
                chosen_category = category
                query = rest

        if chosen_category:
            resolved = _resolve(chosen_category, query, items)
            if resolved:
                canonical = f"@{chosen_category}:{resolved}"
                expanded.append(canonical)
                matches.append(
                    ReferenceMatch(
                        token=part, canonical=canonical, category=chosen_category
                    )
                )
            else:
                expanded.append(part)
                unresolved.append(part)
            continue

        resolved_category = ""
        resolved_value = ""
        for category in _REFERENCE_TYPES:
            candidate = _resolve(category, raw, items)
            if candidate:
                resolved_category = category
                resolved_value = candidate
                break
        if resolved_category:
            canonical = f"@{resolved_category}:{resolved_value}"
            expanded.append(canonical)
            matches.append(
                ReferenceMatch(
                    token=part, canonical=canonical, category=resolved_category
                )
            )
        else:
            expanded.append(part)
            unresolved.append(part)

    return ReferenceParseResult(
        expanded_line=" ".join(expanded),
        matches=matches,
        unresolved_tokens=unresolved,
    )


def suggestions(
    query: str, *, category: str | None = None, limit: int = 8
) -> list[str]:
    """Return canonical suggestion strings for a query."""
    items = _catalog()
    target_categories = [category] if category else list(_REFERENCE_TYPES)
    result: list[str] = []
    for target in target_categories:
        if target is None or target not in items:
            continue
        ranked = rank_candidates(query, items[target], limit=limit)
        for candidate in ranked:
            result.append(f"@{target}:{candidate.value}")
    return result[:limit]
