import { useState, useEffect, useRef, useCallback } from 'react';
import { Play, RotateCcw, AlertCircle } from 'lucide-react';

// ─────────────────────────────────────────────────────────────────
// Presets WGSL · cada uno es un shader completo autosuficiente
// ─────────────────────────────────────────────────────────────────
const SHADER_HEADER = `struct Uniforms {
  time: f32,
  resolution: vec2<f32>,
};

@group(0) @binding(0) var<uniform> u: Uniforms;

@vertex
fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
  // triángulo fullscreen (truco de tres vértices)
  let p = array<vec2<f32>, 3>(
    vec2<f32>(-1.0, -1.0),
    vec2<f32>( 3.0, -1.0),
    vec2<f32>(-1.0,  3.0)
  );
  return vec4<f32>(p[i], 0.0, 1.0);
}
`;

const PRESETS = {
  plasma: SHADER_HEADER + `
@fragment
fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  let uv = pos.xy / u.resolution;
  let c = 0.5 + 0.5 * cos(u.time + uv.xyx + vec3<f32>(0.0, 2.0, 4.0));
  return vec4<f32>(c, 1.0);
}
`,

  circle: SHADER_HEADER + `
@fragment
fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  // SDF de un círculo con anillo y fondo sutil
  let uv = (pos.xy / u.resolution) * 2.0 - 1.0;
  let aspect = u.resolution.x / u.resolution.y;
  let p = vec2<f32>(uv.x * aspect, uv.y);
  let r = 0.4 + 0.05 * sin(u.time * 2.0);
  let d = length(p) - r;
  let fill = smoothstep(0.0, -0.01, d);
  let ring = smoothstep(0.015, 0.0, abs(d));
  let bg = vec3<f32>(0.05, 0.06, 0.08);
  let col = mix(bg, vec3<f32>(0.94, 0.55, 0.72), fill) + vec3<f32>(ring * 0.9);
  return vec4<f32>(col, 1.0);
}
`,

  checker: SHADER_HEADER + `
@fragment
fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  // tablero rotando
  let uv = pos.xy / u.resolution - 0.5;
  let aspect = u.resolution.x / u.resolution.y;
  let p0 = vec2<f32>(uv.x * aspect, uv.y);
  let rot = u.time * 0.25;
  let c = cos(rot);
  let s = sin(rot);
  let p = vec2<f32>(p0.x * c - p0.y * s, p0.x * s + p0.y * c) * 10.0;
  let chk = (floor(p.x) + floor(p.y)) - 2.0 * floor((floor(p.x) + floor(p.y)) * 0.5);
  let col = mix(vec3<f32>(0.93, 0.91, 0.86), vec3<f32>(0.10, 0.09, 0.13), chk);
  return vec4<f32>(col, 1.0);
}
`,

  waves: SHADER_HEADER + `
@fragment
fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  // interferencia de dos ondas senoidales
  let uv = pos.xy / u.resolution;
  let a = sin(uv.x * 20.0 + u.time * 2.0);
  let b = sin(uv.y * 15.0 - u.time * 1.3);
  let v = 0.5 + 0.5 * a * b;
  let col = vec3<f32>(v, pow(v, 2.0) * 0.5 + 0.3, 1.0 - v * 0.7);
  return vec4<f32>(col, 1.0);
}
`,

  sdfBlob: SHADER_HEADER + `
fn sdCircle(p: vec2<f32>, r: f32) -> f32 { return length(p) - r; }
fn smoothMin(a: f32, b: f32, k: f32) -> f32 {
  let h = max(k - abs(a - b), 0.0) / k;
  return min(a, b) - h * h * h * k * (1.0 / 6.0);
}

@fragment
fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  // metaball · fusión de tres SDFs
  let uv = (pos.xy / u.resolution) * 2.0 - 1.0;
  let aspect = u.resolution.x / u.resolution.y;
  let p = vec2<f32>(uv.x * aspect, uv.y);
  let t = u.time;
  let c1 = sdCircle(p - vec2<f32>(cos(t) * 0.4, sin(t * 1.3) * 0.3), 0.25);
  let c2 = sdCircle(p - vec2<f32>(cos(t * 0.7 + 2.0) * 0.35, sin(t * 0.9) * 0.25), 0.22);
  let c3 = sdCircle(p - vec2<f32>(sin(t * 1.1) * 0.3, cos(t * 0.6) * 0.35), 0.2);
  let d = smoothMin(smoothMin(c1, c2, 0.35), c3, 0.35);
  let fill = smoothstep(0.02, -0.02, d);
  let glow = exp(-8.0 * max(d, 0.0));
  let bg = vec3<f32>(0.04, 0.02, 0.06);
  let core = vec3<f32>(0.95, 0.4, 0.6);
  let col = bg + core * (fill * 0.9 + glow * 0.3);
  return vec4<f32>(col, 1.0);
}
`,
};

