'use client';

import { useEffect, useRef, useState } from 'react';

const CARD =
  'group relative grid place-items-center w-[300px] h-[400px] overflow-hidden rounded-[var(--pixel-card-radius,24px)] isolate outline-none select-none [-webkit-tap-highlight-color:transparent] [&>:not(canvas):not([data-rim])]:relative [&>:not(canvas):not([data-rim])]:z-[1]';

const SURFACE = {
  dark: 'bg-[linear-gradient(180deg,#17151c_0%,#0f0e13_100%)] shadow-[0_24px_48px_-24px_rgba(0,0,0,0.85),0_8px_18px_-10px_rgba(0,0,0,0.6)] focus-visible:shadow-[0_24px_48px_-24px_rgba(0,0,0,0.85),0_8px_18px_-10px_rgba(0,0,0,0.6),0_0_0_3px_rgba(127,127,140,0.45)]',
  light:
    'bg-[linear-gradient(180deg,#ffffff_0%,#f6f6f8_100%)] shadow-[0_24px_48px_-26px_rgba(24,24,27,0.32),0_8px_18px_-12px_rgba(24,24,27,0.18)] focus-visible:shadow-[0_24px_48px_-26px_rgba(24,24,27,0.32),0_8px_18px_-12px_rgba(24,24,27,0.18),0_0_0_3px_rgba(127,127,140,0.45)]'
};

const RIM = {
  dark: 'pointer-events-none absolute inset-0 z-[2] rounded-[inherit] transition-shadow duration-[260ms] shadow-[inset_0_0_0_1px_rgba(255,255,255,0.08),inset_0_1px_0_rgba(255,255,255,0.06)] group-data-[active]:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.16),inset_0_1px_0_rgba(255,255,255,0.06)]',
  light:
    'pointer-events-none absolute inset-0 z-[2] rounded-[inherit] transition-shadow duration-[260ms] shadow-[inset_0_0_0_1px_rgba(24,24,27,0.08),inset_0_1px_0_rgba(255,255,255,1)] group-data-[active]:shadow-[inset_0_0_0_1px_rgba(24,24,27,0.16),inset_0_1px_0_rgba(255,255,255,1)]'
};

const VARIANTS = {
  default: {
    gap: 5,
    speed: 35,
    colors: { dark: ['#f8fafc', '#f1f5f9', '#cbd5e1'], light: ['#18181b', '#52525b', '#a1a1aa'] },
    glow: { dark: '#cbd5e1', light: '#71717a' },
    noFocus: false
  },
  blue: {
    gap: 10,
    speed: 25,
    colors: { dark: ['#e0f2fe', '#7dd3fc', '#0ea5e9'], light: ['#0c4a6e', '#0369a1', '#0284c7'] },
    glow: { dark: '#0ea5e9', light: '#0284c7' },
    noFocus: false
  },
  yellow: {
    gap: 3,
    speed: 20,
    colors: { dark: ['#fef08a', '#fde047', '#eab308'], light: ['#713f12', '#a16207', '#ca8a04'] },
    glow: { dark: '#eab308', light: '#ca8a04' },
    noFocus: false
  },
  pink: {
    gap: 6,
    speed: 80,
    colors: { dark: ['#fecdd3', '#fda4af', '#e11d48'], light: ['#831843', '#be123c', '#e11d48'] },
    glow: { dark: '#e11d48', light: '#be123c' },
    noFocus: true
  }
};

const SHAPES = { square: 0, circle: 1, diamond: 2 };
const TRIGGERS = ['hover', 'click', 'always'];
const APPEAR = 0.26;
const EXIT = 0.22;

const clamp = (value, min, max) => Math.min(Math.max(value, min), max);

const smoothstep = (edge0, edge1, value) => {
  const t = clamp((value - edge0) / (edge1 - edge0), 0, 1);
  return t * t * (3 - 2 * t);
};

const easeOutBack = t => {
  const c = 1.9;
  const u = t - 1;
  return 1 + (c + 1) * u * u * u + c * u * u;
};

const toList = value =>
  (Array.isArray(value) ? value : String(value ?? '').split(',')).map(color => color.trim()).filter(Boolean);

const toRgb = (() => {
  let context = null;
  return color => {
    if (typeof document === 'undefined') return [255, 255, 255];
    if (!context) {
      const canvas = document.createElement('canvas');
      canvas.width = 1;
      canvas.height = 1;
      context = canvas.getContext('2d', { willReadFrequently: true });
    }
    if (!context) return [255, 255, 255];
    context.clearRect(0, 0, 1, 1);
    context.fillStyle = '#fff';
    context.fillStyle = color;
    context.fillRect(0, 0, 1, 1);
    const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
    return [r, g, b];
  };
})();

