from __future__ import annotations

from dataclasses import dataclass, field
import re


EMAIL_ADDRESS_RE = re.compile(r"(?<![\w.+-])([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})(?![\w.+-])", re.IGNORECASE)
QUOTED_TEXT_RE = re.compile(r'"([^"]+)"|\'([^\']+)\'')
LEADING_NOISE_RE = re.compile(
    r"^(?:please|pls|can you|could you|will you|wil je|kun je|kun jij|zou je|graag|even)\s+",
    re.IGNORECASE,
)
TEMPORAL_REFERENCE_RE = re.compile(
    r"\b(?:today|tomorrow|yesterday|next|this|last|tonight|morning|afternoon|evening|"
    r"vandaag|morgen|gisteren|volgende|deze|vorige|vanavond|vanochtend|vanmiddag|"
    r"monday|tuesday|wednesday|thursday|friday|saturday|sunday|"
    r"maandag|dinsdag|woensdag|donderdag|vrijdag|zaterdag|zondag|"
    r"\d{4}-\d{2}-\d{2}|\d{1,2}[/-]\d{1,2}(?:[/-]\d{2,4})?)\b",
    re.IGNORECASE,
)

_ORDINAL_PATTERNS: list[tuple[re.Pattern[str], int | None]] = [
    (re.compile(r"\b(?:first|eerste)\b", re.IGNORECASE), 1),
    (re.compile(r"\b(?:second|tweede)\b", re.IGNORECASE), 2),
    (re.compile(r"\b(?:third|derde)\b", re.IGNORECASE), 3),
    (re.compile(r"\b(?:fourth|vierde)\b", re.IGNORECASE), 4),
    (re.compile(r"\b(?:fifth|vijfde)\b", re.IGNORECASE), 5),
    (re.compile(r"\b(?:last|laatste)\b", re.IGNORECASE), -1),
]


@dataclass
class ReferenceSpec:
    query: str = ""
    kind: str = ""
    ordinal: int | None = None
    temporal_hint: str = ""

    def to_dict(self) -> dict:
        return {
            "query": self.query,
            "kind": self.kind,
            "ordinal": self.ordinal,
            "temporal_hint": self.temporal_hint,
        }


@dataclass
class BaseActionIntent:
    action: str = ""
    selection_query: str = ""
    reference_kind: str = ""
    reference_ordinal: int | None = None
    reference_temporal_hint: str = ""
    confidence: float = 0.0
    missing_fields: list[str] = field(default_factory=list)
    used_llm_fallback: bool = False

    def reference_spec(self) -> ReferenceSpec:
        return ReferenceSpec(
            query=self.selection_query,
            kind=self.reference_kind,
            ordinal=self.reference_ordinal,
            temporal_hint=self.reference_temporal_hint,
        )


def detect_keyword_action(text: str, action_terms: dict[str, set[str]], *, required_marker: re.Pattern[str] | None = None) -> str:
    raw = str(text or "").strip()
    lowered = raw.lower()
    if not raw:
        return ""
    if required_marker and not required_marker.search(raw):
        return ""
    priority = ["delete", "update", "create", "send", "reply", "forward"]
    ordered_actions = [action for action in priority if action in action_terms]
    ordered_actions.extend(action for action in action_terms if action not in ordered_actions)
    for action in ordered_actions:
        for token in action_terms.get(action, set()):
            escaped_token = re.escape(token).replace(r"\ ", r"\s+")
            pattern = re.compile(rf"(?<!\w){escaped_token}(?!\w)", re.IGNORECASE)
            if pattern.search(lowered):
                return action
    return ""


def extract_quoted_value(text: str) -> str:
    match = QUOTED_TEXT_RE.search(str(text or ""))
    if not match:
        return ""
    return (match.group(1) or match.group(2) or "").strip()


