from __future__ import annotations

from dataclasses import dataclass
from fnmatch import fnmatch
import re
from time import perf_counter
from typing import TYPE_CHECKING, Any

from app.modules.rag.retrieval.test_filter import build_test_filters

if TYPE_CHECKING:
    from app.modules.rag.persistence.repository import RagRepository

_KNOWN_FILE_EXTENSIONS = (
    ".py",
    ".md",
    ".txt",
    ".yaml",
    ".yml",
    ".json",
    ".toml",
    ".ini",
)
_SYMBOL_TOKEN_RE = re.compile(r"\b([A-Z][A-Za-z0-9_]{2,}|[a-z_][a-z0-9_]{2,})\b")
_DEFAULT_SYMBOL_KINDS = {"class", "function", "method"}


def is_file_path(path: str) -> bool:
    normalized = normalize_path(path)
    if not normalized:
        return False
    tail = normalized.rsplit("/", 1)[-1]
    if "." in tail and not tail.startswith(".") and not tail.endswith("."):
        return True
    return tail.lower().endswith(_KNOWN_FILE_EXTENSIONS)


def normalize_path(path: str) -> str:
    normalized = str(path or "").strip().replace("\\", "/")
    while "//" in normalized:
        normalized = normalized.replace("//", "/")
    if normalized.startswith("./"):
        normalized = normalized[2:]
    return normalized


def normalize_paths(paths: list[str]) -> list[str]:
    deduped: list[str] = []
    for value in paths:
        normalized = normalize_path(value)
        if normalized and normalized not in deduped:
            deduped.append(normalized)
    return deduped


def extract_symbol_tokens(text: str) -> list[str]:
    tokens: list[str] = []
    for match in _SYMBOL_TOKEN_RE.finditer(str(text or "")):
        token = match.group(1).strip()
        if token and token not in tokens:
            tokens.append(token)
    return tokens[:8]


class SessionEmbeddingDimensions:
    def __init__(self) -> None:
        self._cache: dict[str, int] = {}

    def resolve(self, rag_session_id: str) -> int:
        if rag_session_id in self._cache:
            return self._cache[rag_session_id]
        from sqlalchemy import text

        from app.modules.shared.db import get_engine

        with get_engine().connect() as conn:
            row = conn.execute(
                text(
                    """
                    SELECT vector_dims(embedding) AS dim
                    FROM rag_chunks
                    WHERE rag_session_id = :sid
                    LIMIT 1
                    """
                ),
                {"sid": rag_session_id},
            ).mappings().first()
        dim = int(row["dim"]) if row and row.get("dim") else 0
        self._cache[rag_session_id] = dim
        return dim


class GlobPatternMapper:
    @staticmethod
    def to_prefixes(globs: list[str]) -> list[str]:
        result: list[str] = []
        for value in globs:
            item = str(value or "").strip()
            if not item:
                continue
            if item.endswith("/**"):
                prefix = item[:-3]
            elif "*" in item or "?" in item:
                continue
            else:
                prefix = item
            if prefix and prefix not in result:
                result.append(prefix)
        return result

    @staticmethod
    def to_like_patterns(globs: list[str]) -> list[str]:
        result: list[str] = []
        for value in globs:
            item = str(value or "").strip().lower()
            if not item or ("*" not in item and "?" not in item):
                continue
            sql_like = item.replace("**/", "%/").replace("**", "%").replace("*", "%").replace("?", "_")
            if sql_like not in result:
                result.append(sql_like)
        return result


class RagRowDeduplicator:
    def dedupe(self, rows: list[dict]) -> list[dict]:
        result: list[dict] = []
        seen: set[tuple[Any, ...]] = set()
        for row in rows:
            key = self._key(row)
            if key in seen:
                continue
            seen.add(key)
            result.append(row)
        return result

    def _key(self, row: dict) -> tuple[Any, ...]:
        layer = str(row.get("layer") or "")
        path = str(row.get("path") or "")
        span_start = row.get("span_start")
        span_end = row.get("span_end")
        metadata = dict(row.get("metadata") or {})
        if layer == "C0_SOURCE_CHUNKS":
            return (layer, path, span_start, span_end)
        if layer == "C1_SYMBOL_CATALOG":
            return (layer, str(metadata.get("symbol_id") or ""), path, span_start, span_end)
        if layer == "C2_DEPENDENCY_GRAPH":
            return (layer, str(metadata.get("edge_id") or ""), path, span_start, span_end)
        return (layer, path, str(row.get("title") or ""), span_start, span_end)


