"""Modèle de données du générateur : constantes canoniques + champs requis.

Sépare les VALEURS CANONIQUES (imposées par CLAUDE.md, jamais issues du brief —
donc jamais « inventées » projet par projet) de la structure des champs 🔴/🟡 du
template v1.0 (§2-§5).
"""

from __future__ import annotations

from typing import Any, Optional

# --------------------------------------------------------------------------- #
# Version du template canonique consommé (doit rester alignée sur
# TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md §8 · version.schema.json pattern).
# --------------------------------------------------------------------------- #
TEMPLATE_VERSION = "1.0.0"

# --------------------------------------------------------------------------- #
# Paramètres CANONIQUES CLAUDE.md #9/#10. Émis TELS QUELS par le générateur pour
# TOUS les projets. Ne proviennent JAMAIS du brief → aucune invention possible.
# --------------------------------------------------------------------------- #
CANONICAL = {
    "frais_edition_pct": "3 %",
    "marketing_pct": "8.5 %",
    "point_equilibre_pct": "52 %",
    "devises": "USD + DOP",
    "paiement": "Cardnet",
    "format_doc": "Letter US",
}

# Ordre canonique des 4 volets (version.schema.json enum + parser).
VOLETS = ["masterplan", "architecture", "paysage_experience", "ingenierie"]

# --------------------------------------------------------------------------- #
# Champs OBLIGATOIRES (🔴) par volet — cf. tableaux §2-§5 du template.
# Un 🔴 absent ⇒ champs_manquants ⇒ rétrogradation (jamais « complete »).
# Les typologies (§3.2, bloc anti-gap) sont traitées à part par le scorer.
# --------------------------------------------------------------------------- #
REQUIRED = {
    "masterplan": [
        "localisation",       # 1.1
        "terrain_m2",         # 1.2
        "terrain_tareas",     # 1.2
        "zonage",             # 1.3
        "nb_unites",          # 1.4
        "nb_phases",          # 1.4
        "phasage",            # 1.8
    ],
    "architecture": [
        "parti_archi",        # 2.1
        "nb_batiments",       # 2.2
        "nb_niveaux",         # 2.3
    ],
    "paysage_experience": [
        "concept_paysage",    # 3.1
        "amenities",          # 3.2
        "services_inclus",    # 3.3
    ],
    "ingenierie": [
        "cout_construction_usd",  # 4.4
        "revenu_brut_usd",        # 4.8
        "marge_pct",              # 4.9
    ],
}

# Colonnes 🔴 d'une typologie (§3.2). Toutes requises pour lever le blocage prix.
TYPO_REQUIRED = [
    "nom",
    "quantite",
    "surface_interieure_m2",
    "surface_terrasse_m2",
    "surface_totale_m2",
    "prix_usd",
    "prix_dop",
]

# Placeholders signalant l'absence (alignés sur publiciste.parser._ABSENT).
_PLACEHOLDER_ABSENT = {
    "", "—", "-", "n/d", "nd", "non défini", "non defini", "à définir",
    "a definir", "tbd", "todo", "…", "...",
}


def is_filled(value: Any) -> bool:
    """True si `value` est une donnée réelle exploitable (pas un placeholder).

    - None / cellule vide / « non défini » / `{{…}}` → False
    - liste vide → False, liste non vide → True
    - 0 numérique → True (un vrai zéro sourcé est une donnée valide)
    """
    if value is None:
        return False
    if isinstance(value, bool):
        return True
    if isinstance(value, (int, float)):
        return True
    if isinstance(value, (list, tuple)):
        return len(value) > 0
    if isinstance(value, dict):
        return len(value) > 0
    text = str(value).strip().lower()
    if not text:
        return False
    if "{{" in text and "}}" in text:
        return False
    return text not in _PLACEHOLDER_ABSENT


def get(section: Optional[dict], key: str) -> Any:
    """Accès défensif à une clé de section de brief (None si absente)."""
    if not isinstance(section, dict):
        return None
    return section.get(key)
