#!/usr/bin/env python3
"""Speculative Decoding animation for @fminxyz Series 9 Post 2."""

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.animation import FuncAnimation
import numpy as np

fig, ax = plt.subplots(figsize=(10, 10), facecolor='#0d1117')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.axis('off')

BG = '#0d1117'
BLUE = '#58a6ff'
GREEN = '#3fb950'
ORANGE = '#f78166'
PURPLE = '#bc8cff'
GRAY = '#8b949e'
WHITE = '#e6edf3'
YELLOW = '#e3b341'
RED = '#ff7b72'

def clear_ax():
    ax.clear()
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 10)
    ax.axis('off')
    ax.set_facecolor(BG)
    fig.patch.set_facecolor(BG)

def add_title(title, subtitle=None, y=9.3):
    ax.text(5, y, title, ha='center', va='center',
            fontsize=22, fontweight='bold', color=WHITE, fontfamily='monospace')
    if subtitle:
        ax.text(5, y - 0.65, subtitle, ha='center', va='center',
                fontsize=13, color=GRAY)

def draw_block(x, y, w, h, color, text=None, fontsize=11, alpha=1.0):
    rect = patches.FancyBboxPatch((x, y), w, h,
                                   boxstyle="round,pad=0.05",
                                   facecolor=color, edgecolor='none', alpha=alpha)
    ax.add_patch(rect)
    if text:
        ax.text(x + w/2, y + h/2, text, ha='center', va='center',
                fontsize=fontsize, color=WHITE, fontweight='bold')

total_frames = 150