@dataclass(slots=True)
class LayerRetrievalParams:
    rag_session_id: str
    query_text: str
    layer_id: str
    limit: int
    scope: list[str]
    exclude_prefixes: list[str] | None
    exclude_like: list[str] | None
    prefer_path_prefixes: list[str] | None
    prefer_like_patterns: list[str] | None
    dim: int
    include_tests: bool
    retrieval_mode_hint: str | None = None
    c1_kind_filter: set[str] | None = None
    allow_scope_relaxation: bool = True


class LayerBudgetRetriever:
    def __init__(
        self,
        repository: RagRepository,
        dim_resolver: SessionEmbeddingDimensions,
        deduper: RagRowDeduplicator,
    ) -> None:
        self._repository = repository
        self._dim_resolver = dim_resolver
        self._deduper = deduper
        self._explain_refiner = ExplainStrictRefiner()
        self._last_report: dict[str, Any] | None = None

    def retrieve(
        self,
        query: str,
        retrieval_spec,
        retrieval_constraints,
        mapper: GlobPatternMapper,
        *,
        rag_session_id: str,
        query_plan=None,
    ) -> list[dict]:
        filters = retrieval_spec.filters
        path_scope = list(getattr(filters, "path_scope", []) or [])
        test_policy = str(getattr(filters, "test_policy", "EXCLUDE") or "EXCLUDE")
        include_globs = list(getattr(retrieval_constraints, "include_globs", []) or [])
        exclude_globs = list(getattr(retrieval_constraints, "exclude_globs", []) or [])
        prefer_globs = list(getattr(retrieval_constraints, "prefer_globs", []) or [])
        include_prefixes = mapper.to_prefixes(include_globs)
        exclude_from_constraints = mapper.to_prefixes(exclude_globs)
        exclude_like_constraints = mapper.to_like_patterns(exclude_globs)
        prefer_path_prefixes = mapper.to_prefixes(prefer_globs)
        prefer_like_patterns = mapper.to_like_patterns(prefer_globs)
        test_file_globs = list(getattr(retrieval_constraints, "test_file_globs", []) or [])
        test_symbol_patterns = list(getattr(retrieval_constraints, "test_symbol_patterns", []) or [])
        exclude_prefixes, exclude_like = self._test_filters(test_policy)
        if exclude_from_constraints:
            exclude_prefixes = list(dict.fromkeys([*(exclude_prefixes or []), *exclude_from_constraints]))
        if exclude_like_constraints:
            exclude_like = list(dict.fromkeys([*(exclude_like or []), *exclude_like_constraints]))
        effective_scope = path_scope or include_prefixes
        effective_query = f"{query} {' '.join(test_symbol_patterns)}".strip() if test_symbol_patterns else query
        symbol_candidates = list(getattr(query_plan, "symbol_candidates", []) or [])
        symbol_kind_hint = str(getattr(query_plan, "symbol_kind_hint", "") or "").strip().lower()
        sub_intent = str(getattr(query_plan, "sub_intent", "") or "").strip().upper()
        c1_query = self._c1_query(symbol_candidates, query)
        c1_kind_filter = self._c1_kind_filter(symbol_candidates, symbol_kind_hint)
        strict_symbol_mode = bool(symbol_candidates) and sub_intent == "EXPLAIN"
        if strict_symbol_mode and c1_query:
            effective_query = c1_query
        dim = self._dim_resolver.resolve(rag_session_id)
        merged: list[dict] = []
        report = self._empty_report()
        report["fallback"]["reason"] = None
        for layer_query in list(getattr(retrieval_spec, "layer_queries", []) or []):
            layer_id = str(layer_query.layer_id)
            params = LayerRetrievalParams(
                rag_session_id=rag_session_id,
                query_text=c1_query if layer_id == "C1_SYMBOL_CATALOG" and c1_query else effective_query,
                layer_id=layer_id,
                limit=max(1, int(layer_query.top_k)),
                scope=effective_scope,
                exclude_prefixes=exclude_prefixes,
                exclude_like=exclude_like,
                prefer_path_prefixes=prefer_path_prefixes,
                prefer_like_patterns=prefer_like_patterns,
                dim=dim,
                include_tests=test_policy == "INCLUDE",
                retrieval_mode_hint="symbol_search" if layer_id == "C1_SYMBOL_CATALOG" and c1_query else None,
                c1_kind_filter=c1_kind_filter if layer_id == "C1_SYMBOL_CATALOG" else None,
                allow_scope_relaxation=not strict_symbol_mode,
            )
            request = {
                "layer": layer_id,
                "query": params.query_text,
                "path_scope": list(path_scope),
                "include_globs": include_globs,
                "exclude_globs": exclude_globs,
                "prefer_globs": prefer_globs,
                "top_k": params.limit,
                "ranking_profile": str(getattr(retrieval_spec, "rerank_profile", "") or ""),
            }
            report["requests"].append(request)
            started = perf_counter()
            rows, layer_diag = self.retrieve_layer_with_scope_fallback(params)
            report["retrieval_by_layer_ms"][layer_id] = int((perf_counter() - started) * 1000)
            if test_file_globs:
                before = len(rows)
                rows = self._filter_rows_by_globs(rows, test_file_globs)
                filter_stage = "post_rank"
                after = len(rows)
            else:
                before = len(rows)
                after = len(rows)
                filter_stage = "pre_rank"
            if strict_symbol_mode:
                rows = self._explain_refiner.refine(layer_id, rows, symbol_candidates)
            report["applied"].append(
                {
                    "layer": layer_id,
                    "effective_path_scope": list(layer_diag.get("effective_path_scope") or []),
                    "normalized_include_globs": list(include_prefixes),
                    "normalized_exclude_globs": list(exclude_prefixes or []),
                    "normalized_prefer_globs": list(prefer_path_prefixes),
                    "filter_stage": filter_stage,
                    "candidates_before_filter": before,
                    "candidates_after_filter": after,
                }
            )
            report["executed_layers"].append(layer_id)
            report["retrieval_mode_by_layer"][layer_id] = str(
                params.retrieval_mode_hint or layer_diag.get("retrieval_mode") or "disabled"
            )
            report["top_k_by_layer"][layer_id] = params.limit
            report["filters_by_layer"][layer_id] = {
                "path_scope": list(layer_diag.get("effective_path_scope") or []),
                "include_globs": list(include_prefixes),
                "exclude_globs": list(exclude_prefixes or []),
                "prefer_globs": list(prefer_path_prefixes),
            }
            if bool(layer_diag.get("fallback_used")) and params.allow_scope_relaxation:
                report["fallback"]["used"] = True
                report["fallback"]["reason"] = str(layer_diag.get("fallback_reason") or "scope_relaxed")
            merged.extend(rows)
        self._last_report = report
        return self._deduper.dedupe(merged)

    def consume_report(self) -> dict[str, Any] | None:
        report = self._last_report
        self._last_report = None
        return report

    def retrieve_layer_with_scope_fallback(self, params: LayerRetrievalParams) -> tuple[list[dict], dict[str, Any]]:
        options = self._prefixes_options(params.scope) if params.allow_scope_relaxation else [params.scope or None]
        last_mode = "disabled"
        for idx, prefixes in enumerate(options):
            rows, base_mode = self._retrieve_layer(params, prefixes)
            last_mode = base_mode
            if params.c1_kind_filter and params.layer_id == "C1_SYMBOL_CATALOG":
                rows = self._filter_c1_by_kind(rows, params.c1_kind_filter)
            if rows:
                retrieval_mode = self._resolve_mode(base_mode, prefixes)
                fallback_used = idx > 0
                fallback_reason = "scope_relaxed" if fallback_used else None
                return rows, {
                    "effective_path_scope": list(prefixes or []),
                    "retrieval_mode": retrieval_mode,
                    "fallback_used": fallback_used,
                    "fallback_reason": fallback_reason,
                }
        return [], {
            "effective_path_scope": [],
            "retrieval_mode": last_mode,
            "fallback_used": len(options) > 1,
            "fallback_reason": "scope_relaxed_no_hits" if len(options) > 1 else None,
        }

    def _retrieve_layer(self, params: LayerRetrievalParams, prefixes: list[str] | None) -> tuple[list[dict], str]:
        if params.dim <= 0:
            if params.layer_id != "C0_SOURCE_CHUNKS":
                return [], "disabled"
            return self._repository.retrieve_lexical_code(
                params.rag_session_id,
                query_text=params.query_text,
                limit=params.limit,
                path_prefixes=prefixes,
                exclude_path_prefixes=params.exclude_prefixes,
                exclude_like_patterns=params.exclude_like,
                prefer_path_prefixes=params.prefer_path_prefixes,
                prefer_like_patterns=params.prefer_like_patterns,
                prefer_non_tests=not params.include_tests,
            ), "lexical"
        return self._repository.retrieve(
            params.rag_session_id,
            [0.0] * params.dim,
            query_text=params.query_text,
            limit=params.limit,
            layers=[params.layer_id],
            path_prefixes=prefixes,
            exclude_path_prefixes=params.exclude_prefixes,
            exclude_like_patterns=params.exclude_like,
            prefer_path_prefixes=params.prefer_path_prefixes,
            prefer_like_patterns=params.prefer_like_patterns,
            prefer_non_tests=not params.include_tests,
        ), "vector"

    def _test_filters(self, test_policy: str) -> tuple[list[str] | None, list[str] | None]:
        if test_policy == "INCLUDE":
            return None, None
        filters = build_test_filters()
        return filters.exclude_path_prefixes, filters.exclude_like_patterns

    def _prefixes_options(self, path_scope: list[str]) -> list[list[str] | None]:
        if not path_scope:
            return [None]
        parent_prefixes = [
            item.rsplit("/", 1)[0]
            for item in path_scope
            if "/" in item and not is_file_path(item)
        ]
        deduped_parents: list[str] = []
        for item in parent_prefixes:
            if item and item not in deduped_parents:
                deduped_parents.append(item)
        result: list[list[str] | None] = [path_scope]
        if deduped_parents:
            result.append(deduped_parents)
        result.append(None)
        return result

    def _resolve_mode(self, base_mode: str, prefixes: list[str] | None) -> str:
        if base_mode == "disabled":
            return "disabled"
        if prefixes:
            return "exact_path_fetch"
        return base_mode

    def _empty_report(self) -> dict[str, Any]:
        return {
            "executed_layers": [],
            "retrieval_mode_by_layer": {},
            "top_k_by_layer": {},
            "filters_by_layer": {},
            "requests": [],
            "applied": [],
            "fallback": {"used": False, "reason": None},
            "retrieval_by_layer_ms": {},
        }

    def _filter_rows_by_globs(self, rows: list[dict], globs: list[str]) -> list[dict]:
        if not globs:
            return rows
        patterns = [item.strip().lower() for item in globs if item.strip()]
        result: list[dict] = []
        for row in rows:
            path = str(row.get("path") or "").lower()
            if any(fnmatch(path, pattern) or fnmatch(path, f"*/{pattern}") for pattern in patterns):
                result.append(row)
        return result

    def _c1_query(self, symbol_candidates: list[str], query: str) -> str:
        tokens = [str(item).strip() for item in symbol_candidates if str(item).strip()]
        if tokens:
            return " ".join(tokens)
        fallback = extract_symbol_tokens(query)
        return " ".join(fallback) if fallback else str(query or "")

    def _c1_kind_filter(self, symbol_candidates: list[str], symbol_kind_hint: str) -> set[str] | None:
        if symbol_kind_hint in _DEFAULT_SYMBOL_KINDS or symbol_kind_hint == "module":
            return {symbol_kind_hint}
        if symbol_candidates:
            return set(_DEFAULT_SYMBOL_KINDS)
        return None

    def _filter_c1_by_kind(self, rows: list[dict], allowed_kinds: set[str]) -> list[dict]:
        result: list[dict] = []
        for row in rows:
            metadata = dict(row.get("metadata") or {})
            kind = str(metadata.get("kind") or "").strip().lower()
            if kind in allowed_kinds:
                result.append(row)
        return result


