"""Render visitors for ACMS skeleton compression."""

from __future__ import annotations

import re
from typing import Final

from cleveragents.domain.models.core.context_fragment import ContextFragment

_DETAIL_DEPTH_OVERVIEW: Final[int] = 0
_MAX_DOC_HEADINGS: Final[int] = 3
_MAX_DATA_ITEMS: Final[int] = 4


class FragmentRenderVisitor:
    """Render a fragment at a reduced detail depth for one domain family."""

    def render(
        self,
        fragment: ContextFragment,
        *,
        target_depth: int,
        level_name: str,
    ) -> str:
        raise NotImplementedError


class _CodeRenderVisitor(FragmentRenderVisitor):
    def render(
        self,
        fragment: ContextFragment,
        *,
        target_depth: int,
        level_name: str,
    ) -> str:
        subject = _resource_label(fragment)
        summary = _code_structure_summary(fragment.content)
        if target_depth <= _DETAIL_DEPTH_OVERVIEW:
            return f"{subject} [{level_name}]"
        if summary:
            return f"{subject} [{level_name}]: {summary}"
        return f"{subject} [{level_name}]"


class _DocumentRenderVisitor(FragmentRenderVisitor):
    def render(
        self,
        fragment: ContextFragment,
        *,
        target_depth: int,
        level_name: str,
    ) -> str:
        title = _first_non_empty_line(
            fragment.content,
            fallback=_resource_label(fragment),
        )
        if target_depth <= _DETAIL_DEPTH_OVERVIEW:
            return f"{title} [{level_name}]"
        headings = _document_headings(fragment.content)
        if headings:
            return f"{title} [{level_name}]: {'; '.join(headings[:_MAX_DOC_HEADINGS])}"
        return f"{title} [{level_name}]: {_summarise_words(fragment.content, limit=12)}"


class _DataRenderVisitor(FragmentRenderVisitor):
    def render(
        self,
        fragment: ContextFragment,
        *,
        target_depth: int,
        level_name: str,
    ) -> str:
        label = _resource_label(fragment)
        items = _csv_like_items(fragment.content)
        if target_depth <= _DETAIL_DEPTH_OVERVIEW:
            return f"{label} [{level_name}]"
        summary = ", ".join(items[:_MAX_DATA_ITEMS]) or _summarise_words(
            fragment.content,
            10,
        )
        return f"{label} [{level_name}]: {summary}"


class _InfrastructureRenderVisitor(FragmentRenderVisitor):
    def render(
        self,
        fragment: ContextFragment,
        *,
        target_depth: int,
        level_name: str,
    ) -> str:
        label = _resource_label(fragment)
        synopsis = _first_non_empty_line(fragment.content)
        if target_depth <= _DETAIL_DEPTH_OVERVIEW:
            return f"{label} [{level_name}]"
        return f"{label} [{level_name}]: {synopsis}"


class _GenericRenderVisitor(FragmentRenderVisitor):
    def render(
        self,
        fragment: ContextFragment,
        *,
        target_depth: int,
        level_name: str,
    ) -> str:
        label = _resource_label(fragment)
        if target_depth <= _DETAIL_DEPTH_OVERVIEW:
            return f"{label} [{level_name}]"
        return f"{label} [{level_name}]: {_summarise_words(fragment.content, 10)}"


_VISITORS: Final[dict[str, FragmentRenderVisitor]] = {
    "code": _CodeRenderVisitor(),
    "doc": _DocumentRenderVisitor(),
    "data": _DataRenderVisitor(),
    "infra": _InfrastructureRenderVisitor(),
    "generic": _GenericRenderVisitor(),
}


def get_render_visitor(family: str) -> FragmentRenderVisitor:
    """Return the visitor for a resolved fragment family."""

    return _VISITORS[family]


def _code_structure_summary(text: str) -> str:
    """Build a structural summary for depth-1 code skeletons."""

    symbols = _extract_code_symbols(text)
    if symbols:
        return f"symbols={', '.join(symbols[:5])}"
    return ""


def _extract_code_symbols(text: str) -> list[str]:
    """Extract class, function, and import targets from source text."""

    patterns = (
        r"^(?:async\s+)?def\s+([A-Za-z_][A-Za-z0-9_]*)",
        r"^class\s+([A-Za-z_][A-Za-z0-9_]*)",
        r"^function\s+([A-Za-z_][A-Za-z0-9_]*)",
        r"^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)\s*=>|function)",
        r"^fn\s+([A-Za-z_][A-Za-z0-9_]*)",
        r"^import\s+([A-Za-z_][A-Za-z0-9_]*)",
    )
    names: list[str] = []
    seen: set[str] = set()
    for line in text.splitlines():
        stripped = line.strip()
        if not stripped:
            continue
        for pattern in patterns:
            match = re.match(pattern, stripped)
            if match is None:
                continue
            name = match.group(1)
            if name not in seen:
                names.append(name)
                seen.add(name)
            break
    return names


def _resource_label(fragment: ContextFragment) -> str:
    """Return a compact human-readable label for a fragment."""

    resource_uri = fragment.provenance.resource_uri or fragment.uko_node
    stripped = (
        resource_uri.split("://", 1)[1] if "://" in resource_uri else resource_uri
    )
    stripped = stripped.rstrip("/")
    if not stripped:
        return fragment.uko_node
    return stripped.rsplit("/", 1)[-1]


def _first_non_empty_line(text: str, fallback: str = "summary") -> str:
    """Return the first non-empty line from ``text``."""

    for line in text.splitlines():
        stripped = line.strip(" #-*\t")
        if stripped:
            return stripped
    return fallback


def _summarise_words(text: str, limit: int) -> str:
    """Return the first ``limit`` words from ``text``."""

    words = re.findall(r"\S+", text)
    if not words:
        return "summary unavailable"
    return " ".join(words[:limit])


def _document_headings(text: str) -> list[str]:
    """Extract likely top-level headings from a document fragment."""

    headings: list[str] = []
    for line in text.splitlines():
        stripped = line.strip()
        if not stripped:
            continue
        if stripped.startswith("#"):
            headings.append(stripped.lstrip("# ").strip())
            continue
        if re.match(r"^\d+(?:\.\d+)*\s+", stripped):
            headings.append(stripped)
    return headings


def _csv_like_items(text: str) -> list[str]:
    """Extract short comma/newline-separated items from a fragment."""

    items: list[str] = []
    for raw in re.split(r"[,;\n]+", text):
        stripped = raw.strip()
        if stripped:
            items.append(stripped)
    return items
