"""Generate agent architecture diagrams as a 3-slide PowerPoint deck."""

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

# ── Colors ──
BLUE = RGBColor(0x2B, 0x6C, 0xB0)
BLUE_LIGHT = RGBColor(0xD6, 0xE8, 0xF7)
GREEN = RGBColor(0x27, 0x8B, 0x4C)
GREEN_LIGHT = RGBColor(0xD4, 0xED, 0xDA)
ORANGE = RGBColor(0xE8, 0x6C, 0x00)
ORANGE_LIGHT = RGBColor(0xFF, 0xE8, 0xCC)
PURPLE = RGBColor(0x6F, 0x42, 0xC1)
PURPLE_LIGHT = RGBColor(0xE8, 0xDE, 0xF8)
RED = RGBColor(0xDC, 0x35, 0x45)
RED_LIGHT = RGBColor(0xF8, 0xD7, 0xDA)
TEAL = RGBColor(0x0D, 0x6E, 0x6E)
TEAL_LIGHT = RGBColor(0xCC, 0xF0, 0xF0)
GRAY = RGBColor(0x6C, 0x75, 0x7D)
GRAY_LIGHT = RGBColor(0xE9, 0xEC, 0xEF)
DARK = RGBColor(0x21, 0x25, 0x29)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
BG = RGBColor(0xF8, 0xF9, 0xFA)


def _emu(val):
    return int(val) if not isinstance(val, int) else val


def add_box(slide, left, top, width, height, fill_color, border_color,
            text="", font_size=11, font_color=DARK, bold=False,
            align=PP_ALIGN.CENTER, first_line_color=None, uniform=False):
    shape = slide.shapes.add_shape(
        MSO_SHAPE.ROUNDED_RECTANGLE, _emu(left), _emu(top), _emu(width), _emu(height)
    )
    shape.fill.solid()
    shape.fill.fore_color.rgb = fill_color
    shape.line.color.rgb = border_color
    shape.line.width = Pt(1.5)
    shape.shadow.inherit = False
    shape.adjustments[0] = 0.05

    tf = shape.text_frame
    tf.word_wrap = True
    tf.auto_size = None
    tf.margin_left = Pt(6)
    tf.margin_right = Pt(6)
    tf.margin_top = Pt(4)
    tf.margin_bottom = Pt(4)

    for i, line in enumerate(text.split("\n")):
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.alignment = align
        p.space_before = Pt(0)
        p.space_after = Pt(0)
        run = p.add_run()
        run.text = line
        run.font.size = Pt(font_size)
        run.font.color.rgb = font_color
        if i == 0 and bold:
            run.font.bold = True
        if i == 0 and first_line_color:
            run.font.color.rgb = first_line_color
        elif i > 0 and not uniform:
            run.font.size = Pt(font_size - 1)
            if not first_line_color:
                run.font.color.rgb = GRAY
    return shape


def add_arrow(slide, x1, y1, x2, y2, color=GRAY, width=Pt(2)):
    x1, y1, x2, y2 = _emu(x1), _emu(y1), _emu(x2), _emu(y2)
    left, top = min(x1, x2), min(y1, y2)
    cx = abs(x2 - x1) or 1
    cy = abs(y2 - y1) or 1
    flipH = "1" if x2 < x1 else "0"
    flipV = "1" if y2 < y1 else "0"
    xml = f'''<p:cxnSp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
                        xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
                        xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
      <p:nvCxnSpPr><p:cNvPr id="0" name="C"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
      <p:spPr>
        <a:xfrm flipH="{flipH}" flipV="{flipV}">
          <a:off x="{left}" y="{top}"/><a:ext cx="{cx}" cy="{cy}"/>
        </a:xfrm>
        <a:prstGeom prst="straightConnector1"/>
        <a:ln w="{int(width)}">
          <a:solidFill><a:srgbClr val="{color}"/></a:solidFill>
          <a:tailEnd type="triangle" w="med" len="med"/>
        </a:ln>
      </p:spPr>
    </p:cxnSp>'''
    slide.shapes._spTree.append(parse_xml(xml))


