"""
Socle visuel partagé Oncopole — constantes et repères géométriques, communs
aux deux chantiers (exports Joseph et template livré à Claude). Extrait par
mesure directe du XML du template réel (IUCT-O_Portail_Patient_COPIL), pas
inventé — voir chaque constante pour sa source.

Ce module ne dessine rien : `planning_pptx.py` et les futurs générateurs
(Focus, listes) restent responsables de l'assemblage. Objectif : que
BLUE/ORANGE/le rayon d'arrondi/etc. n'existent plus qu'à un seul endroit,
au lieu de trois valeurs légèrement différentes selon le fichier où on les
retrouve (constaté : le thème réel du template porte F19127/163E5B, alors
que `planning_pptx.py` porte de son côté F29700/234964).

Les fonctions de dessin (rectangle gris léger, rectangle contour, ligne de
séparation, ombre discrète) sont volontairement laissées en stub : à
compléter au fur et à mesure des besoins réels des générateurs, plutôt que
deviner une API à l'avance.
"""

from __future__ import annotations  # PEP 585 sans Python 3.9 : l'hébergement est en 3.7


import os

from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.util import Emu, Inches, Pt
from pptx.enum.shapes import MSO_SHAPE


# ══════════════════════════════════════════════════════════════════
# PALETTE
# Valeurs demandées explicitement par Mathieu — à préférer à celles du
# thème du template réel (F19127/163E5B) ou de planning_pptx.py
# (F29700/234964), sauf décision contraire : un seul jeu de couleurs.
# ══════════════════════════════════════════════════════════════════

ORANGE = "F28F20"   # rgb(242, 143, 32)
BLUE_DARK = "173F5B"   # rgb(23, 63, 91)

WHITE = "FFFFFF"
GRAY_LIGHT = "F2F2F2"   # fond des rectangles "accentuer légèrement" — à ajuster à l'œil 
GRAY_BORDER = "D9D9D9"   # contour des rectangles "accentuer beaucoup"
TEXT_DARK = "1A2733"


def rgb(hex_str: str) -> RGBColor:
    """Convertit une chaîne hex 6 caractères (sans #) en `RGBColor` python-pptx."""
    return RGBColor(int(hex_str[0:2], 16), int(hex_str[2:4], 16), int(hex_str[4:6], 16))


# ══════════════════════════════════════════════════════════════════
# TYPOGRAPHIE
# DINRoundPro n'est PAS la police du thème (`Calibri Light`/`Calibri`,
# voir theme1.xml) : elle est posée en override direct sur chaque run de
# titre dans le template réel. Même geste à reproduire ici — poser
# `run.font.name` explicitement, ne jamais compter sur l'héritage du thème.
# Doit être installée (ou embarquée dans le .pptx) sur toute machine qui
# ouvre le fichier, sans quoi PowerPoint substitue une police par défaut.
# ══════════════════════════════════════════════════════════════════

FONT_TITLE = "DINRoundPro"
FONT_BODY = "Calibri"


# ══════════════════════════════════════════════════════════════════
# GÉOMÉTRIE DU MASTER
# Mesurée directement dans slideMaster1.xml / slideLayout2.xml (layout de
# contenu standard) / slideLayout3.xml (layout "chapitre", type secHead)
# du template réel. Toutes les valeurs en inches (cohérent avec
# planning_pptx.py, qui travaille aussi en inches).
# ══════════════════════════════════════════════════════════════════

SLIDE_W = Inches(13.333)
SLIDE_H = Inches(7.5)

# Titre d'une slide de contenu standard (slideLayout2 : type="obj")
TITLE_X = Inches(0.425)
TITLE_Y = Inches(0.40)
TITLE_W = Inches(12.5)
TITLE_H = Inches(1.45)

# Logo (lockup Oncopole/IUCT complet) — bas gauche, aligné sur la marge du titre
LOGO_X = Inches(0.425)
LOGO_Y = Inches(6.62)
LOGO_W = Inches(3.06)
LOGO_H = Inches(0.65)

# Pagination — bas droite
PAGE_NUM_X = Inches(11.67)
PAGE_NUM_Y = Inches(6.94)
PAGE_NUM_W = Inches(1.254)
PAGE_NUM_H = Inches(0.40)

