import { createPreviewLoop } from "../utils.ts";

// disc dimensions
const OUTER_RADIUS_RATIO = 0.42;
const HUB_OUTER_RATIO = 0.28;
const HUB_INNER_RATIO = 0.19;
const HOLE_RADIUS_RATIO = 0.09;
const STREAK_SIZE_RATIO = 1.5;
const OUTER_EDGE_THICKNESS_RATIO = 0.012;

export const category = "media";

export const params = [
  {
    key: "speed",
    type: "number",
    default: 0.2,
    min: 0.1,
    max: 52,
    step: 0.1,
    label: "rps",
  },
];

let angle = 0;

function filledRing(
  ctx: CanvasRenderingContext2D,
  outerR: number,
  innerR: number,
  fillStyle: string,
) {
  ctx.beginPath();
  ctx.arc(0, 0, outerR, 0, Math.PI * 2);
  ctx.arc(0, 0, innerR, 0, Math.PI * 2, true);
  ctx.fillStyle = fillStyle;
  ctx.fill();
}

// text sprite cache
let cachedW = 0;
let cachedH = 0;
let hubTextSprite: HTMLCanvasElement | null = null;

function buildHubTextSprite(
  _outerRadius: number,
  hubOuterRadius: number,
  hubInnerRadius: number,
) {
  const fontSize = Math.round(hubOuterRadius * 0.13);
  const labelRadius = hubInnerRadius +
    (hubOuterRadius - hubInnerRadius) * 0.5;
  const kerning = 0.09;
  const spriteSize = Math.ceil((labelRadius + fontSize) * 2);

  hubTextSprite = document.createElement("canvas");
  hubTextSprite.width = spriteSize;
  hubTextSprite.height = spriteSize;
  const ctx = hubTextSprite.getContext("2d")!;
  ctx.translate(spriteSize / 2, spriteSize / 2);
  ctx.fillStyle = "rgba(15,15,15,0.70)";
  ctx.font = `${fontSize}px 'Maple Mono'`;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";

  function drawArcText(angle: number, label: string) {
    ctx.save();
    ctx.rotate(angle);
    for (let i = 0; i < label.length; i++) {
      const a = -label.length / 2 * kerning + i * kerning;
      ctx.save();
      ctx.rotate(a);
      ctx.fillText(label[i], 0, -labelRadius);
      ctx.restore();
    }
    ctx.restore();
  }

  drawArcText(0, "DEFTOKU");
  drawArcText(2.15, "CD-RW");
  drawArcText(4.35, "700MB");
}

