/**
 * scripts/build-favicons.js
 *
 * Generate favicon + brand mark variants from a single source PNG.
 *
 * Source: logotakedowns.png (the user-supplied logo)
 * Outputs:
 *   public/og/favicon-16.png
 *   public/og/favicon-32.png
 *   public/og/favicon-48.png
 *   public/og/favicon.ico         (multi-size, 16+32+48 inside a real ICO)
 *   public/og/logo-mark.png       (32x32 brand mark for nav)
 *   public/og/logo-mark@2x.png    (64x64 retina brand mark)
 *   public/og/logo-mark-128.png   (128x128 larger displays)
 *   public/og/logo.png            (256x256 fallback for og:image etc.)
 *
 * Run: node scripts/build-favicons.js
 */

'use strict';

const fs   = require('fs');
const path = require('path');
const sharp = require('sharp');

const SRC  = path.join(__dirname, '..', 'logotakedowns.png');
const OUT  = path.join(__dirname, '..', 'public', 'og');

// Flatten the (transparent) source PNG against this solid background colour
// before resizing. Without this step, the dark logo content disappears on
// any dark surface — the alpha=0 background lets the page show through.
// The brand's light glassmorphism UI puts the logo on a soft white card,
// so white is the natural backing. Change if the design context shifts.
const BG = { r: 255, g: 255, b: 255, alpha: 1 };

// PNG outputs at the listed sizes
const PNG_VARIANTS = [
  { name: 'favicon-16.png',      size: 16  },
  { name: 'favicon-32.png',      size: 32  },
  { name: 'favicon-48.png',      size: 48  },
  { name: 'logo-mark.png',       size: 32  },
  { name: 'logo-mark@2x.png',    size: 64  },
  { name: 'logo-mark-128.png',   size: 128 },
  { name: 'logo.png',            size: 256 },
];

// Manual multi-size ICO writer.
// Format: https://en.wikipedia.org/wiki/ICO_(file_format)
// Each PNG image is stored as-is inside the ICO container, with a
// 16-byte directory entry per image. We use PNG-in-ICO (supported by
// every modern OS — Vista+ for Windows, all macOS, all Linux desktops).

function buildIco(pngBuffers /* [{ size, buf }] */) {
  const count = pngBuffers.length;
  const headerSize = 6;
  const dirEntrySize = 16;
  const dirSize = headerSize + dirEntrySize * count;

  // Calculate offsets for each image
  let runningOffset = dirSize;
  const dirEntries = pngBuffers.map(({ size, buf }) => {
    const entry = {
      width:        size === 256 ? 0 : size,    // ICO uses 0 for 256
      height:       size === 256 ? 0 : size,
      colors:       0,                          // 0 = no palette
      reserved:     0,
      planes:       1,
      bitCount:     32,                         // RGBA
      size:         buf.length,
      offset:       runningOffset,
    };
    runningOffset += buf.length;
    return entry;
  });

  const total = runningOffset;
  const ico = Buffer.alloc(total);
  let p = 0;

  // ICONDIR (6 bytes)
  ico.writeUInt16LE(0,           p); p += 2;   // reserved
  ico.writeUInt16LE(1,           p); p += 2;   // type 1 = .ICO
  ico.writeUInt16LE(count,       p); p += 2;   // image count

  // ICONDIRENTRY (16 bytes per image)
  for (const e of dirEntries) {
    ico.writeUInt8(e.width,         p); p += 1;
    ico.writeUInt8(e.height,        p); p += 1;
    ico.writeUInt8(e.colors,        p); p += 1;
    ico.writeUInt8(e.reserved,      p); p += 1;
    ico.writeUInt16LE(e.planes,     p); p += 2;
    ico.writeUInt16LE(e.bitCount,   p); p += 2;
    ico.writeUInt32LE(e.size,       p); p += 4;
    ico.writeUInt32LE(e.offset,     p); p += 4;
  }

  // Image data (raw PNG bytes for each size)
  for (const { buf } of pngBuffers) {
    buf.copy(ico, p);
    p += buf.length;
  }

  return ico;
}

async function main() {
  if (!fs.existsSync(SRC)) {
    console.error('Source not found:', SRC);
    process.exit(1);
  }
  fs.mkdirSync(OUT, { recursive: true });

  // Pre-compose: flatten the source RGBA onto the brand background.
  // .composite() with the BG as the base layer, then layer the source on top.
  // The result is an opaque RGB PNG that does not depend on the page background.
  const base = sharp({
    create: { width: 1654, height: 1654, channels: 4, background: BG },
  }).png();
  const flattened = await sharp(await base.toBuffer())
    .composite([{ input: SRC, top: 0, left: 0 }])
    .png()
    .toBuffer();
  console.log('  flattened', flattened.length, 'bytes (alpha removed, BG=' + JSON.stringify(BG) + ')');

  // 1. PNG variants — resize the already-flattened buffer
  const pngBuffers = [];
  for (const v of PNG_VARIANTS) {
    const outPath = path.join(OUT, v.name);
    const buf = await sharp(flattened)
      .resize(v.size, v.size, { fit: 'contain', background: BG })
      .png()
      .toBuffer();
    fs.writeFileSync(outPath, buf);
    console.log('  wrote', v.name, '(' + buf.length + ' bytes)');
    if (v.name.startsWith('favicon-')) {
      pngBuffers.push({ size: v.size, buf });
    }
  }

  // 2. Multi-size .ico (16 + 32 + 48)
  const ico = buildIco(pngBuffers.filter(b => b.size === 16 || b.size === 32 || b.size === 48));
  fs.writeFileSync(path.join(OUT, 'favicon.ico'), ico);
  console.log('  wrote favicon.ico (' + ico.length + ' bytes, 16+32+48)');

  // 3. Report
  console.log('\nDone. Public/og/ now contains:');
  for (const f of fs.readdirSync(OUT).filter(f => f.startsWith('favicon') || f.startsWith('logo'))) {
    const sz = fs.statSync(path.join(OUT, f)).size;
    console.log('  ' + f.padEnd(28) + sz.toString().padStart(8) + ' bytes');
  }
}

main().catch(err => { console.error(err); process.exit(1); });