def add_line(slide, x1, y1, x2, y2, color=GRAY, width=Pt(2)):
    x1, y1, x2, y2 = _emu(x1), _emu(y1), _emu(x2), _emu(y2)
    left, top = min(x1, x2), min(y1, y2)
    cx = abs(x2 - x1) or 1
    cy = abs(y2 - y1) or 1
    flipH = "1" if x2 < x1 else "0"
    flipV = "1" if y2 < y1 else "0"
    xml = f'''<p:cxnSp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
                        xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
                        xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
      <p:nvCxnSpPr><p:cNvPr id="0" name="L"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
      <p:spPr>
        <a:xfrm flipH="{flipH}" flipV="{flipV}">
          <a:off x="{left}" y="{top}"/><a:ext cx="{cx}" cy="{cy}"/>
        </a:xfrm>
        <a:prstGeom prst="straightConnector1"/>
        <a:ln w="{int(width)}">
          <a:solidFill><a:srgbClr val="{color}"/></a:solidFill>
        </a:ln>
      </p:spPr>
    </p:cxnSp>'''
    slide.shapes._spTree.append(parse_xml(xml))


def add_label(slide, left, top, width, height, text, font_size=10,
              font_color=GRAY, bold=False, align=PP_ALIGN.LEFT):
    txBox = slide.shapes.add_textbox(_emu(left), _emu(top), _emu(width), _emu(height))
    tf = txBox.text_frame
    tf.word_wrap = True
    p = tf.paragraphs[0]
    p.alignment = align
    p.space_before = Pt(0)
    p.space_after = Pt(0)
    run = p.add_run()
    run.text = text
    run.font.size = Pt(font_size)
    run.font.color.rgb = font_color
    run.font.bold = bold
    return txBox


def add_multiline_label(slide, left, top, width, height, lines,
                        font_size=10, align=PP_ALIGN.LEFT):
    """lines: list of (text, color, bold) tuples."""
    txBox = slide.shapes.add_textbox(_emu(left), _emu(top), _emu(width), _emu(height))
    tf = txBox.text_frame
    tf.word_wrap = True
    for i, (text, color, bold) in enumerate(lines):
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.alignment = align
        p.space_before = Pt(1)
        p.space_after = Pt(1)
        run = p.add_run()
        run.text = text
        run.font.size = Pt(font_size)
        run.font.color.rgb = color
        run.font.bold = bold
    return txBox


def new_slide(prs, title, subtitle=""):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    bg = slide.background.fill
    bg.solid()
    bg.fore_color.rgb = BG
    add_label(slide, Inches(0.5), Inches(0.2), Inches(14), Inches(0.6),
              title, font_size=22, font_color=DARK, bold=True, align=PP_ALIGN.LEFT)
    if subtitle:
        add_label(slide, Inches(0.5), Inches(0.62), Inches(14), Inches(0.35),
                  subtitle, font_size=12, font_color=GRAY, align=PP_ALIGN.LEFT)
    return slide


# ═══════════════════════════════════════════════════════════════════════
prs = Presentation()
prs.slide_width = Inches(16)
prs.slide_height = Inches(9)

# ═══════════════════════════════════════════════════════════════════════
# SLIDE 1: Pipeline Architecture
# ═══════════════════════════════════════════════════════════════════════
slide = new_slide(prs,
    "LLM-Powered Scientific Analysis Agent \u2014 Architecture",
    "How data flows from raw measurement to structured scientific results")

# -- Layout: three columns --
col_human = Inches(0.5)
col_pipe  = Inches(4.8)
col_know  = Inches(10.8)

box_w   = Inches(5.0)
box_h   = Inches(0.88)
skill_w = Inches(4.2)
skill_h = Inches(0.75)
human_w = Inches(3.0)
human_h = Inches(0.7)
gap     = Inches(0.12)

# -- Column headers --
add_label(slide, col_human, Inches(1.0), human_w, Inches(0.35),
          "SCIENTIST", font_size=15, font_color=PURPLE, bold=True, align=PP_ALIGN.CENTER)
add_label(slide, col_pipe, Inches(1.0), box_w, Inches(0.35),
          "AGENT PIPELINE", font_size=15, font_color=BLUE, bold=True, align=PP_ALIGN.CENTER)
