"""Assemblage du bundle Chat OTOIA depuis (contrat RBAC + spec + portails + seo).

Sortie déterministe :
  - custom_block : 1 fixture Frappe `Custom Block` par portail (le carrier natif du
    chat, inséré dans le `content` du Workspace du portail côté VPS) ;
  - chat_mount   : 1 config runtime par portail (persona, langues, rôles autorisés,
    portée de connaissance, capabilities, endpoint=null) — ce que le runtime OTOIA
    consomme ; données pures, diffables ;
  - manifest     : traçabilité (comptes, résumé par portail, marque, hand-off VPS).

Anti-invention (#6), garanti ici + par les invariants du CLI :
  - portails = EXACTEMENT les portails métier (portails_spec / portails_business),
    la console technique `plateforme` étant exclue (comme les Workspaces) ;
  - `roles_allowed` / `knowledge_scope` viennent du CONTRAT (surface RBAC du portail),
    jamais de la spec — mêmes valeurs que les Has Role des Workspaces ;
  - `endpoint` reste null (a_confirmer · VPS) — jamais fabriqué ;
  - persona / capabilities / langues repris de sources sourcées (CLAUDE.md, seo_spec),
    jamais inventés ; aucun chiffre émis.

Déterminisme : portails dans l'ordre de la spec ; rôles + DocTypes triés → sortie
reproductible bit-à-bit (aucun horodatage, aucun aléatoire).
"""

from __future__ import annotations

from typing import Any

from . import deps, frappe, knowledge


def _portail_labels(portails_spec: dict) -> dict[str, str]:
    return {p["key"]: p["label"] for p in portails_spec["portails"]}


def build_bundle(
    contract: dict,
    spec: dict,
    portails_spec: dict,
    langs: list[str],
    default_lang: str,
) -> dict[str, Any]:
    """Transforme les entrées en bundle {custom_block, chat_mount, manifest}."""
    labels = _portail_labels(portails_spec)
    keys = [p["key"] for p in portails_spec["portails"]]  # ordre spec = déterminisme

    mount_cfg = spec["mount"]
    id_prefix = mount_cfg["html_id_prefix"]
    css_class = mount_cfg["container_class"]
    bn_prefix = mount_cfg["block_name_prefix"]
    persona = {"nom": spec["persona"]["nom"], "voix": spec["persona"]["voix"]}
    capabilities = [c["module"] for c in spec["capabilities"]]
    ui_label = dict(spec["ui_label"])

    custom_blocks: list[dict] = []
    mounts: list[dict] = []
    manifest_portails: list[dict] = []

    for key in keys:
        label = labels[key]
        roles = knowledge.roles_for(contract, key)
        scope = knowledge.knowledge_scope_for(contract, key)
        html_id = f"{id_prefix}{key}"
        block_name = f"{bn_prefix}{label}"
        html = frappe.mount_html(html_id, key, persona["nom"], css_class)

        custom_blocks.append(frappe.custom_block_fixture(block_name, html))

        mounts.append({
            "portail": key,
            "workspace": label,
            "custom_block": block_name,
            "html_id": html_id,
            "persona": persona,
            "langues": list(langs),
            "lang_defaut": default_lang,
            "ui_label": ui_label,
            "roles_allowed": roles,
            "knowledge_scope": scope,
            "capabilities": list(capabilities),
            "endpoint": None,
        })

        manifest_portails.append({
            "portail": key,
            "workspace": label,
            "custom_block": block_name,
            "nb_roles": len(roles),
            "nb_knowledge_doctypes": len(scope),
        })

    knowledge_union = sorted({dt for m in mounts for dt in m["knowledge_scope"]})
    manifest = {
        "deliverable": spec["deliverable"],
        "sprint": spec["sprint"],
        "roadmap_ref": spec["roadmap_ref"],
        "generated_from": spec["generated_from"],
        "carrier": {
            "doctype": frappe.CUSTOM_BLOCK_DOCTYPE,
            "workspace_content_block_type": frappe.WORKSPACE_CONTENT_BLOCK_TYPE,
            "note": "Chaque Custom Block est inséré dans le `content` (editor.js) du "
                    "Workspace de son portail via un bloc `custom_block` référençant "
                    "`block_name`. La clé exacte du payload editor.js est a_confirmer "
                    "selon le patch de version v15 — étape VPS (agent Frontend).",
        },
        "persona": persona,
        "langues": list(langs),
        "lang_defaut": default_lang,
        "capabilities": list(capabilities),
        "endpoint_statut": spec["endpoint"]["statut"],
        "counts": {
            "portails": len(mounts),
            "custom_blocks": len(custom_blocks),
            "mounts": len(mounts),
            "roles_couverts": sum(m["nb_roles"] for m in manifest_portails),
            "knowledge_doctypes_uniques": len(knowledge_union),
        },
        "portails": manifest_portails,
        "brand": {
            "fond": {"valeur": deps.branding.COLOR_BG, "source": "CLAUDE.md #4"},
            "accent": {"valeur": deps.branding.COLOR_ACCENT, "source": "CLAUDE.md #4"},
            "polices": [
                {"valeur": deps.branding.FONT_DISPLAY, "source": "CLAUDE.md #4"},
                {"valeur": deps.branding.FONT_BODY, "source": "CLAUDE.md #4"},
            ],
        },
        "handoff_vps": [
            "Créer/confirmer le module propriétaire OTO côté app puis importer les "
            "fixtures `Custom Block` (bench).",
            "Insérer dans le `content` de chaque Workspace (frontend/portails/out/"
            "workspace.json) un bloc `custom_block` référençant `block_name` (payload "
            "editor.js a_confirmer selon version).",
            "Renseigner l'endpoint OTOIA (desk/bim-cloud) dans la config runtime + "
            "charger le web-component OTOIA via le thème desk (agent Frontend · #8).",
            "Appliquer les tokens de marque dark+doré (#4) au montage via le thème.",
        ],
    }

    return {"custom_block": custom_blocks, "chat_mount": mounts, "manifest": manifest}
