"""Generate learning loop diagram as PowerPoint, then export to PNG."""

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

# ── palette ──
ANALYSIS = RGBColor(0x43, 0x93, 0xC3)
ANNEAL_BG = RGBColor(0xF0, 0xF0, 0xF0)
FROZEN = RGBColor(0x21, 0x66, 0xAC)
WARM = RGBColor(0xF4, 0xA5, 0x82)
HOT = RGBColor(0xB2, 0x18, 0x2B)
KNOWLEDGE = RGBColor(0x7B, 0x32, 0x94)
SKILL = RGBColor(0xD6, 0x60, 0x4D)
GRAY = RGBColor(0x88, 0x88, 0x88)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
BLACK = RGBColor(0x33, 0x33, 0x33)
BANNER_BG = RGBColor(0xF7, 0xF7, 0xF7)
ARROW_CLR = RGBColor(0x44, 0x44, 0x44)


def add_rounded_box(slide, left, top, width, height, text, fill_color,
                    font_size=11, font_color=WHITE, bold=True):
    shape = slide.shapes.add_shape(
        MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height,
    )
    shape.fill.solid()
    shape.fill.fore_color.rgb = fill_color
    shape.line.fill.background()
    shape.shadow.inherit = False

    tf = shape.text_frame
    tf.word_wrap = True
    tf.auto_size = None
    tf.paragraphs[0].alignment = PP_ALIGN.CENTER
    tf.vertical_anchor = MSO_ANCHOR.MIDDLE

    p = tf.paragraphs[0]
    p.text = text
    p.alignment = PP_ALIGN.CENTER
    for run in p.runs:
        run.font.size = Pt(font_size)
        run.font.color.rgb = font_color
        run.font.bold = bold
    # Re-add with runs for multiline
    tf.clear()
    for i, line in enumerate(text.split("\n")):
        if i == 0:
            p = tf.paragraphs[0]
        else:
            p = tf.add_paragraph()
        p.alignment = PP_ALIGN.CENTER
        run = p.add_run()
        run.text = line
        run.font.size = Pt(font_size)
        run.font.color.rgb = font_color
        run.font.bold = bold

    return shape


def add_arrow(slide, x0, y0, x1, y1, color=ARROW_CLR, width=Pt(2)):
    # Ensure integer EMUs
    x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
    connector = slide.shapes.add_connector(
        1,  # straight connector
        x0, y0, x1, y1,
    )
    connector.line.color.rgb = color
    connector.line.width = width
    # Arrowhead
    from pptx.oxml.ns import qn
    sp_pr = connector._element.find(qn("p:spPr"))
    ln = sp_pr.find(qn("a:ln"))
    if ln is None:
        from lxml import etree
        ln = etree.SubElement(sp_pr, qn("a:ln"))
    tail = ln.makeelement(qn("a:tailEnd"), {"type": "triangle", "w": "med", "len": "med"})
    ln.append(tail)
    return connector


def add_text(slide, left, top, width, height, text, font_size=9,
             font_color=GRAY, bold=False, italic=True, align=PP_ALIGN.CENTER):
    txBox = slide.shapes.add_textbox(left, top, width, height)
    tf = txBox.text_frame
    tf.word_wrap = True
    for i, line in enumerate(text.split("\n")):
        if i == 0:
            p = tf.paragraphs[0]
        else:
            p = tf.add_paragraph()
        p.alignment = align
        run = p.add_run()
        run.text = line
        run.font.size = Pt(font_size)
        run.font.color.rgb = font_color
        run.font.bold = bold
        run.font.italic = italic
    return txBox


# ══════════════════════════════════════════════════════════
#  Build presentation
# ══════════════════════════════════════════════════════════
prs = Presentation()
prs.slide_width = Inches(13.33)
prs.slide_height = Inches(7.5)

slide = prs.slides.add_slide(prs.slide_layouts[6])  # blank layout
slide.background.fill.solid()
slide.background.fill.fore_color.rgb = WHITE

# ── grid helpers (inches) ──
COL_L = Inches(0.5)     # left column (Analysis, Knowledge Base, Skill Grad)
COL_M = Inches(4.5)     # middle column (Annealing, arrows)
COL_R = Inches(8.5)     # right column (Result, Knowledge Synth, Domain Skill)
BOX_W = Inches(3.0)
BOX_H = Inches(1.2)

ROW1 = Inches(1.5)
ROW2 = Inches(3.5)
ROW3 = Inches(5.5)

# ══════════════════════════════════════════════════════════
#  Title
# ══════════════════════════════════════════════════════════
add_text(slide, Inches(0.5), Inches(0.15), Inches(12), Inches(0.5),
         "Constraint Annealing in the Continuous Learning Loop",
         font_size=18, font_color=BLACK, bold=True, italic=False)