class ExplainStrictRefiner:
    def refine(self, layer_id: str, rows: list[dict], symbol_candidates: list[str]) -> list[dict]:
        if not rows or not symbol_candidates:
            return rows
        sorted_rows = self._sort_exact_first(rows, symbol_candidates)
        if layer_id != "C4_SEMANTIC_ROLES":
            return sorted_rows
        exact = [row for row in sorted_rows if self._is_exact_symbol(row, symbol_candidates)]
        if not exact:
            return sorted_rows[:1]
        primary = exact[:1]
        same_path = [
            row
            for row in sorted_rows
            if row not in primary and str(row.get("path") or "") == str(primary[0].get("path") or "")
        ]
        return [*primary, *same_path[:2]]

    def _sort_exact_first(self, rows: list[dict], symbol_candidates: list[str]) -> list[dict]:
        return sorted(rows, key=lambda row: (0 if self._is_exact_symbol(row, symbol_candidates) else 1))

    def _is_exact_symbol(self, row: dict, symbol_candidates: list[str]) -> bool:
        title = str(row.get("title") or "").strip()
        metadata = dict(row.get("metadata") or {})
        qname = str(metadata.get("qname") or "").strip()
        symbol_name = str(metadata.get("symbol_name") or "").strip()
        return any(candidate in {title, qname, symbol_name} for candidate in symbol_candidates)