# Slide "chapitre" (slideLayout3, type secHead) — titre plus grand,
# positionné plus bas (vertical center-ish), sous-titre en dessous
CHAPTER_TITLE_X = Inches(0.425)
CHAPTER_TITLE_Y = Inches(1.87)
CHAPTER_TITLE_W = Inches(12.5)
CHAPTER_TITLE_H = Inches(3.12)
CHAPTER_SUBTITLE_Y = Inches(5.02)
CHAPTER_SUBTITLE_H = Inches(1.41)


# ══════════════════════════════════════════════════════════════════
# MODÈLE .pptx
# Dérivé du .potx fourni par Mathieu (masque, 19 layouts, logo, police
# DINRoundPro embarquée) — voir `reporter/assets/oncopole_template.pptx`.
# `python-pptx` refuse d'ouvrir un `.potx` tel quel : il vérifie le type de
# contenu déclaré dans le XML (`presentationml.template.main+xml`), pas
# l'extension du fichier, et son `Presentation()` n'accepte que
# `presentationml.presentation.main+xml`. Le fichier a donc été repackagé
# avec cette déclaration corrigée — structure interne identique sinon.
#
# Index des layouts qui comptent pour les générateurs (les 19 sont dans le
# fichier, le nom exact fait foi si un doute — `prs.slide_layouts[i].name`) :
# ══════════════════════════════════════════════════════════════════

TEMPLATE_PATH = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
    "assets", "oncopole_template.pptx",
)

BLANK_LAYOUT_IDX = 10      # "Vide" — les tableaux de bord (`generators/dashboards.py`), qui posent leur propre en-tête
CONTENT_LAYOUT_IDX = 1      # "Titre et contenu"
CHAPTER_LAYOUT_IDX = 2      # "Titre de section" — piège : porte 3 images décoratives non-placeholder
                              # (le diviseur de chapitre), qui s'affichent même sans remplir le titre.
                              # Ne jamais l'utiliser comme layout "par défaut" d'une slide de contenu.
TITLE_LAYOUT_IDX = 0      # "Diapositive de titre"

# Layout que les générateurs utilisent pour une slide de contenu générique
# (Gantt, listes, futur Focus) : grand titre + un second placeholder plus
# petit, positionné juste au-dessus du titre (un "chapeau", pas un corps de
# texte) — utilisé pour le nom du projet. Porte aussi le vrai logo
# (`Graphique 5`, une image, pas un placeholder — toujours visible, même non
# rempli). Attention : ce logo occupe x=0.42"-3.49" / y=6.63"-7.28" — tout
# pied de page dessiné à la main dans cette zone se superposera à lui.
SIMPLE_LAYOUT_IDX = 9      # "Simple"
SIMPLE_TITLE_PLACEHOLDER_IDX = 0
SIMPLE_PROJECT_NAME_PLACEHOLDER_IDX = 13


def new_presentation() -> Presentation:
    """
    Présentation neuve partant du masque Oncopole (logo, police, layouts,
    thème) plutôt que du modèle vide livré avec python-pptx — point d'entrée
    unique, pour que la taille de slide et le fichier source ne divergent
    pas d'un générateur à l'autre.
    """
    prs = Presentation(TEMPLATE_PATH)
    prs.slide_width = SLIDE_W
    prs.slide_height = SLIDE_H
    return prs


# ══════════════════════════════════════════════════════════════════
# FORMES — rayons, traits, ombre
# Rayon mesuré sur le seul rectangle à coins arrondis "propre" (non
# surligné à la main) trouvé dans le fichier fourni : adj=3765 (sur
# 100000), soit ~3.8% — un arrondi léger, pas un style "carte" prononcé.
# python-pptx exprime l'ajustement en fraction 0-1 sur
# `shape.adjustments[0]` : ROUNDED_RECT_RADIUS_FRACTION en est déjà la
# conversion.
# ══════════════════════════════════════════════════════════════════

ROUNDED_RECT_RADIUS_FRACTION = 0.05

SEPARATOR_LINE_WEIGHT = Pt(0.75)   # trait fin