add_label(slide, col_know, Inches(1.0), skill_w, Inches(0.35),
          "KNOWLEDGE", font_size=15, font_color=GREEN, bold=True, align=PP_ALIGN.CENTER)

# -- Pipeline stages --
y_start = Inches(1.5)
stages = [
    ("1. OBSERVE",    "Load data, compute statistics, visualize"),
    ("2. PLAN",       "LLM examines data and proposes strategy"),
    ("3. RESEARCH",   "Query literature databases (optional)"),
    ("4. EXECUTE",    "LLM generates analysis script, runs in sandbox"),
    ("5. VALIDATE",   "Metric \u2192 LLM review \u2192 LLM judge \u2192 human"),
    ("6. INTERPRET",  "LLM synthesizes findings, generate scientific claims"),
    ("7. REPORT",     "Save results, scripts, plots, HTML report"),
]

stage_y = []
for i, (title, desc) in enumerate(stages):
    y = y_start + i * (box_h + gap)
    add_box(slide, col_pipe, y, box_w, box_h, BLUE_LIGHT, BLUE,
            text=f"{title}\n{desc}", font_size=13, bold=True)
    stage_y.append(y)

# Vertical arrows between stages
for i in range(len(stages) - 1):
    x_mid = col_pipe + box_w / 2
    add_arrow(slide, x_mid, stage_y[i] + box_h, x_mid, stage_y[i + 1], BLUE, Pt(2))

# -- Skills (right column) --
skill_links = [
    (1, "SKILL (LONG-TERM MEMORY)\nPlanning rules +\nValidation criteria"),
    (3, "SKILL (LONG-TERM MEMORY)\nFitting templates\n(code snippets, constraints)"),
    (5, "SKILL (LONG-TERM MEMORY)\nInterpretation tables\n(reference values, assignments)"),
]

for stage_idx, skill_text in skill_links:
    sy = stage_y[stage_idx] + (box_h - skill_h) / 2
    add_box(slide, col_know, sy, skill_w, skill_h, GREEN_LIGHT, GREEN,
            text=skill_text, font_size=12, bold=True, first_line_color=GREEN)
    arrow_y = stage_y[stage_idx] + box_h / 2
    add_arrow(slide, col_know, arrow_y, col_pipe + box_w, arrow_y, GREEN, Pt(1.5))

# Label between skill boxes
add_label(slide, col_know, stage_y[2] + Inches(0.1), skill_w, Inches(0.3),
          "Long-term memory \u2014 persists across sessions",
          font_size=11, font_color=GREEN, align=PP_ALIGN.CENTER)

# -- Short-term memory (right of VALIDATE) --
add_box(slide, col_know, stage_y[4] + (box_h - skill_h) / 2, skill_w, skill_h,
        ORANGE_LIGHT, ORANGE,
        text="SHORT-TERM MEMORY\nVerification history: what was tried, why it failed",
        font_size=12, font_color=DARK, bold=True)

arrow_y_val = stage_y[4] + box_h / 2
add_arrow(slide, col_know, arrow_y_val - Inches(0.07),
          col_pipe + box_w, arrow_y_val - Inches(0.07), ORANGE, Pt(1.5))
add_arrow(slide, col_pipe + box_w, arrow_y_val + Inches(0.07),
          col_know, arrow_y_val + Inches(0.07), ORANGE, Pt(1.5))

# -- Graduation arrow (short-term -> long-term) --
grad_x = col_know + skill_w + Inches(0.2)
stm_mid_y = stage_y[4] + box_h / 2
fit_mid_y = stage_y[3] + box_h / 2

add_line(slide,  col_know + skill_w, stm_mid_y, grad_x, stm_mid_y, GRAY, Pt(1.5))
add_line(slide,  grad_x, stm_mid_y, grad_x, fit_mid_y, GRAY, Pt(1.5))
add_arrow(slide, grad_x, fit_mid_y, col_know + skill_w, fit_mid_y, GRAY, Pt(1.5))