const PRESET_ORDER = [
  { key: 'plasma',  label: 'plasma',      hint: 'cos-gradient clásico' },
  { key: 'circle',  label: 'círculo sdf', hint: 'signed distance field' },
  { key: 'checker', label: 'tablero',     hint: 'patrón rotando' },
  { key: 'waves',   label: 'ondas',       hint: 'interferencia senoidal' },
  { key: 'sdfBlob', label: 'metaball',    hint: 'fusión suave de SDFs' },
];

// ─────────────────────────────────────────────────────────────────
// Hook: gestión de todo el ciclo WebGPU
// ─────────────────────────────────────────────────────────────────
function useWebGPU(canvasRef, code) {
  const gpu = useRef({
    device: null,
    context: null,
    format: null,
    pipeline: null,
    bindGroup: null,
    uniformBuffer: null,
    startTime: 0,
    raf: 0,
  });
  const [status, setStatus] = useState('init');   // init · ready · unsupported · error
  const [shaderError, setShaderError] = useState(null);
  const [fps, setFps] = useState(0);
  const [timeT, setTimeT] = useState(0);

  // ── Inicialización ──
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        if (!navigator.gpu) { setStatus('unsupported'); return; }
        const adapter = await navigator.gpu.requestAdapter();
        if (!adapter) { setStatus('unsupported'); return; }
        const device = await adapter.requestDevice();
        if (cancelled) return;
        const canvas = canvasRef.current;
        if (!canvas) return;
        canvas.width = 400;
        canvas.height = 400;
        const context = canvas.getContext('webgpu');
        const format = navigator.gpu.getPreferredCanvasFormat();
        context.configure({ device, format, alphaMode: 'premultiplied' });
        const uniformBuffer = device.createBuffer({
          size: 16, // f32 time + pad + vec2<f32> resolution
          usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
        });
        gpu.current = {
          ...gpu.current,
          device, context, format, uniformBuffer,
          startTime: performance.now(),
        };
        device.lost.then(() => setStatus('error'));
        setStatus('ready');
      } catch (e) {
        console.error(e);
        setShaderError(String(e.message || e));
        setStatus('error');
      }
    })();
    return () => { cancelled = true; };
  }, [canvasRef]);

  // ── Compilación del shader (debounced) ──
  const compileShader = useCallback(async (wgsl) => {
    const { device, format, uniformBuffer } = gpu.current;
    if (!device) return;

    device.pushErrorScope('validation');
    const module = device.createShaderModule({ code: wgsl });
    const info = await module.getCompilationInfo();
    const errors = info.messages.filter(m => m.type === 'error');
    if (errors.length > 0) {
      const msg = errors.map(m => `línea ${m.lineNum}: ${m.message}`).join('\n');
      setShaderError(msg);
      await device.popErrorScope();
      return;
    }

    try {
      const pipeline = device.createRenderPipeline({
        layout: 'auto',
        vertex:   { module, entryPoint: 'vs' },
        fragment: { module, entryPoint: 'fs', targets: [{ format }] },
        primitive:{ topology: 'triangle-list' },
      });
      const bindGroup = device.createBindGroup({
        layout: pipeline.getBindGroupLayout(0),
        entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
      });
      gpu.current.pipeline = pipeline;
      gpu.current.bindGroup = bindGroup;
      setShaderError(null);
    } catch (e) {
      setShaderError(String(e.message || e));
    }
    const err = await device.popErrorScope();
    if (err) setShaderError(err.message);
  }, []);

  useEffect(() => {
    if (status !== 'ready') return;
    const id = setTimeout(() => compileShader(code), 180);
    return () => clearTimeout(id);
  }, [code, status, compileShader]);

  // ── Render loop ──
  useEffect(() => {
    if (status !== 'ready') return;
    let running = true;
    let frames = 0;
    let lastFpsSample = performance.now();

    const loop = () => {
      if (!running) return;
      const { device, context, pipeline, bindGroup, uniformBuffer, startTime } = gpu.current;
      const canvas = canvasRef.current;

      if (device && context && pipeline && bindGroup && canvas) {
        // redimensionado
        const dpr = Math.min(window.devicePixelRatio || 1, 2);
        const w = Math.max(1, Math.floor(canvas.clientWidth * dpr));
        const h = Math.max(1, Math.floor(canvas.clientHeight * dpr));
        if (canvas.width !== w || canvas.height !== h) {
          canvas.width = w;
          canvas.height = h;
        }

        const t = (performance.now() - startTime) / 1000;
        const data = new Float32Array([t, 0, canvas.width, canvas.height]);
        device.queue.writeBuffer(uniformBuffer, 0, data);

        const encoder = device.createCommandEncoder();
        const pass = encoder.beginRenderPass({
          colorAttachments: [{
            view: context.getCurrentTexture().createView(),
            clearValue: { r: 0, g: 0, b: 0, a: 1 },
            loadOp: 'clear',
            storeOp: 'store',
          }],
        });
        pass.setPipeline(pipeline);
        pass.setBindGroup(0, bindGroup);
        pass.draw(3);
        pass.end();
        device.queue.submit([encoder.finish()]);

        frames++;
        const now = performance.now();
        if (now - lastFpsSample >= 500) {
          setFps(Math.round((frames * 1000) / (now - lastFpsSample)));
          setTimeT(t);
          frames = 0;
          lastFpsSample = now;
        }
      }
      requestAnimationFrame(loop);
    };
    requestAnimationFrame(loop);
    return () => { running = false; };
  }, [status, canvasRef]);

  const resetTime = useCallback(() => {
    gpu.current.startTime = performance.now();
  }, []);

  return { status, shaderError, fps, timeT, resetTime };
}

