#!/usr/bin/env python3
"""
Phase 1A — Génération de paires ChatML CIM-10 depuis le FHIR JSON.

Sources : smt_cim10_fhir.json (19 161 concepts)
Produit : data/processed/cim10_chatml.jsonl

Types d'exemples générés :
  1. code → description (lookup)
  2. description → code (codage)
  3. discrimination entre codes frères (même parent)
  4. inclusions/exclusions (ce qui est compris / exclu d'un code)
"""

import json
import random
from pathlib import Path

random.seed(42)

BASE = Path(__file__).resolve().parent.parent
RAW = BASE / "data" / "raw"
OUT = BASE / "data" / "processed"
OUT.mkdir(parents=True, exist_ok=True)

SYSTEM_MSG = "Tu es un médecin DIM expert en codage CIM-10 pour le PMSI français."

# --- Chapitres CIM-10 ---
CHAPTERS = {
    "A": "Certaines maladies infectieuses et parasitaires",
    "B": "Certaines maladies infectieuses et parasitaires",
    "C": "Tumeurs",
    "D": "Tumeurs / Maladies du sang",
    "E": "Maladies endocriniennes, nutritionnelles et métaboliques",
    "F": "Troubles mentaux et du comportement",
    "G": "Maladies du système nerveux",
    "H": "Maladies de l'œil et de l'oreille",
    "I": "Maladies de l'appareil circulatoire",
    "J": "Maladies de l'appareil respiratoire",
    "K": "Maladies de l'appareil digestif",
    "L": "Maladies de la peau et du tissu cellulaire sous-cutané",
    "M": "Maladies du système ostéo-articulaire",
    "N": "Maladies de l'appareil génito-urinaire",
    "O": "Grossesse, accouchement et puerpéralité",
    "P": "Certaines affections dont l'origine se situe dans la période périnatale",
    "Q": "Malformations congénitales et anomalies chromosomiques",
    "R": "Symptômes, signes et résultats anormaux",
    "S": "Lésions traumatiques et empoisonnements",
    "T": "Lésions traumatiques et empoisonnements",
    "V": "Causes externes de morbidité et de mortalité",
    "W": "Causes externes de morbidité et de mortalité",
    "X": "Causes externes de morbidité et de mortalité",
    "Y": "Causes externes de morbidité et de mortalité",
    "Z": "Facteurs influant sur l'état de santé et motifs de recours aux services de santé",
    "U": "Codes d'utilisation particulière",
}


def load_fhir():
    """Charger et indexer les concepts FHIR."""
    with open(RAW / "smt_cim10_fhir.json") as f:
        data = json.load(f)
    concepts = data["concept"]

    # Index par code
    by_code = {}
    for c in concepts:
        by_code[c["code"]] = c

    return concepts, by_code


def get_props(concept):
    """Extraire les propriétés d'un concept sous forme de dict (multi-valeurs en listes)."""
    props = {}
    for p in concept.get("property", []):
        key = p["code"]
        val = p.get("valueCode", p.get("valueString", ""))
        if key in props:
            if isinstance(props[key], list):
                props[key].append(val)
            else:
                props[key] = [props[key], val]
        else:
            props[key] = val
    return props


def clean_display(display):
    """Nettoyer le libellé (enlever les codes entre crochets type [G31.0])."""
    import re
    # Retirer les références entre crochets comme [G31.0†]
    cleaned = re.sub(r'\s*\[[\w.†*+]+\]\s*', ' ', display)
    # Retirer les guillemets décoratifs
    cleaned = cleaned.replace('"', '').replace('"', '').replace('"', '')
    # Nettoyer les espaces multiples et les tirets isolés
    cleaned = re.sub(r'\s*-\s*-\s*', ' - ', cleaned)
    cleaned = re.sub(r'\s+', ' ', cleaned).strip()
    return cleaned


def make_chatml(system, user, assistant):
    """Créer un exemple ChatML."""
    return {
        "messages": [
            {"role": "system", "content": system},
            {"role": "user", "content": user},
            {"role": "assistant", "content": assistant},
        ]
    }


def generate_lookup_pairs(concepts, by_code):
    """Type 1 : code → description."""
    pairs = []
    for c in concepts:
        props = get_props(c)
        if props.get("type") not in ("category",):
            continue

        code = c["code"]
        display = clean_display(c["display"])

        if not display or len(display) < 3:
            continue

        chapter = CHAPTERS.get(code[0], "")
        parent_code = props.get("parent", "")
        parent_display = ""
        if parent_code and parent_code in by_code:
            parent_display = clean_display(by_code[parent_code]["display"])

        # Construire la réponse enrichie
        answer_parts = [f"{code} — {display}"]
        if chapter:
            answer_parts.append(f"Chapitre : {chapter}")
        if parent_display and parent_code != code:
            answer_parts.append(f"Catégorie parente : {parent_code} ({parent_display})")

        # Ajouter inclusions si présentes
        incl = props.get("inclusionNote", "")
        if incl and len(incl) < 300:
            answer_parts.append(f"Comprend : {incl}")

        # Varier les formulations de question
        templates = [
            f"Que désigne le code CIM-10 {code} ?",
            f"Quel est le libellé du code {code} ?",
            f"Décris le code CIM-10 {code}.",
        ]
        question = random.choice(templates)

        pairs.append(make_chatml(SYSTEM_MSG, question, "\n".join(answer_parts)))

    return pairs