add_label(slide, grad_x + Inches(0.05), (fit_mid_y + stm_mid_y) / 2 - Inches(0.2),
          Inches(1.2), Inches(0.5),
          "Recurring\npatterns\ngraduate to skills",
          font_size=10, font_color=GRAY, bold=True, align=PP_ALIGN.CENTER)

# -- Retry loops (left of pipeline, between scientist and pipeline) --
val_mid  = stage_y[4] + box_h / 2
exec_mid = stage_y[3] + box_h / 2
plan_mid = stage_y[1] + box_h / 2

# Inner retry: VALIDATE -> EXECUTE (refine fit, orange)
retry1_x = col_pipe - Inches(0.45)
add_line(slide,  col_pipe, val_mid - Inches(0.08), retry1_x, val_mid - Inches(0.08), ORANGE, Pt(2))
add_line(slide,  retry1_x, val_mid - Inches(0.08), retry1_x, exec_mid, ORANGE, Pt(2))
add_arrow(slide, retry1_x, exec_mid, col_pipe, exec_mid, ORANGE, Pt(2))

add_label(slide, retry1_x - Inches(0.65), (val_mid + exec_mid) / 2 - Inches(0.2),
          Inches(0.8), Inches(0.4),
          "Refine\nfit", font_size=11, font_color=ORANGE, bold=True, align=PP_ALIGN.CENTER)

# Outer retry: VALIDATE -> PLAN (revise model, same orange)
retry2_x = col_pipe - Inches(1.0)
add_line(slide,  col_pipe, val_mid + Inches(0.08), retry1_x, val_mid + Inches(0.08), ORANGE, Pt(2))
add_line(slide,  retry1_x, val_mid + Inches(0.08), retry2_x, val_mid + Inches(0.08), ORANGE, Pt(2))
add_line(slide,  retry2_x, val_mid + Inches(0.08), retry2_x, plan_mid, ORANGE, Pt(2))
add_arrow(slide, retry2_x, plan_mid, col_pipe, plan_mid, ORANGE, Pt(2))

add_label(slide, retry2_x - Inches(0.8), (val_mid + exec_mid) / 2 + Inches(0.1) - box_h,
          Inches(0.8), Inches(0.4),
          "Revise\nmodel", font_size=11, font_color=ORANGE, bold=True, align=PP_ALIGN.CENTER)

# -- Human interaction (left column) --
human_links = [
    (0, "Provides data, metadata,\nhints, skill selection",    False),
    (1, "Reviews and refines\nanalysis plan",                   False),
    (4, "Reviews results if automated\nvalidation cannot converge", False),
    (6, "Receives results, report,\ngenerated scripts, plots", True),
]

for stage_idx, text, is_output in human_links:
    hy = stage_y[stage_idx] + (box_h - human_h) / 2
    add_box(slide, col_human, hy, human_w, human_h, PURPLE_LIGHT, PURPLE,
            text=text, font_size=12, font_color=DARK, uniform=True)
    arrow_y = stage_y[stage_idx] + box_h / 2
    if is_output:
        add_arrow(slide, col_pipe, arrow_y, col_human + human_w, arrow_y, PURPLE, Pt(1.5))
    else:
        add_arrow(slide, col_human + human_w, arrow_y, col_pipe, arrow_y, PURPLE, Pt(1.5))


# ═══════════════════════════════════════════════════════════════════════
# SLIDE 2: Memory Model
# ═══════════════════════════════════════════════════════════════════════
slide = new_slide(prs,
    "Memory Model \u2014 Long-Term vs Short-Term Knowledge",
    "How domain expertise and per-analysis experience work together")

# ── Layout ──
panel_y = Inches(1.3)
panel_h = Inches(5.8)
gap_between = Inches(0.6)
ltm_x = Inches(0.8)
ltm_w = Inches(7.0)
stm_x = ltm_x + ltm_w + gap_between
stm_w = Inches(7.0)

# ── Left panel: Long-term memory ──
add_box(slide, ltm_x, panel_y, ltm_w, panel_h, GREEN_LIGHT, GREEN,
        text="", font_size=1)

