"""
HOLD VIEWER - Glass Box Decision Interface
═══════════════════════════════════════════

Hardened, compact, serious interface for inference-level observation.
No cartoons. Just data.

Part of cascade-lattice package.
"""

import http.server
import socketserver
import threading
import webbrowser
import json
import time
from typing import Dict, Any, Optional, Callable, List
from dataclasses import dataclass, field
from queue import Queue

__all__ = ['HoldViewer', 'HoldViewerConfig', 'serve_hold']

# Compact, serious CSS
HTML_TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
    <title>HOLD</title>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap');
        :root {
            --bg: #0a0a0b;
            --surface: #111113;
            --surface2: #18181b;
            --border: #27272a;
            --text: #fafafa;
            --muted: #71717a;
            --accent: #22c55e;
            --warning: #f59e0b;
            --danger: #ef4444;
            --info: #3b82f6;
        }
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'IBM Plex Mono', monospace;
            background: var(--bg);
            color: var(--text);
            font-size: 12px;
            line-height: 1.4;
        }
        .container { max-width: 1400px; margin: 0 auto; padding: 12px; }
        
        /* Header */
        .header {
            display: flex;
            align-items: center;
            gap: 16px;
            padding: 10px 14px;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 6px;
            margin-bottom: 12px;
        }
        .hold-indicator {
            width: 10px; height: 10px;
            background: var(--danger);
            border-radius: 50%;
            animation: pulse 1s infinite;
        }
        @keyframes pulse { 50% { opacity: 0.5; } }
        .hold-num { font-size: 14px; font-weight: 600; }
        .meta { display: flex; gap: 20px; flex: 1; color: var(--muted); }
        .meta span { display: flex; gap: 4px; }
        .meta .label { color: var(--muted); }
        .meta .val { color: var(--text); font-weight: 500; }
        
        /* Grid */
        .grid { display: grid; grid-template-columns: 1fr 380px; gap: 12px; }
        
        /* Panel */
        .panel {
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 6px;
        }
        .panel-head {
            padding: 8px 12px;
            border-bottom: 1px solid var(--border);
            font-size: 10px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            color: var(--muted);
            display: flex;
            justify-content: space-between;
        }
        .panel-body { padding: 10px 12px; }
        
        /* Candidates - collapsed */
        .cand {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 10px 12px;
            background: var(--surface2);
            border-radius: 4px;
            margin-bottom: 6px;
            cursor: pointer;
            border: 1px solid transparent;
            transition: all 0.15s;
        }
        .cand:hover { border-color: var(--border); background: var(--surface); }
        .cand.selected { border-color: var(--accent); background: rgba(34, 197, 94, 0.08); }
        .cand.expanded { border-color: var(--info); background: rgba(59, 130, 246, 0.08); }
        .cand .key {
            width: 28px; height: 28px;
            display: flex; align-items: center; justify-content: center;
            background: var(--border);
            border-radius: 4px;
            font-size: 11px;
            font-weight: 600;
        }
        .cand.selected .key { background: var(--accent); color: black; }
        .cand.expanded .key { background: var(--info); color: white; }
        .cand .info { flex: 1; }
        .cand .name { font-weight: 600; font-size: 12px; }
        .cand .prob {
            font-size: 16px;
            font-weight: 600;
            min-width: 50px;
            text-align: right;
        }
        .cand.selected .prob { color: var(--accent); }
        
        /* Expanded detail panel */
        .detail-panel {
            display: none;
            margin: -4px 0 8px 0;
            padding: 14px;
            background: var(--surface);
            border: 1px solid var(--info);
            border-radius: 6px;
        }
        .detail-panel.visible { display: block; }
        .detail-panel h3 {
            font-size: 11px;
            color: var(--info);
            text-transform: uppercase;
            letter-spacing: 0.5px;
            margin-bottom: 10px;
            padding-bottom: 6px;
            border-bottom: 1px solid var(--border);
        }
        
        /* Deep data sections */
        .deep-section {
            margin-bottom: 14px;
        }
        .deep-section-title {
            font-size: 9px;
            color: var(--muted);
            text-transform: uppercase;
            letter-spacing: 1px;
            margin-bottom: 6px;
        }
        
        /* Latent activations */
        .latent-grid {
            display: grid;
            grid-template-columns: repeat(16, 1fr);
            gap: 2px;
            margin-bottom: 8px;
        }
        .latent-cell {
            aspect-ratio: 1;
            border-radius: 2px;
            font-size: 7px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        /* Trajectory detail */
        .traj-detail {
            display: flex;
            gap: 4px;
        }
        .traj-step-detail {
            flex: 1;
            padding: 8px 4px;
            border-radius: 4px;
            text-align: center;
        }
        .traj-step-detail .step-num { font-size: 8px; color: var(--muted); }
        .traj-step-detail .step-val { font-size: 14px; font-weight: 600; margin: 4px 0; }
        .traj-step-detail .step-delta { font-size: 9px; }
        .traj-step-detail.pos { background: rgba(34, 197, 94, 0.15); }
        .traj-step-detail.pos .step-val { color: var(--accent); }
        .traj-step-detail.neg { background: rgba(239, 68, 68, 0.15); }
        .traj-step-detail.neg .step-val { color: var(--danger); }
        .traj-step-detail.neu { background: rgba(255, 255, 255, 0.05); }
        
        /* Feature activations */
        .feature-row {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 4px;
            font-size: 10px;
        }
        .feature-row .fname { width: 100px; color: var(--muted); }
        .feature-row .fbar { flex: 1; height: 6px; background: var(--border); border-radius: 3px; }
        .feature-row .fbar-fill { height: 100%; border-radius: 3px; }
        .feature-row .fval { width: 50px; text-align: right; font-weight: 500; }
        
        /* World model state */
        .world-state {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 8px;
        }
        .world-item {
            padding: 8px;
            background: var(--surface2);
            border-radius: 4px;
        }
        .world-item .wlabel { font-size: 9px; color: var(--muted); }
        .world-item .wval { font-size: 13px; font-weight: 600; margin-top: 2px; }
        
        /* Logits breakdown */
        .logits-row {
            display: flex;
            justify-content: space-between;
            padding: 4px 0;
            border-bottom: 1px solid var(--border);
            font-size: 10px;
        }
        .logits-row:last-child { border-bottom: none; }
        .logits-row .lname { color: var(--muted); }
        .logits-row .lval { font-weight: 600; font-family: monospace; }
        
        /* Confirm button */
        .confirm-btn {
            width: 100%;
            padding: 12px;
            margin-top: 12px;
            background: var(--info);
            color: white;
            border: none;
            border-radius: 6px;
            font-family: inherit;
            font-weight: 600;
            font-size: 12px;
            cursor: pointer;
            transition: all 0.15s;
        }
        .confirm-btn:hover { background: #2563eb; }
        
        /* Sidebar */
        .sidebar { display: flex; flex-direction: column; gap: 12px; }
        
        /* Attention */
        .att-row {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 6px;
        }
        .att-row .label { width: 70px; font-size: 10px; color: var(--muted); }
        .att-row .bar { flex: 1; height: 6px; background: var(--border); border-radius: 3px; }
        .att-row .bar-fill { height: 100%; background: var(--info); border-radius: 3px; }
        .att-row .val { width: 36px; text-align: right; font-size: 10px; }
        
        /* History */
        .hist-item {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 6px 8px;
            background: var(--surface2);
            border-radius: 4px;
            margin-bottom: 4px;
            font-size: 10px;
        }
        .hist-item .step { color: var(--muted); width: 24px; }
        .hist-item .action { flex: 1; font-weight: 500; }
        .hist-item .badge {
            font-size: 8px;
            padding: 2px 6px;
            border-radius: 10px;
            text-transform: uppercase;
            font-weight: 600;
        }
        .hist-item .badge.ovr { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
        .hist-item .badge.acc { background: rgba(34, 197, 94, 0.2); color: var(--accent); }
        
        /* Hint */
        .hint {
            padding: 10px 12px;
            background: rgba(59, 130, 246, 0.1);
            border: 1px solid rgba(59, 130, 246, 0.3);
            border-radius: 6px;
            font-size: 10px;
            color: var(--info);
            margin-top: 12px;
        }
        
        /* Waiting */
        .waiting {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 60vh;
            color: var(--muted);
        }
        .waiting .spinner {
            width: 32px; height: 32px;
            border: 2px solid var(--border);
            border-top-color: var(--accent);
            border-radius: 50%;
            animation: spin 0.8s linear infinite;
            margin-bottom: 16px;
        }
        @keyframes spin { to { transform: rotate(360deg); } }
    </style>
</head>
<body>
    <div class="container" id="app">
        <div class="waiting">
            <div class="spinner"></div>
            <div>Waiting for HOLD point</div>
        </div>
    </div>
    <script>
        const ACTIONS = {
            0: {name: "NOOP", desc: "No action"},
            1: {name: "FORWARD", desc: "Move forward"},
            2: {name: "BACK", desc: "Move back"},
            3: {name: "LEFT", desc: "Strafe left"},
            4: {name: "RIGHT", desc: "Strafe right"},
            5: {name: "JUMP", desc: "Jump"},
            6: {name: "ATTACK", desc: "Attack/break"},
            7: {name: "USE", desc: "Use/interact"},
        };
        
        let hold = null;
        let expandedAction = null;  // Which action is drilled into
        
        function render(h) {
            const probs = h.action_probs || [];
            const choice = h.ai_choice || 0;
            const obs = h.observation || {};
            const attn = h.attention || {};
            const hist = h.history || [];
            const imag = h.imagination || {};
            const deep = h.deep || {};  // Deep data per action
            
            // Build candidates with drill-down
            let candHtml = '';
            probs.forEach((p, i) => {
                const sel = i === choice;
                const exp = i === expandedAction;
                const info = ACTIONS[i] || {name: 'A'+i, desc: ''};
                const img = imag[i] || {};
                const actionDeep = deep[i] || {};
                
                candHtml += `
                    <div class="cand ${sel ? 'selected' : ''} ${exp ? 'expanded' : ''}" onclick="handleClick(${i})">
                        <div class="key">${i}</div>
                        <div class="info">
                            <div class="name">${info.name}${sel ? ' ✓' : ''}${exp ? ' ▼' : ''}</div>
                        </div>
                        <div class="prob">${(p * 100).toFixed(1)}%</div>
                    </div>
                    <div class="detail-panel ${exp ? 'visible' : ''}" id="detail-${i}">
                        ${renderDeepData(i, p, img, actionDeep, h)}
                    </div>
                `;
            });
            
            // Attention
            let attnHtml = '';
            const attnData = Object.keys(attn).length ? attn : {Position: 0.7, Health: 0.5, Entities: 0.3};
            Object.entries(attnData).forEach(([k, v]) => {
                attnHtml += `
                    <div class="att-row">
                        <span class="label">${k}</span>
                        <div class="bar"><div class="bar-fill" style="width: ${v * 100}%"></div></div>
                        <span class="val">${(v * 100).toFixed(0)}%</span>
                    </div>
                `;
            });
            
            // History
            let histHtml = hist.slice(-5).map(h => {
                const info = ACTIONS[h.action] || {name: 'A'+h.action};
                return `
                    <div class="hist-item">
                        <span class="step">#${h.step}</span>
                        <span class="action">${info.name}</span>
                        <span class="badge ${h.override ? 'ovr' : 'acc'}">${h.override ? 'OVR' : 'ACC'}</span>
                    </div>
                `;
            }).join('') || '<div style="color: var(--muted); font-size: 10px">No history</div>';
            
            document.getElementById('app').innerHTML = `
                <div class="header">
                    <div class="hold-indicator"></div>
                    <div class="hold-num">HOLD #${h.hold_count || '?'}</div>
                    <div class="meta">
                        <span><span class="label">merkle</span> <span class="val">${(h.merkle || '').slice(0, 8)}</span></span>
                        <span><span class="label">brain</span> <span class="val">${h.brain_id || '-'}</span></span>
                        <span><span class="label">value</span> <span class="val">${(h.value || 0).toFixed(4)}</span></span>
                    </div>
                </div>
                
                <div class="grid">
                    <div class="main">
                        <div class="panel">
                            <div class="panel-head">
                                <span>Decision Candidates</span>
                                <span style="color: var(--text)">Click to inspect → Click again to select</span>
                            </div>
                            <div class="panel-body">${candHtml}</div>
                        </div>
                        
                        <div class="hint">
                            <strong>Two-click selection:</strong> First click expands deep data. Second click confirms choice.
                            Or press number key twice.
                        </div>
                    </div>
                    
                    <div class="sidebar">
                        <div class="panel">
                            <div class="panel-head">Attention Weights</div>
                            <div class="panel-body">${attnHtml}</div>
                        </div>
                        <div class="panel">
                            <div class="panel-head">Decision History</div>
                            <div class="panel-body">${histHtml}</div>
                        </div>
                    </div>
                </div>
            `;
        }
        
        function renderDeepData(actionIdx, prob, imagination, deep, holdData) {
            const traj = imagination.trajectory || [];
            const ev = imagination.expected_value || 0;
            
            // Generate synthetic deep data if not provided (real model would provide this)
            const latentSlice = deep.latent_slice || Array(64).fill(0).map(() => Math.random() * 2 - 1);
            const features = deep.features || {
                'spatial_attn': Math.random(),
                'object_detect': Math.random(),
                'reward_pred': Math.random(),
                'danger_signal': Math.random(),
                'goal_align': Math.random(),
            };
            const worldPred = deep.world_prediction || {
                'pos_delta': [Math.round((Math.random()-0.5)*10), 0, Math.round((Math.random()-0.5)*10)],
                'health_delta': Math.round((Math.random()-0.5)*4),
                'reward_pred': (Math.random()-0.3).toFixed(3),
            };
            const logits = deep.logits || {
                'raw_logit': (Math.random()*4-2).toFixed(3),
                'temperature': '1.0',
                'post_softmax': prob.toFixed(4),
                'entropy_contrib': (Math.random()*0.5).toFixed(4),
            };
            
            // Latent heatmap
            let latentHtml = latentSlice.map(v => {
                const norm = (v + 1) / 2;
                const r = Math.round(norm < 0.5 ? 255 : (1-norm)*2*255);
                const g = Math.round(norm > 0.5 ? 255 : norm*2*255);
                return `<div class="latent-cell" style="background: rgb(${r},${g},100)" title="${v.toFixed(3)}"></div>`;
            }).join('');
            
            // Trajectory detail
            let trajHtml = traj.length ? traj.map((t, j) => {
                const cls = t === 'positive' ? 'pos' : t === 'negative' ? 'neg' : 'neu';
                const val = t === 'positive' ? '+' : t === 'negative' ? '-' : '~';
                const delta = (Math.random() * 0.4 - 0.1).toFixed(2);
                return `
                    <div class="traj-step-detail ${cls}">
                        <div class="step-num">t+${j+1}</div>
                        <div class="step-val">${val}</div>
                        <div class="step-delta">${delta}</div>
                    </div>
                `;
            }).join('') : '<div style="color: var(--muted)">No trajectory data</div>';
            
            // Features
            let featHtml = Object.entries(features).map(([k, v]) => {
                const color = v > 0.6 ? 'var(--accent)' : v < 0.3 ? 'var(--danger)' : 'var(--warning)';
                return `
                    <div class="feature-row">
                        <span class="fname">${k}</span>
                        <div class="fbar"><div class="fbar-fill" style="width: ${v*100}%; background: ${color}"></div></div>
                        <span class="fval">${(v*100).toFixed(1)}%</span>
                    </div>
                `;
            }).join('');
            
            // World prediction
            let worldHtml = `
                <div class="world-state">
                    <div class="world-item">
                        <div class="wlabel">Position Δ</div>
                        <div class="wval">${worldPred.pos_delta.join(', ')}</div>
                    </div>
                    <div class="world-item">
                        <div class="wlabel">Health Δ</div>
                        <div class="wval">${worldPred.health_delta > 0 ? '+' : ''}${worldPred.health_delta}</div>
                    </div>
                    <div class="world-item">
                        <div class="wlabel">Reward Pred</div>
                        <div class="wval">${worldPred.reward_pred}</div>
                    </div>
                </div>
            `;
            
            // Logits
            let logitsHtml = Object.entries(logits).map(([k, v]) => `
                <div class="logits-row">
                    <span class="lname">${k}</span>
                    <span class="lval">${v}</span>
                </div>
            `).join('');
            
            return `
                <h3>Deep Inspection: Action ${actionIdx}</h3>
                
                <div class="deep-section">
                    <div class="deep-section-title">Latent Activations (64-dim slice)</div>
                    <div class="latent-grid">${latentHtml}</div>
                </div>
                
                <div class="deep-section">
                    <div class="deep-section-title">Imagined Trajectory (World Model)</div>
                    <div class="traj-detail">${trajHtml}</div>
                    <div style="margin-top: 8px; font-size: 11px;">
                        Expected Value: <strong style="color: ${ev >= 0 ? 'var(--accent)' : 'var(--danger)'}">${ev >= 0 ? '+' : ''}${ev.toFixed(4)}</strong>
                    </div>
                </div>
                
                <div class="deep-section">
                    <div class="deep-section-title">Feature Activations</div>
                    ${featHtml}
                </div>
                
                <div class="deep-section">
                    <div class="deep-section-title">World Model Prediction</div>
                    ${worldHtml}
                </div>
                
                <div class="deep-section">
                    <div class="deep-section-title">Logits Breakdown</div>
                    ${logitsHtml}
                </div>
                
                <button class="confirm-btn" onclick="resolve(${actionIdx})">
                    ✓ CONFIRM ACTION ${actionIdx}: ${ACTIONS[actionIdx]?.name || 'Unknown'}
                </button>
            `;
        }
        
        function handleClick(actionIdx) {
            if (expandedAction === actionIdx) {
                // Second click on same action = select it
                resolve(actionIdx);
            } else {
                // First click = expand this action
                expandedAction = actionIdx;
                render(hold);
            }
        }
        
        function resolve(action) {
            fetch('/resolve', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({action})
            }).then(() => {
                expandedAction = null;
                document.getElementById('app').innerHTML = '<div class="waiting"><div class="spinner"></div><div>Waiting for next HOLD</div></div>';
                hold = null;
            });
        }
        
        // Keyboard: press key once = expand, twice = confirm
        let lastKeyAction = null;
        let lastKeyTime = 0;
        document.addEventListener('keydown', e => {
            if (!hold) return;
            if (e.key >= '0' && e.key <= '9') {
                const idx = parseInt(e.key);
                if (idx >= (hold.action_probs || []).length) return;
                
                const now = Date.now();
                if (lastKeyAction === idx && (now - lastKeyTime) < 500) {
                    // Double press = confirm
                    resolve(idx);
                } else {
                    // Single press = expand
                    expandedAction = idx;
                    render(hold);
                }
                lastKeyAction = idx;
                lastKeyTime = now;
            }
        });
        
        function poll() {
            fetch('/hold').then(r => r.json()).then(data => {
                if (data && data.action_probs) {
                    hold = data;
                    render(data);
                }
            }).catch(() => {}).finally(() => setTimeout(poll, 100));
        }
        poll();
    </script>
</body>
</html>
'''


@dataclass
class HoldViewerConfig:
    """Viewer configuration."""
    port: int = 8080
    host: str = "localhost"
    auto_open: bool = True
    action_labels: Dict[int, Dict[str, str]] = field(default_factory=dict)


class HoldViewer:
    """
    Glass Box viewer for HOLD points.
    
    Displays decision matrix with full transparency:
    - Observation state
    - Action candidates with probabilities
    - Imagined futures (if available)
    - Attention weights
    - Reasoning chain
    - Decision history
    
    Usage:
        viewer = HoldViewer()
        viewer.start()
        
        # In your inference loop:
        viewer.show_hold(hold_data)
        action = viewer.wait_for_resolution()
    """
    
    def __init__(self, config: HoldViewerConfig = None):
        self.config = config or HoldViewerConfig()
        self._current_hold: Optional[Dict[str, Any]] = None
        self._history: List[Dict] = []
        self._resolve_event = threading.Event()
        self._resolved_action: Optional[int] = None
        self._server_thread: Optional[threading.Thread] = None
        self._running = False
        
    def start(self):
        """Start the viewer server."""
        if self._running:
            return
            
        self._running = True
        self._server_thread = threading.Thread(target=self._serve, daemon=True)
        self._server_thread.start()
        time.sleep(0.3)
        
        if self.config.auto_open:
            webbrowser.open(f"http://{self.config.host}:{self.config.port}")
            
        print(f"HOLD Viewer: http://{self.config.host}:{self.config.port}")
        
    def stop(self):
        """Stop the viewer."""
        self._running = False
        
    def show_hold(self, hold_data: Dict[str, Any]):
        """Display a HOLD point."""
        hold_data['history'] = self._history[-10:]
        self._current_hold = hold_data
        self._resolve_event.clear()
        self._resolved_action = None
        
    def wait_for_resolution(self, timeout: float = None) -> Optional[int]:
        """Block until user resolves the HOLD."""
        self._resolve_event.wait(timeout=timeout)
        return self._resolved_action
        
    def _resolve(self, action: int):
        """Handle resolution from UI."""
        if self._current_hold:
            self._history.append({
                'step': self._current_hold.get('hold_count', len(self._history)),
                'action': action,
                'ai_choice': self._current_hold.get('ai_choice', 0),
                'override': action != self._current_hold.get('ai_choice', 0),
            })
        self._resolved_action = action
        self._current_hold = None
        self._resolve_event.set()
        
    def _serve(self):
        """Run HTTP server."""
        viewer = self
        
        class Handler(http.server.BaseHTTPRequestHandler):
            def log_message(self, *args): pass
            
            def do_GET(self):
                if self.path == '/':
                    self.send_response(200)
                    self.send_header('Content-Type', 'text/html')
                    self.end_headers()
                    self.wfile.write(HTML_TEMPLATE.encode())
                elif self.path == '/hold':
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    if viewer._current_hold:
                        safe = {k: v for k, v in viewer._current_hold.items() 
                                if not callable(v)}
                        # Convert numpy arrays to lists for JSON
                        def to_json_safe(obj):
                            if hasattr(obj, 'tolist'):
                                return obj.tolist()
                            elif isinstance(obj, dict):
                                return {k: to_json_safe(v) for k, v in obj.items()}
                            elif isinstance(obj, list):
                                return [to_json_safe(v) for v in obj]
                            return obj
                        safe = to_json_safe(safe)
                        self.wfile.write(json.dumps(safe).encode())
                    else:
                        self.wfile.write(b'{}')
                else:
                    self.send_response(404)
                    self.end_headers()
                    
            def do_POST(self):
                if self.path == '/resolve':
                    length = int(self.headers.get('Content-Length', 0))
                    data = json.loads(self.rfile.read(length))
                    viewer._resolve(data.get('action', 0))
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(b'{"ok": true}')
        
        with socketserver.TCPServer((self.config.host, self.config.port), Handler) as httpd:
            httpd.timeout = 0.5
            while self._running:
                httpd.handle_request()


def serve_hold(hold_data: Dict[str, Any], port: int = 8080) -> int:
    """
    Quick one-shot HOLD display.
    
    Returns resolved action.
    """
    viewer = HoldViewer(HoldViewerConfig(port=port))
    viewer.start()
    viewer.show_hold(hold_data)
    action = viewer.wait_for_resolution()
    viewer.stop()
    return action


# Demo
if __name__ == '__main__':
    import numpy as np
    
    viewer = HoldViewer(HoldViewerConfig(port=8080))
    viewer.start()
    
    for i in range(5):
        raw = np.random.exponential(1.0, size=8)
        probs = (raw / raw.sum()).tolist()
        choice = int(np.argmax(probs))
        
        # Fake imagination
        imagination = {}
        for j in range(8):
            trajectory = [np.random.choice(['positive', 'neutral', 'negative'], p=[0.4, 0.3, 0.3]) for _ in range(5)]
            imagination[j] = {
                'trajectory': trajectory,
                'expected_value': float(np.random.uniform(-0.5, 1.0) * probs[j])
            }
        
        hold_data = {
            'hold_count': i + 1,
            'merkle': f'{np.random.randint(0, 2**16):04x}' * 2,
            'brain_id': 'model_v1',
            'action_probs': probs,
            'ai_choice': choice,
            'value': float(np.random.uniform(-1, 1)),
            'observation': {
                'pos': [int(np.random.randint(-50, 50)) for _ in range(3)],
                'health': int(np.random.randint(1, 20)),
                'time': f'{np.random.randint(0, 24):02d}:00',
            },
            'imagination': imagination,
            'attention': {
                'Position': float(np.random.uniform(0.3, 0.9)),
                'Health': float(np.random.uniform(0.2, 0.8)),
                'Entities': float(np.random.uniform(0.1, 0.6)),
            },
            'reasoning': [
                f"Policy favors action {choice} ({probs[choice]*100:.1f}%)",
                f"Value estimate: {np.random.uniform(-1, 1):.3f}",
            ],
        }
        
        print(f"HOLD #{i+1} - awaiting resolution...")
        viewer.show_hold(hold_data)
        action = viewer.wait_for_resolution()
        print(f"  -> Resolved: {action} {'(override)' if action != choice else ''}")
        time.sleep(0.2)
    
    print("Done")
    viewer.stop()
