import "./generate-app-icons.mjs";

process.exit(0);

import { Buffer } from "node:buffer";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { deflateSync } from "node:zlib";

const root = dirname(dirname(fileURLToPath(import.meta.url)));
const iconsDir = join(root, "src-tauri", "icons");

const icoSizes = [16, 24, 32, 48, 64, 128, 256];

function clamp(value, min = 0, max = 1) {
  return Math.min(max, Math.max(min, value));
}

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

function hex(value) {
  const clean = value.replace("#", "");
  return {
    r: Number.parseInt(clean.slice(0, 2), 16),
    g: Number.parseInt(clean.slice(2, 4), 16),
    b: Number.parseInt(clean.slice(4, 6), 16),
  };
}

function mix(a, b, t) {
  const u = clamp(t);
  return {
    r: a.r + (b.r - a.r) * u,
    g: a.g + (b.g - a.g) * u,
    b: a.b + (b.b - a.b) * u,
  };
}

function addGlow(color, glow, amount) {
  return {
    r: clamp(color.r + glow.r * amount, 0, 255),
    g: clamp(color.g + glow.g * amount, 0, 255),
    b: clamp(color.b + glow.b * amount, 0, 255),
  };
}

function blend(base, layer) {
  const alpha = clamp(layer.a);
  const outA = alpha + base.a * (1 - alpha);
  if (outA <= 0) return { r: 0, g: 0, b: 0, a: 0 };

  return {
    r: (layer.r * alpha + base.r * base.a * (1 - alpha)) / outA,
    g: (layer.g * alpha + base.g * base.a * (1 - alpha)) / outA,
    b: (layer.b * alpha + base.b * base.a * (1 - alpha)) / outA,
    a: outA,
  };
}

function roundedRectDistance(x, y, cx, cy, hx, hy, radius) {
  const qx = Math.abs(x - cx) - hx + radius;
  const qy = Math.abs(y - cy) - hy + radius;
  const outsideX = Math.max(qx, 0);
  const outsideY = Math.max(qy, 0);
  return Math.hypot(outsideX, outsideY) + Math.min(Math.max(qx, qy), 0) - radius;
}

function segmentDistance(px, py, ax, ay, bx, by) {
  const vx = bx - ax;
  const vy = by - ay;
  const wx = px - ax;
  const wy = py - ay;
  const lenSq = vx * vx + vy * vy;
  const t = lenSq === 0 ? 0 : clamp((wx * vx + wy * vy) / lenSq);
  return Math.hypot(px - (ax + vx * t), py - (ay + vy * t));
}

function polygonContains(x, y, points) {
  let inside = false;
  for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
    const xi = points[i][0];
    const yi = points[i][1];
    const xj = points[j][0];
    const yj = points[j][1];
    const intersects = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
    if (intersects) inside = !inside;
  }
  return inside;
}

function polygonDistance(x, y, points) {
  let distance = Infinity;
  for (let i = 0; i < points.length; i += 1) {
    const a = points[i];
    const b = points[(i + 1) % points.length];
    distance = Math.min(distance, segmentDistance(x, y, a[0], a[1], b[0], b[1]));
  }
  return polygonContains(x, y, points) ? -distance : distance;
}

function drawStroke(color, x, y, ax, ay, bx, by, radius, strokeColor, alpha = 1) {
  const d = segmentDistance(x, y, ax, ay, bx, by);
  const a = smoothstep(radius + 1.4, radius - 0.8, d) * alpha;
  if (a <= 0) return color;
  return blend(color, { ...strokeColor, a });
}

function drawCircle(color, x, y, cx, cy, radius, circleColor, alpha = 1) {
  const d = Math.hypot(x - cx, y - cy);
  const a = smoothstep(radius + 1.4, radius - 0.8, d) * alpha;
  if (a <= 0) return color;
  return blend(color, { ...circleColor, a });
}

function sampleScene(x, y) {
  const dark = hex("#10111f");
  const purple = hex("#bd34fe");
  const indigo = hex("#646cff");
  const cyan = hex("#41d1ff");
  const yellow = hex("#ffd343");
  const white = hex("#f7fbff");

  let color = { r: 0, g: 0, b: 0, a: 0 };
  const badge = roundedRectDistance(x, y, 128, 128, 108, 108, 48);

  const shadowA = Math.exp(-Math.max(badge, 0) / 12) * 0.22;
  if (badge > 0 && shadowA > 0.005) {
    color = blend(color, { r: 0, g: 0, b: 0, a: shadowA });
  }

  const badgeA = smoothstep(1.6, -1.2, badge);
  if (badgeA > 0) {
    const diagonal = (x + y) / 512;
    let base = mix(dark, indigo, diagonal * 0.75);

    const purpleGlow = Math.exp(-((x - 68) ** 2 + (y - 62) ** 2) / 6200);
    const cyanGlow = Math.exp(-((x - 184) ** 2 + (y - 72) ** 2) / 5600);
    const yellowGlow = Math.exp(-((x - 184) ** 2 + (y - 202) ** 2) / 8400);

    base = addGlow(base, purple, purpleGlow * 0.58);
    base = addGlow(base, cyan, cyanGlow * 0.55);
    base = addGlow(base, yellow, yellowGlow * 0.22);

    color = blend(color, { ...base, a: badgeA });

    const rim = smoothstep(4.2, 0.4, Math.abs(badge));
    color = blend(color, { ...white, a: rim * 0.18 * badgeA });
  }

  const bolt = [
    [144, 40],
    [198, 40],
    [164, 111],
    [207, 111],
    [113, 218],
    [143, 142],
    [103, 142],
  ];
  const boltD = polygonDistance(x, y, bolt);
  const boltA = smoothstep(2.5, -1, boltD);
  if (boltA > 0) {
    color = blend(color, { ...yellow, a: boltA * 0.24 });
  }

  const glow = hex("#41d1ff");
  const line = hex("#eef7ff");
  const nodeFill = hex("#131827");

  for (const segment of [
    [82, 73, 82, 181],
    [82, 116, 166, 116],
  ]) {
    color = drawStroke(color, x, y, ...segment, 12, glow, 0.24);
    color = drawStroke(color, x, y, ...segment, 5.2, line, 0.94);
  }

  for (const [cx, cy, accent] of [
    [82, 73, cyan],
    [166, 116, purple],
    [82, 181, yellow],
  ]) {
    color = drawCircle(color, x, y, cx, cy, 22, accent, 0.25);
    color = drawCircle(color, x, y, cx, cy, 14.5, white, 0.96);
    color = drawCircle(color, x, y, cx, cy, 8.4, nodeFill, 1);
    color = drawCircle(color, x, y, cx, cy, 4.2, accent, 0.95);
  }

  const spark = [
    [186, 161],
    [195, 181],
    [215, 190],
    [195, 199],
    [186, 219],
    [177, 199],
    [157, 190],
    [177, 181],
  ];
  const sparkD = polygonDistance(x, y, spark);
  const sparkA = smoothstep(1.8, -0.8, sparkD);
  if (sparkA > 0) {
    color = blend(color, { ...yellow, a: sparkA * 0.92 });
  }

  return color;
}