# Banner
banner = slide.shapes.add_shape(
    MSO_SHAPE.ROUNDED_RECTANGLE, Inches(0.8), Inches(0.75), Inches(11.5), Inches(0.5),
)
banner.fill.solid()
banner.fill.fore_color.rgb = BANNER_BG
banner.line.color.rgb = RGBColor(0xCC, 0xCC, 0xCC)
banner.line.width = Pt(0.5)
add_text(slide, Inches(0.8), Inches(0.78), Inches(11.5), Inches(0.45),
         "Better skills  \u2192  better plans  \u2192  less annealing  \u2192  higher quality  \u2192  better knowledge  \u2192  better skills",
         font_size=10, font_color=RGBColor(0x55, 0x55, 0x55), italic=True)

# ══════════════════════════════════════════════════════════
#  ROW 1: Analysis → Annealing → Result
# ══════════════════════════════════════════════════════════
add_rounded_box(slide, COL_L, ROW1, BOX_W, BOX_H,
                "Analysis\n(Curve Fitting Agent)", ANALYSIS, font_size=13)

# Annealing box
ann_shape = slide.shapes.add_shape(
    MSO_SHAPE.ROUNDED_RECTANGLE, COL_M, ROW1, Inches(3.5), BOX_H,
)
ann_shape.fill.solid()
ann_shape.fill.fore_color.rgb = ANNEAL_BG
ann_shape.line.color.rgb = RGBColor(0xBB, 0xBB, 0xBB)
ann_shape.line.width = Pt(1)

add_text(slide, COL_M + Inches(0.2), ROW1 + Inches(0.02), Inches(3.1), Inches(0.35),
         "Constraint Annealing   P \u221d exp(\u2212\u0394E/kT)",
         font_size=10, font_color=BLACK, bold=True, italic=False)

# Mini temperature bars
bar_data = [("T=0  Frozen", FROZEN, WHITE), ("T=1  Warm", WARM, BLACK), ("T=2  Hot", HOT, WHITE)]
for j, (lbl, clr, fc) in enumerate(bar_data):
    by = ROW1 + Inches(0.38) + Emu(int(j * Inches(0.25)))
    bar = slide.shapes.add_shape(
        MSO_SHAPE.ROUNDED_RECTANGLE,
        COL_M + Inches(0.4), by, Inches(2.7), Inches(0.22),
    )
    bar.fill.solid()
    bar.fill.fore_color.rgb = clr
    bar.line.fill.background()
    tf = bar.text_frame
    tf.paragraphs[0].alignment = PP_ALIGN.CENTER
    tf.vertical_anchor = MSO_ANCHOR.MIDDLE
    run = tf.paragraphs[0].add_run()
    run.text = lbl
    run.font.size = Pt(8)
    run.font.color.rgb = fc
    run.font.bold = True

# Result box
add_rounded_box(slide, COL_R, ROW1, BOX_W, BOX_H,
                "Result +\nquality_history", ANALYSIS, font_size=12, font_color=WHITE)

# quality_history bullets
qh_items = ["\u2022 verification iterations", "\u2022 alternative models",
            "\u2022 script errors", "\u2022 judge reasoning"]
add_text(slide, COL_R + BOX_W + Inches(0.15), ROW1 + Inches(0.05),
         Inches(1.8), BOX_H,
         "\n".join(qh_items),
         font_size=8, font_color=RGBColor(0x55, 0x55, 0x55), italic=False,
         align=PP_ALIGN.LEFT)

# Arrows: Analysis → Annealing → Result
add_arrow(slide, COL_L + BOX_W, ROW1 + BOX_H / 2,
          COL_M, ROW1 + BOX_H / 2)
add_arrow(slide, COL_M + Inches(3.5), ROW1 + BOX_H / 2,
          COL_R, ROW1 + BOX_H / 2)

# ══════════════════════════════════════════════════════════
#  ROW 2: Knowledge Synthesis ← arrow ← Result (right)
#          Knowledge Base (left) ← arrow ← Synthesis
# ══════════════════════════════════════════════════════════
add_rounded_box(slide, COL_R, ROW2, BOX_W, BOX_H,
                "Knowledge Synthesis\n(failure / method)", KNOWLEDGE, font_size=12)

add_rounded_box(slide, COL_L, ROW2, BOX_W, BOX_H,
                "Knowledge Base\n(accumulated entries)", KNOWLEDGE, font_size=12,
                font_color=WHITE)