export function draw(
  canvas: HTMLCanvasElement,
  _freq: Uint8Array,
  options: Record<string, string | number | HTMLImageElement>,
  dt = 1,
) {
  const ctx = canvas.getContext("2d");
  if (!ctx) return;

  // background
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  const cx = canvas.width / 2;
  const cy = canvas.height / 2;
  const outerRadius = Math.min(canvas.width, canvas.height) *
    OUTER_RADIUS_RATIO;
  const hubOuterRadius = outerRadius * HUB_OUTER_RATIO;
  const hubInnerRadius = outerRadius * HUB_INNER_RATIO;
  const holeRadius = outerRadius * HOLE_RADIUS_RATIO;
  const streakSize = outerRadius * STREAK_SIZE_RATIO;
  const rotationSpeed = Number(options.speed) * dt;

  // rebuild text sprite if canvas size changed
  if (canvas.width !== cachedW || canvas.height !== cachedH) {
    cachedW = canvas.width;
    cachedH = canvas.height;
    buildHubTextSprite(outerRadius, hubOuterRadius, hubInnerRadius);
  }

  // stationary lighting layer (disc surface)
  ctx.save();
  ctx.translate(cx, cy);

  // clip to disc donut shape
  filledRing(ctx, outerRadius, hubInnerRadius, "");
  ctx.clip();

  // base radial gradiant
  const baseGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, outerRadius);
  baseGrad.addColorStop(0.0, "#ffffff");
  baseGrad.addColorStop(0.1, "#8ab4f8");
  baseGrad.addColorStop(0.4, "#5a8fe0");
  baseGrad.addColorStop(0.7, "#3a5bb0");
  baseGrad.addColorStop(1.0, "#1a1a40");
  ctx.fillStyle = baseGrad;
  ctx.fillRect(-outerRadius, -outerRadius, outerRadius * 2, outerRadius * 2);

  // helper to draw iridescent streaks using rotated linear gradients
  function drawStreak(angleDegrees: number, colorStops: [number, string][]) {
    ctx!.save();
    ctx!.rotate(angleDegrees * Math.PI / 180);

    const grad = ctx!.createLinearGradient(
      -streakSize,
      -streakSize,
      streakSize,
      streakSize,
    );
    colorStops.forEach((stop) => grad.addColorStop(stop[0], stop[1]));

    ctx!.fillStyle = grad;
    ctx!.fillRect(-streakSize, -streakSize, streakSize * 2, streakSize * 2);
    ctx!.restore();
  }

  // streak 1: peach/orange/yellow (bottom left)
  drawStreak(-30, [
    [0.0, "rgba(255,255,255,0)"],
    [0.2, "rgba(255, 200, 100, 0.6)"],
    [0.4, "rgba(255, 100, 150, 0.3)"],
    [0.6, "rgba(100, 200, 255, 0.5)"],
    [1.0, "rgba(255,255,255,0)"],
  ]);

  // streak 2: cyan/purple (top left -> bottom right)
  drawStreak(45, [
    [0.0, "rgba(100, 200, 255, 0.4)"],
    [0.5, "rgba(200, 100, 255, 0.6)"],
    [0.7, "rgba(255, 150, 200, 0.3)"],
    [1.0, "rgba(255,255,255,0)"],
  ]);

  ctx.restore(); // end stationary lighting

  // rotating elements (hub, text, rings)
  ctx.save();
  ctx.translate(cx, cy);
  ctx.rotate(angle);

  // hub ring - lead in
  filledRing(ctx, hubOuterRadius, hubInnerRadius, "rgba(200,200,255,0.3)");

  // hub ring - outer edge (filled ring instead of stroke)
  const edgeHalf = 1.5;
  filledRing(
    ctx,
    hubOuterRadius + edgeHalf,
    hubOuterRadius - edgeHalf,
    "rgba(80,100,160,0.3)",
  );

  // center
  ctx.fillStyle = "black";
  ctx.beginPath();
  ctx.arc(0, 0, holeRadius, 0, Math.PI * 2);
  ctx.fill();

  // hub text from sprite
  if (hubTextSprite) {
    const s = hubTextSprite.width;
    ctx.drawImage(hubTextSprite, -s / 2, -s / 2);
  }

  // outer edge ring (no data)
  filledRing(
    ctx,
    outerRadius,
    outerRadius * (1 - OUTER_EDGE_THICKNESS_RATIO),
    "rgba(255,255,255,0.35)",
  );

  // outer edge ring — filled ring instead of stroke
  const rimHalf = 0.5;
  filledRing(
    ctx,
    outerRadius + rimHalf,
    outerRadius - rimHalf,
    "rgba(200,200,255,0.1)",
  );

  // inner ring (clear plastic look)
  filledRing(ctx, hubInnerRadius, holeRadius, "rgba(255,255,255,0.15)");

  ctx.restore(); // end rotating elements

  // specular highlight
  ctx.save();
  ctx.translate(cx, cy);
  ctx.rotate(Math.PI / 6); // rotation
  ctx.beginPath();
  ctx.arc(0, 0, outerRadius, 0, Math.PI * 2);
  ctx.clip();
  const specGrad = ctx.createLinearGradient(
    -outerRadius * 0.8,
    0,
    -outerRadius * 0.1,
    0,
  );
  specGrad.addColorStop(0.0, "rgba(255,255,255,0)");
  specGrad.addColorStop(0.3, "rgba(255,255,255,0.2)");
  specGrad.addColorStop(0.5, "rgba(255,255,255,0.3)");
  specGrad.addColorStop(0.7, "rgba(200, 230, 255, 0.3)");
  specGrad.addColorStop(1.0, "rgba(255,255,255,0)");
  ctx.fillStyle = specGrad;
  ctx.fillRect(
    -outerRadius,
    -outerRadius,
    outerRadius,
    outerRadius * 2,
  );
  ctx.restore();

  angle += rotationSpeed;
}

export function preview(
  canvas: HTMLCanvasElement,
  freq: Uint8Array,
  getOptions: () => Record<string, string | number | HTMLImageElement>,
  fps: number,
) {
  angle = 0;

  return createPreviewLoop(canvas, fps, (offscreen, dt) => {
    draw(offscreen, freq, getOptions(), dt);
  });
}