add_label(slide, ltm_x + Inches(0.3), panel_y + Inches(0.15), Inches(6), Inches(0.45),
          "LONG-TERM MEMORY (Skills)", font_size=18, font_color=GREEN, bold=True)
add_label(slide, ltm_x + Inches(0.3), panel_y + Inches(0.55), Inches(6), Inches(0.35),
          "Persists across sessions \u2022 Written by domain experts \u2022 Shared across users",
          font_size=12, font_color=DARK)

# Skill file header
file_x = ltm_x + Inches(0.4)
file_y = panel_y + Inches(1.2)
file_w = Inches(6.2)

add_box(slide, file_x, file_y, file_w, Inches(0.55), WHITE, GREEN,
        text="xps.md  (or xrd.md, dsc.md, raman.md, ...)",
        font_size=13, font_color=GREEN, bold=True)

# Skill sections — concise, larger font
sections = [
    ("Planning", "Use Shirley background, Voigt profiles,\nspin-orbit ratio 2:1 for 2p levels"),
    ("Fitting", "Code snippets for Shirley implementation,\ndoublet constraints, lmfit patterns"),
    ("Interpretation", "Binding energy reference tables,\ncharge referencing conventions"),
    ("Validation", "FWHM 0.8\u20133.0 eV, R\u00b2 > 0.99,\ncheck for differential charging"),
]

sec_h = Inches(0.85)
sec_gap = Inches(0.12)
for i, (heading, content) in enumerate(sections):
    sy = file_y + Inches(0.65) + i * (sec_h + sec_gap)
    add_box(slide, file_x + Inches(0.15), sy, file_w - Inches(0.3), sec_h,
            WHITE, GREEN,
            text=f"{heading}\n{content}",
            font_size=12, font_color=DARK, bold=True, align=PP_ALIGN.LEFT)

# ── Right panel: Short-term memory ──
add_box(slide, stm_x, panel_y, stm_w, panel_h, ORANGE_LIGHT, ORANGE,
        text="", font_size=1)

add_label(slide, stm_x + Inches(0.3), panel_y + Inches(0.15), Inches(6), Inches(0.45),
          "SHORT-TERM MEMORY (History)", font_size=18, font_color=ORANGE, bold=True)
add_label(slide, stm_x + Inches(0.3), panel_y + Inches(0.55), Inches(6), Inches(0.35),
          "Lives within one pipeline run \u2022 Accumulated by the agent",
          font_size=12, font_color=DARK)

# Attempt boxes — larger, cleaner
attempt_x = stm_x + Inches(0.4)
attempt_w = Inches(6.2)
attempt_h = Inches(1.2)
attempt_gap = Inches(0.18)
attempts = [
    ("Attempt 1: Voigt + Shirley", "R\u00b2 = 0.86 \u2014 systematic residuals at peaks\nDiagnosis: symmetric Voigt can't capture asymmetry",
     GRAY_LIGHT, GRAY, "\u2717", RED),
    ("Attempt 2: Asymmetric Voigt", "R\u00b2 = 0.99 \u2014 but asymmetry collapsed to 0\nDiagnosis: convergence failure, not physical",
     GRAY_LIGHT, GRAY, "\u2717", RED),
    ("Attempt 3: Doniach-Sunjic + Shirley", "R\u00b2 = 0.99 \u2014 residuals acceptable\nTi4+ at 458.8 eV, Ti3+ at 457.0 eV \u2014 accepted",
     WHITE, GREEN, "\u2713", GREEN),
]

for i, (title, content, bg_color, border, marker, marker_color) in enumerate(attempts):
    ay = panel_y + Inches(1.2) + i * (attempt_h + attempt_gap)
    add_box(slide, attempt_x, ay, attempt_w, attempt_h, bg_color, border,
            text=f"{title}\n{content}",
            font_size=12, font_color=DARK, bold=True, align=PP_ALIGN.LEFT)
    add_label(slide, attempt_x + attempt_w - Inches(0.55), ay + Inches(0.05),
              Inches(0.45), Inches(0.4),
              marker, font_size=22, font_color=marker_color, bold=True, align=PP_ALIGN.CENTER)