# Ombre "discrète" — mesurée sur les ombres portées déjà présentes dans le
# fichier (celles à 50800/25400, pas l'aberrante à 292100/139700 d'une
# seule image). python-pptx n'expose PAS d'API publique pour un outerShdw
# personnalisé (seulement `shape.shadow.inherit`) : il faudra manipuler
# `shape._element` directement (lxml) pour écrire ce bloc XML. Valeurs de
# référence, en EMU sauf `alpha`/`direction` :
SHADOW_BLUR_RADIUS = Emu(50800)     # ≈ 4pt
SHADOW_DISTANCE = Emu(25400)     # ≈ 2pt
SHADOW_DIRECTION = 2700000     # 45°, unités de 1/60000 de degré (format OOXML)
SHADOW_ALPHA_PERCENT = 40           # 40% d'opacité noire — sur 100, pas sur 100000

# Le dégradé sur la ligne de séparation ("éventuellement un dégradé léger
# entre le début et la fin") n'existe dans aucune slide du fichier fourni —
# à construire de zéro. `shape.fill.gradient()` marche très bien nativement
# (vérifié sur python-pptx==0.6.23, la version figée en prod) — MAIS
# uniquement sur le fill d'une FORME, pas sur `line.fill` d'un connecteur
# (`add_connector()`) : là, `.gradient()` pose un `<a:gradFill/>` vide, sans
# `<a:lin>`, et `gradient_angle` lève alors "not a linear gradient". D'où le
# choix : représenter la ligne de séparation par un RECTANGLE très fin
# (`MSO_SHAPE.RECTANGLE`, hauteur ~1-2pt, `line.fill.background()` pour
# cacher son propre contour) et dégrader son `fill`, jamais un connecteur.


# ══════════════════════════════════════════════════════════════════
# FORMES — à implémenter au fil des besoins réels des générateurs
# ══════════════════════════════════════════════════════════════════

def add_rounded_rect_soft(slide, x, y, w, h):
    """Rectangle à coins arrondis, fond gris sans contour — pour accentuer légèrement un bloc de contenu."""
    # Add a rounded rectangle
    shape = slide.shapes.add_shape(
        MSO_SHAPE.ROUNDED_RECTANGLE, 
        x, y, w, h
    )

    # Adjust the corner radius
    # The adjustment index for rounded rectangle corner radius is typically 0
    # Values are usually between 0 and 1 for the normalized adjustment handle position
    shape.adjustments[0] = ROUNDED_RECT_RADIUS_FRACTION
    shape.fill.solid()
    shape.fill.fore_color.rgb = rgb(GRAY_LIGHT)
    shape.line.fill.background()
    shape.shadow.inherit = False


def add_rounded_rect_outline(slide, x, y, w, h, line_color_hex=ORANGE):
    """Rectangle à coins arrondis, fond blanc + contour — pour appuyer fort ou conclure."""
    shape = slide.shapes.add_shape(
        MSO_SHAPE.ROUNDED_RECTANGLE, 
        x, y, w, h
    )
    shape.adjustments[0] = ROUNDED_RECT_RADIUS_FRACTION
    shape.fill.solid()
    shape.fill.fore_color.rgb = rgb(WHITE)
    shape.line.color.rgb = rgb(line_color_hex)
    shape.line.width = Pt(2)
    shape.shadow.inherit = False


def add_separator_line(slide, x, y, w, h, gradient=False, line_color_hex=GRAY_BORDER):
    """Ligne fine de séparation, dégradé optionnel début→fin."""
    bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, w, h)
    bar.line.fill.background()  # pas de contour sur le rectangle lui-même
    bar.shadow.inherit = False

    if gradient:
        bar.fill.gradient()
        stops = bar.fill.gradient_stops
        stops[0].position = 0.0
        stops[0].color.rgb = rgb(line_color_hex)
        stops[1].position = 1.0
        stops[1].color.rgb = rgb(WHITE)
        bar.fill.gradient_angle = 0.0   # 0° = horizontal, gauche → droite
    else:
        bar.fill.solid()
        bar.fill.fore_color.rgb = rgb(line_color_hex)

    return bar
