const path = require('node:path');
const fs = require('fs-extra');
const fsp = require('node:fs/promises');
const { exec } = require('node:child_process');
const { promisify } = require('node:util');
const kleur = require('kleur');
const ui = require('../utils/ui');
const { printCompactHeader } = require('../utils/brand');
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
const { writeAndroid12Variant } = require('../utils/png-padding');

const execAsync = promisify(exec);

const ASSETS_DIR = path.join('assets', 'images');
const LIGHT_NAME = 'splash_logo_light.png';
const DARK_NAME = 'splash_logo_dark.png';
const LIGHT_ANDROID12_NAME = 'splash_logo_light_android12.png';
const DARK_ANDROID12_NAME = 'splash_logo_dark_android12.png';

const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);

/**
 * Parse the PNG IHDR chunk to read color type and detect transparency.
 *
 * PNG layout: 8-byte signature + chunks. The IHDR chunk is always first,
 * placed at byte offset 8. It holds: length(4) + "IHDR"(4) + data(13) + crc(4).
 * Color type is the 10th byte of data → absolute offset 25.
 *
 * Color types with alpha or transparency support:
 *   4 = grayscale + alpha
 *   6 = truecolor + alpha
 *   3 = indexed (transparency only if a tRNS chunk is present)
 *
 * @param {string} filePath
 * @returns {Promise<{ valid: boolean, hasAlpha: boolean, colorType: number, width: number, height: number }>}
 */
async function inspectPng(filePath) {
  const handle = await fsp.open(filePath, 'r');
  try {
    const header = Buffer.alloc(33);
    await handle.read(header, 0, 33, 0);
    const sig = header.subarray(0, 8);
    if (!sig.equals(PNG_SIGNATURE)) {
      return { valid: false, hasAlpha: false, colorType: -1, width: 0, height: 0 };
    }
    const width = header.readUInt32BE(16);
    const height = header.readUInt32BE(20);
    const colorType = header.readUInt8(25);

    let hasAlpha = colorType === 4 || colorType === 6;

    if (!hasAlpha && colorType === 3) {
      const { size } = await handle.stat();
      const remaining = Math.min(size - 33, 256 * 1024);
      if (remaining > 0) {
        const tail = Buffer.alloc(remaining);
        await handle.read(tail, 0, remaining, 33);
        if (tail.includes(Buffer.from('tRNS'))) {
          hasAlpha = true;
        }
      }
    }

    return { valid: true, hasAlpha, colorType, width, height };
  } finally {
    await handle.close();
  }
}

async function assertKasyProject(projectDir, t) {
  const kitSetupPath = path.join(projectDir, 'kit_setup.json');
  const pubspecPath = path.join(projectDir, 'pubspec.yaml');
  if (!(await fs.pathExists(kitSetupPath)) && !(await fs.pathExists(pubspecPath))) {
    throw new Error(t('splash.error.notKasyProject'));
  }
  const assetsDir = path.join(projectDir, ASSETS_DIR);
  await fs.ensureDir(assetsDir);
}

/**
 * @param {string} flagValue
 */
function resolveInputPath(flagValue) {
  if (!flagValue) return null;
  const expanded = flagValue.startsWith('~')
    ? path.join(require('node:os').homedir(), flagValue.slice(1))
    : flagValue;
  return path.resolve(expanded);
}

/**
 * @param {string} projectDir
 * @param {{ light?: string, dark?: string, skipGenerate?: boolean, language?: string }} options
 */
