"use client";

import React, { useEffect, useRef } from "react";
import { createProgram } from "./gl-utils.js";

/**
 * Стеклянный оверлей поверх матрицы, показывается только когда `active`
 * (состояние "глаза закрыты" — см. isVpnOn в Hero.tsx). Бить можно кликом/тапом
 * в любом месте панели: каждый удар добавляет трещины, растущие из точки
 * удара; после нескольких ударов стекло осыпается и через паузу собирается
 * заново целым — эффект зациклен, стекло можно бить сколько угодно раз.
 *
 * Не связан с WASM-движком матрицы: работает на main thread (один
 * полноэкранный quad, без инстансинга — GPU-нагрузка пренебрежимо мала),
 * это отдельный DOM-канвас, наложенный поверх канваса матрицы обычным
 * порядком отрисовки браузера (в отличие от фонового слоя глубины,
 * встроенного внутрь matrix_worker.js — см. комментарий у fsBackground в
 * shaders.js: слою СВЕРХУ не нужно бороться с alpha:false соседнего канваса,
 * это ограничение работает только "сквозь" элемент, а не поверх него).
 */

const vs = `#version 300 es
in vec2 a_position;
out vec2 v_uv;
void main() {
  v_uv = a_position * 0.5 + 0.5;
  gl_Position = vec4(a_position, 0.0, 1.0);
}`.trim();

const fs = `#version 300 es
precision highp float;
in vec2 v_uv;
out vec4 outColor;

uniform float u_time;
uniform vec2 u_resolution;
uniform vec3 u_impacts[6]; // xy - точка удара (uv, y вверх), z - момент удара (сек), <=0 - слот пуст
uniform float u_shardDisplace; // 0 = стекло целое, 1 = осколки полностью разлетелись/исчезли
uniform float u_paneAlpha;     // общая непрозрачность панели (fade-in при пересборке)

// Стекло показывается ТОЛЬКО когда isVpnOn=true (глаза закрыты, см. Hero.tsx:
// active={isVpnOn}), поэтому акцент всегда фиолетовый (та же палитра "vpn on",
// что в fsBackground/атласе капель) — отдельный uniform под циан не нужен.
const vec3 ACCENT = vec3(0.75, 0.1, 1.0);

vec2 hash2(vec2 p) {
  p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
  return fract(sin(p) * 43758.5453123);
}
float hash1(vec2 p) { return fract(sin(dot(p, vec2(41.3, 289.1))) * 43758.5453123); }

void main() {
  vec2 uv = v_uv;
  vec2 aspect = vec2(u_resolution.x / max(u_resolution.y, 1.0), 1.0);

  // Фон панели до первого удара — тонкая горизонтальная скан-линия вместо
  // "стеклянного" диагонального блика: тот же приём, что CRT-скан-линии в
  // postProcessSource, а не типовой шейдерный клише-рефлекс стекла.
  float scan = sin(uv.y * u_resolution.y * 1.4 + u_time * 2.0) * 0.5 + 0.5;
  vec3 col = ACCENT * scan * 0.02;
  float alpha = scan * 0.02 + 0.01;

  // --- Ячейки Вороного = осколки стекла ---
  vec2 cellSize = vec2(0.085);
  vec2 gridUv = uv / cellSize;
  vec2 gridId = floor(gridUv);
  vec2 gridF = fract(gridUv);

  float minDist1 = 8.0, minDist2 = 8.0;
  vec2 cellSeed1 = vec2(0.0);
  for (int y = -1; y <= 1; y++) {
    for (int x = -1; x <= 1; x++) {
      vec2 neighbor = vec2(float(x), float(y));
      vec2 cell = gridId + neighbor;
      vec2 point = neighbor + hash2(cell) - gridF;
      float d = dot(point, point);
      if (d < minDist1) {
        minDist2 = minDist1;
        minDist1 = d;
        cellSeed1 = cell;
      } else if (d < minDist2) {
        minDist2 = d;
      }
    }
  }
  float cellEdge = sqrt(minDist2) - sqrt(minDist1);

  // --- Влияние ударов: трещины растут кругом от каждой точки удара во времени ---
  float influence = 0.0;
  for (int i = 0; i < 6; i++) {
    vec3 imp = u_impacts[i];
    if (imp.z <= 0.0) continue;
    float age = u_time - imp.z;
    if (age < 0.0) continue;
    // Трещина быстро "выстреливает" до локального радиуса и на нём
    // застывает — один удар даёт компактное пятно, а не весь экран;
    // полное покрытие достигается только накоплением НЕСКОЛЬКИХ ударов.
    float radius = 0.15 * (1.0 - exp(-age * 8.0));
    float d = length((uv - imp.xy) * aspect);
    influence = max(influence, smoothstep(radius, radius - 0.06, d));
  }

  // Резкая линия трещины + мягкое фиолетовое свечение вокруг неё (bloom),
  // тем же акцентным цветом — на прежних Вороных-гранях, но теперь в
  // основной палитре сайта и с лёгким неоновым ореолом вместо плоской линии.
  float crackLine = smoothstep(0.05, 0.0, cellEdge) * influence;
  float crackGlow = exp(-max(cellEdge, 0.0) / 0.05) * influence * 0.5;
  col += ACCENT * crackGlow;
  col = mix(col, ACCENT * 1.3, crackLine * 0.85);
  alpha = max(alpha, max(crackLine * 0.6, crackGlow * 0.35));

  // Едва заметная "мозаика" осколков внутри уже треснувшей зоны.
  col += ACCENT * (hash1(cellSeed1) - 0.5) * 0.05 * influence;
  alpha += influence * 0.015;
  float crack = crackLine;

  // --- Разлёт осколков при полном разбитии ---
  if (u_shardDisplace > 0.001) {
    float fall = u_shardDisplace * u_shardDisplace;
    alpha *= max(0.0, 1.0 - fall);
    col += ACCENT * fall * 0.3 * step(0.001, crack + influence * 0.3);
  }

  outColor = vec4(col, clamp(alpha, 0.0, 1.0) * u_paneAlpha);
}`.trim();

