/**
 * Sprite loading + chroma keying.
 *
 * CHROMA KEY: gameplay sprites are generated by gpt-image-2 on SOLID MAGENTA
 * (#FF00FF) because the model has no reliable alpha channel. We key the
 * magenta out here at load time (with a despill pass for fringe pixels) and
 * auto-trim the transparent margins so sprites hug their hitboxes.
 *
 * FALLBACK: if any sprite file fails to load (404 / gen script never ran),
 * we draw a procedural stand-in and record it in `fallbacks` so the game can
 * shout about it loudly in the console.
 */

export interface Sprite {
  canvas: HTMLCanvasElement;
  generated: boolean; // false => procgen fallback
}

export interface Assets {
  playerIdle: Sprite;
  playerRun: Sprite;
  enemy: Sprite;
  enemySpore: Sprite;
  pickup: Sprite;
  pickupDash: Sprite;
  platform: Sprite;
  ground: Sprite;
  background: Sprite; // full-bleed, NOT chroma keyed
  backgroundGrove: Sprite;
  backgroundShrine: Sprite;
  fallbacks: string[];
}

interface SpriteDef {
  key: keyof Omit<Assets, 'fallbacks'>;
  file: string;
  chroma: boolean;
  /** optional pre-crop (fractions of source size) applied before keying/trim */
  crop?: { x: number; y: number; w: number; h: number };
  fallback: (ctx: CanvasRenderingContext2D, w: number, h: number) => void;
}

const DEFS: SpriteDef[] = [
  { key: 'playerIdle', file: 'player-idle.png', chroma: true, fallback: fbPlayer },
  { key: 'playerRun', file: 'player-run.png', chroma: true, fallback: fbPlayer },
  { key: 'enemy', file: 'enemy.png', chroma: true, fallback: fbEnemy },
  { key: 'enemySpore', file: 'enemy-spore.png', chroma: true, fallback: fbEnemy },
  { key: 'pickup', file: 'pickup-wings.png', chroma: true, fallback: fbPickup },
  { key: 'pickupDash', file: 'pickup-dash.png', chroma: true, fallback: fbPickup },
  // the generated platform plate has a second slab at the bottom edge of the
  // frame — pre-crop to the top 70% so trim isolates the main slab
  { key: 'platform', file: 'platform.png', chroma: true, crop: { x: 0, y: 0, w: 1, h: 0.7 }, fallback: fbPlatform },
  { key: 'ground', file: 'ground.png', chroma: true, fallback: fbGround },
  { key: 'background', file: 'background.png', chroma: false, fallback: fbBackground },
  { key: 'backgroundGrove', file: 'background-grove.png', chroma: false, fallback: fbBackground },
  { key: 'backgroundShrine', file: 'background-shrine.png', chroma: false, fallback: fbBackground },
];

export async function loadAssets(onProgress?: (done: number, total: number) => void): Promise<Assets> {
  const out = { fallbacks: [] as string[] } as Assets;
  let done = 0;
  await Promise.all(
    DEFS.map(async (def) => {
      const url = `${import.meta.env.BASE_URL}sprites/${def.file}`;
      const img = await loadImage(url);
      let canvas: HTMLCanvasElement;
      let generated = true;
      if (img) {
        canvas = def.chroma ? keyAndTrim(img, def.crop) : toCanvas(img);
      } else {
        generated = false;
        out.fallbacks.push(def.file);
        const size = def.key.startsWith('background') ? [1024, 1536] : [256, 256];
        canvas = makeCanvas(size[0], size[1]);
        def.fallback(canvas.getContext('2d')!, size[0], size[1]);
      }
      (out[def.key] as Sprite) = { canvas, generated };
      onProgress?.(++done, DEFS.length);
    }),
  );
  if (out.fallbacks.length) {
    console.warn(
      `[assets] PROCGEN FALLBACK in use for: ${out.fallbacks.join(', ')} — run \`npm run gen:sprites\` to generate real art.`,
    );
  }
  return out;
}

function loadImage(url: string): Promise<HTMLImageElement | null> {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = () => resolve(null);
    img.src = url;
  });
}

function makeCanvas(w: number, h: number): HTMLCanvasElement {
  const c = document.createElement('canvas');
  c.width = w;
  c.height = h;
  return c;
}

function toCanvas(img: HTMLImageElement): HTMLCanvasElement {
  const c = makeCanvas(img.naturalWidth, img.naturalHeight);
  c.getContext('2d')!.drawImage(img, 0, 0);
  return c;
}