def generate_coding_pairs(concepts, by_code):
    """Type 2 : description → code (codage direct)."""
    pairs = []
    for c in concepts:
        props = get_props(c)
        if props.get("type") not in ("category",):
            continue

        code = c["code"]
        display = clean_display(c["display"])

        if not display or len(display) < 5:
            continue

        # Réponse JSON structurée (format du pipeline T2A)
        answer = json.dumps({
            "code": code,
            "confidence": "high",
            "justification": f"Correspondance directe avec le libellé CIM-10 : {code} {display}."
        }, ensure_ascii=False)

        templates = [
            f"Quel est le code CIM-10 pour : {display} ?",
            f"Code CIM-10 pour « {display} » ?",
            f"Codage CIM-10 du diagnostic : {display}",
        ]
        question = random.choice(templates)

        pairs.append(make_chatml(SYSTEM_MSG, question, answer))

    return pairs


def generate_discrimination_pairs(concepts, by_code):
    """Type 3 : discrimination entre codes frères (même parent)."""
    pairs = []

    # Grouper par parent
    children_by_parent = {}
    for c in concepts:
        props = get_props(c)
        if props.get("type") != "category":
            continue
        parent = props.get("parent", "")
        if parent and parent in by_code:
            children_by_parent.setdefault(parent, []).append(c)

    for parent_code, children in children_by_parent.items():
        if len(children) < 2 or len(children) > 15:
            continue

        parent = by_code[parent_code]
        parent_display = clean_display(parent["display"])

        # Construire la question
        question = f"Quels sont les sous-codes de {parent_code} ({parent_display}) et comment les distinguer ?"

        # Construire la réponse
        lines = [f"La catégorie {parent_code} ({parent_display}) comprend les codes suivants :\n"]
        for child in children:
            child_display = clean_display(child["display"])
            child_props = get_props(child)
            line = f"- {child['code']} : {child_display}"
            # Ajouter une note d'inclusion courte si disponible
            incl = child_props.get("inclusionNote", "")
            if incl and len(incl) < 150:
                line += f" (comprend : {incl})"
            lines.append(line)

        lines.append(f"\nLe choix du code dépend de la précision diagnostique disponible. "
                      f"En l'absence de précision, utiliser le code SAI (.9) s'il existe.")

        answer = "\n".join(lines)

        # Limiter la taille
        if len(answer) > 2000:
            continue

        pairs.append(make_chatml(SYSTEM_MSG, question, answer))

    return pairs


def generate_inclusion_exclusion_pairs(concepts, by_code):
    """Type 4 : questions sur les inclusions/exclusions d'un code."""
    pairs = []
    for c in concepts:
        props = get_props(c)
        if props.get("type") not in ("category",):
            continue

        code = c["code"]
        display = clean_display(c["display"])
        incl = props.get("inclusionNote", "")
        excl_note = props.get("exclusionNote", "")
        excl_codes = props.get("exclusion", "")
        note = props.get("note", "")

        # Il faut au moins une inclusion OU exclusion
        if not incl and not excl_note:
            continue

        # Construire la réponse
        answer_parts = [f"Code {code} — {display}\n"]

        if incl:
            answer_parts.append(f"Ce code COMPREND :\n{incl}")

        if excl_note:
            answer_parts.append(f"\nCe code EXCLUT :\n{excl_note}")

        if note:
            answer_parts.append(f"\nNote : {note}")

        answer = "\n".join(answer_parts)
        if len(answer) > 2000:
            continue

        templates = [
            f"Quelles sont les inclusions et exclusions du code {code} ({display}) ?",
            f"Que comprend et que exclut le code CIM-10 {code} ?",
        ]
        question = random.choice(templates)

        pairs.append(make_chatml(SYSTEM_MSG, question, answer))

    return pairs


def main():
    print("Chargement du FHIR JSON...")
    concepts, by_code = load_fhir()
    print(f"  {len(concepts)} concepts chargés")

    print("\nGénération des paires...")

    print("  Type 1 : code → description (lookup)")
    lookup = generate_lookup_pairs(concepts, by_code)
    print(f"    → {len(lookup)} exemples")

    print("  Type 2 : description → code (codage)")
    coding = generate_coding_pairs(concepts, by_code)
    print(f"    → {len(coding)} exemples")

    print("  Type 3 : discrimination codes frères")
    discrim = generate_discrimination_pairs(concepts, by_code)
    print(f"    → {len(discrim)} exemples")

    print("  Type 4 : inclusions / exclusions")
    incl_excl = generate_inclusion_exclusion_pairs(concepts, by_code)
    print(f"    → {len(incl_excl)} exemples")

    # Fusionner et mélanger
    all_pairs = lookup + coding + discrim + incl_excl
    random.shuffle(all_pairs)

    # Écrire en JSONL
    output_path = OUT / "cim10_chatml.jsonl"
    with open(output_path, "w") as f:
        for pair in all_pairs:
            f.write(json.dumps(pair, ensure_ascii=False) + "\n")

    print(f"\nTotal : {len(all_pairs)} exemples → {output_path}")
    print(f"Taille : {output_path.stat().st_size / 1024 / 1024:.1f} Mo")

    # Stats par type
    print("\nRépartition :")
    print(f"  Lookup (code→desc)  : {len(lookup)}")
    print(f"  Codage (desc→code)  : {len(coding)}")
    print(f"  Discrimination      : {len(discrim)}")
    print(f"  Inclusions/Exclus.  : {len(incl_excl)}")


if __name__ == "__main__":
    main()
