"""Asset: Create interactive HTML viewer for frame-by-frame ball tracking"""

from pathlib import Path
from typing import Dict, List
from dagster import asset, AssetExecutionContext


@asset(
    io_manager_key="json_io_manager",
    compute_kind="html",
    description="Create interactive HTML viewer with frame + 3D court visualization"
)
def create_interactive_viewer(
    context: AssetExecutionContext,
    extract_video_frames: Dict,
    compute_ball_3d_coordinates: List[Dict]
) -> Dict:
    """
    Create interactive HTML viewer showing:
    - Left: Original video frame
    - Right: Interactive 3D court (Three.js - rotatable with mouse)
    - Controls: Prev/Next buttons + slider

    Outputs:
        - data/{run_id}/viewer/index.html

    Returns:
        Dict with viewer_path
    """
    run_id = context.run_id
    frames_dir = Path(extract_video_frames['frames_dir'])

    # Create viewer directory
    viewer_dir = Path(f"data/{run_id}/viewer")
    viewer_dir.mkdir(parents=True, exist_ok=True)

    # Copy frames to viewer directory
    import shutil
    viewer_frames_dir = viewer_dir / "frames"
    viewer_frames_dir.mkdir(exist_ok=True)

    # Filter frames with detections
    frames_with_ball = [f for f in compute_ball_3d_coordinates if f['x_m'] is not None]

    context.log.info(f"Creating viewer for {len(frames_with_ball)} frames with ball detections")

    # Copy only frames with detections
    for frame_data in frames_with_ball:
        frame_num = frame_data['frame']
        src = frames_dir / f"frame_{frame_num:04d}.jpg"
        dst = viewer_frames_dir / f"frame_{frame_num:04d}.jpg"
        if src.exists():
            shutil.copy2(src, dst)

    context.log.info(f"Copied {len(frames_with_ball)} frames to viewer directory")

    # Generate HTML
    html_content = _generate_html(frames_with_ball, run_id)

    # Save HTML
    html_path = viewer_dir / "index.html"
    with open(html_path, 'w') as f:
        f.write(html_content)

    context.log.info(f"✓ Interactive viewer created: {html_path}")
    context.log.info(f"   Open in browser: file://{html_path.absolute()}")

    return {
        "viewer_path": str(html_path),
        "num_frames": len(frames_with_ball)
    }


def _generate_html(frames_data: List[Dict], run_id: str) -> str:
    """Generate HTML with Three.js for real 3D visualization"""

    # Convert frames data to JSON
    import json
    frames_json = json.dumps(frames_data, indent=2)

    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ball Tracking 3D Viewer - Run {run_id[:8]}</title>
    <style>
        * {{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }}

        body {{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #1a1a1a;
            color: #ffffff;
            padding: 20px;
            overflow-x: hidden;
        }}

        .container {{
            max-width: 1900px;
            margin: 0 auto;
        }}

        h1 {{
            text-align: center;
            margin-bottom: 25px;
            font-size: 28px;
            color: #00ff88;
        }}

        .viewer {{
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-bottom: 20px;
            height: 600px;
        }}

        .panel {{
            background: #2a2a2a;
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.3);
            display: flex;
            flex-direction: column;
        }}

        .panel h2 {{
            margin-bottom: 15px;
            font-size: 18px;
            color: #00ff88;
        }}

        #frameImage {{
            width: 100%;
            height: 100%;
            object-fit: contain;
            border-radius: 4px;
        }}

        #canvas3d {{
            width: 100%;
            height: 100%;
            border-radius: 4px;
            display: block;
        }}

        .controls {{
            background: #2a2a2a;
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.3);
        }}

        .controls-row {{
            display: flex;
            align-items: center;
            gap: 15px;
            margin-bottom: 15px;
        }}

        button {{
            background: #00ff88;
            color: #1a1a1a;
            border: none;
            padding: 10px 24px;
            border-radius: 4px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }}

        button:hover {{
            background: #00cc6a;
            transform: translateY(-1px);
        }}

        button:active {{
            transform: translateY(0);
        }}

        button:disabled {{
            background: #444;
            color: #888;
            cursor: not-allowed;
            transform: none;
        }}

        input[type="range"] {{
            flex: 1;
            height: 6px;
            background: #444;
            border-radius: 3px;
            outline: none;
            -webkit-appearance: none;
        }}

        input[type="range"]::-webkit-slider-thumb {{
            -webkit-appearance: none;
            width: 18px;
            height: 18px;
            background: #00ff88;
            border-radius: 50%;
            cursor: pointer;
        }}

        input[type="range"]::-moz-range-thumb {{
            width: 18px;
            height: 18px;
            background: #00ff88;
            border-radius: 50%;
            cursor: pointer;
            border: none;
        }}

        .info {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
        }}

        .info-item {{
            background: #1a1a1a;
            padding: 12px;
            border-radius: 4px;
        }}

        .info-label {{
            font-size: 12px;
            color: #888;
            margin-bottom: 4px;
        }}

        .info-value {{
            font-size: 20px;
            font-weight: 600;
            color: #00ff88;
        }}

        .kbd {{
            display: inline-block;
            padding: 3px 8px;
            background: #444;
            border-radius: 3px;
            font-family: monospace;
            font-size: 14px;
            margin: 0 4px;
        }}

        .help {{
            margin-top: 15px;
            padding: 10px;
            background: #1a1a1a;
            border-radius: 4px;
            font-size: 14px;
            color: #888;
        }}
    </style>
