const fsp = require('node:fs/promises');
const { PNG } = require('pngjs');

const ANDROID12_SAFE_RATIO = 0.4;
const ANDROID_ADAPTIVE_LOGO_RATIO = 0.65;
const ANDROID_ADAPTIVE_CANVAS = 1024;

async function readPng(filePath) {
  const buffer = await fsp.readFile(filePath);
  return new Promise((resolve, reject) => {
    new PNG().parse(buffer, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

function resizeBilinear(src, dstW, dstH) {
  const dst = new PNG({ width: dstW, height: dstH });
  const srcW = src.width;
  const srcH = src.height;
  const xRatio = srcW > 1 ? (srcW - 1) / dstW : 0;
  const yRatio = srcH > 1 ? (srcH - 1) / dstH : 0;

  for (let y = 0; y < dstH; y++) {
    const sy = (y + 0.5) * yRatio;
    const y0 = Math.floor(sy);
    const y1 = Math.min(y0 + 1, srcH - 1);
    const wy = sy - y0;

    for (let x = 0; x < dstW; x++) {
      const sx = (x + 0.5) * xRatio;
      const x0 = Math.floor(sx);
      const x1 = Math.min(x0 + 1, srcW - 1);
      const wx = sx - x0;

      const i00 = (y0 * srcW + x0) * 4;
      const i10 = (y0 * srcW + x1) * 4;
      const i01 = (y1 * srcW + x0) * 4;
      const i11 = (y1 * srcW + x1) * 4;

      const dstIdx = (y * dstW + x) * 4;
      for (let c = 0; c < 4; c++) {
        const top = src.data[i00 + c] * (1 - wx) + src.data[i10 + c] * wx;
        const bot = src.data[i01 + c] * (1 - wx) + src.data[i11 + c] * wx;
        dst.data[dstIdx + c] = Math.round(top * (1 - wy) + bot * wy);
      }
    }
  }
  return dst;
}

function compositeOnTransparentSquare(logo, canvasSize) {
  const canvas = new PNG({ width: canvasSize, height: canvasSize });
  canvas.data.fill(0);

  const offsetX = Math.floor((canvasSize - logo.width) / 2);
  const offsetY = Math.floor((canvasSize - logo.height) / 2);

  for (let y = 0; y < logo.height; y++) {
    for (let x = 0; x < logo.width; x++) {
      const srcIdx = (y * logo.width + x) * 4;
      const dstIdx = ((y + offsetY) * canvasSize + (x + offsetX)) * 4;
      canvas.data[dstIdx] = logo.data[srcIdx];
      canvas.data[dstIdx + 1] = logo.data[srcIdx + 1];
      canvas.data[dstIdx + 2] = logo.data[srcIdx + 2];
      canvas.data[dstIdx + 3] = logo.data[srcIdx + 3];
    }
  }
  return canvas;
}

async function writePng(png, filePath) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    png.pack()
      .on('data', (chunk) => chunks.push(chunk))
      .on('end', async () => {
        try {
          await fsp.writeFile(filePath, Buffer.concat(chunks));
          resolve();
        } catch (e) {
          reject(e);
        }
      })
      .on('error', reject);
  });
}

/**
 * Read a PNG and write a new one of the same dimensions, with the source
 * logo scaled down to fit inside the Android 12+ splash safe area (centered,
 * transparent padding around it). This is what `windowSplashScreenAnimatedIcon`
 * needs so the OS-applied circular mask doesn't clip the logo edges.
 *
 * @param {string} srcPath
 * @param {string} dstPath
 * @param {number} safeRatio fraction of the canvas the logo should occupy (default 0.6)
 */
async function writeAndroid12Variant(srcPath, dstPath, safeRatio = ANDROID12_SAFE_RATIO) {
  const src = await readPng(srcPath);
  const canvasSize = Math.max(src.width, src.height);
  const safeSide = Math.round(canvasSize * safeRatio);

  const aspect = src.width / src.height;
  let logoW;
  let logoH;
  if (aspect >= 1) {
    logoW = safeSide;
    logoH = Math.round(safeSide / aspect);
  } else {
    logoH = safeSide;
    logoW = Math.round(safeSide * aspect);
  }

  const resized = resizeBilinear(src, logoW, logoH);
  const composited = compositeOnTransparentSquare(resized, canvasSize);
  await writePng(composited, dstPath);
  return { canvasSize, logoW, logoH };
}

function sampleSolidBackground(png) {
  const inset = Math.max(2, Math.floor(Math.min(png.width, png.height) * 0.02));
  const corners = [
    [inset, inset],
    [png.width - inset - 1, inset],
    [inset, png.height - inset - 1],
    [png.width - inset - 1, png.height - inset - 1],
  ];

  let r = 0;
  let g = 0;
  let b = 0;
  let opaque = 0;
  for (const [x, y] of corners) {
    const idx = (y * png.width + x) * 4;
    const alpha = png.data[idx + 3];
    if (alpha < 250) continue;
    r += png.data[idx];
    g += png.data[idx + 1];
    b += png.data[idx + 2];
    opaque += 1;
  }

  if (opaque === 0) {
    return { r: 255, g: 255, b: 255, sampled: false };
  }
  return {
    r: Math.round(r / opaque),
    g: Math.round(g / opaque),
    b: Math.round(b / opaque),
    sampled: true,
  };
}

function fillSolidColor(png, color) {
  for (let i = 0; i < png.data.length; i += 4) {
    png.data[i] = color.r;
    png.data[i + 1] = color.g;
    png.data[i + 2] = color.b;
    png.data[i + 3] = 255;
  }
}

function compositeLogoOnBackground(background, logo) {
  const offsetX = Math.floor((background.width - logo.width) / 2);
  const offsetY = Math.floor((background.height - logo.height) / 2);

  for (let y = 0; y < logo.height; y++) {
    for (let x = 0; x < logo.width; x++) {
      const srcIdx = (y * logo.width + x) * 4;
      const alpha = logo.data[srcIdx + 3] / 255;
      if (alpha === 0) continue;

      const dstIdx = ((y + offsetY) * background.width + (x + offsetX)) * 4;
      const inv = 1 - alpha;
      background.data[dstIdx] = Math.round(logo.data[srcIdx] * alpha + background.data[dstIdx] * inv);
      background.data[dstIdx + 1] = Math.round(logo.data[srcIdx + 1] * alpha + background.data[dstIdx + 1] * inv);
      background.data[dstIdx + 2] = Math.round(logo.data[srcIdx + 2] * alpha + background.data[dstIdx + 2] * inv);
      background.data[dstIdx + 3] = 255;
    }
  }
}

/**
 * Generate the Android adaptive icon background asset. Reads the user-supplied
 * icon PNG, samples a solid background color from its corners (falling back to
 * white when the image is transparent), and writes a square canvas with the
 * logo scaled down and centered. The result is what `flutter_launcher_icons`
 * passes to `adaptive_icon_background`, fully filling the launcher's circular
 * mask without the default white halo around square icons.
 *
 * @param {string} srcPath source icon PNG path
 * @param {string} dstPath output path for icon_android.png
 * @param {object} [options]
 * @param {number} [options.canvasSize] output square size (default 1024)
 * @param {number} [options.logoRatio] logo fill ratio inside canvas (default 0.65)
 * @param {string} [options.backgroundColor] explicit hex like "#01171f" to override sampling
 */
async function writeAndroidAdaptiveBackground(srcPath, dstPath, options = {}) {
  const canvasSize = options.canvasSize || ANDROID_ADAPTIVE_CANVAS;
  const logoRatio = options.logoRatio || ANDROID_ADAPTIVE_LOGO_RATIO;

  const src = await readPng(srcPath);

  let color;
  if (options.backgroundColor) {
    const hex = options.backgroundColor.replace('#', '');
    color = {
      r: parseInt(hex.slice(0, 2), 16),
      g: parseInt(hex.slice(2, 4), 16),
      b: parseInt(hex.slice(4, 6), 16),
      sampled: false,
    };
  } else {
    color = sampleSolidBackground(src);
  }

  const canvas = new PNG({ width: canvasSize, height: canvasSize });
  fillSolidColor(canvas, color);

  const logoSize = Math.round(canvasSize * logoRatio);
  const resized = resizeBilinear(src, logoSize, logoSize);
  compositeLogoOnBackground(canvas, resized);

  await writePng(canvas, dstPath);
  return { canvasSize, logoSize, color };
}

/**
 * Write a fully transparent square PNG. Used to satisfy
 * `adaptive_icon_foreground` when the entire image already lives in the
 * background layer.
 *
 * @param {string} dstPath
 * @param {number} [size]
 */
async function writeTransparentSquare(dstPath, size = ANDROID_ADAPTIVE_CANVAS) {
  const png = new PNG({ width: size, height: size });
  png.data.fill(0);
  await writePng(png, dstPath);
  return { size };
}

module.exports = {
  writeAndroid12Variant,
  writeAndroidAdaptiveBackground,
  writeTransparentSquare,
  ANDROID12_SAFE_RATIO,
  ANDROID_ADAPTIVE_LOGO_RATIO,
  ANDROID_ADAPTIVE_CANVAS,
};