# Arrow ↓ from Result to Knowledge Synthesis
add_arrow(slide, COL_R + BOX_W / 2, ROW1 + BOX_H,
          COL_R + BOX_W / 2, ROW2)
add_text(slide, COL_R + BOX_W / 2 + Inches(0.1), ROW1 + BOX_H + Inches(0.15),
         Inches(1.8), Inches(0.4),
         "quality_history\nfeeds synthesis", font_size=8, font_color=KNOWLEDGE)

# Arrow ← from Synthesis to Knowledge Base
add_arrow(slide, COL_R, ROW2 + BOX_H / 2,
          COL_L + BOX_W, ROW2 + BOX_H / 2, color=KNOWLEDGE)

# Arrow ↑ from Knowledge Base back to Analysis
add_arrow(slide, COL_L + BOX_W / 2, ROW2,
          COL_L + BOX_W / 2, ROW1 + BOX_H, color=KNOWLEDGE, width=Pt(2.5))
add_text(slide, COL_L - Inches(0.6), ROW1 + BOX_H + Inches(0.5),
         Inches(1.8), Inches(0.5),
         "prior knowledge\ninjected into\nnext analysis", font_size=8, font_color=KNOWLEDGE,
         align=PP_ALIGN.LEFT)

# ══════════════════════════════════════════════════════════
#  ROW 3: Skill Graduation → Domain Skill
# ══════════════════════════════════════════════════════════
add_rounded_box(slide, COL_L, ROW3, BOX_W, BOX_H,
                "Skill Graduation\n(LLM distills knowledge)", SKILL, font_size=12)

add_rounded_box(slide, COL_R, ROW3, BOX_W, BOX_H,
                "Domain Skill (.md)\nplan / analysis /\ninterp / validation",
                SKILL, font_size=11)

# Arrow ↓ from Knowledge Base to Skill Graduation
add_arrow(slide, COL_L + BOX_W / 2, ROW2 + BOX_H,
          COL_L + BOX_W / 2, ROW3, color=KNOWLEDGE)
add_text(slide, COL_L + BOX_W / 2 + Inches(0.15), ROW2 + BOX_H + Inches(0.15),
         Inches(1.8), Inches(0.4),
         "enough evidence\naccumulated", font_size=8, font_color=KNOWLEDGE)

# Arrow → from Skill Graduation to Domain Skill
add_arrow(slide, COL_L + BOX_W, ROW3 + BOX_H / 2,
          COL_R, ROW3 + BOX_H / 2)

# Arrow ↑ from Domain Skill back to Analysis (the big return loop)
# Go: right edge of Domain Skill → up along right margin → top → left to Analysis
margin_x = COL_R + BOX_W + Inches(0.4)

# Segment 1: right from Domain Skill
add_arrow(slide, COL_R + BOX_W, ROW3 + BOX_H / 2,
          margin_x, ROW3 + BOX_H / 2, color=SKILL, width=Pt(2.5))
# Segment 2: up along right margin (no arrowhead — use plain line)
seg2 = slide.shapes.add_connector(
    1, int(margin_x), int(ROW3 + BOX_H / 2), int(margin_x), int(ROW1 + BOX_H / 2),
)
seg2.line.color.rgb = SKILL
seg2.line.width = Pt(2.5)
# Segment 3: left to Analysis with arrowhead
add_arrow(slide, margin_x, ROW1 + BOX_H / 2,
          COL_L + BOX_W, ROW1 + BOX_H / 2, color=SKILL, width=Pt(2.5))

# Label on the right margin arrow
add_text(slide, margin_x + Inches(0.1), ROW2 + Inches(0.1),
         Inches(2.0), Inches(0.8),
         "MANDATORY skill rules\ninjected at every stage\n(reduces need for annealing)",
         font_size=9, font_color=SKILL, bold=False, italic=True,
         align=PP_ALIGN.LEFT)

# update_skill note
add_text(slide, COL_M, ROW3 + BOX_H + Inches(0.05), Inches(3.5), Inches(0.3),
         "update_skill() when new evidence arrives",
         font_size=8, font_color=GRAY)

# ══════════════════════════════════════════════════════════
#  Save
# ══════════════════════════════════════════════════════════
pptx_path = "docs/learning_loop.pptx"
prs.save(pptx_path)
print(f"Saved {pptx_path}")

# Export to PNG via soffice if available
import subprocess, shutil
if shutil.which("soffice"):
    subprocess.run([
        "soffice", "--headless", "--convert-to", "png",
        "--outdir", "docs", pptx_path,
    ], capture_output=True)
    print("Saved docs/learning_loop.png")
else:
    print("soffice not found — open the .pptx to view or install LibreOffice for PNG export")