const MAX_IMPACTS = 6;
const SHATTER_AFTER_HITS = 6;
const BREAK_DURATION = 0.9;
const REFORM_DURATION = 0.7;

export interface GlassShatterProps {
  active?: boolean;
}

export const GlassShatter: React.FC<GlassShatterProps> = ({
  active = false,
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const activeRef = useRef(active);
  const glRef = useRef<WebGL2RenderingContext | null>(null);
  const startTimeRef = useRef(0);

  // Пул точек удара (как Shockwave-пул в lib.rs): t0 <= 0 значит слот свободен.
  const impactsRef = useRef(
    Array.from({ length: MAX_IMPACTS }, () => ({ x: 0, y: 0, t0: 0 })),
  );
  const hitCountRef = useRef(0);
  const phaseRef = useRef<"idle" | "breaking" | "reforming">("idle");
  const phaseStartRef = useRef(0);

  useEffect(() => {
    activeRef.current = active;
    if (active) {
      // Каждое новое появление стекла (переход в состояние "глаза закрыты") —
      // свежая целая панель, а не продолжение прошлого разбития.
      hitCountRef.current = 0;
      phaseRef.current = "idle";
      impactsRef.current.forEach((imp) => (imp.t0 = 0));
    }
  }, [active]);

  useEffect(() => {
    const container = containerRef.current;
    if (!container || typeof window === "undefined") return;

    const canvas = document.createElement("canvas");
    Object.assign(canvas.style, {
      position: "absolute",
      inset: "0",
      width: "100%",
      height: "100%",
      display: "block",
      pointerEvents: "none",
      transition: "opacity 0.6s ease-in-out",
      opacity: "0",
    });
    container.appendChild(canvas);

    const gl = canvas.getContext("webgl2", {
      alpha: true,
      premultipliedAlpha: false,
      antialias: false,
    });
    if (!gl) return;
    glRef.current = gl;

    const program = createProgram(gl, vs, fs);
    if (!program) return;
    gl.useProgram(program);

    const quad = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, quad);
    gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
      gl.STATIC_DRAW,
    );
    const aPos = gl.getAttribLocation(program, "a_position");
    gl.enableVertexAttribArray(aPos);
    gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

    const uniforms = {
      time: gl.getUniformLocation(program, "u_time"),
      res: gl.getUniformLocation(program, "u_resolution"),
      impacts: gl.getUniformLocation(program, "u_impacts[0]"),
      shardDisplace: gl.getUniformLocation(program, "u_shardDisplace"),
      paneAlpha: gl.getUniformLocation(program, "u_paneAlpha"),
    };

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = Math.max(1, container.clientWidth * dpr);
      canvas.height = Math.max(1, container.clientHeight * dpr);
      gl.viewport(0, 0, canvas.width, canvas.height);
    };
    resize();
    const resizeObserver = new ResizeObserver(resize);
    resizeObserver.observe(container);

    startTimeRef.current = performance.now();
    let visible = false;
    let rafId = 0;
    let paused = document.hidden;

    const draw = (now: number) => {
      rafId = requestAnimationFrame(draw);
      if (paused) return;
      const t = (now - startTimeRef.current) / 1000;

      const isActive = activeRef.current;
      if (isActive !== visible) {
        visible = isActive;
        canvas.style.opacity = visible ? "1" : "0";
      }
      if (!isActive) return;

      const phase = phaseRef.current;
      let shardDisplace = 0;
      let paneAlpha = 1;
      if (phase === "breaking") {
        const dt = t - phaseStartRef.current;
        shardDisplace = Math.min(1, dt / BREAK_DURATION);
        if (shardDisplace >= 1) {
          phaseRef.current = "reforming";
          phaseStartRef.current = t;
          impactsRef.current.forEach((imp) => (imp.t0 = 0));
          hitCountRef.current = 0;
        }
      } else if (phase === "reforming") {
        const dt = t - phaseStartRef.current;
        paneAlpha = Math.min(1, dt / REFORM_DURATION);
        if (dt >= REFORM_DURATION) phaseRef.current = "idle";
      }

      gl.clearColor(0, 0, 0, 0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.uniform1f(uniforms.time, t);
      gl.uniform2f(uniforms.res, canvas.width, canvas.height);
      gl.uniform1f(uniforms.shardDisplace, shardDisplace);
      gl.uniform1f(uniforms.paneAlpha, paneAlpha);

      const flat = new Float32Array(MAX_IMPACTS * 3);
      impactsRef.current.forEach((imp, i) => {
        flat[i * 3] = imp.x;
        flat[i * 3 + 1] = imp.y;
        flat[i * 3 + 2] = imp.t0;
      });
      gl.uniform3fv(uniforms.impacts, flat);
      gl.drawArrays(gl.TRIANGLES, 0, 6);
    };
    rafId = requestAnimationFrame(draw);

    const onVisibilityChange = () => {
      paused = document.hidden;
    };
    document.addEventListener("visibilitychange", onVisibilityChange);

    return () => {
      document.removeEventListener("visibilitychange", onVisibilityChange);
      resizeObserver.disconnect();
      cancelAnimationFrame(rafId);
      if (container.contains(canvas)) container.removeChild(canvas);
      glRef.current = null;
    };
  }, []);

  // Клики/тапы по панели — окно, а не сам канвас (у канваса pointer-events:
  // none, чтобы не перехватывать переключатель VPN и кнопки, см. тот же
  // приём в NetrunnerMatrix.tsx).
  useEffect(() => {
    const container = containerRef.current;
    if (!container || typeof window === "undefined") return;

    const registerImpact = (clientX: number, clientY: number) => {
      if (!activeRef.current || phaseRef.current !== "idle") return;
      const rect = container.getBoundingClientRect();
      const px = clientX - rect.left;
      const py = clientY - rect.top;
      if (px < 0 || py < 0 || px > rect.width || py > rect.height) return;

      const t = (performance.now() - startTimeRef.current) / 1000;
      const impacts = impactsRef.current;
      let slot = impacts.find((imp) => imp.t0 <= 0);
      if (!slot) {
        slot = impacts.reduce((oldest, imp) =>
          imp.t0 < oldest.t0 ? imp : oldest,
        );
      }
      slot.x = px / rect.width;
      slot.y = 1 - py / rect.height;
      slot.t0 = t;

      hitCountRef.current += 1;
      if (hitCountRef.current >= SHATTER_AFTER_HITS) {
        phaseRef.current = "breaking";
        phaseStartRef.current = t;
      }
    };

    const handlePointerDown = (e: PointerEvent | TouchEvent) => {
      if (
        e.target instanceof Element &&
        e.target.closest("button, a, input, textarea, label")
      )
        return;
      const point = "touches" in e ? e.touches[0] : e;
      if (!point) return;
      registerImpact(point.clientX, point.clientY);
    };

    const options = { passive: true };
    window.addEventListener("pointerdown", handlePointerDown as EventListener, options);
    window.addEventListener("touchstart", handlePointerDown as EventListener, options);
    return () => {
      window.removeEventListener("pointerdown", handlePointerDown as EventListener);
      window.removeEventListener("touchstart", handlePointerDown as EventListener);
    };
  }, []);

  return (
    <div
      ref={containerRef}
      className="absolute inset-0 overflow-hidden pointer-events-none"
    ></div>
  );
};
