"""Modele Frappe/ERPNext v15 : structure native du fixture `DocType`.

Separe la CONNAISSANCE FRAPPE (types de champ legitimes, drapeaux de docfield,
modele DocPerm, enveloppe du document DocType) de l'assemblage metier
(`builder.py`). Contrainte #1 « ERPNext natif = priorite absolue » : on produit
le DocType standard du moteur Frappe, porteur des dossiers d'incitation CONFOTUR.

Aucun acces VPS : chaque fonction renvoie un dict serialisable que l'agent
ONAPI/Legal importera via `bench` (hand-off · #8).

Nota vs `crm/dossier_vente/dvlib/frappe.py` : ce module ajoute la permission
native `report` (le role `legal-directeur` la porte cote RBAC). Les deux modules
restent volontairement independants — chaque livrable possede sa connaissance
Frappe, on n'ouvre que ce que le contrat emploie reellement.
"""

from __future__ import annotations

from typing import Any

# Types de champ Frappe employes par ce DocType. Hors de cet ensemble = invention
# -> refusee par `docfield()`.
VALID_FIELDTYPES: frozenset[str] = frozenset(
    {
        "Section Break",
        "Column Break",
        "Data",
        "Select",
        "Link",
        "Date",
        "Datetime",
        "Small Text",
        "Text",
        "Check",
    }
)

# Types « de mise en page » : ni requis, ni porteurs de donnee.
LAYOUT_FIELDTYPES: frozenset[str] = frozenset({"Section Break", "Column Break"})

# Permissions natives du child table `DocPerm` d'un DocType Frappe (v15).
# `report` est inclus (porte par le role legal-directeur cote RBAC).
VALID_PERMS: frozenset[str] = frozenset(
    {"read", "write", "create", "submit", "cancel", "amend", "delete",
     "print", "email", "report", "import", "export", "share"}
)

# Ordre de serialisation stable des drapeaux DocPerm (diff propre).
_PERM_ORDER = ("read", "write", "create", "submit", "cancel", "amend", "delete",
               "print", "email", "report", "import", "export", "share")

# Drapeaux de docfield connus (defaut 0) — on ne serialise que ceux a 1.
_DOCFIELD_FLAGS = ("reqd", "read_only", "in_list_view", "in_standard_filter", "hidden", "bold")


def docfield(
    fieldname: str,
    fieldtype: str,
    *,
    label: str | None = None,
    options: str | None = None,
    default: str | None = None,
    flags: dict[str, int] | None = None,
) -> dict[str, Any]:
    """Une ligne de la table enfant `fields` d'un DocType."""
    if fieldtype not in VALID_FIELDTYPES:
        raise ValueError(f"Fieldtype non natif Frappe pour {fieldname!r} : {fieldtype!r}")
    row: dict[str, Any] = {"fieldname": fieldname, "fieldtype": fieldtype}
    if label is not None:
        row["label"] = label
    if options is not None:
        row["options"] = options
    if default is not None:
        row["default"] = default
    flags = flags or {}
    for f in _DOCFIELD_FLAGS:
        v = int(flags.get(f, 0))
        if v not in (0, 1):
            raise ValueError(f"Drapeau {f} de {fieldname!r} non booleen : {v!r}")
        if v:
            row[f] = 1
    return row


def section_break(fieldname: str, label: str) -> dict[str, Any]:
    return {"fieldname": fieldname, "fieldtype": "Section Break", "label": label}


def permission_row(role: str, perms: set[str], *, permlevel: int = 0) -> dict[str, Any]:
    """Ligne `permissions` (DocPerm integre) d'un DocType.

    Emet uniquement les drapeaux a 1 (defaut sur = refus), dans l'ordre stable
    `_PERM_ORDER` — diff reproductible.
    """
    unknown = perms - VALID_PERMS
    if unknown:
        raise ValueError(f"Permission(s) non native(s) pour {role!r} : {sorted(unknown)}")
    row: dict[str, Any] = {"role": role, "permlevel": permlevel}
    for p in _PERM_ORDER:
        if p in perms:
            row[p] = 1
    return row


def naming_series_field(series: str) -> dict[str, Any]:
    """Le champ Select `naming_series` requis quand `autoname = "naming_series:"`.

    Frappe nomme le document depuis ce champ ; ses `options` portent le motif de
    serie. Rendu `read_only` (serie imposee, pas de saisie libre).
    """
    return docfield(
        "naming_series",
        "Select",
        label="Serie de nommage",
        options=series,
        default=series,
        flags={"read_only": 1},
    )


def doctype_doc(
    *,
    name: str,
    module: str,
    is_submittable: int,
    title_field: str,
    search_fields: str,
    track_changes: int,
    track_seen: int,
    fields: list[dict],
    permissions: list[dict],
) -> dict[str, Any]:
    """Le document `DocType` complet (fixture custom pret pour `bench`)."""
    return {
        "doctype": "DocType",
        "name": name,
        "module": module,
        "custom": 1,
        "is_submittable": 1 if is_submittable else 0,
        "naming_rule": "By \"Naming Series\" field",
        "autoname": "naming_series:",
        "title_field": title_field,
        "search_fields": search_fields,
        "track_changes": 1 if track_changes else 0,
        "track_seen": 1 if track_seen else 0,
        "editable_grid": 1,
        "engine": "InnoDB",
        "field_order": [f["fieldname"] for f in fields],
        "fields": fields,
        "permissions": permissions,
    }
