import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { X, AlertCircle, RotateCcw, ChevronDown, ChevronRight, Trash2, GripVertical } from 'lucide-react';

// ═══════════════════════════════════════════════════════════════════
// CATÁLOGO DE NODOS — cada uno emite un snippet WGSL
// Convención: cada nodo compila a `fn node_<idx>(c, uv) -> vec4<f32>`
// Parámetros: hasta 4 floats empaquetados en u.params[idx] (vec4<f32>)
// ═══════════════════════════════════════════════════════════════════

const MAX_NODES = 16;
const ACCENT = '#5eead4';
const GEN_COLOR = '#5eead4';
const OP_COLOR  = '#c4b5fd';

const NODES = {
  // ── GENERADORES (ignoran c, producen nuevo color) ──
  solid: {
    kind: 'gen', label: 'solid', desc: 'color constante',
    params: [
      { k: 'r', label: 'r', min: 0, max: 1, d: 0.35, step: 0.01 },
      { k: 'g', label: 'g', min: 0, max: 1, d: 0.25, step: 0.01 },
      { k: 'b', label: 'b', min: 0, max: 1, d: 0.55, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  return vec4<f32>(p.x, p.y, p.z, 1.0);`,
  },

  gradient: {
    kind: 'gen', label: 'gradient', desc: 'gradiente en ángulo',
    params: [
      { k: 'angle', label: 'ángulo', min: 0, max: 6.2832, d: 0.8, step: 0.01 },
      { k: 'hue',   label: 'tono',   min: 0, max: 1,      d: 0.5, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let dir = vec2<f32>(cos(p.x), sin(p.x));
  let t = dot(uv - 0.5, dir) + 0.5;
  let col = 0.5 + 0.5 * cos(6.28318 * (p.y + vec3<f32>(0.0, 0.33, 0.67) + t));
  return vec4<f32>(col, 1.0);`,
  },

  plasma: {
    kind: 'gen', label: 'plasma', desc: 'onda trigonométrica',
    params: [
      { k: 'speed', label: 'velocidad', min: 0,   max: 3,  d: 1,   step: 0.01 },
      { k: 'scale', label: 'escala',    min: 0.5, max: 10, d: 2,   step: 0.1 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let t = u.time * p.x;
  let col = 0.5 + 0.5 * cos(t + uv.xyx * p.y + vec3<f32>(0.0, 2.0, 4.0));
  return vec4<f32>(col, 1.0);`,
  },

  checker: {
    kind: 'gen', label: 'checker', desc: 'tablero rotando',
    params: [
      { k: 'scale', label: 'escala',    min: 1,  max: 30, d: 8,    step: 0.5 },
      { k: 'rot',   label: 'rotación',  min: -2, max: 2,  d: 0.25, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let aspect = u.resolution.x / u.resolution.y;
  let q0 = vec2<f32>((uv.x - 0.5) * aspect, uv.y - 0.5);
  let a = u.time * p.y;
  let rm = mat2x2<f32>(cos(a), -sin(a), sin(a), cos(a));
  let q = rm * q0 * p.x;
  let chk = (floor(q.x) + floor(q.y)) - 2.0 * floor((floor(q.x) + floor(q.y)) * 0.5);
  return vec4<f32>(vec3<f32>(chk), 1.0);`,
  },

  circle: {
    kind: 'gen', label: 'circle', desc: 'sdf de círculo',
    params: [
      { k: 'radius', label: 'radio',    min: 0,     max: 1,    d: 0.4,   step: 0.01 },
      { k: 'soft',   label: 'suavidad', min: 0.001, max: 0.1,  d: 0.008, step: 0.001 },
      { k: 'pulse',  label: 'pulso',    min: 0,     max: 1,    d: 0.1,   step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let aspect = u.resolution.x / u.resolution.y;
  let pos = vec2<f32>((uv.x - 0.5) * aspect, uv.y - 0.5);
  let r = p.x + p.z * 0.15 * sin(u.time * 2.0);
  let d = length(pos) - r;
  let fill = smoothstep(p.y, -p.y, d);
  return mix(c, vec4<f32>(1.0), fill);`,
  },

  stripes: {
    kind: 'gen', label: 'stripes', desc: 'rayas animadas',
    params: [
      { k: 'freq',  label: 'frecuencia', min: 1,  max: 80, d: 20, step: 0.5 },
      { k: 'speed', label: 'velocidad',  min: -5, max: 5,  d: 1,  step: 0.05 },
      { k: 'angle', label: 'ángulo',     min: 0,  max: 3.1416, d: 0.5, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let dir = vec2<f32>(cos(p.z), sin(p.z));
  let x = dot(uv, dir);
  let v = 0.5 + 0.5 * sin(x * p.x + u.time * p.y);
  return vec4<f32>(vec3<f32>(v), 1.0);`,
  },

  noise: {
    kind: 'gen', label: 'noise', desc: 'hash pseudo-aleatorio',
    params: [
      { k: 'scale', label: 'escala', min: 1, max: 200, d: 80, step: 1 },
      { k: 'seed',  label: 'seed',  min: 0, max: 100, d: 7,  step: 1 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let q = floor(uv * p.x + p.y);
  let h = fract(sin(dot(q, vec2<f32>(12.9898, 78.233))) * 43758.5453);
  return vec4<f32>(vec3<f32>(h), 1.0);`,
  },

  // ── OPERADORES (transforman c) ──
  invert: {
    kind: 'op', label: 'invert', desc: '1 − rgb',
    params: [],
    body: () => `
  return vec4<f32>(1.0 - c.rgb, c.a);`,
  },

  gamma: {
    kind: 'op', label: 'gamma', desc: 'pow(rgb, γ)',
    params: [
      { k: 'g', label: 'γ', min: 0.1, max: 5, d: 1, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let g = max(0.01, p.x);
  return vec4<f32>(pow(max(c.rgb, vec3<f32>(0.0)), vec3<f32>(g)), c.a);`,
  },

  brightness: {
    kind: 'op', label: 'brightness', desc: 'rgb + v',
    params: [
      { k: 'v', label: 'valor', min: -1, max: 1, d: 0, step: 0.01 },
    ],
    body: (i) => `
  return vec4<f32>(c.rgb + vec3<f32>(u.params[${i}].x), c.a);`,
  },

  contrast: {
    kind: 'op', label: 'contrast', desc: '(rgb − 0.5)·k + 0.5',
    params: [
      { k: 'k', label: 'k', min: 0, max: 3, d: 1, step: 0.01 },
    ],
    body: (i) => `
  return vec4<f32>((c.rgb - vec3<f32>(0.5)) * u.params[${i}].x + vec3<f32>(0.5), c.a);`,
  },

  saturate: {
    kind: 'op', label: 'saturate', desc: 'lerp(luma, rgb, s)',
    params: [
      { k: 's', label: 's', min: 0, max: 2, d: 1, step: 0.01 },
    ],
    body: (i) => `
  let luma = dot(c.rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
  return vec4<f32>(mix(vec3<f32>(luma), c.rgb, u.params[${i}].x), c.a);`,
  },

  hueShift: {
    kind: 'op', label: 'hue shift', desc: 'rotar matiz',
    params: [
      { k: 'h', label: 'h', min: 0, max: 1, d: 0, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let k = vec3<f32>(0.57735);
  let ca = cos(p.x * 6.2832);
  let sa = sin(p.x * 6.2832);
  let rot = c.rgb * ca + cross(k, c.rgb) * sa + k * dot(k, c.rgb) * (1.0 - ca);
  return vec4<f32>(rot, c.a);`,
  },

  tint: {
    kind: 'op', label: 'tint', desc: 'rgb × tinte',
    params: [
      { k: 'r', label: 'r', min: 0, max: 2, d: 1, step: 0.01 },
      { k: 'g', label: 'g', min: 0, max: 2, d: 1, step: 0.01 },
      { k: 'b', label: 'b', min: 0, max: 2, d: 1, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  return vec4<f32>(c.rgb * vec3<f32>(p.x, p.y, p.z), c.a);`,
  },

  posterize: {
    kind: 'op', label: 'posterize', desc: 'cuantizar a N niveles',
    params: [
      { k: 'levels', label: 'niveles', min: 2, max: 16, d: 5, step: 1 },
    ],
    body: (i) => `
  let n = max(2.0, u.params[${i}].x);
  return vec4<f32>(floor(c.rgb * n) / n, c.a);`,
  },

  vignette: {
    kind: 'op', label: 'vignette', desc: 'oscurecer bordes',
    params: [
      { k: 'strength', label: 'fuerza', min: 0, max: 2,   d: 1,   step: 0.01 },
      { k: 'radius',   label: 'radio',  min: 0, max: 1.4, d: 0.5, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let d = length(uv - vec2<f32>(0.5));
  let v = 1.0 - smoothstep(p.y, p.y + 0.3, d) * p.x;
  return vec4<f32>(c.rgb * v, c.a);`,
  },

  ripple: {
    kind: 'op', label: 'ripple', desc: 'modular brillo con ondas',
    params: [
      { k: 'freq',  label: 'frecuencia', min: 1,  max: 100, d: 30,   step: 1 },
      { k: 'amp',   label: 'amplitud',   min: 0,  max: 1,   d: 0.2,  step: 0.01 },
      { k: 'speed', label: 'velocidad',  min: -5, max: 5,   d: 2,    step: 0.05 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  let d = length(uv - vec2<f32>(0.5));
  let w = sin(d * p.x - u.time * p.z) * p.y;
  return vec4<f32>(c.rgb * (1.0 + w), c.a);`,
  },

  pulse: {
    kind: 'op', label: 'pulse', desc: 'multiplicar por onda',
    params: [
      { k: 'freq',   label: 'frecuencia', min: 0, max: 10, d: 2,   step: 0.05 },
      { k: 'amount', label: 'cantidad',   min: 0, max: 1,  d: 0.3, step: 0.01 },
    ],
    body: (i) => `
  let p = u.params[${i}];
  return vec4<f32>(c.rgb * (1.0 + p.y * sin(u.time * p.x)), c.a);`,
  },
};

const NODES_BY_KIND = {
  gen: Object.entries(NODES).filter(([, v]) => v.kind === 'gen').map(([k, v]) => ({ name: k, ...v })),
  op:  Object.entries(NODES).filter(([, v]) => v.kind === 'op' ).map(([k, v]) => ({ name: k, ...v })),
};

// ═══════════════════════════════════════════════════════════════════
// Compilador: DAG → WGSL
// ═══════════════════════════════════════════════════════════════════

function compileDagToWGSL(pipeline) {
  const safePipeline = pipeline.slice(0, MAX_NODES);
  const fns = safePipeline.map((step, idx) => {
    const def = NODES[step.name];
    return `fn node_${idx}(c: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {${def.body(idx)}
}`;
  }).join('\n\n');

  const chain = safePipeline.length === 0
    ? '  // pipeline vacío · fondo por defecto\n  c = vec4<f32>(0.04, 0.04, 0.06, 1.0);'
    : safePipeline.map((_, idx) => `  c = node_${idx}(c, uv);`).join('\n');

  return `struct Uniforms {
  time: f32,
  _pad: f32,
  resolution: vec2<f32>,
  params: array<vec4<f32>, ${MAX_NODES}>,
};

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

@vertex
fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
  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);
}

${fns}

@fragment
fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  let uv = pos.xy / u.resolution;
  var c = vec4<f32>(0.0, 0.0, 0.0, 1.0);
${chain}
  return c;
}
`;
}

// ═══════════════════════════════════════════════════════════════════
// Uniform buffer: escribe los valores actuales de params + time/res
// ═══════════════════════════════════════════════════════════════════

const UNIFORM_FLOATS = 4 + MAX_NODES * 4;   // header (4) + params (MAX_NODES × 4)
const UNIFORM_BYTES  = UNIFORM_FLOATS * 4;  // 272 bytes

function writeUniforms(device, buffer, time, width, height, pipeline) {
  const data = new Float32Array(UNIFORM_FLOATS);
  data[0] = time;
  data[1] = 0;
  data[2] = width;
  data[3] = height;
  for (let i = 0; i < Math.min(pipeline.length, MAX_NODES); i++) {
    const step = pipeline[i];
    const def = NODES[step.name];
    const offset = 4 + i * 4;
    for (let j = 0; j < 4; j++) {
      const p = def.params[j];
      data[offset + j] = p ? (step.params[p.k] ?? p.d) : 0;
    }
  }
  device.queue.writeBuffer(buffer, 0, data);
}

// ═══════════════════════════════════════════════════════════════════
// Hook WebGPU + compilación de DAG
// ═══════════════════════════════════════════════════════════════════

function useWebGPUDag(canvasRef, pipeline, topologyKey) {
  const gpu = useRef({
    device: null, context: null, format: null,
    pipeline: null, bindGroup: null, uniformBuffer: null,
    startTime: 0,
  });
  const pipelineRef = useRef(pipeline);
  useEffect(() => { pipelineRef.current = pipeline; }, [pipeline]);

  const [status, setStatus] = useState('init');
  const [shaderError, setShaderError] = useState(null);
  const [fps, setFps] = useState(0);

  // ── Init GPU ──
  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: UNIFORM_BYTES,
          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]);

  // ── Recompilar shader (solo en cambios de topología) ──
  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) {
      setShaderError(errors.map(m => `línea ${m.lineNum}: ${m.message}`).join('\n'));
      await device.popErrorScope();
      return;
    }
    try {
      const p = device.createRenderPipeline({
        layout: 'auto',
        vertex:   { module, entryPoint: 'vs' },
        fragment: { module, entryPoint: 'fs', targets: [{ format }] },
        primitive:{ topology: 'triangle-list' },
      });
      const bg = device.createBindGroup({
        layout: p.getBindGroupLayout(0),
        entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
      });
      gpu.current.pipeline = p;
      gpu.current.bindGroup = bg;
      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 wgsl = compileDagToWGSL(pipelineRef.current);
    compileShader(wgsl);
  }, [topologyKey, status, compileShader]);

  // ── Render loop (lee params actuales cada frame via ref) ──
  useEffect(() => {
    if (status !== 'ready') return;
    let running = true;
    let frames = 0;
    let lastFpsSample = performance.now();

    const loop = () => {
      if (!running) return;
      const { device, context, pipeline: gpuPipe, bindGroup, uniformBuffer, startTime } = gpu.current;
      const canvas = canvasRef.current;
      if (device && context && gpuPipe && bindGroup && canvas) {
        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;
        writeUniforms(device, uniformBuffer, t, canvas.width, canvas.height, pipelineRef.current);

        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(gpuPipe);
        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)));
          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, resetTime };
}

// ═══════════════════════════════════════════════════════════════════
// Utilidades
// ═══════════════════════════════════════════════════════════════════
let _uid = 0;
const uid = () => `n${++_uid}_${Date.now().toString(36)}`;
function defaultParams(name) {
  const d = NODES[name];
  return Object.fromEntries((d.params || []).map(p => [p.k, p.d]));
}

// ═══════════════════════════════════════════════════════════════════
// Sub-componentes
// ═══════════════════════════════════════════════════════════════════

function PaletteItem({ node, color, onDragStart, onClick }) {
  return (
    <div
      draggable
      onDragStart={(e) => {
        e.dataTransfer.setData('text/node-name', node.name);
        e.dataTransfer.effectAllowed = 'copy';
        onDragStart?.();
      }}
      onClick={onClick}
      className="group flex items-center gap-2 px-2 py-1 rounded cursor-grab active:cursor-grabbing select-none transition-colors hover:bg-neutral-800/60"
      style={{ borderLeft: `2px solid ${color}` }}
      title={node.desc}
    >
      <span className="font-mono text-[11px] font-medium flex-1" style={{ color }}>{node.label}</span>
      <span className="font-mono text-[8px] text-neutral-600 opacity-0 group-hover:opacity-100 transition-opacity uppercase tracking-wider">drag</span>
    </div>
  );
}

function ParamSlider({ param, value, onChange, color }) {
  const display = param.step >= 1 ? Math.round(value) : Number(value).toFixed(param.step < 0.1 ? 3 : 2);
  return (
    <div className="flex items-center gap-2">
      <span className="font-mono text-[10px] text-neutral-400 w-14 shrink-0">{param.label}</span>
      <input
        type="range"
        min={param.min} max={param.max} step={param.step}
        value={value}
        onChange={(e) => onChange(Number(e.target.value))}
        className="flex-1"
        style={{ accentColor: color, minWidth: '60px' }}
      />
      <span className="font-mono text-[10px] text-neutral-500 w-10 text-right tabular-nums shrink-0">{display}</span>
    </div>
  );
}

function PipelineNode({ step, index, onRemove, onParamChange, onDragStart, onDrop, isDragging }) {
  const def = NODES[step.name];
  const color = def.kind === 'gen' ? GEN_COLOR : OP_COLOR;
  return (
    <div
      draggable
      onDragStart={(e) => {
        e.dataTransfer.setData('text/reorder-index', String(index));
        e.dataTransfer.effectAllowed = 'move';
        onDragStart?.(index);
      }}
      onDragOver={(e) => e.preventDefault()}
      onDrop={(e) => onDrop?.(e, index)}
      className="rounded-lg transition-all"
      style={{
        background: `linear-gradient(180deg, ${color}14 0%, ${color}06 100%)`,
        border: `1px solid ${color}30`,
        opacity: isDragging ? 0.4 : 1,
      }}
    >
      <div className="flex items-center gap-2 px-2.5 py-1.5 border-b" style={{ borderColor: `${color}20` }}>
        <GripVertical size={11} className="text-neutral-600 cursor-grab shrink-0" />
        <span className="font-mono text-[9px] uppercase tracking-wider text-neutral-500 shrink-0">
          {def.kind} · {index}
        </span>
        <span className="font-mono text-xs font-semibold flex-1 truncate" style={{ color }}>{def.label}</span>
        <button
          onClick={() => onRemove(index)}
          className="text-neutral-500 hover:text-neutral-200 p-0.5"
          aria-label="Eliminar"
        >
          <X size={11} />
        </button>
      </div>
      {def.params.length > 0 && (
        <div className="px-2.5 py-2 flex flex-col gap-1.5">
          {def.params.map(p => (
            <ParamSlider
              key={p.k}
              param={p}
              value={step.params[p.k] ?? p.d}
              onChange={(v) => onParamChange(index, p.k, v)}
              color={color}
            />
          ))}
        </div>
      )}
      {def.params.length === 0 && (
        <div className="px-2.5 py-1.5 font-mono text-[9px] text-neutral-600 italic">sin parámetros</div>
      )}
    </div>
  );
}

function StatusBadge({ status }) {
  const map = {
    init:        { color: '#fbbf24', label: 'inicializando' },
    ready:       { color: ACCENT,    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">
            chrome/edge 113+, safari 18+, o firefox nightly con flag. si estás en un navegador compatible, prueba abrir el artifact en pestaña nueva.
          </div>
        </div>
      )}
      {status === 'error' && (
        <div className="max-w-sm">
          <div className="font-display text-xl italic mb-2" style={{ color: '#f43f5e' }}>error</div>
          <div className="font-mono text-[10px] text-neutral-400 whitespace-pre-wrap">{error}</div>
        </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════
// APP
// ═══════════════════════════════════════════════════════════════════

export default function App() {
  const canvasRef = useRef(null);
  const [pipeline, setPipeline] = useState(() => [
    { id: uid(), name: 'plasma',   params: defaultParams('plasma') },
    { id: uid(), name: 'vignette', params: defaultParams('vignette') },
  ]);
  const [dragIndex, setDragIndex] = useState(null);
  const [wgslOpen, setWgslOpen] = useState(false);

  // La clave de topología cambia SOLO cuando cambia la estructura (no params)
  const topologyKey = useMemo(() => pipeline.map(s => s.name).join('|'), [pipeline]);

  const { status, shaderError, fps, resetTime } = useWebGPUDag(canvasRef, pipeline, topologyKey);

  const generatedWGSL = useMemo(() => compileDagToWGSL(pipeline), [topologyKey]);

  const addNode = (name) => {
    if (pipeline.length >= MAX_NODES) return;
    setPipeline(p => [...p, { id: uid(), name, params: defaultParams(name) }]);
  };
  const removeNode = (idx) => setPipeline(p => p.filter((_, i) => i !== idx));
  const changeParam = (idx, key, value) => {
    setPipeline(p => p.map((s, i) => i === idx ? { ...s, params: { ...s.params, [key]: value } } : s));
  };

  const handleDropOnZone = (e) => {
    e.preventDefault();
    const nodeName = e.dataTransfer.getData('text/node-name');
    const reorderIdx = e.dataTransfer.getData('text/reorder-index');
    if (nodeName && NODES[nodeName]) {
      addNode(nodeName);
    } else if (reorderIdx !== '') {
      const from = Number(reorderIdx);
      setPipeline(p => {
        const copy = [...p];
        const [m] = copy.splice(from, 1);
        copy.push(m);
        return copy;
      });
    }
    setDragIndex(null);
  };

  const handleDropOnNode = (e, targetIdx) => {
    e.preventDefault();
    e.stopPropagation();
    const nodeName = e.dataTransfer.getData('text/node-name');
    const reorderIdx = e.dataTransfer.getData('text/reorder-index');
    if (nodeName && NODES[nodeName]) {
      setPipeline(p => {
        const copy = [...p];
        copy.splice(targetIdx, 0, { id: uid(), name: nodeName, params: defaultParams(nodeName) });
        return copy;
      });
    } else if (reorderIdx !== '') {
      const from = Number(reorderIdx);
      if (from === targetIdx) return;
      setPipeline(p => {
        const copy = [...p];
        const [m] = copy.splice(from, 1);
        const adj = from < targetIdx ? targetIdx - 1 : targetIdx;
        copy.splice(adj, 0, m);
        return copy;
      });
    }
    setDragIndex(null);
  };

  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); }
        input[type="range"] { height: 4px; }
      `}</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 · 003</span>
                <span className="font-mono text-[10px] text-neutral-700">/</span>
                <span className="font-mono text-[10px] text-neutral-500">shader dag · 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">
                composición de <em className="italic" style={{ color: ACCENT }}>fragmentos</em>
              </h1>
              <p className="font-mono text-[11px] text-neutral-500 mt-1.5 max-w-2xl">
                cada nodo emite un snippet WGSL · el DAG se concatena en un único fragment shader · los sliders actualizan uniforms sin recompilar
              </p>
            </div>
            <div className="flex items-center gap-3">
              <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>
              <button
                onClick={() => setPipeline([])}
                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"
              >
                <Trash2 size={11} /> vaciar
              </button>
            </div>
          </div>
        </header>

        {/* MAIN · 3 columnas: paleta | pipeline+sliders | canvas+wgsl */}
        <main style={{
          display: 'grid',
          gridTemplateColumns: '180px minmax(280px, 1fr) minmax(320px, 420px)',
          minHeight: 'calc(100vh - 110px)',
          overflowX: 'auto',
        }}>
          {/* ── PALETA ── */}
          <aside className="border-r border-white/5 p-4 overflow-y-auto" style={{ maxHeight: 'calc(100vh - 110px)' }}>
            <div className="font-mono text-[10px] uppercase tracking-[0.25em] text-neutral-500 mb-4">primitivas</div>

            <section className="mb-5">
              <div className="flex items-center gap-2 mb-2 px-1">
                <span className="w-1.5 h-1.5 rounded-full" style={{ background: GEN_COLOR, boxShadow: `0 0 8px ${GEN_COLOR}` }} />
                <span className="font-display text-sm italic" style={{ color: GEN_COLOR }}>generadores</span>
              </div>
              <div className="font-mono text-[9px] text-neutral-600 mb-2 px-1">producen color · ignoran c</div>
              <div className="flex flex-col gap-0.5">
                {NODES_BY_KIND.gen.map(n => (
                  <PaletteItem key={n.name} node={n} color={GEN_COLOR} onClick={() => addNode(n.name)} />
                ))}
              </div>
            </section>

            <section>
              <div className="flex items-center gap-2 mb-2 px-1">
                <span className="w-1.5 h-1.5 rounded-full" style={{ background: OP_COLOR, boxShadow: `0 0 8px ${OP_COLOR}` }} />
                <span className="font-display text-sm italic" style={{ color: OP_COLOR }}>operadores</span>
              </div>
              <div className="font-mono text-[9px] text-neutral-600 mb-2 px-1">transforman c · punto a punto</div>
              <div className="flex flex-col gap-0.5">
                {NODES_BY_KIND.op.map(n => (
                  <PaletteItem key={n.name} node={n} color={OP_COLOR} onClick={() => addNode(n.name)} />
                ))}
              </div>
            </section>

            <div className="mt-6 pt-4 border-t border-white/5 font-mono text-[9px] text-neutral-600 leading-relaxed">
              tip · click o drag para añadir. reordena arrastrando nodos del pipeline.
            </div>
          </aside>

          {/* ── PIPELINE (vertical, con sliders integrados) ── */}
          <section className="p-4 overflow-y-auto border-r border-white/5" style={{ maxHeight: 'calc(100vh - 110px)' }}>
            <div className="flex items-baseline gap-3 mb-3">
              <span className="font-mono text-[10px] uppercase tracking-[0.25em] text-neutral-500">pipeline</span>
              <span className="font-mono text-[10px] text-neutral-600">
                {pipeline.length}/{MAX_NODES} nodos
              </span>
            </div>

            <div
              onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }}
              onDrop={handleDropOnZone}
              className="rounded-xl border border-dashed border-white/10 p-3 transition-colors hover:border-white/20"
              style={{ background: 'rgba(255,255,255,0.015)', minHeight: '300px' }}
            >
              {pipeline.length === 0 ? (
                <div className="flex flex-col items-center justify-center py-16 text-center">
                  <div className="font-display text-lg italic text-neutral-500 mb-1">pipeline vacío</div>
                  <div className="font-mono text-[10px] text-neutral-600">arrastra una primitiva de la izquierda</div>
                </div>
              ) : (
                <div className="flex flex-col gap-2">
                  {pipeline.map((step, i) => (
                    <PipelineNode
                      key={step.id}
                      step={step}
                      index={i}
                      onRemove={removeNode}
                      onParamChange={changeParam}
                      onDragStart={setDragIndex}
                      onDrop={handleDropOnNode}
                      isDragging={dragIndex === i}
                    />
                  ))}
                </div>
              )}
            </div>

            <div className="mt-4 font-mono text-[9px] text-neutral-600 leading-relaxed border-l-2 border-neutral-800 pl-3">
              <span style={{ color: GEN_COLOR }}>gen</span> produce · <span style={{ color: OP_COLOR }}>op</span> transforma · el flujo es c ← node₀ ← node₁ ← … ← nodeₙ
            </div>
          </section>

          {/* ── CANVAS + WGSL ── */}
          <aside className="p-4 overflow-y-auto 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">output</span>
              <span className="font-mono text-[9px] text-neutral-600">fragment · fullscreen triangle</span>
            </div>
            <div
              className="rounded-xl border border-white/10 overflow-hidden relative"
              style={{
                aspectRatio: '1/1',
                background: '#000',
                boxShadow: `0 0 0 1px ${ACCENT}10, 0 20px 60px -30px ${ACCENT}30`,
              }}
            >
              <canvas ref={canvasRef} className="block" style={{ width: '100%', height: '100%' }} />
              {status !== 'ready' && <StatusOverlay status={status} error={shaderError} />}
            </div>

            {shaderError && status === 'ready' && (
              <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-[10px] text-rose-300 whitespace-pre-wrap leading-relaxed">{shaderError}</pre>
                <div className="font-mono text-[9px] text-neutral-500 mt-2">último pipeline válido sigue activo</div>
              </div>
            )}

            {/* WGSL viewer */}
            <div className="rounded-lg border border-white/5" style={{ background: 'rgba(255,255,255,0.015)' }}>
              <button
                onClick={() => setWgslOpen(o => !o)}
                className="w-full flex items-center gap-2 px-3 py-2 font-mono text-[10px] uppercase tracking-[0.2em] text-neutral-500 hover:text-neutral-300"
              >
                {wgslOpen ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
                wgsl generado
                <span className="ml-auto text-neutral-600 normal-case tracking-normal">{generatedWGSL.split('\n').length} líneas</span>
              </button>
              {wgslOpen && (
                <pre className="font-mono text-[10px] text-neutral-400 px-3 pb-3 overflow-auto leading-relaxed" style={{ maxHeight: '260px' }}>
{generatedWGSL}
                </pre>
              )}
            </div>

            <div className="font-mono text-[9px] text-neutral-600 leading-relaxed border-l-2 border-neutral-800 pl-3">
              los sliders escriben a <span style={{ color: ACCENT }}>u.params[idx]</span> sin recompilar · solo cambios de topología regeneran WGSL
            </div>
          </aside>
        </main>
      </div>
    </div>
  );
}
