from __future__ import annotations

import re


class DocsExactAnchorMatcher:
    _TOKEN_RE = re.compile(r"[a-z0-9_./{}-]+", re.IGNORECASE)
    _STRICT_ANCHOR_TYPES = {"endpoint", "entity"}

    def filter_rows(self, rows: list[dict], *, anchor_type: str, anchor_value: str | None) -> tuple[list[dict], bool]:
        if anchor_type == "none" or not anchor_value or anchor_type not in self._STRICT_ANCHOR_TYPES:
            return list(rows), False
        exact = [row for row in rows if self._matches(row, anchor_type=anchor_type, anchor_value=anchor_value)]
        if exact:
            return self._expand_support_rows(rows, exact), True
        return [], False

    def resolved_entity_candidates(self, rows: list[dict]) -> list[str]:
        values: list[str] = []
        for row in rows:
            metadata = dict(row.get("metadata") or {})
            for key in ("entity", "component"):
                candidate = metadata.get(key)
                if candidate:
                    values.append(str(candidate))
        return self._dedupe(values)

    def resolved_anchor_candidates(self, rows: list[dict]) -> list[str]:
        values: list[str] = []
        for row in rows:
            metadata = dict(row.get("metadata") or {})
            for candidate in (
                metadata.get("endpoint"),
                metadata.get("document_id"),
                metadata.get("doc_id"),
                metadata.get("entity"),
                metadata.get("component"),
                row.get("path"),
            ):
                if candidate:
                    values.append(str(candidate))
        return self._dedupe(values)

    def _matches(self, row: dict, *, anchor_type: str, anchor_value: str) -> bool:
        haystacks = [
            str(row.get("path") or "").lower(),
            str(row.get("title") or "").lower(),
            str(row.get("content") or "").lower(),
            str(dict(row.get("metadata") or {}).get("endpoint") or "").lower(),
            str(dict(row.get("metadata") or {}).get("entity") or "").lower(),
            str(dict(row.get("metadata") or {}).get("component") or "").lower(),
            str(dict(row.get("metadata") or {}).get("document_id") or "").lower(),
            str(dict(row.get("metadata") or {}).get("doc_id") or "").lower(),
        ]
        needle = anchor_value.lower().strip()
        if anchor_type == "endpoint":
            return any(needle == value or f" {needle}" in f" {value}" for value in haystacks if value)
        return any(needle in value for value in haystacks if value)

    def _expand_support_rows(self, rows: list[dict], exact: list[dict]) -> list[dict]:
        doc_ids = self._doc_ids(exact)
        paths = {str(row.get("path") or "").strip() for row in exact if str(row.get("path") or "").strip()}
        expanded = list(exact)
        for row in rows:
            metadata = dict(row.get("metadata") or {})
            row_doc_ids = {
                str(metadata.get("document_id") or "").strip(),
                str(metadata.get("doc_id") or "").strip(),
            }
            row_path = str(row.get("path") or "").strip()
            if row_path and row_path in paths:
                expanded.append(row)
                continue
            if any(candidate and candidate in doc_ids for candidate in row_doc_ids):
                expanded.append(row)
        return self._dedupe_rows(expanded)

    def _doc_ids(self, rows: list[dict]) -> set[str]:
        values: set[str] = set()
        for row in rows:
            metadata = dict(row.get("metadata") or {})
            for candidate in (metadata.get("document_id"), metadata.get("doc_id")):
                value = str(candidate or "").strip()
                if value:
                    values.add(value)
        return values

    def _dedupe_rows(self, rows: list[dict]) -> list[dict]:
        result: list[dict] = []
        seen: set[tuple[str, str, str, int | None, int | None]] = set()
        for row in rows:
            key = (
                str(row.get("layer") or ""),
                str(row.get("path") or ""),
                str(row.get("title") or ""),
                row.get("span_start"),
                row.get("span_end"),
            )
            if key in seen:
                continue
            seen.add(key)
            result.append(row)
        return result

    def _dedupe(self, values: list[str]) -> list[str]:
        result: list[str] = []
        seen: set[str] = set()
        for value in values:
            normalized = value.strip()
            if not normalized:
                continue
            key = normalized.lower()
            if key in seen:
                continue
            seen.add(key)
            result.append(normalized)
        return result
