"""Détection de la pertinence guadeloupéenne et ultramarine d'un texte.

Le tableau A de l'annexe cote lui-même les 27 lois promulguées ; le tableau B
ne cote pas les textes en cours. Pour ces derniers, la cotation est déduite du
vocabulaire employé par le rapport, avec des poids déclarés dans
`pipeline/config.yaml`.

Toute cotation obtenue ainsi est signalée : le texte porte `a_verifier = 1` et
un motif explicite. L'interface l'affiche comme telle. Une cotation déduite
n'est pas une cotation établie.
"""

from __future__ import annotations

import functools
import re
from dataclasses import dataclass, field

import yaml

from pipeline import chemins
from pipeline.modeles import Pertinence
from pipeline.parseurs.dates_fr import sans_accents

# Tournures par lesquelles le corpus **écarte** un territoire qu'il vient de
# nommer : « PPL montagne — sans portée pour la Guadeloupe », « PJL logement —
# pas de volet outre-mer à ce stade ». Compter la mention sans lire la négation
# donnerait à ces textes la cotation la plus forte, à l'exact inverse de ce que
# la source affirme.
NEGATIONS = (
    "sans portee",
    "sans objet",
    "sans incidence",
    "sans specificite",
    "sans volet",
    "sans consequence",
    "ne concerne pas",
    "n'est pas concerne",
    "pas concernee",
    "pas de volet",
    "pas d'incidence",
    "non concerne",
    "hors champ",
    "exclue",
    "exclus",
)

# Fenêtre de voisinage, en caractères, autour d'une mention de territoire.
_AVANT = 90
_APRES = 60


@dataclass(slots=True)
class Cotation:
    """Résultat d'une détection, avec ce qui l'a motivée."""

    pertinence: Pertinence | None
    score: int
    expressions: list[str]
    exclusions: list[str] = field(default_factory=list)

    @property
    def note(self) -> str | None:
        if self.exclusions and not self.expressions:
            return (
                "Le rapport écarte explicitement ce territoire : "
                + ", ".join(f"« {e} »" for e in self.exclusions[:3])
            )
        if not self.expressions:
            return None

        note = "Détecté d'après : " + ", ".join(self.expressions[:6])
        if self.exclusions:
            note += " — mais le rapport écarte par ailleurs : " + ", ".join(
                f"« {e} »" for e in self.exclusions[:2]
            )
        return note


@functools.lru_cache(maxsize=1)
def _configuration() -> dict:
    return yaml.safe_load(chemins.CONFIG.read_text(encoding="utf-8"))


@functools.lru_cache(maxsize=1)
def _motifs() -> list[tuple[re.Pattern[str], str, int]]:
    """Compile un motif par expression, ancré sur des frontières de mot."""
    expressions = _configuration()["guadeloupe"]["expressions"]
    compiles: list[tuple[re.Pattern[str], str, int]] = []

    for expression, poids in expressions.items():
        plat = sans_accents(str(expression)).lower()
        compiles.append((re.compile(rf"(?<!\w){re.escape(plat)}\w{{0,3}}(?!\w)"), plat, int(poids)))

    # Les expressions les plus lourdes d'abord : le compte-rendu cite en
    # priorité ce qui a réellement décidé de la cotation.
    compiles.sort(key=lambda triplet: -triplet[2])
    return compiles


def coter(*fragments: str | None) -> Cotation:
    """Cote la pertinence guadeloupéenne d'un ensemble de fragments de texte.

    Une mention niée ne compte pas. « Sans portée pour la Guadeloupe » nomme le
    territoire pour l'écarter : la lire comme une pertinence forte inverserait
    le sens de la source.
    """
    plat = sans_accents(" ".join(f for f in fragments if f)).lower()
    if not plat.strip():
        return Cotation(pertinence=None, score=0, expressions=[])

    score = 0
    trouvees: list[str] = []
    exclusions: list[str] = []

    for motif, expression, poids in _motifs():
        positions = [m.start() for m in motif.finditer(plat)]
        if not positions:
            continue

        retenues = [p for p in positions if not _est_niee(plat, p)]
        if retenues:
            score += poids
            trouvees.append(expression)
        else:
            exclusions.append(_extrait_autour(plat, positions[0]))

    seuils = _configuration()["guadeloupe"]["seuils"]
    if score >= seuils["forte"]:
        pertinence = Pertinence.FORTE
    elif score >= seuils["moyenne"]:
        pertinence = Pertinence.MOYENNE
    elif score >= seuils["faible"]:
        pertinence = Pertinence.FAIBLE
    elif exclusions:
        # Le territoire est nommé, mais pour être écarté : la pertinence est
        # faible, et le motif dit pourquoi.
        pertinence = Pertinence.FAIBLE
    else:
        pertinence = None

    return Cotation(
        pertinence=pertinence, score=score, expressions=trouvees, exclusions=exclusions
    )


def _est_niee(plat: str, position: int) -> bool:
    """Vrai si une tournure d'exclusion entoure la mention trouvée."""
    voisinage = plat[max(0, position - _AVANT) : position + _APRES]
    return any(negation in voisinage for negation in NEGATIONS)


def _extrait_autour(plat: str, position: int, largeur: int = 70) -> str:
    debut = max(0, position - largeur)
    fin = min(len(plat), position + largeur)
    return " ".join(plat[debut:fin].split())


MOTIF_VERIFICATION = (
    "Pertinence Guadeloupe déduite automatiquement : l'annexe du rapport ne "
    "cote que les lois promulguées, pas les textes encore en discussion."
)
