const canvas = document.querySelector("#canvas");
const statusEl = document.querySelector("#status");

let gl;
let vao;
let program;
let uniforms;
let startTime = performance.now();
let lastTime = startTime;
let frame = 0;
let mouse = new Float32Array(4);
let channelResolution = new Float32Array(12);
const renderWidth = Math.max(1, Math.floor(typeof shaderWidth === "number" ? shaderWidth : 640));
const renderHeight = Math.max(1, Math.floor(typeof shaderHeight === "number" ? shaderHeight : 360));

try {
  ({ gl, vao } = createGL(canvas));
  fitCanvas();
  const built = buildFragmentSource({ code: shaderSource });
  program = createProgram(gl, built.source, "shader.js", built.lineOffset);
  uniforms = collectUniforms(gl, program);
  createNoiseChannels(gl);
  statusEl.textContent = "";
  requestAnimationFrame(render);
} catch (error) {
  showError(error);
}

canvas.addEventListener("pointerdown", (event) => {
  canvas.setPointerCapture(event.pointerId);
  const p = pointer(event);
  mouse[0] = p.x;
  mouse[1] = p.y;
  mouse[2] = p.x;
  mouse[3] = p.y;
});

canvas.addEventListener("pointermove", (event) => {
  if (mouse[2] <= 0) return;
  const p = pointer(event);
  mouse[0] = p.x;
  mouse[1] = p.y;
});

canvas.addEventListener("pointerup", () => {
  mouse[2] = -Math.abs(mouse[2]);
  mouse[3] = -Math.abs(mouse[3]);
  mouse[0] = 0;
  mouse[1] = 0;
});

window.addEventListener("resize", fitCanvas);
window.addEventListener("keydown", (event) => {
  if (event.key.toLowerCase() !== "f" || event.repeat) return;
  event.preventDefault();
  toggleFullscreen();
});

function render(now) {
  const size = setCanvasSize(canvas, gl, renderWidth, renderHeight);
  const time = (now - startTime) / 1000;
  const delta = Math.min(0.1, (now - lastTime) / 1000);
  lastTime = now;

  gl.useProgram(program);
  setShadertoyUniforms(gl, uniforms, {
    time,
    delta,
    frameRate: delta > 0 ? 1 / delta : 60,
    frame,
    channelTime: new Float32Array([time, time, time, time]),
    mouse,
    date: currentDate(),
  }, size, channelResolution);
  drawFullscreen(gl, vao);
  frame += 1;
  requestAnimationFrame(render);
}

function createNoiseChannels(gl) {
  const size = 256;
  const pixels = new Uint8Array(size * size * 4);
  let seed = 0x12345678;
  for (let index = 0; index < size * size; index += 1) {
    seed = (1664525 * seed + 1013904223) >>> 0;
    const offset = index * 4;
    pixels[offset] = seed & 255;
    pixels[offset + 1] = (seed >>> 8) & 255;
    pixels[offset + 2] = (seed >>> 16) & 255;
    pixels[offset + 3] = 255;
  }

  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

  for (let index = 0; index < 4; index += 1) {
    gl.activeTexture(gl.TEXTURE0 + index);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    channelResolution[index * 3] = size;
    channelResolution[index * 3 + 1] = size;
    channelResolution[index * 3 + 2] = 1;
  }
}

function pointer(event) {
  const rect = canvas.getBoundingClientRect();
  return {
    x: (event.clientX - rect.left) * (renderWidth / rect.width),
    y: renderHeight - (event.clientY - rect.top) * (renderHeight / rect.height),
  };
}

function fitCanvas() {
  const scale = Math.min(window.innerWidth / renderWidth, window.innerHeight / renderHeight);
  canvas.style.width = `${Math.floor(renderWidth * scale)}px`;
  canvas.style.height = `${Math.floor(renderHeight * scale)}px`;
}

function toggleFullscreen() {
  if (document.fullscreenElement) {
    document.exitFullscreen();
    return;
  }
  document.documentElement.requestFullscreen();
}

function currentDate() {
  const date = new Date();
  const start = new Date(date.getFullYear(), date.getMonth(), date.getDate());
  return new Float32Array([
    date.getFullYear(),
    date.getMonth() + 1,
    date.getDate(),
    (date.getTime() - start.getTime()) / 1000,
  ]);
}

function showError(error) {
  statusEl.textContent = error instanceof Error ? error.message : String(error);
  console.error(error);
}