// ─────────────────────────────────────────────────────────────────
// APP
// ─────────────────────────────────────────────────────────────────
export default function App() {
  const canvasRef = useRef(null);
  const [code, setCode] = useState(PRESETS.plasma);
  const [activePreset, setActivePreset] = useState('plasma');
  const { status, shaderError, fps, timeT, resetTime } = useWebGPU(canvasRef, code);

  const loadPreset = (key) => {
    setActivePreset(key);
    setCode(PRESETS[key]);
  };

  const ACCENT = '#5eead4'; // teal-300

  return (
    <div className="w-full min-h-screen text-neutral-200" style={{
      background: 'radial-gradient(ellipse at top, #0f1419 0%, #06080a 60%, #030506 100%)',
      fontFamily: "'JetBrains Mono', ui-monospace, monospace",
    }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
        .font-display { font-family: 'Fraunces', serif; font-optical-sizing: auto; }
        .font-mono { font-family: 'JetBrains Mono', ui-monospace, monospace; }
        .grid-bg {
          background-image:
            linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
            linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
          background-size: 24px 24px;
        }
        ::-webkit-scrollbar { width: 8px; height: 8px; }
        ::-webkit-scrollbar-track { background: transparent; }
        ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 4px; }
        ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.16); }
        textarea.wgsl {
          tab-size: 2;
          -moz-tab-size: 2;
        }
      `}</style>

      <div className="grid-bg min-h-screen">
        {/* HEADER */}
        <header className="px-6 pt-6 pb-5 border-b border-white/5">
          <div className="flex items-end justify-between flex-wrap gap-4">
            <div>
              <div className="flex items-baseline gap-3">
                <span className="font-mono text-[10px] uppercase tracking-[0.3em] text-neutral-500">lab · 002</span>
                <span className="font-mono text-[10px] text-neutral-700">/</span>
                <span className="font-mono text-[10px] text-neutral-500">shaders · webgpu</span>
                <span className="font-mono text-[10px] text-neutral-700">/</span>
                <StatusBadge status={status} />
              </div>
              <h1 className="font-display text-3xl font-light tracking-tight mt-1.5">
                laboratorio de <em className="italic" style={{color: ACCENT}}>píxels</em>
              </h1>
              <p className="font-mono text-[11px] text-neutral-500 mt-1.5 max-w-2xl">
                editor WGSL en vivo · cada tecla recompila el fragment shader · el uniform block expone <span style={{color: ACCENT}}>time</span> y <span style={{color: ACCENT}}>resolution</span>
              </p>
            </div>
            <div className="flex items-center gap-3">
              <span className="font-mono text-[10px] text-neutral-600">t = {timeT.toFixed(2)}s</span>
              <span className="font-mono text-[10px] text-neutral-600">{fps} fps</span>
              <button
                onClick={resetTime}
                className="flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-wider text-neutral-500 hover:text-neutral-200 border border-white/10 hover:border-white/30 px-3 py-1.5 rounded transition-colors"
              >
                <RotateCcw size={11} /> reset t
              </button>
            </div>
          </div>
        </header>

        {/* MAIN · 2 columnas: canvas | editor */}
        <main style={{
          display: 'grid',
          gridTemplateColumns: 'minmax(280px, 1fr) minmax(340px, 1fr)',
          minHeight: 'calc(100vh - 110px)',
          overflowX: 'auto',
        }}>
          {/* IZQUIERDA · CANVAS */}
          <section className="p-4 md:p-6 border-r border-white/5 flex flex-col gap-3" style={{maxHeight: 'calc(100vh - 110px)'}}>
            <div className="flex items-baseline gap-3">
              <span className="font-mono text-[10px] uppercase tracking-[0.25em] text-neutral-500">canvas</span>
              <span className="font-mono text-[9px] text-neutral-600">fragment shader · fullscreen triangle</span>
            </div>
            <div
              className="flex-1 rounded-xl border border-white/10 overflow-hidden relative"
              style={{
                background: '#000',
                boxShadow: `0 0 0 1px ${ACCENT}10, 0 20px 60px -30px ${ACCENT}30`,
                minHeight: '320px',
              }}
            >
              <canvas
                ref={canvasRef}
                className="block"
                style={{width: '100%', height: '100%'}}
              />
              {status !== 'ready' && <StatusOverlay status={status} error={shaderError} />}
            </div>
            <div className="font-mono text-[9px] text-neutral-600">
              consejo · el vertex shader genera un triángulo que cubre toda la pantalla. todo el trabajo interesante pasa en el <span style={{color: ACCENT}}>fragment</span>.
            </div>
          </section>

          {/* DERECHA · EDITOR */}
          <section className="p-4 md:p-6 flex flex-col gap-3" style={{maxHeight: 'calc(100vh - 110px)'}}>
            {/* presets */}
            <div>
              <div className="font-mono text-[10px] uppercase tracking-[0.25em] text-neutral-500 mb-2">presets</div>
              <div className="flex flex-wrap gap-1.5">
                {PRESET_ORDER.map(p => {
                  const active = p.key === activePreset;
                  return (
                    <button
                      key={p.key}
                      onClick={() => loadPreset(p.key)}
                      className="font-mono text-[11px] px-2.5 py-1 rounded transition-all"
                      style={{
                        background: active ? `${ACCENT}18` : 'rgba(255,255,255,0.02)',
                        border: `1px solid ${active ? ACCENT + '60' : 'rgba(255,255,255,0.08)'}`,
                        color: active ? ACCENT : '#a3a3a3',
                      }}
                      title={p.hint}
                    >
                      {p.label}
                    </button>
                  );
                })}
              </div>
            </div>

            {/* editor */}
            <div className="flex flex-col flex-1 gap-2 min-h-0">
              <div className="flex items-baseline gap-3">
                <span className="font-mono text-[10px] uppercase tracking-[0.25em] text-neutral-500">fuente · wgsl</span>
                {shaderError ? (
                  <span className="font-mono text-[10px] text-rose-400 flex items-center gap-1">
                    <AlertCircle size={10} /> error de compilación
                  </span>
                ) : (
                  <span className="font-mono text-[10px]" style={{color: ACCENT}}>✓ compilado</span>
                )}
              </div>
              <textarea
                className="wgsl flex-1 w-full rounded-lg p-3 font-mono text-[12px] leading-relaxed resize-none outline-none"
                value={code}
                onChange={(e) => setCode(e.target.value)}
                spellCheck={false}
                style={{
                  background: 'rgba(255,255,255,0.02)',
                  border: `1px solid ${shaderError ? '#f43f5e40' : 'rgba(255,255,255,0.08)'}`,
                  color: '#d4d4d4',
                  minHeight: '300px',
                }}
              />
            </div>

            {/* errores */}
            {shaderError && (
              <div className="rounded-lg p-3" style={{
                background: '#f43f5e0a',
                border: '1px solid #f43f5e30',
              }}>
                <div className="font-mono text-[10px] uppercase tracking-[0.2em] text-rose-400 mb-1.5 flex items-center gap-1.5">
                  <AlertCircle size={11} /> compilación fallida
                </div>
                <pre className="font-mono text-[11px] text-rose-300 whitespace-pre-wrap leading-relaxed">{shaderError}</pre>
              </div>
            )}

            <div className="font-mono text-[9px] text-neutral-600 leading-relaxed border-l-2 border-neutral-800 pl-3">
              uniforms · <span style={{color: ACCENT}}>u.time</span> (f32, segundos desde inicio) · <span style={{color: ACCENT}}>u.resolution</span> (vec2&lt;f32&gt;, px). el último pipeline válido se mantiene hasta que la próxima compilación tenga éxito.
            </div>
          </section>
        </main>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Sub-componentes
// ─────────────────────────────────────────────────────────────────
function StatusBadge({ status }) {
  const map = {
    init:        { color: '#fbbf24', label: 'inicializando' },
    ready:       { color: '#5eead4', label: 'activo' },
    unsupported: { color: '#f43f5e', label: 'sin webgpu' },
    error:       { color: '#f43f5e', label: 'error' },
  };
  const s = map[status] || map.init;
  return (
    <span className="flex items-center gap-1.5">
      <span className="w-1.5 h-1.5 rounded-full" style={{background: s.color, boxShadow: `0 0 8px ${s.color}`}} />
      <span className="font-mono text-[10px]" style={{color: s.color}}>{s.label}</span>
    </span>
  );
}

function StatusOverlay({ status, error }) {
  if (status === 'ready') return null;
  return (
    <div className="absolute inset-0 flex items-center justify-center p-6 text-center" style={{background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(4px)'}}>
      {status === 'init' && (
        <div className="font-mono text-[11px] text-neutral-400">inicializando adaptador GPU…</div>
      )}
      {status === 'unsupported' && (
        <div className="max-w-sm">
          <div className="font-display text-xl italic mb-2" style={{color: '#f43f5e'}}>WebGPU no disponible</div>
          <div className="font-mono text-[10px] text-neutral-400 leading-relaxed">
            este navegador no expone <code>navigator.gpu</code>. prueba con chrome/edge recientes, o safari 18+, o activa el flag en firefox nightly.
          </div>
        </div>
      )}
      {status === 'error' && (
        <div className="max-w-sm">
          <div className="font-display text-xl italic mb-2" style={{color: '#f43f5e'}}>error de inicialización</div>
          <div className="font-mono text-[10px] text-neutral-400 whitespace-pre-wrap">{error}</div>
        </div>
      )}
    </div>
  );
}