def detect_reference_ordinal(text: str) -> int | None:
    raw = str(text or "")
    for pattern, ordinal in _ORDINAL_PATTERNS:
        if pattern.search(raw):
            return ordinal
    contextual_digit_match = re.search(
        r"\b(?:nr\.?|nummer|number|no\.?|email|e-?mail|mail|bericht|afs?praak|meeting|event)\s*(\d{1,2})\b|#\s*(\d{1,2})\b",
        raw,
        re.IGNORECASE,
    )
    if contextual_digit_match:
        try:
            return int(contextual_digit_match.group(1) or contextual_digit_match.group(2) or 0)
        except ValueError:
            return None
    digit_match = re.search(r"\b(\d{1,2})(?:st|nd|rd|th|e)\b", raw, re.IGNORECASE)
    if digit_match:
        try:
            return int(digit_match.group(1))
        except ValueError:
            return None
    return None


def extract_temporal_reference_hint(text: str) -> str:
    match = TEMPORAL_REFERENCE_RE.search(str(text or ""))
    if not match:
        return ""
    return str(match.group(0) or "").strip()


def build_reference_spec(text: str, query: str) -> ReferenceSpec:
    clean_query = str(query or "").strip()
    ordinal = detect_reference_ordinal(text)
    temporal_hint = extract_temporal_reference_hint(clean_query)
    if not temporal_hint:
        temporal_hint = extract_temporal_reference_hint(text)
    quoted = extract_quoted_value(text)

    if not clean_query and ordinal is None and not temporal_hint:
        return ReferenceSpec()
    if quoted and clean_query and clean_query.casefold() == quoted.casefold():
        kind = "quoted_reference"
    elif ordinal is not None and clean_query:
        kind = "ordinal_query_reference"
    elif ordinal is not None:
        kind = "ordinal_reference"
    elif temporal_hint:
        kind = "constrained_reference"
    else:
        kind = "search_query_reference"
    return ReferenceSpec(
        query=clean_query,
        kind=kind,
        ordinal=ordinal,
        temporal_hint=temporal_hint,
    )


def apply_reference(intent: BaseActionIntent, spec: ReferenceSpec) -> None:
    intent.selection_query = spec.query
    intent.reference_kind = spec.kind
    intent.reference_ordinal = spec.ordinal
    intent.reference_temporal_hint = spec.temporal_hint


def merge_reference(primary: ReferenceSpec, fallback: ReferenceSpec) -> ReferenceSpec:
    return ReferenceSpec(
        query=primary.query or fallback.query,
        kind=primary.kind or fallback.kind,
        ordinal=primary.ordinal if primary.ordinal is not None else fallback.ordinal,
        temporal_hint=primary.temporal_hint or fallback.temporal_hint,
    )


def merge_base_intent_fields(primary: BaseActionIntent, fallback: BaseActionIntent) -> dict:
    merged_reference = merge_reference(primary.reference_spec(), fallback.reference_spec())
    return {
        "action": primary.action or fallback.action,
        "selection_query": merged_reference.query,
        "reference_kind": merged_reference.kind,
        "reference_ordinal": merged_reference.ordinal,
        "reference_temporal_hint": merged_reference.temporal_hint,
        "confidence": max(primary.confidence, fallback.confidence),
        "missing_fields": list(primary.missing_fields),
        "used_llm_fallback": primary.used_llm_fallback or fallback.used_llm_fallback,
    }


def ordinal_to_index(ordinal: int | None, size: int) -> int | None:
    if ordinal is None or size <= 0:
        return None
    if ordinal == -1:
        return size - 1
    if ordinal <= 0:
        return None
    index = ordinal - 1
    if 0 <= index < size:
        return index
    return None


def score_action_intent(*, action: str, missing_fields: list[str], base: float = 0.1, weights: dict[str, float] | None = None) -> float:
    score = base
    if action:
        score += 0.25
    for field_name, weight in (weights or {}).items():
        if field_name:
            score += weight
    if missing_fields:
        score -= min(0.28, 0.08 * len(missing_fields))
    return max(0.0, min(round(score, 3), 0.98))