</head>
<body>
    <div class="container">
        <h1>🎾 Pickleball Ball Tracking 3D Viewer</h1>

        <div class="viewer">
            <div class="panel">
                <h2>📹 Video Frame</h2>
                <img id="frameImage" alt="Video frame">
            </div>

            <div class="panel">
                <h2>🗺️ Interactive 3D Court (drag to rotate, scroll to zoom)</h2>
                <div id="canvas3d"></div>
            </div>
        </div>

        <div class="controls">
            <div class="controls-row">
                <button id="prevBtn">← Prev</button>
                <input type="range" id="frameSlider" min="0" max="0" value="0">
                <button id="nextBtn">Next →</button>
                <button id="playBtn">▶ Play</button>
            </div>

            <div class="info">
                <div class="info-item">
                    <div class="info-label">Frame</div>
                    <div class="info-value" id="frameNum">-</div>
                </div>
                <div class="info-item">
                    <div class="info-label">X Position (m)</div>
                    <div class="info-value" id="posX">-</div>
                </div>
                <div class="info-item">
                    <div class="info-label">Y Position (m)</div>
                    <div class="info-value" id="posY">-</div>
                </div>
                <div class="info-item">
                    <div class="info-label">Z Height (m)</div>
                    <div class="info-value" id="posZ">-</div>
                </div>
                <div class="info-item">
                    <div class="info-label">Confidence</div>
                    <div class="info-value" id="confidence">-</div>
                </div>
                <div class="info-item">
                    <div class="info-label">On Ground</div>
                    <div class="info-value" id="onGround">-</div>
                </div>
            </div>

            <div class="help">
                💡 <strong>Controls:</strong>
                <span class="kbd">←/→</span> Navigate frames
                <span class="kbd">Space</span> Play/Pause
                <span class="kbd">Mouse drag</span> Rotate 3D view
                <span class="kbd">Mouse wheel</span> Zoom
            </div>
        </div>
    </div>

    <!-- Three.js from CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        // Frame data from Python
        const framesData = {frames_json};

        let currentIndex = 0;
        let isPlaying = false;
        let playInterval = null;

        // DOM elements
        const frameImage = document.getElementById('frameImage');
        const canvas3dContainer = document.getElementById('canvas3d');
        const prevBtn = document.getElementById('prevBtn');
        const nextBtn = document.getElementById('nextBtn');
        const playBtn = document.getElementById('playBtn');
        const frameSlider = document.getElementById('frameSlider');
        const frameNum = document.getElementById('frameNum');
        const posX = document.getElementById('posX');
        const posY = document.getElementById('posY');
        const posZ = document.getElementById('posZ');
        const confidence = document.getElementById('confidence');
        const onGround = document.getElementById('onGround');

        // Initialize
        frameSlider.max = framesData.length - 1;

        // Setup Three.js scene
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x1a1a1a);

        const camera = new THREE.PerspectiveCamera(
            60,
            canvas3dContainer.clientWidth / canvas3dContainer.clientHeight,
            0.1,
            1000
        );
        camera.position.set(15, 12, 15);
        camera.lookAt(6.7, 3, 0);

        const renderer = new THREE.WebGLRenderer({{ antialias: true }});
        renderer.setSize(canvas3dContainer.clientWidth, canvas3dContainer.clientHeight);
        canvas3dContainer.appendChild(renderer.domElement);

        // Lighting
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(10, 20, 10);
        scene.add(directionalLight);

        // Grid helper
        const gridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x222222);
        scene.add(gridHelper);

        // Court dimensions
        const courtLength = 13.4;
        const courtWidth = 6.1;
        const netHeight = 0.914;

        // Court floor (green)
        const courtGeometry = new THREE.PlaneGeometry(courtLength, courtWidth);
        const courtMaterial = new THREE.MeshStandardMaterial({{
            color: 0x00aa44,
            side: THREE.DoubleSide,
            roughness: 0.8
        }});
        const court = new THREE.Mesh(courtGeometry, courtMaterial);
        court.rotation.x = -Math.PI / 2;
        court.position.set(courtLength / 2, 0, courtWidth / 2);
        scene.add(court);

        // Court boundaries (white lines)
        const boundaryMaterial = new THREE.LineBasicMaterial({{ color: 0xffffff, linewidth: 3 }});
        const boundaryPoints = [
            new THREE.Vector3(0, 0.01, 0),
            new THREE.Vector3(courtLength, 0.01, 0),
            new THREE.Vector3(courtLength, 0.01, courtWidth),
            new THREE.Vector3(0, 0.01, courtWidth),
            new THREE.Vector3(0, 0.01, 0)
        ];
        const boundaryGeometry = new THREE.BufferGeometry().setFromPoints(boundaryPoints);
        const boundary = new THREE.Line(boundaryGeometry, boundaryMaterial);
        scene.add(boundary);

        // Net (mesh) - positioned at middle of court LENGTH (X=6.7m), spanning full WIDTH (Z axis)
        const netGeometry = new THREE.BoxGeometry(0.05, netHeight, courtWidth);
        const netMaterial = new THREE.MeshStandardMaterial({{
            color: 0xcccccc,
            transparent: true,
            opacity: 0.7,
            side: THREE.DoubleSide
        }});
        const net = new THREE.Mesh(netGeometry, netMaterial);
        net.position.set(courtLength / 2, netHeight / 2, courtWidth / 2);
        scene.add(net);

        // Net poles at both ends of the net (Z=0 and Z=courtWidth)
        const poleGeometry = new THREE.CylinderGeometry(0.05, 0.05, netHeight, 8);
        const poleMaterial = new THREE.MeshStandardMaterial({{ color: 0x666666 }});

        const pole1 = new THREE.Mesh(poleGeometry, poleMaterial);
        pole1.position.set(courtLength / 2, netHeight / 2, 0);
        scene.add(pole1);

        const pole2 = new THREE.Mesh(poleGeometry, poleMaterial);
        pole2.position.set(courtLength / 2, netHeight / 2, courtWidth);
        scene.add(pole2);

        // Ball (sphere)
        let ballMesh = null;
        let ballShadow = null;
        let ballLine = null;

        function createBall() {{
            // Remove old ball if exists
            if (ballMesh) scene.remove(ballMesh);
            if (ballShadow) scene.remove(ballShadow);
            if (ballLine) scene.remove(ballLine);

            // Create ball
            const ballGeometry = new THREE.SphereGeometry(0.074 / 2, 32, 32);
            const ballMaterial = new THREE.MeshStandardMaterial({{
                color: 0xffff00,
                emissive: 0xff4444,
                emissiveIntensity: 0.3,
                metalness: 0.5,
                roughness: 0.3
            }});
            ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);

            // Shadow (circle on ground)
            const shadowGeometry = new THREE.CircleGeometry(0.1, 32);
            const shadowMaterial = new THREE.MeshBasicMaterial({{
                color: 0x000000,
                transparent: true,
                opacity: 0.3,
                side: THREE.DoubleSide
            }});
            ballShadow = new THREE.Mesh(shadowGeometry, shadowMaterial);
            ballShadow.rotation.x = -Math.PI / 2;

            scene.add(ballMesh);
            scene.add(ballShadow);
        }}

        function updateBall(x, y, z) {{
            if (!ballMesh) createBall();

            ballMesh.position.set(x, z, y);
            ballShadow.position.set(x, 0.01, y);

            // Draw line from shadow to ball if in air
            if (z > 0.1) {{
                if (ballLine) scene.remove(ballLine);

                const lineMaterial = new THREE.LineBasicMaterial({{
                    color: 0xffffff,
                    transparent: true,
                    opacity: 0.3
                }});
                const linePoints = [
                    new THREE.Vector3(x, z, y),
                    new THREE.Vector3(x, 0, y)
                ];
                const lineGeometry = new THREE.BufferGeometry().setFromPoints(linePoints);
                ballLine = new THREE.Line(lineGeometry, lineMaterial);
                scene.add(ballLine);
            }} else {{
                if (ballLine) {{
                    scene.remove(ballLine);
                    ballLine = null;
                }}
            }}
        }}

        // Mouse controls for camera
        let isDragging = false;
        let previousMousePosition = {{ x: 0, y: 0 }};
        let cameraAngle = {{ theta: Math.PI / 4, phi: Math.PI / 3 }};
        let cameraDistance = 22;

        canvas3dContainer.addEventListener('mousedown', (e) => {{
            isDragging = true;
            previousMousePosition = {{ x: e.clientX, y: e.clientY }};
        }});

        document.addEventListener('mouseup', () => {{
            isDragging = false;
        }});

        canvas3dContainer.addEventListener('mousemove', (e) => {{
            if (!isDragging) return;

            const deltaX = e.clientX - previousMousePosition.x;
            const deltaY = e.clientY - previousMousePosition.y;

            cameraAngle.theta += deltaX * 0.01;
            cameraAngle.phi = Math.max(0.1, Math.min(Math.PI / 2 - 0.1, cameraAngle.phi - deltaY * 0.01));

            previousMousePosition = {{ x: e.clientX, y: e.clientY }};

            updateCameraPosition();
        }});

        canvas3dContainer.addEventListener('wheel', (e) => {{
            e.preventDefault();
            cameraDistance += e.deltaY * 0.01;
            cameraDistance = Math.max(5, Math.min(50, cameraDistance));
            updateCameraPosition();
        }});

        function updateCameraPosition() {{
            const centerX = courtLength / 2;
            const centerZ = courtWidth / 2;

            camera.position.x = centerX + cameraDistance * Math.sin(cameraAngle.phi) * Math.cos(cameraAngle.theta);
            camera.position.y = cameraDistance * Math.cos(cameraAngle.phi);
            camera.position.z = centerZ + cameraDistance * Math.sin(cameraAngle.phi) * Math.sin(cameraAngle.theta);

            camera.lookAt(centerX, 0, centerZ);
        }}

        // Handle window resize
        window.addEventListener('resize', () => {{
            camera.aspect = canvas3dContainer.clientWidth / canvas3dContainer.clientHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(canvas3dContainer.clientWidth, canvas3dContainer.clientHeight);
        }});

        // Animation loop
        function animate() {{
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        }}
        animate();

        // Render current frame
        function render() {{
            if (framesData.length === 0) return;

            const frame = framesData[currentIndex];

            // Update image - frames are in ./frames/ directory relative to HTML
            const framePath = `frames/frame_${{String(frame.frame).padStart(4, '0')}}.jpg`;
            frameImage.src = framePath;

            // Update info
            frameNum.textContent = `${{currentIndex + 1}} / ${{framesData.length}} (Frame #${{frame.frame}})`;
            posX.textContent = frame.x_m !== null ? frame.x_m.toFixed(2) : 'N/A';
            posY.textContent = frame.y_m !== null ? frame.y_m.toFixed(2) : 'N/A';
            posZ.textContent = frame.z_m !== null ? frame.z_m.toFixed(2) : 'N/A';
            confidence.textContent = frame.confidence.toFixed(2);
            onGround.textContent = frame.on_ground ? '✓ Yes' : '✗ No';

            // Update slider
            frameSlider.value = currentIndex;

            // Update buttons
            prevBtn.disabled = currentIndex === 0;
            nextBtn.disabled = currentIndex === framesData.length - 1;

            // Update ball position in 3D
            if (frame.x_m !== null && frame.y_m !== null && frame.z_m !== null) {{
                updateBall(frame.x_m, frame.y_m, frame.z_m);
            }}
        }}

        // Navigation functions
        function nextFrame() {{
            if (currentIndex < framesData.length - 1) {{
                currentIndex++;
                render();
            }}
        }}

        function prevFrame() {{
            if (currentIndex > 0) {{
                currentIndex--;
                render();
            }}
        }}

        function gotoFrame(index) {{
            currentIndex = Math.max(0, Math.min(index, framesData.length - 1));
            render();
        }}

        function togglePlay() {{
            if (isPlaying) {{
                clearInterval(playInterval);
                isPlaying = false;
                playBtn.textContent = '▶ Play';
            }} else {{
                isPlaying = true;
                playBtn.textContent = '⏸ Pause';
                playInterval = setInterval(() => {{
                    if (currentIndex < framesData.length - 1) {{
                        nextFrame();
                    }} else {{
                        clearInterval(playInterval);
                        isPlaying = false;
                        playBtn.textContent = '▶ Play';
                        currentIndex = 0;
                        render();
                    }}
                }}, 100); // 10 fps
            }}
        }}

        // Event listeners
        prevBtn.addEventListener('click', prevFrame);
        nextBtn.addEventListener('click', nextFrame);
        playBtn.addEventListener('click', togglePlay);
        frameSlider.addEventListener('input', (e) => gotoFrame(parseInt(e.target.value)));

        // Keyboard shortcuts
        document.addEventListener('keydown', (e) => {{
            switch(e.key) {{
                case 'ArrowLeft':
                    prevFrame();
                    break;
                case 'ArrowRight':
                    nextFrame();
                    break;
                case ' ':
                    e.preventDefault();
                    togglePlay();
                    break;
            }}
        }});

        // Initial render
        render();
    </script>
</body>
</html>
"""

    return html
