// Loads assets from a packed binary bundle generated by scripts/generate-save-editor-assets.js
// Format: [U32LE indexLen][JSON index][fragment data]

let bundleIndex = null;
let dataOffset = 0;
let bundleBuffer = null;
let bundlePromise = null;

async function loadBundle() {
  if (!bundlePromise) {
    bundlePromise = fetch('/save-editor.bin').then(async (resp) => {
      bundleBuffer = await resp.arrayBuffer();
      const indexLen = new DataView(bundleBuffer).getUint32(0, true);
      const indexJson = new TextDecoder().decode(new Uint8Array(bundleBuffer, 4, indexLen));
      bundleIndex = JSON.parse(indexJson);
      dataOffset = 4 + indexLen;
    });
  }
  await bundlePromise;
}

function getAsset(type, name) {
  const entry = bundleIndex[`${type}/${name}`];
  if (!entry) return null;
  const [offset, size] = entry;
  return bundleBuffer.slice(dataOffset + offset, dataOffset + offset + size);
}

export async function fetchAnimation(name) {
  await loadBundle();
  return getAsset('animations', name);
}

export async function fetchTexture(name) {
  await loadBundle();
  return getAsset('textures', name);
}

async function fetchBitmap(name) {
  await loadBundle();
  return getAsset('bitmaps', name);
}

async function fetchSound(name) {
  await loadBundle();
  return getAsset('sounds', name);
}

/**
 * Build a WAV file from raw MxCh sound data.
 * Layout: bytes 0-15 = PCMWAVEFORMAT, 16-19 = m_dataSize, 20-23 = m_flags, 24+ = PCM data.
 * Uses actual available size since sector interleaving may clip the last chunk.
 */
function buildWav(buffer) {
  const dataSize = buffer.byteLength - 24;
  const wavSize = 44 + dataSize;
  const wav = new ArrayBuffer(wavSize);
  const view = new DataView(wav);
  const bytes = new Uint8Array(wav);

  // RIFF header
  bytes.set([0x52, 0x49, 0x46, 0x46]); // "RIFF"
  view.setUint32(4, wavSize - 8, true);
  bytes.set([0x57, 0x41, 0x56, 0x45], 8); // "WAVE"

  // fmt chunk — copy PCMWAVEFORMAT (16 bytes) directly from source header
  bytes.set([0x66, 0x6D, 0x74, 0x20], 12); // "fmt "
  view.setUint32(16, 16, true);
  bytes.set(new Uint8Array(buffer, 0, 16), 20);

  // data chunk
  bytes.set([0x64, 0x61, 0x74, 0x61], 36); // "data"
  view.setUint32(40, dataSize, true);
  bytes.set(new Uint8Array(buffer, 24, dataSize), 44);

  return wav;
}

export async function fetchSoundAsWav(name) {
  const buffer = await fetchSound(name);
  if (!buffer) return null;
  return buildWav(buffer);
}

/**
 * Decode a raw Windows DIB (no BM file header) into RGBA ImageData.
 * Supports 8-bit indexed color only.
 */
function decodeDib(buffer) {
  const view = new DataView(buffer);
  const width = view.getInt32(4, true);
  const height = view.getInt32(8, true);
  const bpp = view.getUint16(14, true);
  if (bpp !== 8) return null;

  // Palette: 256 BGRA entries starting at offset 40
  const palette = new Uint8Array(buffer, 40, 1024);

  // Pixel data starts after header + palette
  const pixelOffset = 40 + 1024;
  const rowStride = (width + 3) & ~3; // rows padded to 4-byte boundary
  const absHeight = Math.abs(height);
  const bottomUp = height > 0;

  const imageData = new ImageData(width, absHeight);
  const pixels = new Uint8Array(buffer, pixelOffset);

  for (let y = 0; y < absHeight; y++) {
    const srcRow = bottomUp ? (absHeight - 1 - y) : y;
    for (let x = 0; x < width; x++) {
      const idx = pixels[srcRow * rowStride + x] * 4;
      const dst = (y * width + x) * 4;
      imageData.data[dst] = palette[idx + 2];     // R (from BGR)
      imageData.data[dst + 1] = palette[idx + 1]; // G
      imageData.data[dst + 2] = palette[idx];     // B
      imageData.data[dst + 3] = 255;
    }
  }

  return imageData;
}

/**
 * Fetch a bitmap from an SI file and return a blob URL for use in <img> tags.
 */
export async function fetchBitmapAsURL(name) {
  const buffer = await fetchBitmap(name);
  if (!buffer) return null;

  const imageData = decodeDib(buffer);
  if (!imageData) return null;

  const canvas = new OffscreenCanvas(imageData.width, imageData.height);
  const ctx = canvas.getContext('2d');
  ctx.putImageData(imageData, 0, 0);

  const blob = await canvas.convertToBlob({ type: 'image/png' });
  return URL.createObjectURL(blob);
}