async function runSplash(projectDir, options = {}) {
  const language = options.language || detectDefaultLanguage();
  const t = createTranslator(language);

  printCompactHeader();
  ui.intro(kleur.bold().cyan(t('splash.intro')));

  await assertKasyProject(projectDir, t);

  const lightPath = resolveInputPath(options.light);
  const darkPath = resolveInputPath(options.dark);

  if (!lightPath || !darkPath) {
    ui.log.error(t('splash.error.bothRequired'));
    ui.log.message(kleur.dim('kasy splash --light <light.png> --dark <dark.png>'));
    process.exit(1);
  }

  for (const [role, p] of [['light', lightPath], ['dark', darkPath]]) {
    if (!(await fs.pathExists(p))) {
      ui.log.error(t('splash.error.fileNotFound', { path: p }));
      process.exit(1);
    }
  }

  const inspectSpinner = ui.spinner();
  inspectSpinner.start(t('splash.validating'));

  let lightInfo;
  let darkInfo;
  try {
    lightInfo = await inspectPng(lightPath);
    darkInfo = await inspectPng(darkPath);
  } catch (err) {
    inspectSpinner.stop(`✖ ${err.message || t('splash.error.notPng')}`);
    process.exit(1);
  }

  if (!lightInfo.valid || !darkInfo.valid) {
    inspectSpinner.stop(`✖ ${t('splash.error.notPng')}`);
    process.exit(1);
  }

  inspectSpinner.stop(t('splash.validated'));

  const warnings = [];
  if (!lightInfo.hasAlpha) {
    warnings.push(t('splash.warn.noAlphaLight', { path: path.basename(lightPath) }));
  }
  if (!darkInfo.hasAlpha) {
    warnings.push(t('splash.warn.noAlphaDark', { path: path.basename(darkPath) }));
  }
  if (lightInfo.width < 768 || lightInfo.height < 768) {
    warnings.push(t('splash.warn.smallLight', {
      w: lightInfo.width,
      h: lightInfo.height,
    }));
  }
  if (darkInfo.width < 768 || darkInfo.height < 768) {
    warnings.push(t('splash.warn.smallDark', {
      w: darkInfo.width,
      h: darkInfo.height,
    }));
  }
  if (warnings.length > 0) {
    ui.note(warnings.map((w) => `${kleur.yellow('⚠')} ${w}`).join('\n'), t('splash.warn.title'));
  }

  const destLight = path.join(projectDir, ASSETS_DIR, LIGHT_NAME);
  const destDark = path.join(projectDir, ASSETS_DIR, DARK_NAME);
  const destLightA12 = path.join(projectDir, ASSETS_DIR, LIGHT_ANDROID12_NAME);
  const destDarkA12 = path.join(projectDir, ASSETS_DIR, DARK_ANDROID12_NAME);

  const copySpinner = ui.spinner();
  copySpinner.start(t('splash.copying'));
  await fs.copy(lightPath, destLight, { overwrite: true });
  await fs.copy(darkPath, destDark, { overwrite: true });
  copySpinner.stop(t('splash.copied'));

  const a12Spinner = ui.spinner();
  a12Spinner.start(t('splash.android12Generating'));
  await writeAndroid12Variant(destLight, destLightA12);
  await writeAndroid12Variant(destDark, destDarkA12);
  a12Spinner.stop(t('splash.android12Generated'));

  if (options.skipGenerate) {
    ui.note(t('splash.skipGenerate.hint'), t('splash.skipGenerate.title'));
    ui.outro(t('splash.done'));
    return;
  }

  const genSpinner = ui.spinner();
  genSpinner.start(t('splash.generating'));
  const result = await runFlutterNativeSplash(projectDir);
  if (result.ok) {
    genSpinner.stop(t('splash.generated'));
  } else {
    genSpinner.stop(`⚠ ${t('splash.error.generateFailed')}`);
    if (result.stderr) {
      ui.log.message(kleur.dim(result.stderr.split('\n').slice(0, 8).join('\n')));
    }
    ui.log.message(kleur.dim('dart run flutter_native_splash:create'));
    process.exit(1);
  }

  const summary = [
    `${kleur.bold(t('splash.summary.light'))}: ${kleur.white(LIGHT_NAME)} (${lightInfo.width}x${lightInfo.height})`,
    `${kleur.bold(t('splash.summary.dark'))}:  ${kleur.white(DARK_NAME)} (${darkInfo.width}x${darkInfo.height})`,
  ].join('\n');
  ui.note(summary, t('splash.summary.title'));

  ui.outro(t('splash.done'));
}

async function runFlutterNativeSplash(projectDir) {
  try {
    const { stdout, stderr } = await execAsync(
      'dart run flutter_native_splash:create',
      {
        cwd: projectDir,
        maxBuffer: 16 * 1024 * 1024,
        timeout: 240_000,
      },
    );
    return { ok: true, stdout, stderr };
  } catch (err) {
    return {
      ok: false,
      error: err.message,
      stdout: err.stdout || '',
      stderr: err.stderr || '',
    };
  }
}

module.exports = { runSplash, inspectPng };