# Arrows between attempts
for i in range(2):
    ay_from = panel_y + Inches(1.2) + i * (attempt_h + attempt_gap) + attempt_h
    ay_to = panel_y + Inches(1.2) + (i + 1) * (attempt_h + attempt_gap)
    add_arrow(slide, attempt_x + attempt_w / 2, ay_from,
              attempt_x + attempt_w / 2, ay_to, ORANGE, Pt(2))

# Key insight
insight_y = panel_y + Inches(1.2) + 3 * (attempt_h + attempt_gap) - Inches(0.05)
add_label(slide, attempt_x, insight_y, attempt_w, Inches(0.4),
          "Each attempt sees full history \u2192 no circular failures",
          font_size=12, font_color=ORANGE, bold=True, align=PP_ALIGN.CENTER)

# ── Graduation arrow (bottom, connecting both panels) ──
grad_y = panel_y + panel_h + Inches(0.4)
arrow_start_x = stm_x + stm_w / 2
arrow_end_x = ltm_x + ltm_w / 2

add_arrow(slide, arrow_start_x, panel_y + panel_h,
          arrow_start_x, grad_y, GRAY, Pt(2))
add_line(slide, arrow_start_x, grad_y, arrow_end_x, grad_y, GRAY, Pt(2))
add_arrow(slide, arrow_end_x, grad_y,
          arrow_end_x, panel_y + panel_h, GRAY, Pt(2))

add_label(slide, arrow_end_x + Inches(0.5), grad_y - Inches(0.25),
          Inches(5.0), Inches(0.5),
          "Recurring short-term patterns graduate into long-term skills\n"
          "e.g., Doniach-Sunjic consistently beats Voigt for metallic states \u2192 added to xps.md",
          font_size=11, font_color=DARK, bold=True, align=PP_ALIGN.CENTER)


# ═══════════════════════════════════════════════════════════════════════
# SLIDE 3: Series Processing
# ═══════════════════════════════════════════════════════════════════════
slide = new_slide(prs,
    "Series Processing \u2014 Locked Configuration with Outlier Detection",
    "Careful first, consistent after \u2014 deviations signal interesting physics")

# ── First spectrum (full planning) ──
spec1_x = Inches(5.5)
spec1_y = Inches(1.4)
spec1_w = Inches(5.0)
spec1_h = Inches(1.8)

add_box(slide, spec1_x, spec1_y, spec1_w, spec1_h, BLUE_LIGHT, BLUE,
        text="SPECTRUM 1 \u2014 Full Analysis\nComplete LLM planning + quality control\nHuman reviews and approves fitting strategy\nAll verification layers active",
        font_size=12, font_color=DARK, bold=True)

# Magnifying glass label
add_label(slide, spec1_x - Inches(2.2), spec1_y + Inches(0.3), Inches(2.0), Inches(1.2),
          "Full pipeline:\nObserve \u2192 Plan \u2192\nExecute \u2192 Validate \u2192\nInterpret",
          font_size=11, font_color=BLUE, bold=True, align=PP_ALIGN.CENTER)
add_arrow(slide, spec1_x - Inches(0.3), spec1_y + spec1_h / 2,
          spec1_x, spec1_y + spec1_h / 2, BLUE, Pt(1.5))

# Arrow down to locked config
lock_y = spec1_y + spec1_h + Inches(0.3)
lock_h = Inches(1.3)
add_arrow(slide, spec1_x + spec1_w / 2, spec1_y + spec1_h,
          spec1_x + spec1_w / 2, lock_y, DARK, Pt(2.5))

# Locked config box
add_box(slide, spec1_x, lock_y, spec1_w, lock_h, GRAY_LIGHT, DARK,
        text="LOCKED CONFIGURATION\nModel type, background method, line shapes,\nconstraints, initial guesses \u2014 frozen after first spectrum",
        font_size=12, font_color=DARK, bold=True)

# Lock icon label
add_label(slide, spec1_x + spec1_w + Inches(0.3), lock_y + Inches(0.2),
          Inches(3.5), Inches(0.9),
          "Why lock?\n\u2022 Consistency across all spectra\n\u2022 Trends become meaningful\n\u2022 Deviations = real physics, not modeling differences",
          font_size=10, font_color=DARK, align=PP_ALIGN.LEFT)

