"""Extraction strategy — adaptive input classification and strategy selection.

Pure functions (no LLM, no DB) that classify input text and choose
extraction mode based on heuristics.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from enum import Enum

__all__ = [
    "ExtractionMode",
    "ExtractionStrategy",
    "InputClassification",
    "InputType",
    "classify_input",
    "select_strategy",
]


class InputType(str, Enum):
    """Input text classification types."""

    SHORT = "short"
    MEDIUM = "medium"
    LONG = "long"
    STRUCTURED = "structured"


class ExtractionMode(str, Enum):
    """Extraction pipeline modes."""

    SINGLE_SHOT = "single_shot"
    CHUNKED = "chunked"


@dataclass
class InputClassification:
    """Result of input text analysis.

    Attributes:
        input_type: Classified input type.
        char_count: Number of characters.
        estimated_tokens: Estimated token count (chars // 4).
        has_headers: Whether headers were detected.
        has_bullets: Whether bullet points were detected.
        has_tables: Whether tables were detected.
        section_count: Number of text sections.
        line_count: Number of lines.
    """

    input_type: InputType
    char_count: int
    estimated_tokens: int
    has_headers: bool
    has_bullets: bool
    has_tables: bool
    section_count: int
    line_count: int


@dataclass
class ExtractionStrategy:
    """Selected extraction strategy.

    Attributes:
        mode: Extraction mode (single_shot or chunked).
        reason: Human-readable reason for the selection.
        max_tokens_per_call: Max tokens per LLM call.
        estimated_chunks: Number of expected chunks (1 for single-shot).
        chunk_context_hint: Hint about document type for chunked mode.
    """

    mode: ExtractionMode
    reason: str
    max_tokens_per_call: int
    estimated_chunks: int
    chunk_context_hint: str | None


def classify_input(text: str) -> InputClassification:
    """Classify input text using heuristics (no LLM).

    Args:
        text: Input text to classify.

    Returns:
        InputClassification with detected features.
    """
    if not text:
        return InputClassification(
            input_type=InputType.SHORT,
            char_count=0,
            estimated_tokens=0,
            has_headers=False,
            has_bullets=False,
            has_tables=False,
            section_count=0,
            line_count=0,
        )

    char_count = len(text)
    line_count = text.count("\n") + 1
    estimated_tokens = char_count // 4

    has_headers = bool(
        re.search(
            r"(?m)^(?:#{1,4}\s|(?:\d+[.)]\s)|(?:[A-ZÀ-Ú\s]{5,}$))",
            text,
        )
    )
    has_bullets = bool(re.search(r"(?m)^[\s]*[-*•]\s", text))
    has_tables = bool(re.search(r"(?m)^.*\|.*\|", text))

    sections = re.split(r"\n\s*\n", text)
    section_count = len([s for s in sections if s.strip()])

    is_structured = (has_headers or has_bullets or has_tables) and char_count > 500

    if char_count < 500:
        input_type = InputType.SHORT
    elif is_structured:
        input_type = InputType.STRUCTURED
    else:
        input_type = InputType.MEDIUM if char_count <= 2000 else InputType.LONG

    return InputClassification(
        input_type=input_type,
        char_count=char_count,
        estimated_tokens=estimated_tokens,
        has_headers=has_headers,
        has_bullets=has_bullets,
        has_tables=has_tables,
        section_count=section_count,
        line_count=line_count,
    )


def select_strategy(classification: InputClassification) -> ExtractionStrategy:
    """Select extraction strategy from classification.

    Args:
        classification: Result of classify_input().

    Returns:
        ExtractionStrategy with mode and parameters.
    """
    c = classification

    if c.input_type == InputType.SHORT:
        return ExtractionStrategy(
            mode=ExtractionMode.SINGLE_SHOT,
            reason="short message",
            max_tokens_per_call=4096,
            estimated_chunks=1,
            chunk_context_hint=None,
        )

    if c.input_type == InputType.MEDIUM:
        return ExtractionStrategy(
            mode=ExtractionMode.SINGLE_SHOT,
            reason=f"medium message ({c.char_count} chars)",
            max_tokens_per_call=6144,
            estimated_chunks=1,
            chunk_context_hint=None,
        )

    # LONG or STRUCTURED
    est_chunks = max(2, min(8, c.char_count // 1500))

    hint: str | None = None
    if c.input_type == InputType.STRUCTURED:
        hint = "structured document with tables" if c.has_tables else "structured document with headers/sections"
    else:
        hint = "long unstructured text"

    return ExtractionStrategy(
        mode=ExtractionMode.CHUNKED,
        reason=f"{c.input_type.value} ({c.char_count} chars, ~{est_chunks} chunks, {c.section_count} sections)",
        max_tokens_per_call=4096,
        estimated_chunks=est_chunks,
        chunk_context_hint=hint,
    )