def animate(frame):
    clear_ax()
    phase = frame / total_frames

    # Phase 1: Problem — autoregressive bottleneck (0-0.22)
    if phase < 0.22:
        t = phase / 0.22
        add_title('Speculative Decoding', 'Латентность авторегрессии — главная проблема', y=9.3)

        # Show sequential token generation
        ax.text(5, 8.3, 'Наивная генерация: 1 forward → 1 токен', ha='center',
                fontsize=13, color=ORANGE)

        tokens = ['The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy']
        tok_w = 0.9
        tok_x = 0.5

        n_shown = min(8, max(1, int(t * 12)))
        for i in range(n_shown):
            alpha_val = 1.0 if i < n_shown - 1 else min(1.0, (t * 12 - i) * 2)
            draw_block(tok_x + i * (tok_w + 0.1), 6.8, tok_w, 0.7,
                      BLUE if i < n_shown - 1 else GREEN, tokens[i],
                      fontsize=10, alpha=alpha_val)
            if i < n_shown - 1:
                ax.text(tok_x + i * (tok_w + 0.1) + tok_w + 0.05, 7.15,
                       '→', ha='center', fontsize=14, color=GRAY, alpha=alpha_val)

        # Time cost
        if t > 0.5:
            ax.text(5, 5.8, f'Каждый токен = 1 forward pass ≈ 50 мс', ha='center',
                    fontsize=13, color=RED, alpha=min(1.0, (t-0.5)*4))
        if t > 0.7:
            ax.text(5, 5.1, f'1000 токенов → 50 секунд ожидания 😱', ha='center',
                    fontsize=13, color=RED, fontweight='bold', alpha=min(1.0, (t-0.7)*5))
        if t > 0.85:
            ax.text(5, 4.2, 'GPU занят sequential операциями — нельзя параллелить', ha='center',
                    fontsize=11, color=GRAY, style='italic', alpha=min(1.0, (t-0.85)*7))

    # Phase 2: Key insight — draft + verify (0.22-0.50)
    elif phase < 0.50:
        t = (phase - 0.22) / 0.28
        add_title('Идея: черновик + проверка', 'Маленькая модель пишет, большая верифицирует', y=9.3)

        # Draft model (small)
        draft_alpha = min(1.0, t * 4)
        rect = patches.FancyBboxPatch((0.5, 6.5), 3.5, 1.5,
                                       boxstyle="round,pad=0.1",
                                       facecolor='#1f2937', edgecolor=BLUE,
                                       linewidth=2, alpha=draft_alpha)
        ax.add_patch(rect)
        ax.text(2.25, 7.55, '📝 Draft Model', ha='center', va='center',
                fontsize=14, color=BLUE, fontweight='bold', alpha=draft_alpha)
        ax.text(2.25, 7.0, 'Llama-68M · 5 мс/токен', ha='center', va='center',
                fontsize=11, color=GRAY, alpha=draft_alpha)

        # Target model (large)
        target_alpha = min(1.0, max(0, t * 4 - 0.5))
        rect2 = patches.FancyBboxPatch((5.5, 6.5), 3.5, 1.5,
                                        boxstyle="round,pad=0.1",
                                        facecolor='#1f2937', edgecolor=GREEN,
                                        linewidth=2, alpha=target_alpha)
        ax.add_patch(rect2)
        ax.text(7.25, 7.55, '✅ Target Model', ha='center', va='center',
                fontsize=14, color=GREEN, fontweight='bold', alpha=target_alpha)
        ax.text(7.25, 7.0, 'Llama-70B · 50 мс/шаг', ha='center', va='center',
                fontsize=11, color=GRAY, alpha=target_alpha)

        # Step visualization
        if t > 0.35:
            step_alpha = min(1.0, (t - 0.35) * 5)

            # Draft proposes γ=4 tokens
            ax.text(5, 5.8, 'Шаг 1: Draft предлагает γ=4 токена', ha='center',
                    fontsize=13, color=BLUE, alpha=step_alpha)
            draft_tokens = ['quick', 'brown', 'fox', 'jumps']
            for i, tok in enumerate(draft_tokens):
                draw_block(1.0 + i * 1.2, 4.8, 1.0, 0.6, BLUE, tok,
                          fontsize=10, alpha=step_alpha)

        if t > 0.6:
            verify_alpha = min(1.0, (t - 0.6) * 5)
            ax.text(5, 4.1, 'Шаг 2: Target верифицирует ВСЕ 4 токена за 1 pass', ha='center',
                    fontsize=13, color=GREEN, alpha=verify_alpha)
            # Show accept/reject
            results = ['✅', '✅', '✅', '❌']
            result_colors = [GREEN, GREEN, GREEN, RED]
            for i, (r, rc) in enumerate(zip(results, result_colors)):
                ax.text(1.5 + i * 1.2, 3.4, r, ha='center', fontsize=18,
                       color=rc, alpha=verify_alpha)

        if t > 0.82:
            final_alpha = min(1.0, (t - 0.82) * 6)
            ax.text(5, 2.6, '3 токена приняты + 1 от Target = 4 за цену 1!', ha='center',
                    fontsize=13, color=YELLOW, fontweight='bold', alpha=final_alpha)

    # Phase 3: Math and acceptance (0.50-0.75)
    elif phase < 0.75:
        t = (phase - 0.50) / 0.25
        add_title('Математика принятия', 'Почему результат идентичен target-only?', y=9.3)

        # Acceptance formula
        formulas = [
            ('Вероятность принятия токена:', 'α = min(1,  p_target(x) / p_draft(x))', BLUE),
            ('Среднее токенов за step:', 'E[n] = (1 − α^(γ+1)) / (1 − α)', GREEN),
            ('Speedup:', 'S = E[n] · T_target / (γ · T_draft + T_target)', PURPLE),
        ]

        for i, (label, formula, color) in enumerate(formulas):
            y = 7.5 - i * 2.2
            alpha_val = min(1.0, max(0, t * 4 - i * 0.8))
            rect = patches.FancyBboxPatch((0.3, y - 0.7), 9.4, 1.5,
                                          boxstyle="round,pad=0.1",
                                          facecolor='#161b22', edgecolor=color,
                                          linewidth=1.5, alpha=alpha_val)
            ax.add_patch(rect)
            ax.text(0.7, y + 0.25, label, ha='left', va='center',
                    fontsize=11, color=GRAY, alpha=alpha_val)
            ax.text(5, y - 0.2, formula, ha='center', va='center',
                    fontsize=13, color=color, fontweight='bold',
                    fontfamily='monospace', alpha=alpha_val)

        # Key insight box
        if t > 0.65:
            insight_alpha = min(1.0, (t - 0.65) * 3)
            rect = patches.FancyBboxPatch((0.5, 0.3), 9.0, 0.9,
                                          boxstyle="round,pad=0.1",
                                          facecolor='#1a2730', edgecolor=YELLOW,
                                          linewidth=2, alpha=insight_alpha)
            ax.add_patch(rect)
            ax.text(5, 0.75, '🔑 Если α ≈ 0.9, γ=4 → E[n] ≈ 3.5 → Speedup ≈ 2.5–3×', ha='center',
                    va='center', fontsize=13, color=YELLOW, alpha=insight_alpha)

    # Phase 4: Real-world results (0.75-1.0)
    else:
        t = (phase - 0.75) / 0.25
        add_title('Результаты в production', 'Speculative Decoding 2024–2026', y=9.4)

        results = [
            ('Llama-3-70B', 'Llama-68M', '2.3–3.1×', 'Codeium (код)', BLUE),
            ('Gemini 1.5 Pro', 'Gemini Nano', '2.8×', 'Google prod.', GREEN),
            ('Self-speculative', 'Layer skip', '1.5–2×', 'без draft модели', PURPLE),
            ('Medusa heads', 'N draft heads', '2.4×', 'parallel heads', ORANGE),
        ]

        for i, (big, small, speedup, note, color) in enumerate(results):
            y = 8.0 - i * 1.7
            alpha_val = min(1.0, max(0, t * 4 - i * 0.5))
            rect = patches.FancyBboxPatch((0.3, y - 0.6), 9.4, 1.2,
                                          boxstyle="round,pad=0.1",
                                          facecolor='#161b22', edgecolor=color,
                                          linewidth=2, alpha=alpha_val)
            ax.add_patch(rect)
            ax.text(0.8, y, big, ha='left', va='center',
                    fontsize=12, color=WHITE, fontweight='bold', alpha=alpha_val)
            ax.text(3.5, y, f'+ {small}', ha='center', va='center',
                    fontsize=11, color=GRAY, alpha=alpha_val)
            ax.text(6.5, y, speedup, ha='center', va='center',
                    fontsize=20, color=color, fontweight='bold', alpha=alpha_val)
            ax.text(9.5, y, note, ha='right', va='center',
                    fontsize=10, color=GRAY, alpha=alpha_val)

        if t > 0.75:
            ax.text(5, 0.6, '@fminxyz · Series 9: LLM Efficiency',
                    ha='center', fontsize=11, color=GRAY, alpha=min(1.0, (t-0.75)*4))

ani = FuncAnimation(fig, animate, frames=total_frames, interval=1000/30, blit=False)
ani.save('/root/Strategy/content/drafts/speculative_decoding_animation.mp4',
         writer='ffmpeg', fps=30, dpi=108,
         savefig_kwargs={'facecolor': BG})
plt.close()
print("speculative_decoding_animation.mp4 saved")