# Fan-out arrows to series
series_y = lock_y + lock_h + Inches(0.5)
n_spectra = 6
spec_w = Inches(2.0)
spec_h = Inches(1.3)
series_gap = Inches(0.35)
total_series_w = n_spectra * spec_w + (n_spectra - 1) * series_gap
series_start_x = Inches(8.0) - total_series_w / 2

# Fan-out arrows from locked config
lock_center_x = spec1_x + spec1_w / 2
for i in range(n_spectra):
    sx = series_start_x + i * (spec_w + series_gap)
    target_x = sx + spec_w / 2
    add_arrow(slide, lock_center_x, lock_y + lock_h,
              target_x, series_y, DARK, Pt(1.5))

# Series spectrum boxes
spectra_data = [
    ("Spectrum 2", "R\u00b2 = 0.98", True, BLUE_LIGHT, BLUE),
    ("Spectrum 3", "R\u00b2 = 0.97", True, BLUE_LIGHT, BLUE),
    ("Spectrum 4", "R\u00b2 = 0.99", True, BLUE_LIGHT, BLUE),
    ("Spectrum 5", "R\u00b2 = 0.81", False, RED_LIGHT, RED),     # outlier
    ("Spectrum 6", "R\u00b2 = 0.96", True, BLUE_LIGHT, BLUE),
    ("\u00b7 \u00b7 \u00b7", "Spectrum N", True, GRAY_LIGHT, GRAY),
]

for i, (name, quality, ok, bg_color, border) in enumerate(spectra_data):
    sx = series_start_x + i * (spec_w + series_gap)
    marker = "\u2713" if ok and i < 5 else ("\u26a0" if not ok else "")
    add_box(slide, sx, series_y, spec_w, spec_h, bg_color, border,
            text=f"{name}\n{quality}\n{marker}",
            font_size=11, font_color=DARK, bold=True)

# Outlier callout (from spectrum 5)
outlier_idx = 3
outlier_x = series_start_x + outlier_idx * (spec_w + series_gap) + spec_w / 2
outlier_bottom = series_y + spec_h

callout_y = outlier_bottom + Inches(0.4)
callout_w = Inches(4.0)
callout_h = Inches(1.5)
callout_x = outlier_x - callout_w / 2

add_arrow(slide, outlier_x, outlier_bottom, outlier_x, callout_y, RED, Pt(2))

add_box(slide, callout_x, callout_y, callout_w, callout_h, RED_LIGHT, RED,
        text="FLAGGED OUTLIER\nR\u00b2 significantly below series median\nMay indicate interesting physics:\n\u2022 Phase transition at this temperature\n\u2022 Structural change in the sample\n\u2022 New chemical state appearing",
        font_size=11, font_color=DARK, bold=True, align=PP_ALIGN.LEFT)

# "Not an error" emphasis
add_label(slide, callout_x, callout_y + callout_h + Inches(0.05), callout_w, Inches(0.3),
          "Outliers are flagged for analysis, NOT discarded \u2014 they're often the most interesting data",
          font_size=10, font_color=RED, bold=True, align=PP_ALIGN.CENTER)

# Trend analysis label (bottom right)
trend_x = series_start_x + 4 * (spec_w + series_gap)
trend_y = series_y + spec_h + Inches(0.3)
add_box(slide, trend_x, trend_y, Inches(4.5), Inches(1.1), TEAL_LIGHT, TEAL,
        text="TREND ANALYSIS\nParameter evolution across series\n(e.g., peak position vs temperature)\nFlagged spectra highlighted in plots",
        font_size=11, font_color=DARK, bold=True)
add_arrow(slide, trend_x + Inches(2.25), series_y + spec_h,
          trend_x + Inches(2.25), trend_y, TEAL, Pt(1.5))


# ═══════════════════════════════════════════════════════════════════════
# Save
# ═══════════════════════════════════════════════════════════════════════
out_path = "docs/agent_architecture_flowchart.pptx"
prs.save(out_path)
print(f"Saved: {out_path} ({len(prs.slides)} slides)")
