"""Generation deterministe des mots-cles SEO FR/EN/ES.

PRINCIPE ANTI-INVENTION (#6) : un mot-cle n'est JAMAIS un fait invente. C'est une
composition de :
  - tokens factuels issus de `projets_master.json` (nom, localisation) — sourcables ;
  - vocabulaire editorial generique du lexique (immobilier / a vendre / ...) —
    non chiffre, sans reference a un projet particulier.
Chaque mot-cle porte donc une liste `sources` non vide (soit `lexicon:*`, soit
`projet:<code>.<champ>`), et ne peut contenir que des chiffres deja presents dans
son champ projet source. Cette regle est verifiee par un invariant du generateur.

Sortie : liste triee de dicts {term, lang, scope, projet, intent, category,
sources}. Tri stable (lang, scope, projet, term) -> diffable + re-generable.
"""

from __future__ import annotations


def _confotur_source(projets: list[dict], marker: str) -> str | None:
    """Code du premier projet documentant le regime dans `inclus` (ou None)."""
    for p in projets:
        if any(marker in (s or "") for s in p.get("inclus", [])):
            return p["code"]
    return None


def generate(spec: dict, master: dict) -> list[dict]:
    site = spec["site"]
    lex = spec["lexicon"]
    langs = site["langs"]
    projets = master["projets"]

    property_generic = lex["property_generic"]
    property_types = lex["property_types"]
    intents = lex["intents"]
    intents_for_type = set(lex.get("intents_for_type", [i["key"] for i in intents]))
    country = lex["country"]
    regime = lex.get("regime")
    confotur_code = None
    if regime:
        confotur_code = _confotur_source(projets, regime["requires_inclus"])

    lang_index = {lang: i for i, lang in enumerate(langs)}
    acc: list[dict] = []

    def add(term, lang, scope, projet, intent, category, sources):
        term = " ".join(str(term).split()).strip()
        acc.append({
            "term": term,
            "lang": lang,
            "scope": scope,
            "projet": projet,
            "intent": intent,
            "category": category,
            "sources": list(sources),
        })

    for lang in langs:
        pg = property_generic[lang]
        ctry = country[lang]

        # --- Mots-cles GLOBAUX (marque / pays / regime) : independants d'un
        #     projet mais adosses au lexique editorial (jamais un fait invente).
        add(f"{pg} {ctry}", lang, "global", None, None, "generic",
            ["lexicon:property_generic", "lexicon:country"])
        for pt in property_types:
            add(f"{pt[lang]} {ctry}", lang, "global", None, None, pt["key"],
                ["lexicon:property_types", "lexicon:country"])
        for it in intents:
            add(f"{pg} {it[lang]} {ctry}", lang, "global", None, it["key"], "generic",
                ["lexicon:property_generic", "lexicon:intents", "lexicon:country"])
        if regime and confotur_code:
            add(f"{pg} {regime['term'][lang]} {ctry}", lang, "global", None, None,
                regime["key"],
                ["lexicon:regime", "lexicon:country", f"projet:{confotur_code}.inclus"])

        # --- Mots-cles PAR PROJET : nom (factuel) + localisation (factuel)
        #     croises avec le lexique editorial.
        for p in projets:
            code, nom, loc = p["code"], p["nom"], p["localisation"]
            nom_src = f"projet:{code}.nom"
            loc_src = f"projet:{code}.localisation"

            add(nom, lang, "projet", code, None, None, [nom_src])
            add(f"{nom} {ctry}", lang, "projet", code, None, None,
                [nom_src, "lexicon:country"])
            for it in intents:
                add(f"{nom} {it[lang]}", lang, "projet", code, it["key"], None,
                    [nom_src, "lexicon:intents"])

            add(f"{pg} {loc}", lang, "projet", code, None, "generic",
                ["lexicon:property_generic", loc_src])
            for pt in property_types:
                add(f"{pt[lang]} {loc}", lang, "projet", code, None, pt["key"],
                    ["lexicon:property_types", loc_src])
            for pt in property_types:
                for it in intents:
                    if it["key"] not in intents_for_type:
                        continue
                    add(f"{pt[lang]} {it[lang]} {loc}", lang, "projet", code,
                        it["key"], pt["key"],
                        ["lexicon:property_types", "lexicon:intents", loc_src])

    # --- Dedup (term, lang) en conservant la premiere occurrence, puis tri
    #     deterministe.
    seen: set[tuple[str, str]] = set()
    deduped: list[dict] = []
    for k in acc:
        key = (k["lang"], k["term"])
        if key in seen:
            continue
        seen.add(key)
        deduped.append(k)

    scope_order = {"global": 0, "projet": 1}
    deduped.sort(key=lambda k: (
        lang_index[k["lang"]], scope_order[k["scope"]], k["projet"] or "", k["term"]
    ))
    return deduped