class ResolvedSymbolSourceHydrator:
    def __init__(
        self,
        retriever: LayerBudgetRetriever,
        dim_resolver: SessionEmbeddingDimensions,
        deduper: RagRowDeduplicator,
    ) -> None:
        self._retriever = retriever
        self._dim_resolver = dim_resolver
        self._deduper = deduper

    def hydrate(
        self,
        rag_session_id: str,
        base_query: str,
        rag_rows: list[dict],
        symbol_resolution: dict,
        retrieval_spec,
        retrieval_constraints,
        mapper: GlobPatternMapper,
    ) -> list[dict]:
        if str(symbol_resolution.get("status") or "") != "resolved":
            return self._deduper.dedupe(rag_rows)
        resolved = str(symbol_resolution.get("resolved_symbol") or "").strip()
        if not resolved:
            return self._deduper.dedupe(rag_rows)
        alternatives = [str(item).strip() for item in symbol_resolution.get("alternatives", []) if str(item).strip()]
        target_names = [resolved]
        for name in alternatives:
            if name not in target_names:
                target_names.append(name)
            if len(target_names) >= 3:
                break
        filters = retrieval_spec.filters
        test_policy = str(getattr(filters, "test_policy", "EXCLUDE") or "EXCLUDE")
        exclude_prefixes = mapper.to_prefixes(list(getattr(retrieval_constraints, "exclude_globs", []) or []))
        exclude_like = mapper.to_like_patterns(list(getattr(retrieval_constraints, "exclude_globs", []) or []))
        prefer_path_prefixes = mapper.to_prefixes(list(getattr(retrieval_constraints, "prefer_globs", []) or []))
        prefer_like_patterns = mapper.to_like_patterns(list(getattr(retrieval_constraints, "prefer_globs", []) or []))
        dim = self._dim_resolver.resolve(rag_session_id)
        c0_limit = self._layer_top_k(retrieval_spec, "C0_SOURCE_CHUNKS", fallback=6)
        extra_rows: list[dict] = []
        for symbol_row in self._symbol_rows(rag_rows, target_names):
            symbol_path = str(symbol_row.get("path") or "").strip()
            if not symbol_path:
                continue
            symbol_meta = dict(symbol_row.get("metadata") or {})
            symbol_blob_sha = str(symbol_meta.get("blob_sha") or "").strip()
            params = LayerRetrievalParams(
                rag_session_id=rag_session_id,
                query_text=str(symbol_row.get("title") or "").strip() or base_query,
                layer_id="C0_SOURCE_CHUNKS",
                limit=min(c0_limit, 4),
                scope=[symbol_path],
                exclude_prefixes=exclude_prefixes or None,
                exclude_like=exclude_like or None,
                prefer_path_prefixes=prefer_path_prefixes,
                prefer_like_patterns=prefer_like_patterns,
                dim=dim,
                include_tests=test_policy == "INCLUDE",
            )
            rows, _ = self._retriever.retrieve_layer_with_scope_fallback(params)
            for row in rows:
                row_meta = dict(row.get("metadata") or {})
                if symbol_blob_sha and str(row_meta.get("blob_sha") or "") != symbol_blob_sha:
                    continue
                if not self._overlaps_symbol(row, symbol_row.get("span_start"), symbol_row.get("span_end")):
                    continue
                extra_rows.append(row)
        return self._deduper.dedupe([*rag_rows, *extra_rows])

    def _symbol_rows(self, rows: list[dict], target_names: list[str]) -> list[dict]:
        c1_rows = [row for row in rows if str(row.get("layer") or "") == "C1_SYMBOL_CATALOG"]
        by_id = {
            str(dict(row.get("metadata") or {}).get("symbol_id") or ""): row
            for row in c1_rows
            if str(dict(row.get("metadata") or {}).get("symbol_id") or "").strip()
        }
        selected: list[dict] = []
        seen_ids: set[str] = set()
        for row in c1_rows:
            title = str(row.get("title") or "").strip()
            qname = str(dict(row.get("metadata") or {}).get("qname") or "").strip()
            if title not in target_names and qname not in target_names:
                continue
            symbol_id = str(dict(row.get("metadata") or {}).get("symbol_id") or "")
            if symbol_id in seen_ids:
                continue
            seen_ids.add(symbol_id)
            selected.append(row)
            parent_id = str(dict(row.get("metadata") or {}).get("parent_symbol_id") or "")
            parent = by_id.get(parent_id)
            if parent is not None and parent_id not in seen_ids:
                seen_ids.add(parent_id)
                selected.append(parent)
        return selected

    def _layer_top_k(self, retrieval_spec, layer_id: str, *, fallback: int) -> int:
        for item in list(getattr(retrieval_spec, "layer_queries", []) or []):
            if str(item.layer_id) == layer_id:
                return max(1, int(item.top_k))
        return fallback

    def _overlaps_symbol(self, row: dict, symbol_start, symbol_end) -> bool:
        if symbol_start is None and symbol_end is None:
            return True
        row_start = row.get("span_start")
        row_end = row.get("span_end")
        if row_start is None or row_end is None:
            return True
        start = int(symbol_start if symbol_start is not None else row_start)
        end = int(symbol_end if symbol_end is not None else row_end)
        return int(row_start) <= end and int(row_end) >= start


