#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Harness 소개 덱 생성 — python-pptx. (스크립트가 진실원천: 내용 수정 후 재생성)
실행:  python docs/gen_intro_deck.py   →  docs/Harness_소개덱_v1.pptx
"""
import os
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE

# ── 테마(하네스 브랜드: 딥 인디고 + 퍼플/시안 액센트) ─────────────────
INK    = RGBColor(0x0F, 0x14, 0x2E)   # 딥 인디고(배경/제목)
INDIGO = RGBColor(0x31, 0x2E, 0x81)   # 인디고
PURPLE = RGBColor(0x7C, 0x3A, 0xED)   # 퍼플 액센트
CYAN   = RGBColor(0x06, 0xB6, 0xD4)   # 시안 액센트
AMBER  = RGBColor(0xF5, 0x9E, 0x0B)   # 앰버 포인트
LIGHT  = RGBColor(0xEE, 0xEC, 0xFB)   # 연보라 배경
CARD   = RGBColor(0xF6, 0xF7, 0xFB)
GRAY   = RGBColor(0x55, 0x5A, 0x6B)
WHITE  = RGBColor(0xFF, 0xFF, 0xFF)
FONT   = "맑은 고딕"

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
BANNER = os.path.join(ROOT, "harness_banner.png")
ICON   = os.path.join(ROOT, "harness_icon.png")

prs = Presentation()
prs.slide_width  = Inches(13.333)
prs.slide_height = Inches(7.5)
SW, SH = prs.slide_width, prs.slide_height
BLANK = prs.slide_layouts[6]


def _set(run, size, bold=False, color=INK, font=FONT):
    run.font.size = Pt(size); run.font.bold = bold
    run.font.color.rgb = color; run.font.name = font


def box(slide, x, y, w, h, fill=None, line=None, line_w=1.0, round_=False):
    shp = slide.shapes.add_shape(
        MSO_SHAPE.ROUNDED_RECTANGLE if round_ else MSO_SHAPE.RECTANGLE,
        Inches(x), Inches(y), Inches(w), Inches(h))
    shp.shadow.inherit = False
    if fill is None: shp.fill.background()
    else: shp.fill.solid(); shp.fill.fore_color.rgb = fill
    if line is None: shp.line.fill.background()
    else: shp.line.color.rgb = line; shp.line.width = Pt(line_w)
    return shp


def text(slide, x, y, w, h, runs, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP,
         space=4, line_spacing=None):
    """runs: list of paragraphs; each para = list of (txt, size, bold, color)."""
    tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
    tf = tb.text_frame; tf.word_wrap = True; tf.vertical_anchor = anchor
    tf.margin_left = tf.margin_right = Inches(0.05)
    tf.margin_top = tf.margin_bottom = Inches(0.02)
    for i, para in enumerate(runs):
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.alignment = align; p.space_after = Pt(space)
        if line_spacing: p.line_spacing = line_spacing
        for spec in para:
            txt, size, bold, color = spec[0], spec[1], spec[2], spec[3]
            fnt = spec[4] if len(spec) > 4 else FONT
            r = p.add_run(); r.text = txt; _set(r, size, bold, color, fnt)
    return tb


def header(slide, title, kicker=None, n=None):
    """상단 헤더 바 + 제목."""
    box(slide, 0, 0, 13.333, 1.15, fill=INK)
    box(slide, 0, 1.15, 13.333, 0.06, fill=PURPLE)
    runs = []
    if kicker:
        runs.append([(kicker, 12, True, CYAN)])
    runs.append([(title, 26, True, WHITE)])
    text(slide, 0.6, 0.12, 11.5, 0.95, runs, anchor=MSO_ANCHOR.MIDDLE, space=2)
    if n is not None:
        text(slide, 12.2, 0.12, 0.9, 0.95, [[(f"{n:02d}", 22, True, AMBER)]],
             align=PP_ALIGN.RIGHT, anchor=MSO_ANCHOR.MIDDLE)


def footer(slide):
    text(slide, 0.6, 7.05, 8, 0.35, [[("Harness — Claude Code Team-Architecture Factory", 9, False, GRAY)]])
    text(slide, 11.0, 7.05, 1.9, 0.35, [[("v1.3.1", 9, False, GRAY)]], align=PP_ALIGN.RIGHT)


def card(slide, x, y, w, h, title, lines, accent=PURPLE, tsize=15):
    box(slide, x, y, w, h, fill=CARD, line=RGBColor(0xE2,0xE3,0xEE), line_w=0.75, round_=True)
    box(slide, x, y, 0.12, h, fill=accent)
    runs = [[(title, tsize, True, INK)]]
    for ln in lines:
        runs.append([("· ", 11, True, accent), (ln, 11.5, False, GRAY)])
    text(slide, x+0.3, y+0.22, w-0.45, h-0.4, runs, space=4)


# ── 슬라이드 1: 타이틀 ───────────────────────────────────────────────
s = prs.slides.add_slide(BLANK)
box(s, 0, 0, 13.333, 7.5, fill=INK)
box(s, 0, 0, 0.25, 7.5, fill=PURPLE)
box(s, 0.25, 0, 0.1, 7.5, fill=CYAN)
if os.path.exists(BANNER):
    try: s.shapes.add_picture(BANNER, Inches(8.0), Inches(0.7), height=Inches(2.2))
    except Exception: pass
text(s, 0.9, 2.0, 9.5, 2.4, [
    [("Harness", 60, True, WHITE)],
    [("Claude Code를 위한 팀 아키텍처 팩토리", 22, True, CYAN)],
], space=10)
text(s, 0.95, 4.5, 11, 1.6, [
    [("도메인 한 문장을 ", 16, False, LIGHT), ("에이전트 팀 + 스킬", 16, True, AMBER),
     ("로 변환하는 메타 스킬", 16, False, LIGHT)],
    [("\"이 프로젝트용 하네스 구성해줘\"  →  전문 에이전트 팀이 자동 생성·협업", 13, False, RGBColor(0xB9,0xBD,0xD6))],
], space=8)
text(s, 0.95, 6.6, 11, 0.5, [[("소개 덱 · 2026-06 · Apache-2.0", 11, False, RGBColor(0x8A,0x8F,0xB0))]])

# ── 슬라이드 2: 왜 필요한가 ──────────────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "왜 Harness인가?", "THE PROBLEM", 2)
text(s, 0.6, 1.5, 12.1, 1.0, [
    [("복잡한 작업일수록 ", 16, False, GRAY), ("여러 전문 에이전트의 협업", 16, True, INK),
     ("이 필요하지만 —", 16, False, GRAY)],
], space=4)
card(s, 0.6, 2.6, 3.95, 3.6, "매번 수작업", [
    "에이전트 역할을 매번 즉석에서 prompt에 작성",
    "다음 세션에서 재사용 불가",
    "팀 통신·협업 프로토콜이 매번 제각각",
], accent=AMBER)
card(s, 4.75, 2.6, 3.95, 3.6, "구성 일관성 부재", [
    "누가(에이전트) / 어떻게(스킬) 가 뒤섞임",
    "패턴 없이 임기응변 → 누락·중복",
    "품질이 작성자 역량에 좌우",
], accent=AMBER)
card(s, 8.9, 2.6, 3.85, 3.6, "Harness의 답", [
    "한 문장 → 에이전트 팀 + 스킬 자동 생성",
    "파일로 영속화 → 세션 간 재사용",
    "6가지 검증된 아키텍처 패턴 적용",
], accent=PURPLE)
footer(s)

# ── 슬라이드 3: Harness란 ────────────────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "Harness란 무엇인가", "THE FACTORY", 3)
text(s, 0.6, 1.5, 12.1, 1.4, [
    [("Harness는 ", 18, False, GRAY), ("메타 스킬(스킬을 만드는 스킬)", 18, True, PURPLE),
     ("입니다.", 18, False, GRAY)],
    [("도메인 설명을 입력하면 → 전문 에이전트 팀과 그들이 사용할 스킬 세트를 ",
      14, False, GRAY), ("코드(.md 파일)로 생성", 14, True, INK), ("합니다.", 14, False, GRAY)],
], space=8)
# 플로우 4단계
labels = [("도메인 한 문장", CYAN), ("팩토리(메타 스킬)", PURPLE),
          ("에이전트 팀 + 스킬", INDIGO), ("작업 자동 수행", AMBER)]
x = 0.6
for i, (lab, col) in enumerate(labels):
    box(s, x, 3.3, 2.7, 1.5, fill=col, round_=True)
    text(s, x+0.15, 3.3, 2.4, 1.5, [[(lab, 14, True, WHITE)]],
         align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
    if i < 3:
        text(s, x+2.75, 3.3, 0.5, 1.5, [[("→", 26, True, GRAY)]],
             align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
    x += 3.18
text(s, 0.6, 5.3, 12.1, 1.4, [
    [("핵심: ", 14, True, INK), ("에이전트(누가) 와 스킬(어떻게) 를 분리", 14, True, PURPLE),
     ("한다. 에이전트는 역할·협업을, 스킬은 방법·절차를 담는다.", 14, False, GRAY)],
    [("→ 다른 도메인에서도 스킬을 재사용하고, 에이전트만 갈아끼울 수 있다.", 13, False, GRAY)],
], space=6)
footer(s)

# ── 슬라이드 4: 핵심 가치 ────────────────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "핵심 가치 세 가지", "WHY IT WORKS", 4)
card(s, 0.6, 1.6, 3.95, 4.6, "1. 분리(Separation)", [
    "에이전트 = 누가 (역할·원칙·통신)",
    "스킬 = 어떻게 (절차·패턴·스크립트)",
    "오케스트레이터 = 언제·어떤 순서로",
    "→ 책임이 명확, 변경이 국소적",
], accent=CYAN, tsize=16)
card(s, 4.75, 1.6, 3.95, 4.6, "2. 재사용(Reuse)", [
    ".claude/agents·skills 파일로 영속",
    "세션이 바뀌어도 그대로 재사용",
    "스킬은 여러 에이전트가 공유 가능",
    "→ 빌드할수록 자산이 쌓인다",
], accent=PURPLE, tsize=16)
card(s, 8.9, 1.6, 3.85, 4.6, "3. 진화(Evolution)", [
    "고정물이 아니라 살아있는 시스템",
    "실행 후 피드백 → 에이전트·스킬 갱신",
    "CLAUDE.md 변경 이력으로 추적",
    "→ 퇴행 없이 점점 좋아진다",
], accent=AMBER, tsize=16)
footer(s)

# ── 슬라이드 5: 6가지 아키텍처 패턴 ──────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "6가지 팀 아키텍처 패턴", "PATTERNS", 5)
pats = [
    ("파이프라인", "순차 의존 작업 — A→B→C 단계별 전달"),
    ("팬아웃/팬인", "병렬 독립 작업 후 결과 통합"),
    ("전문가 풀", "상황별로 필요한 전문가만 선택 호출"),
    ("생성-검증", "생성 후 독립 에이전트가 품질 검수"),
    ("감독자", "중앙 에이전트가 상태 관리·동적 분배"),
    ("계층적 위임", "상위가 하위에 재귀적으로 위임"),
]
gx, gy = 0.6, 1.55
for i, (name, desc) in enumerate(pats):
    r, c = divmod(i, 3)
    x = gx + c*4.18; y = gy + r*2.65
    card(s, x, y, 3.95, 2.4, name, [desc], accent=[CYAN,PURPLE,INDIGO][c], tsize=16)
footer(s)

# ── 슬라이드 6: 7-Phase 워크플로우 ───────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "구성 워크플로우 (7 Phase)", "HOW", 6)
phases = [
    ("0", "현황 감사", "기존 에이전트·스킬·CLAUDE.md 확인, drift 감지"),
    ("1", "도메인 분석", "작업 유형·기술스택·데이터모델 파악"),
    ("2", "팀 아키텍처 설계", "실행 모드(팀/서브/하이브리드) + 패턴 선택"),
    ("3", "에이전트 정의", ".claude/agents/*.md (역할·협업·model:opus)"),
    ("4", "스킬 생성", ".claude/skills/*/SKILL.md (방법·references)"),
    ("5", "통합·오케스트레이션", "오케스트레이터 스킬 + CLAUDE.md 포인터"),
    ("6", "검증·테스트", "구조·트리거·드라이런 검증"),
]
y = 1.5
for num, title, desc in phases:
    box(s, 0.6, y, 0.62, 0.62, fill=PURPLE, round_=True)
    text(s, 0.6, y, 0.62, 0.62, [[(num, 18, True, WHITE)]], align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
    text(s, 1.45, y, 3.2, 0.62, [[(title, 15, True, INK)]], anchor=MSO_ANCHOR.MIDDLE)
    text(s, 4.7, y, 8.0, 0.62, [[(desc, 12.5, False, GRAY)]], anchor=MSO_ANCHOR.MIDDLE)
    y += 0.72
text(s, 0.6, 6.55, 12, 0.4, [[("이후 Phase 7(진화): 매 실행 후 피드백을 반영해 지속 갱신", 12, True, CYAN)]])
footer(s)

# ── 슬라이드 7: 설계가 먼저다 ────────────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "설계가 먼저다", "DESIGN FIRST", 7)
text(s, 0.6, 1.5, 12.1, 1.3, [
    [("플러그인은 하네스를 ", 16, False, GRAY), ("배포·증폭하는 그릇", 16, True, INK),
     ("일 뿐, 설계를 대신 잘해주지 않는다.", 16, False, GRAY)],
    [("허술한 설계는 ", 13.5, False, GRAY), ("잘 배포될수록 더 큰 부채", 13.5, True, AMBER),
     ("가 된다 — 결함이 여러 사람·세션·프로젝트로 그대로 퍼진다.", 13.5, False, GRAY)],
], space=6)
box(s, 0.6, 2.95, 5.85, 1.0, fill=CYAN, round_=True)
text(s, 0.8, 2.95, 5.5, 1.0, [[("① 제대로 설계하고 동작·검증한 뒤", 15, True, WHITE)]], anchor=MSO_ANCHOR.MIDDLE)
text(s, 6.5, 2.95, 0.6, 1.0, [[("→", 26, True, GRAY)]], align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
box(s, 7.2, 2.95, 5.55, 1.0, fill=PURPLE, round_=True)
text(s, 7.4, 2.95, 5.2, 1.0, [[("② 비로소 플러그인으로 묶는다", 15, True, WHITE)]], anchor=MSO_ANCHOR.MIDDLE)
text(s, 0.6, 4.2, 12, 0.4, [[("패키징 전 점검 — 좋은 설계의 4축", 13, True, INK)]])
axes = [("에이전트 분리", "누가? 역할 겹침·과부하 없게", CYAN),
        ("스킬 경계", "어떻게? 절차와 역할 분리", PURPLE),
        ("아키텍처 패턴", "6패턴 중 작업에 맞게", INDIGO),
        ("트리거 설계", "발동/비발동 정확히", AMBER)]
x = 0.6
for name, desc, col in axes:
    card(s, x, 4.65, 2.92, 1.8, name, [desc], accent=col, tsize=14)
    x += 3.07
footer(s)

# ── 슬라이드 8: 플러그인 생태계 ──────────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "플러그인 생태계", "ECOSYSTEM", 8)
text(s, 0.6, 1.45, 12, 0.7, [
    [("Harness는 ", 15, False, GRAY), ("Claude Code 플러그인 마켓플레이스", 15, True, INK),
     ("로 배포됩니다.", 15, False, GRAY)],
])
card(s, 0.6, 2.3, 6.0, 3.9, "harness (메타 스킬)", [
    "도메인 → 에이전트 팀 + 스킬 자동 생성",
    "6가지 아키텍처 패턴 내장",
    "설치: harness@ythong-harness",
    "용도: 새 하네스를 처음부터 설계",
], accent=PURPLE, tsize=17)
card(s, 6.8, 2.3, 5.95, 3.9, "zio-harness (도메인 플러그인)", [
    "React + Spring Boot + Mobile 풀스택",
    "orchestrator·analyst·bot·agent 팀",
    "PROJECT_MAP.md로 폴더구조 기억",
    "용도: 이미 완성된 풀스택 하네스 즉시 사용",
], accent=CYAN, tsize=17)
text(s, 0.6, 6.45, 12, 0.5, [
    [("직접 도메인 플러그인 작성 → ", 12.5, False, GRAY), ("docs/plugin-guide.md", 12.5, True, PURPLE),
     (" 참조", 12.5, False, GRAY)],
])
footer(s)

# ── 슬라이드 9: 사용법 ───────────────────────────────────────────────
s = prs.slides.add_slide(BLANK); header(s, "시작하기 — 3단계", "GET STARTED", 9)
steps = [
    ("1. 마켓플레이스 추가", "claude plugin marketplace add\n   https://git.zioinfo.co.kr/ythong/harness"),
    ("2. 설치 + 플래그", "claude plugin install harness@ythong-harness\nexport CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"),
    ("3. 한 문장으로 실행", 'claude "핀테크 리스크 평가 팀용 하네스 구성해줘"'),
]
y = 1.6
for title, code in steps:
    box(s, 0.6, y, 12.1, 1.45, fill=CARD, line=RGBColor(0xE2,0xE3,0xEE), line_w=0.75, round_=True)
    box(s, 0.6, y, 0.12, 1.45, fill=PURPLE)
    text(s, 0.95, y+0.18, 11.5, 0.5, [[(title, 15, True, INK)]])
    text(s, 0.95, y+0.68, 11.5, 0.7, [[(code, 13, False, INDIGO, "Consolas")]])
    y += 1.65
text(s, 0.6, 6.7, 12, 0.4, [[("요구사항: Claude Code v2.x 이상 · Agent Teams 실험 플래그", 11.5, False, GRAY)]])
footer(s)

# ── 슬라이드 9: 마무리 ───────────────────────────────────────────────
s = prs.slides.add_slide(BLANK)
box(s, 0, 0, 13.333, 7.5, fill=INK)
box(s, 0, 0, 13.333, 0.18, fill=PURPLE)
box(s, 0, 7.32, 13.333, 0.18, fill=CYAN)
if os.path.exists(ICON):
    try: s.shapes.add_picture(ICON, Inches(5.9), Inches(0.9), height=Inches(1.7))
    except Exception: pass
text(s, 0.9, 2.9, 11.5, 2.2, [
    [("한 문장으로, 팀을 짓는다.", 34, True, WHITE)],
    [("Harness — 도메인을 에이전트 팀과 스킬로.", 18, True, CYAN)],
], align=PP_ALIGN.CENTER, space=12)
text(s, 0.9, 5.4, 11.5, 1.2, [
    [("\"하네스 구성해줘\"  ·  \"build a harness for this project\"", 15, False, LIGHT)],
    [("git.zioinfo.co.kr/ythong/harness  ·  Apache-2.0", 12, False, RGBColor(0x9A,0x9F,0xC0))],
], align=PP_ALIGN.CENTER, space=8)

out = os.path.join(HERE, "Harness_소개덱_v1.pptx")
try:
    prs.save(out)
except PermissionError:
    out = os.path.join(HERE, "Harness_소개덱_v1.1.pptx")
    prs.save(out)
    print("(원본 파일이 열려 있어 v1.1로 저장)")
print("생성 완료:", out, "(", len(prs.slides._sldIdLst), "슬라이드 )")