function renderPng(size) {
  const scale = 256 / size;
  const samples = size <= 32 ? 5 : 3;
  const data = Buffer.alloc(size * size * 4);

  for (let y = 0; y < size; y += 1) {
    for (let x = 0; x < size; x += 1) {
      let r = 0;
      let g = 0;
      let b = 0;
      let a = 0;

      for (let sy = 0; sy < samples; sy += 1) {
        for (let sx = 0; sx < samples; sx += 1) {
          const scene = sampleScene((x + (sx + 0.5) / samples) * scale, (y + (sy + 0.5) / samples) * scale);
          r += scene.r * scene.a;
          g += scene.g * scene.a;
          b += scene.b * scene.a;
          a += scene.a;
        }
      }

      const total = samples * samples;
      const alpha = a / total;
      const offset = (y * size + x) * 4;
      data[offset] = alpha > 0 ? Math.round(r / a) : 0;
      data[offset + 1] = alpha > 0 ? Math.round(g / a) : 0;
      data[offset + 2] = alpha > 0 ? Math.round(b / a) : 0;
      data[offset + 3] = Math.round(alpha * 255);
    }
  }

  return encodePng(size, size, data);
}

const crcTable = new Uint32Array(256);
for (let n = 0; n < 256; n += 1) {
  let c = n;
  for (let k = 0; k < 8; k += 1) {
    c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
  }
  crcTable[n] = c >>> 0;
}

function crc32(buffer) {
  let crc = 0xffffffff;
  for (const byte of buffer) {
    crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
  }
  return (crc ^ 0xffffffff) >>> 0;
}

function chunk(type, payload) {
  const typeBytes = Buffer.from(type);
  const out = Buffer.alloc(12 + payload.length);
  out.writeUInt32BE(payload.length, 0);
  typeBytes.copy(out, 4);
  payload.copy(out, 8);
  out.writeUInt32BE(crc32(Buffer.concat([typeBytes, payload])), out.length - 4);
  return out;
}

function encodePng(width, height, rgba) {
  const header = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
  const ihdr = Buffer.alloc(13);
  ihdr.writeUInt32BE(width, 0);
  ihdr.writeUInt32BE(height, 4);
  ihdr[8] = 8;
  ihdr[9] = 6;

  const raw = Buffer.alloc((width * 4 + 1) * height);
  for (let y = 0; y < height; y += 1) {
    const rowStart = y * (width * 4 + 1);
    raw[rowStart] = 0;
    rgba.copy(raw, rowStart + 1, y * width * 4, (y + 1) * width * 4);
  }

  return Buffer.concat([
    header,
    chunk("IHDR", ihdr),
    chunk("IDAT", deflateSync(raw, { level: 9 })),
    chunk("IEND", Buffer.alloc(0)),
  ]);
}

function makeIco(images) {
  const header = Buffer.alloc(6);
  header.writeUInt16LE(0, 0);
  header.writeUInt16LE(1, 2);
  header.writeUInt16LE(images.length, 4);

  const entries = Buffer.alloc(images.length * 16);
  let offset = 6 + images.length * 16;

  images.forEach((image, index) => {
    const entry = index * 16;
    entries[entry] = image.size >= 256 ? 0 : image.size;
    entries[entry + 1] = image.size >= 256 ? 0 : image.size;
    entries[entry + 2] = 0;
    entries[entry + 3] = 0;
    entries.writeUInt16LE(1, entry + 4);
    entries.writeUInt16LE(32, entry + 6);
    entries.writeUInt32LE(image.png.length, entry + 8);
    entries.writeUInt32LE(offset, entry + 12);
    offset += image.png.length;
  });

  return Buffer.concat([header, entries, ...images.map((image) => image.png)]);
}

mkdirSync(iconsDir, { recursive: true });

const iconPng = renderPng(512);
const icoImages = icoSizes.map((size) => ({ size, png: renderPng(size) }));

writeFileSync(join(iconsDir, "icon.png"), iconPng);
writeFileSync(join(iconsDir, "icon.ico"), makeIco(icoImages));

console.log(`Wrote ${join("src-tauri", "icons", "icon.png")} (${iconPng.length} bytes)`);
console.log(`Wrote ${join("src-tauri", "icons", "icon.ico")} (${icoImages.length} sizes)`);