/** Remove the magenta key, despill fringes, trim transparent margins. */
function keyAndTrim(img: HTMLImageElement, crop?: SpriteDef['crop']): HTMLCanvasElement {
  const sx = crop ? Math.floor(img.naturalWidth * crop.x) : 0;
  const sy = crop ? Math.floor(img.naturalHeight * crop.y) : 0;
  const sw = crop ? Math.floor(img.naturalWidth * crop.w) : img.naturalWidth;
  const sh = crop ? Math.floor(img.naturalHeight * crop.h) : img.naturalHeight;

  const c = makeCanvas(sw, sh);
  const ctx = c.getContext('2d', { willReadFrequently: true })!;
  ctx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh);
  const data = ctx.getImageData(0, 0, sw, sh);
  const px = data.data;

  for (let i = 0; i < px.length; i += 4) {
    const r = px[i];
    const g = px[i + 1];
    const b = px[i + 2];
    const magenta = Math.min(r, b) - g; // how strongly "magenta" the pixel is
    if (magenta > 70 && r > 90 && b > 90) {
      px[i + 3] = 0; // hard key
    } else if (magenta > 20) {
      // despill: pull pink fringe back toward neutral and feather alpha
      const spill = magenta - 20;
      px[i] = r - spill * 0.7;
      px[i + 2] = b - spill * 0.7;
      px[i + 3] = Math.max(0, px[i + 3] - spill * 2);
    }
  }
  ctx.putImageData(data, 0, 0);

  // auto-trim transparent margins
  let minX = sw, minY = sh, maxX = 0, maxY = 0;
  for (let y = 0; y < sh; y++) {
    for (let x = 0; x < sw; x++) {
      if (px[(y * sw + x) * 4 + 3] > 12) {
        if (x < minX) minX = x;
        if (x > maxX) maxX = x;
        if (y < minY) minY = y;
        if (y > maxY) maxY = y;
      }
    }
  }
  if (maxX <= minX || maxY <= minY) return c; // fully transparent? keep as-is
  const tw = maxX - minX + 1;
  const th = maxY - minY + 1;
  const trimmed = makeCanvas(tw, th);
  trimmed.getContext('2d')!.drawImage(c, minX, minY, tw, th, 0, 0, tw, th);
  return trimmed;
}

// ---------------------------------------------------------------------------
// Procedural fallbacks (only used when generated art is missing)
// ---------------------------------------------------------------------------

function fbPlayer(ctx: CanvasRenderingContext2D, w: number, h: number): void {
  ctx.fillStyle = '#3d7a8c';
  ctx.beginPath();
  ctx.roundRect(w * 0.3, h * 0.25, w * 0.4, h * 0.6, 24);
  ctx.fill();
  ctx.fillStyle = '#ffb84d';
  ctx.fillRect(w * 0.38, h * 0.34, w * 0.24, h * 0.1);
}

function fbEnemy(ctx: CanvasRenderingContext2D, w: number, h: number): void {
  ctx.fillStyle = '#4a4456';
  ctx.beginPath();
  ctx.arc(w / 2, h * 0.6, w * 0.3, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillStyle = '#ffaa33';
  ctx.beginPath();
  ctx.arc(w * 0.42, h * 0.55, 10, 0, Math.PI * 2);
  ctx.arc(w * 0.58, h * 0.55, 10, 0, Math.PI * 2);
  ctx.fill();
}

function fbPickup(ctx: CanvasRenderingContext2D, w: number, h: number): void {
  ctx.fillStyle = '#ffd166';
  ctx.beginPath();
  ctx.moveTo(w / 2, h * 0.2);
  ctx.lineTo(w * 0.7, h * 0.5);
  ctx.lineTo(w / 2, h * 0.8);
  ctx.lineTo(w * 0.3, h * 0.5);
  ctx.closePath();
  ctx.fill();
}

function fbPlatform(ctx: CanvasRenderingContext2D, w: number, h: number): void {
  ctx.fillStyle = '#3a4a42';
  ctx.fillRect(0, h * 0.3, w, h * 0.4);
  ctx.fillStyle = '#5a7a5a';
  ctx.fillRect(0, h * 0.3, w, h * 0.08);
}

function fbGround(ctx: CanvasRenderingContext2D, w: number, h: number): void {
  ctx.fillStyle = '#2c333d';
  ctx.fillRect(0, 0, w, h);
  ctx.fillStyle = '#4a6a55';
  ctx.fillRect(0, 0, w, h * 0.1);
}

function fbBackground(ctx: CanvasRenderingContext2D, w: number, h: number): void {
  const grad = ctx.createLinearGradient(0, 0, 0, h);
  grad.addColorStop(0, '#101824');
  grad.addColorStop(0.6, '#15222e');
  grad.addColorStop(1, '#0a1016');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, w, h);
}
