from __future__ import annotations


class SubIntentDetector:
    """Rule-based sub-intent detector.

    Heuristics distinguish two close classes of questions:
    - FIND_ENTRYPOINTS: "where does this endpoint/route start?"
    - TRACE_FLOW: "what happens step by step for this symbol/request?"
    """

    _OPEN_VERBS = ("открой", "посмотри", "покажи", "выведи", "show", "open")
    _EXPLAIN_MARKERS = ("объясни", "как работает", "почему", "что делает", "зачем", "логика", "флоу", "flow")
    _TEST_MARKERS = ("тест", "pytest", "unit test", "юнит")
    _TRACE_FLOW_MARKERS = (
        "trace flow",
        "execution trace",
        "dataflow",
        "request flow",
        "request path",
        "поток",
        "поток данных",
        "поток выполнения",
        "поток запроса",
        "как проходит запрос",
        "как обрабатывается запрос",
        "обработка запроса",
        "что происходит при запросе",
        "какие шаги выполняются",
        "по шагам",
        "при запуске",
        "трассиров",
        "кто читает",
        "кто пишет",
        "что вызывает",
        "где создается",
        "где создаётся",
    )
    _ARCHITECTURE_MARKERS = (
        "архитектур",
        "architecture",
        "какие сервисы",
        "какие обработчики",
        "какие компоненты",
        "бизнес-логика",
        "business logic",
        "io",
    )
    _ENTRYPOINTS_MARKERS = (
        "entrypoint",
        "entrypoints",
        "endpoint",
        "route",
        "handler",
        "точки входа",
        "точка входа",
        "маршрут",
        "роут",
        "url",
        "main",
        "запуск",
        "как запускается",
        "где вход",
    )
    _HTTP_ENTRYPOINT_MARKERS = ("endpoint", "route", "handler", "маршрут", "роут", "url")

    def detect(self, raw: str, *, has_file_path: bool, negations: set[str]) -> str:
        raw_text = " ".join((raw or "").split())
        text = raw_text.lower()
        if not text:
            return "EXPLAIN"
        if has_file_path and self._has_open_verb(text) and not self._has_explain_markers(text):
            return "OPEN_FILE"
        if has_file_path and ("файл" in text or "file" in text) and not self._has_explain_markers(text):
            return "OPEN_FILE"
        if "tests" not in negations and any(marker in text for marker in self._TEST_MARKERS):
            return "FIND_TESTS"
        has_flow = self._has_trace_flow_markers(text)
        has_entrypoints = any(marker in text for marker in self._ENTRYPOINTS_MARKERS)
        has_target = has_file_path or self._has_http_path(text) or self._has_symbol_like_target(raw_text)
        if has_flow and has_target:
            return "TRACE_FLOW"
        if self._has_http_path(text) and any(marker in text for marker in self._HTTP_ENTRYPOINT_MARKERS):
            return "FIND_ENTRYPOINTS"
        if has_entrypoints:
            return "FIND_ENTRYPOINTS"
        if has_flow:
            return "TRACE_FLOW"
        if any(marker in text for marker in self._ARCHITECTURE_MARKERS):
            return "ARCHITECTURE"
        return "EXPLAIN"

    def _has_open_verb(self, text: str) -> bool:
        return any(text.startswith(verb) or f" {verb} " in f" {text} " for verb in self._OPEN_VERBS)

    def _has_explain_markers(self, text: str) -> bool:
        return any(marker in text for marker in self._EXPLAIN_MARKERS)

    def _has_trace_flow_markers(self, text: str) -> bool:
        return any(marker in text for marker in self._TRACE_FLOW_MARKERS)

    def _has_http_path(self, text: str) -> bool:
        return any(token.startswith("/") and len(token) > 1 for token in text.split())

    def _has_symbol_like_target(self, text: str) -> bool:
        tokens = [token.strip(".,:;?!()[]{}\"'") for token in text.split()]
        for token in tokens:
            if not token:
                continue
            if token.startswith("/"):
                return True
            if "_" in token:
                return True
            if len(token) >= 4 and any(char.isupper() for char in token):
                return True
        return False