export default function PixelCard({
  variant = 'default',
  colors,
  gap,
  speed,
  pixelSize = 2,
  shape = 'square',
  origin = 'center',
  revealDuration = 600,
  spotlight = 0.5,
  glow = 0.5,
  glowColor,
  opacity = 1,
  fade = 0,
  trigger = 'hover',
  clickPulse = true,
  intro = true,
  introDelay = 0,
  noFocus,
  theme = 'dark',
  radius = 24,
  className = '',
  style,
  children
}) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  const engineRef = useRef(null);
  const settingsRef = useRef(null);
  const [active, setActive] = useState(false);

  const tone = theme === 'light' ? 'light' : 'dark';
  const preset = VARIANTS[variant] || VARIANTS.default;
  const palette = colors ? toList(colors) : preset.colors[tone];
  const focusOff = noFocus ?? preset.noFocus;

  settingsRef.current = {
    colors: palette.length ? palette : preset.colors[tone],
    gap: Math.max(2, Number(gap ?? preset.gap) || preset.gap),
    speed: clamp(Number(speed ?? preset.speed) || 0, 0, 100),
    pixelSize: Math.max(0.5, pixelSize),
    shape: SHAPES[shape] ?? 0,
    origin,
    revealDuration: Math.max(50, revealDuration),
    spotlight: clamp(spotlight, 0, 1),
    glow: clamp(glow, 0, 1),
    glowColor: toRgb(glowColor || preset.glow[tone]),
    opacity: clamp(opacity, 0, 1),
    fade: clamp(fade, 0, 1),
    trigger: TRIGGERS.includes(trigger) ? trigger : 'hover',
    clickPulse,
    intro,
    introDelay: Math.max(0, introDelay)
  };

  useEffect(() => {
    const container = containerRef.current;
    const canvas = canvasRef.current;
    if (!container || !canvas) return undefined;
    const ctx = canvas.getContext('2d');
    if (!ctx) return undefined;
    const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;

    const state = {
      alive: true,
      raf: 0,
      last: 0,
      time: 0,
      visible: true,
      width: 0,
      height: 0,
      ratio: 1,
      key: '',
      count: 0,
      x: new Float32Array(0),
      y: new Float32Array(0),
      tint: new Uint8Array(0),
      size: new Float32Array(0),
      phase: new Float32Array(0),
      rate: new Float32Array(0),
      jitter: new Float32Array(0),
      presence: new Float32Array(0),
      delay: new Float32Array(0),
      mask: new Float32Array(0),
      maskKey: '',
      mode: '',
      active: false,
      changedAt: 0,
      originX: 0,
      originY: 0,
      maxDistance: 1,
      pointerX: 0,
      pointerY: 0,
      spotX: 0,
      spotY: 0,
      spotVX: 0,
      spotVY: 0,
      spotPower: 0,
      hovering: false,
      pulses: [],
      dt: 1 / 60,
      releaseAt: -1,
      introAt: -1
    };

    const build = () => {
      const s = settingsRef.current;
      const rect = container.getBoundingClientRect();
      const width = Math.max(1, Math.round(rect.width));
      const height = Math.max(1, Math.round(rect.height));
      const ratio = Math.min(window.devicePixelRatio || 1, 2);
      const key = [width, height, ratio, s.gap, s.pixelSize, s.colors.join(',')].join('|');
      if (key === state.key) return;
      state.key = key;
      state.width = width;
      state.height = height;
      state.ratio = ratio;
      canvas.width = Math.round(width * ratio);
      canvas.height = Math.round(height * ratio);

      const columns = Math.floor((width - 1) / s.gap) + 1;
      const rows = Math.floor((height - 1) / s.gap) + 1;
      const offsetX = (width - (columns - 1) * s.gap) / 2;
      const offsetY = (height - (rows - 1) * s.gap) / 2;
      const count = columns * rows;
      const previous = state.presence;
      state.count = count;
      state.x = new Float32Array(count);
      state.y = new Float32Array(count);
      state.tint = new Uint8Array(count);
      state.size = new Float32Array(count);
      state.phase = new Float32Array(count);
      state.rate = new Float32Array(count);
      state.jitter = new Float32Array(count);
      state.presence = new Float32Array(count);
      state.delay = new Float32Array(count);
      let i = 0;
      for (let column = 0; column < columns; column++) {
        for (let row = 0; row < rows; row++) {
          state.x[i] = offsetX + column * s.gap;
          state.y[i] = offsetY + row * s.gap;
          state.tint[i] = Math.floor(Math.random() * s.colors.length);
          state.size[i] = s.pixelSize * (0.25 + Math.random() * 0.75);
          state.phase[i] = Math.random() * Math.PI * 2;
          state.rate[i] = 0.55 + Math.random() * 0.9;
          state.jitter[i] = Math.random();
          if (previous.length) state.presence[i] = previous[Math.min(i, previous.length - 1)];
          i++;
        }
      }
      state.maxDistance = Math.hypot(width, height);
      schedule();
    };

    const schedule = () => {
      const s = settingsRef.current;
      const span = s.revealDuration / 1000;
      let far = 1;
      for (let i = 0; i < state.count; i++) {
        far = Math.max(far, Math.hypot(state.x[i] - state.originX, state.y[i] - state.originY));
      }
      state.maxDistance = far;
      for (let i = 0; i < state.count; i++) {
        const distance = Math.hypot(state.x[i] - state.originX, state.y[i] - state.originY) / far;
        state.delay[i] = state.active
          ? distance * span * 0.82 + state.jitter[i] * span * 0.18
          : (1 - distance) * span * 0.45 + state.jitter[i] * span * 0.1;
      }
    };

    const setActiveState = (value, x, y) => {
      if (state.active === value) return;
      const s = settingsRef.current;
      state.active = value;
      state.changedAt = state.time;
      if (value) {
        const usePointer = s.origin === 'pointer' && x !== undefined && y !== undefined;
        state.originX = usePointer ? x : state.width / 2;
        state.originY = usePointer ? y : state.height / 2;
      }
      schedule();
      setActive(value);
      wake();
    };

    const draw = () => {
      const s = settingsRef.current;
      const { width, height, ratio } = state;
      ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
      ctx.clearRect(0, 0, width, height);

      const maskKey = `${state.count}|${width}|${height}|${s.fade}`;
      if (state.maskKey !== maskKey) {
        state.maskKey = maskKey;
        state.mask = new Float32Array(state.count);
        const near = Math.min(width, height) * 0.05;
        const far = Math.min(width, height) * 0.42;
        for (let i = 0; i < state.count; i++) {
          const edge = Math.min(state.x[i], width - state.x[i], state.y[i], height - state.y[i]);
          const t = clamp((edge - near) / (far - near), 0, 1);
          state.mask[i] = 1 - s.fade * t * t * t * (t * (t * 6 - 15) + 10);
        }
      }

      let presenceSum = 0;
      const elapsed = state.time - state.changedAt;
      const appearStep = state.dt / APPEAR;
      const exitStep = state.dt / EXIT;
      for (let i = 0; i < state.count; i++) {
        const current = state.presence[i];
        const ready = elapsed >= state.delay[i] || reduce;
        const target = state.active ? (ready ? 1 : 0) : ready ? 0 : current;
        if (reduce) state.presence[i] = target;
        else if (target > current) state.presence[i] = Math.min(1, current + appearStep);
        else if (target < current) state.presence[i] = Math.max(0, current - exitStep);
        presenceSum += state.presence[i];
      }
      const presenceAvg = state.count ? presenceSum / state.count : 0;

      if (s.glow > 0 && presenceAvg > 0.001) {
        const [r, g, b] = s.glowColor;
        const cx = state.spotPower > 0.01 ? state.spotX : state.originX;
        const cy = state.spotPower > 0.01 ? state.spotY : state.originY;
        const reach = Math.max(width, height) * 0.85;
        const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, reach);
        const strength = s.glow * s.opacity * 0.32 * Math.min(1, presenceAvg * 1.4);
        gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${strength})`);
        gradient.addColorStop(0.45, `rgba(${r}, ${g}, ${b}, ${strength * 0.35})`);
        gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`);
        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, width, height);
      }

      const spotRadius = Math.min(width, height) * 0.38 + 30;
      const spotStrength = s.spotlight * state.spotPower;
      const pulses = state.pulses;
      const twinkle = reduce ? 0 : smoothstep(0, 100, s.speed) > 0 ? 1 : 0;
      const frequency = (s.speed / 35) * 1.35;

      for (let color = 0; color < s.colors.length; color++) {
        ctx.fillStyle = s.colors[color];
        for (let i = 0; i < state.count; i++) {
          if (state.tint[i] !== color) continue;
          const presence = state.presence[i];
          if (presence <= 0.002) continue;
          const x = state.x[i];
          const y = state.y[i];
          let size = state.size[i];
          let alpha = 0.5 + presence * 0.38;
          if (twinkle) {
            const wave = Math.sin(state.phase[i] + state.time * frequency * state.rate[i]);
            size *= 0.62 + 0.38 * wave;
            alpha += 0.12 * wave + (state.jitter[i] > 0.92 ? 0.35 * Math.max(0, wave) : 0);
          }
          size *= easeOutBack(presence);
          const flash = presence < 1 && state.active ? Math.sin(presence * Math.PI) : 0;
          size *= 1 + flash * 0.9;
          alpha += flash * 0.6;
          if (spotStrength > 0.001) {
            const near = smoothstep(spotRadius, 0, Math.hypot(x - state.spotX, y - state.spotY));
            size *= 1 + spotStrength * near * near * 2.4;
            alpha += spotStrength * near * 0.45;
          }
          alpha = Math.min(1, alpha) * state.mask[i] * s.opacity;
          if (alpha <= 0.004) continue;
          for (let p = 0; p < pulses.length; p++) {
            const pulse = pulses[p];
            const band = (Math.hypot(x - pulse.x, y - pulse.y) - pulse.radius) / 26;
            const wave = Math.exp(-band * band) * pulse.power;
            size *= 1 + wave * 1.4;
            alpha = Math.min(1, alpha + wave * 0.5);
          }
          if (size <= 0.05) continue;
          ctx.globalAlpha = alpha;
          if (s.shape === 1) {
            ctx.beginPath();
            ctx.arc(x, y, size * 0.5, 0, Math.PI * 2);
            ctx.fill();
          } else if (s.shape === 2) {
            const half = size * 0.62;
            ctx.beginPath();
            ctx.moveTo(x, y - half);
            ctx.lineTo(x + half, y);
            ctx.lineTo(x, y + half);
            ctx.lineTo(x - half, y);
            ctx.closePath();
            ctx.fill();
          } else {
            ctx.fillRect(x - size * 0.5, y - size * 0.5, size, size);
          }
        }
      }
      ctx.globalAlpha = 1;
      return presenceSum;
    };

    const frame = now => {
      state.raf = 0;
      if (!state.alive) return;
      const s = settingsRef.current;
      const dt = state.last ? Math.min(0.05, (now - state.last) / 1000) : 1 / 60;
      state.last = now;
      state.dt = dt;
      state.time += dt;
      build();

      if (state.introAt >= 0 && state.time >= state.introAt) {
        state.introAt = -1;
        if (!state.active) {
          setActiveState(true);
          if (state.mode !== 'always') state.releaseAt = state.time + s.revealDuration / 1000 + 0.7;
        }
      }

      if (state.releaseAt >= 0 && state.time >= state.releaseAt && !state.hovering && state.mode !== 'always') {
        state.releaseAt = -1;
        setActiveState(false);
      }

      const want = state.hovering && !reduce ? 1 : 0;
      state.spotPower += (want - state.spotPower) * (1 - Math.exp(-(want ? 6 : 3) * dt));
      const stiffness = 120;
      const damping = 2 * Math.sqrt(stiffness) * 0.9;
      state.spotVX += (stiffness * (state.pointerX - state.spotX) - damping * state.spotVX) * dt;
      state.spotVY += (stiffness * (state.pointerY - state.spotY) - damping * state.spotVY) * dt;
      state.spotX += state.spotVX * dt;
      state.spotY += state.spotVY * dt;

      const pulseSpeed = state.maxDistance / Math.max(0.35, (s.revealDuration / 1000) * 0.9);
      state.pulses = state.pulses.filter(pulse => {
        pulse.radius += pulseSpeed * dt;
        pulse.power = Math.max(0, 1 - pulse.radius / (state.maxDistance + 40));
        return pulse.power > 0;
      });

      const presence = draw();
      const settling =
        state.active ||
        state.introAt >= 0 ||
        presence > 0 ||
        state.pulses.length > 0 ||
        state.spotPower > 0.002 ||
        Math.abs(state.spotVX) + Math.abs(state.spotVY) > 0.5;
      if (settling && state.visible) state.raf = requestAnimationFrame(frame);
      else state.last = 0;
    };

    const wake = () => {
      if (!state.raf && state.alive && state.visible) state.raf = requestAnimationFrame(frame);
    };

    const local = event => {
      const rect = container.getBoundingClientRect();
      return [event.clientX - rect.left, event.clientY - rect.top];
    };

    const onEnter = event => {
      const [x, y] = local(event);
      state.hovering = event.pointerType !== 'touch';
      state.pointerX = x;
      state.pointerY = y;
      if (state.spotPower < 0.01) {
        state.spotX = x;
        state.spotY = y;
        state.spotVX = 0;
        state.spotVY = 0;
      }
      if (state.mode === 'hover') {
        state.releaseAt = -1;
        state.introAt = -1;
        setActiveState(true, x, y);
      }
      wake();
    };

    const onMove = event => {
      const [x, y] = local(event);
      state.pointerX = x;
      state.pointerY = y;
      wake();
    };

    const onLeave = event => {
      state.hovering = false;
      if (state.mode === 'hover') {
        if (event.pointerType === 'touch') state.releaseAt = state.time + 1.2;
        else setActiveState(false);
      }
      wake();
    };

    const onDown = event => {
      const [x, y] = local(event);
      if (state.mode === 'click') {
        state.introAt = -1;
        state.releaseAt = -1;
        setActiveState(!state.active, x, y);
      } else if (!state.active) {
        setActiveState(true, x, y);
      }
      if (settingsRef.current.clickPulse && !reduce && state.active) {
        state.pulses.push({ x, y, radius: 0, power: 1 });
      }
      wake();
    };

    const onOutside = event => {
      if (state.mode === 'click' && state.active && !container.contains(event.target)) setActiveState(false);
    };

    const onKey = event => {
      if (state.mode !== 'click' || event.target !== container) return;
      if (event.key !== 'Enter' && event.key !== ' ') return;
      event.preventDefault();
      setActiveState(!state.active);
    };

    const onFocus = event => {
      if (event.currentTarget.contains(event.relatedTarget)) return;
      if (state.mode === 'hover') setActiveState(true);
    };

    const onBlur = event => {
      if (event.currentTarget.contains(event.relatedTarget)) return;
      if (state.mode === 'hover' && !state.hovering) setActiveState(false);
    };

    const sync = () => {
      const mode = settingsRef.current.trigger;
      if (state.mode === mode) return;
      const previous = state.mode;
      state.mode = mode;
      if (!previous) return;
      if (mode === 'always') {
        state.releaseAt = -1;
        setActiveState(true);
      } else if (previous === 'always' && !state.hovering) {
        setActiveState(false);
      }
    };

    container.addEventListener('pointerenter', onEnter);
    container.addEventListener('pointermove', onMove);
    container.addEventListener('pointerleave', onLeave);
    container.addEventListener('pointerdown', onDown);
    container.addEventListener('keydown', onKey);
    document.addEventListener('pointerdown', onOutside);

    const resizeObserver = new ResizeObserver(() => {
      build();
      wake();
    });
    resizeObserver.observe(container);

    let introStarted = false;
    const visibility = new IntersectionObserver(entries => {
      state.visible = entries.some(entry => entry.isIntersecting);
      const s = settingsRef.current;
      if (state.visible && !introStarted && (s.trigger === 'always' || (s.intro && !reduce))) {
        introStarted = true;
        build();
        state.introAt = state.time + s.introDelay / 1000;
      }
      if (state.visible) wake();
    });
    visibility.observe(container);

    engineRef.current = { onFocus, onBlur, wake, sync };
    state.mode = settingsRef.current.trigger;
    build();
    state.pointerX = state.width / 2;
    state.pointerY = state.height / 2;
    state.spotX = state.pointerX;
    state.spotY = state.pointerY;
    state.originX = state.width / 2;
    state.originY = state.height / 2;

    return () => {
      state.alive = false;
      cancelAnimationFrame(state.raf);
      resizeObserver.disconnect();
      visibility.disconnect();
      container.removeEventListener('pointerenter', onEnter);
      container.removeEventListener('pointermove', onMove);
      container.removeEventListener('pointerleave', onLeave);
      container.removeEventListener('pointerdown', onDown);
      container.removeEventListener('keydown', onKey);
      document.removeEventListener('pointerdown', onOutside);
      engineRef.current = null;
    };
  }, []);

  useEffect(() => {
    engineRef.current?.sync();
    engineRef.current?.wake();
  });

  return (
    <div
      ref={containerRef}
      className={`${CARD} ${SURFACE[tone]} ${className}`.trim()}
      data-theme={tone}
      data-active={active || undefined}
      style={{ '--pixel-card-radius': `${radius}px`, ...style }}
      onFocus={focusOff ? undefined : event => engineRef.current?.onFocus(event)}
      onBlur={focusOff ? undefined : event => engineRef.current?.onBlur(event)}
      tabIndex={focusOff ? -1 : 0}
    >
      <canvas
        className="pointer-events-none absolute inset-0 z-0 block h-full w-full"
        ref={canvasRef}
        aria-hidden="true"
      />
      {children}
      <span data-rim aria-hidden="true" className={RIM[tone]} />
    </div>
  );
}