class RagDbAdapter:
    def __init__(self, repository: RagRepository, dim_resolver: SessionEmbeddingDimensions) -> None:
        self._repository = repository
        self._mapper = GlobPatternMapper()
        self._deduper = RagRowDeduplicator()
        self._retriever = LayerBudgetRetriever(repository, dim_resolver, self._deduper)
        self._hydrator = ResolvedSymbolSourceHydrator(self._retriever, dim_resolver, self._deduper)
        self._last_retrieval_report: dict[str, Any] | None = None

    def retrieve(self, rag_session_id: str, query: str, retrieval_spec, retrieval_constraints=None) -> list[dict]:
        rows = self._retriever.retrieve(
            query=query,
            retrieval_spec=retrieval_spec,
            retrieval_constraints=retrieval_constraints,
            mapper=self._mapper,
            rag_session_id=rag_session_id,
            query_plan=None,
        )
        self._last_retrieval_report = self._retriever.consume_report()
        return rows

    def retrieve_with_plan(
        self,
        rag_session_id: str,
        query: str,
        retrieval_spec,
        retrieval_constraints=None,
        *,
        query_plan=None,
    ) -> list[dict]:
        rows = self._retriever.retrieve(
            query=query,
            retrieval_spec=retrieval_spec,
            retrieval_constraints=retrieval_constraints,
            mapper=self._mapper,
            rag_session_id=rag_session_id,
            query_plan=query_plan,
        )
        self._last_retrieval_report = self._retriever.consume_report()
        return rows

    def hydrate_resolved_symbol_sources(
        self,
        rag_session_id: str,
        base_query: str,
        rag_rows: list[dict],
        symbol_resolution: dict,
        retrieval_spec,
        retrieval_constraints=None,
    ) -> list[dict]:
        return self._hydrator.hydrate(
            rag_session_id=rag_session_id,
            base_query=base_query,
            rag_rows=rag_rows,
            symbol_resolution=symbol_resolution,
            retrieval_spec=retrieval_spec,
            retrieval_constraints=retrieval_constraints,
            mapper=self._mapper,
        )

    def consume_retrieval_report(self) -> dict[str, Any] | None:
        report = self._last_retrieval_report
        self._last_retrieval_report = None
        return report

    def retrieve_exact_files(
        self,
        rag_session_id: str,
        *,
        repo_id: str | None = None,
        paths: list[str],
        layers: list[str] | None = None,
        limit: int = 200,
        query: str = "",
        ranking_profile: str = "",
    ) -> list[dict]:
        normalized_paths = normalize_paths(paths)
        started = perf_counter()
        rows = self._repository.retrieve_exact_files(
            rag_session_id,
            repo_id=repo_id,
            paths=normalized_paths,
            layers=layers,
            limit=limit,
        )
        elapsed = int((perf_counter() - started) * 1000)
        layer_id = list(layers or ["C0_SOURCE_CHUNKS"])[0]
        self._last_retrieval_report = {
            "executed_layers": [layer_id],
            "retrieval_mode_by_layer": {layer_id: "exact_path_fetch"},
            "top_k_by_layer": {layer_id: int(limit)},
            "filters_by_layer": {
                layer_id: {
                    "path_scope": list(normalized_paths),
                    "include_globs": [],
                    "exclude_globs": [],
                    "prefer_globs": [],
                }
            },
            "requests": [
                {
                    "layer": layer_id,
                    "query": query,
                    "path_scope": list(normalized_paths),
                    "include_globs": [],
                    "exclude_globs": [],
                    "prefer_globs": [],
                    "top_k": int(limit),
                    "ranking_profile": ranking_profile,
                }
            ],
            "applied": [
                {
                    "layer": layer_id,
                    "effective_path_scope": list(normalized_paths),
                    "normalized_include_globs": [],
                    "normalized_exclude_globs": [],
                    "normalized_prefer_globs": [],
                    "filter_stage": "pre_rank",
                    "candidates_before_filter": len(rows),
                    "candidates_after_filter": len(rows),
                }
            ],
            "fallback": {"used": False, "reason": None},
            "retrieval_by_layer_ms": {layer_id: elapsed},
        }
        return self._deduper.dedupe(rows)

    def force_symbol_context_c0(
        self,
        rag_session_id: str,
        *,
        rag_rows: list[dict],
        symbol_resolution: dict,
        limit: int = 20,
    ) -> list[dict]:
        if str(symbol_resolution.get("status") or "") != "resolved":
            return rag_rows
        if float(symbol_resolution.get("confidence") or 0.0) < 0.8:
            return rag_rows
        target = str(symbol_resolution.get("resolved_symbol") or "").strip()
        if not target:
            return rag_rows
        c1_rows = [row for row in rag_rows if str(row.get("layer") or "") == "C1_SYMBOL_CATALOG"]
        target_row = next(
            (
                row
                for row in c1_rows
                if str(row.get("title") or "").strip() == target
                or str(dict(row.get("metadata") or {}).get("qname") or "").strip() == target
            ),
            None,
        )
        if target_row is None:
            return rag_rows
        target_path = normalize_path(str(target_row.get("path") or ""))
        if not target_path:
            return rag_rows
        c0_rows = self.retrieve_exact_files(
            rag_session_id,
            paths=[target_path],
            layers=["C0_SOURCE_CHUNKS"],
            limit=limit,
            query=target,
            ranking_profile="symbol_context",
        )
        preserved = [row for row in rag_rows if str(row.get("layer") or "") != "C0_SOURCE_CHUNKS"]
        return self._deduper.dedupe([*preserved, *c0_rows])
