const path = require('node:path');
const { spawnSync } = require('node:child_process');
const fs = require('fs-extra');
const kleur = require('kleur');
const ui = require('../utils/ui');
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
const { printCompactHeader } = require('../utils/brand');
const { readBundleId, readPackageName } = require('../utils/mobile-identity');
const { spawnFlutterWithSpinner } = require('../utils/flutter-run');

function runCmd(cmd, args) {
  const res = spawnSync(cmd, args, { encoding: 'utf8' });
  return {
    code: res.status,
    stdout: (res.stdout || '').trim(),
    stderr: (res.stderr || '').trim(),
  };
}

async function listFlutterDevices(projectDir) {
  const res = spawnSync('flutter', ['devices', '--machine'], {
    cwd: projectDir,
    encoding: 'utf8',
  });
  if (res.status !== 0) return [];
  try {
    const parsed = JSON.parse(res.stdout);
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

function classifyTarget(device) {
  const platform = (device.targetPlatform || '').toLowerCase();
  if (platform === 'ios') {
    return device.emulator ? 'ios-simulator' : 'ios-device';
  }
  if (platform.startsWith('android')) {
    return device.emulator ? 'android-emulator' : 'android-device';
  }
  if (platform.startsWith('web')) {
    return 'web';
  }
  return 'unknown';
}

function filterDevices(devices, options) {
  return devices.filter((d) => {
    const kind = classifyTarget(d);
    if (options.device && d.id === options.device) return true;
    if (options.ios && (kind === 'ios-simulator' || kind === 'ios-device')) {
      return true;
    }
    if (
      options.android &&
      (kind === 'android-emulator' || kind === 'android-device')
    ) {
      return true;
    }
    if (options.web && kind === 'web') return true;
    if (!options.ios && !options.android && !options.web && !options.device) {
      return true;
    }
    return false;
  });
}

async function pickDevice(devices, t) {
  if (devices.length === 0) return null;
  if (devices.length === 1) return devices[0];
  const choice = await ui.select({
    message: t('reset.prompt.pickDevice'),
    options: devices.map((d) => ({
      value: d.id,
      label: `${d.name} ${kleur.dim(`(${classifyTarget(d)})`)}`,
    })),
  });
  return devices.find((d) => d.id === choice) || null;
}

function resetIosSimulator(device, bundleId, t) {
  ui.log.message(kleur.dim(`xcrun simctl uninstall ${device.id} ${bundleId}`));
  const res = runCmd('xcrun', ['simctl', 'uninstall', device.id, bundleId]);
  if (res.code !== 0) {
    // simctl returns 0 even when app wasn't installed; non-zero is a real error
    ui.log.warn(res.stderr || t('reset.warn.iosUninstallFailed'));
    return false;
  }
  ui.log.success(t('reset.success.uninstalled'));
  return true;
}

function resetIosDevice(device, bundleId, t) {
  // Try Apple's devicectl (ships with Xcode 15+). Falls back to a manual
  // instruction when devicectl isn't available or the uninstall fails.
  const probe = runCmd('xcrun', ['devicectl', '--version']);
  if (probe.code !== 0) {
    noticeIosPhysical(t);
    return false;
  }
  ui.log.message(
    kleur.dim(
      `xcrun devicectl device uninstall app --device ${device.id} ${bundleId}`
    )
  );
  const res = runCmd('xcrun', [
    'devicectl',
    'device',
    'uninstall',
    'app',
    '--device',
    device.id,
    bundleId,
  ]);
  if (res.code !== 0) {
    ui.log.warn(res.stderr || t('reset.warn.iosDeviceUninstallFailed'));
    noticeIosPhysical(t);
    return false;
  }
  ui.log.success(t('reset.success.uninstalled'));
  return true;
}

function resetAndroid(device, packageName, t) {
  // Force-stop first — `adb uninstall` returns DELETE_FAILED_INTERNAL_ERROR
  // when the app has lingering processes or pending PM state (common on
  // Pixel emulators after a crash or hot-restart).
  runCmd('adb', ['-s', device.id, 'shell', 'am', 'force-stop', packageName]);

  ui.log.message(kleur.dim(`adb -s ${device.id} uninstall ${packageName}`));
  const res = runCmd('adb', ['-s', device.id, 'uninstall', packageName]);
  const out = `${res.stdout}\n${res.stderr}`.toLowerCase();
  if (out.includes('success')) {
    ui.log.success(t('reset.success.uninstalled'));
    return true;
  }
  if (out.includes('not installed')) {
    ui.log.message(kleur.dim(`– ${t('reset.info.notInstalled')}`));
    return true;
  }

  // Fallback: `pm uninstall --user 0` bypasses several PM states that
  // `adb uninstall` can't (DELETE_FAILED_INTERNAL_ERROR, multi-user, etc).
  ui.log.message(
    kleur.dim(`adb -s ${device.id} shell pm uninstall --user 0 ${packageName}`)
  );
  const fallback = runCmd('adb', [
    '-s', device.id, 'shell', 'pm', 'uninstall', '--user', '0', packageName,
  ]);
  const fallbackOut = `${fallback.stdout}\n${fallback.stderr}`.toLowerCase();
  if (fallbackOut.includes('success')) {
    ui.log.success(t('reset.success.uninstalled'));
    return true;
  }
  if (fallbackOut.includes('not installed') || fallbackOut.includes('unknown package')) {
    ui.log.message(kleur.dim(`– ${t('reset.info.notInstalled')}`));
    return true;
  }

  ui.log.warn(
    res.stdout || res.stderr || fallback.stdout || fallback.stderr ||
    t('reset.warn.androidUninstallFailed')
  );
  return false;
}

/// Reads the package name of the device's default home launcher.
/// Used so we can clear ITS cache (not the app's) — the launcher caches
/// widget previews aggressively and a gray preview persists across
/// uninstalls of the app itself.
function detectAndroidLauncher(device) {
  const res = runCmd('adb', [
    '-s', device.id,
    'shell', 'cmd', 'package', 'resolve-activity',
    '--brief',
    '-c', 'android.intent.category.HOME',
    '-a', 'android.intent.action.MAIN',
  ]);
  if (res.code !== 0) return null;
  // Output ends with a line like:
  //   com.google.android.apps.nexuslauncher/.NexusLauncherActivity
  const lines = res.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
  for (let i = lines.length - 1; i >= 0; i--) {
    const line = lines[i];
    if (line.includes('/') && !line.startsWith('priority=')) {
      return line.split('/')[0];
    }
  }
  return null;
}

/// Clears the launcher's preview cache and force-stops it.
/// pm clear-cache removes cached widget previews from disk; force-stop
/// kicks any in-memory composition. Neither resets the user's home
/// screen — that would be `pm clear` (which we intentionally do NOT do).
function clearAndroidLauncherCache(device, launcherPkg, t) {
  ui.log.message(
    kleur.dim(`adb -s ${device.id} shell pm clear-cache ${launcherPkg}`)
  );
  runCmd('adb', [
    '-s', device.id, 'shell', 'pm', 'clear-cache', launcherPkg,
  ]);
  ui.log.message(
    kleur.dim(`adb -s ${device.id} shell am force-stop ${launcherPkg}`)
  );
  const stop = runCmd('adb', [
    '-s', device.id, 'shell', 'am', 'force-stop', launcherPkg,
  ]);
  if (stop.code !== 0) {
    ui.log.warn(t('reset.warn.launcherCacheFailed'));
    return false;
  }
  ui.log.success(t('reset.success.launcherCleared', { pkg: launcherPkg }));
  return true;
}

function noticeIosPhysical(t) {
  ui.log.warn(t('reset.warn.iosDeviceManual'));
}

function isXcodeRunning() {
  const res = spawnSync('pgrep', ['-x', 'Xcode'], { encoding: 'utf8' });
  return res.status === 0 && (res.stdout || '').trim().length > 0;
}

async function ensureXcodeClosed(t) {
  if (!isXcodeRunning()) return true;
  const choice = await ui.select({
    message: t('reset.prompt.xcodeOpen'),
    options: [
      { value: 'close', label: t('reset.prompt.xcodeOpen.close') },
      { value: 'skip', label: t('reset.prompt.xcodeOpen.skip') },
      { value: 'cancel', label: t('reset.prompt.xcodeOpen.cancel') },
    ],
    initialValue: 'close',
  });
  if (choice === 'cancel') return false;
  if (choice === 'close') {
    spawnSync('osascript', ['-e', 'tell application "Xcode" to quit'], {
      encoding: 'utf8',
    });
    // Give Xcode a moment to release the debug session before we proceed.
    await new Promise((resolve) => setTimeout(resolve, 1500));
    ui.log.message(kleur.dim(t('reset.info.xcodeClosed')));
  }
  return true;
}

function noticeWeb(t) {
  ui.log.warn(t('reset.warn.webIncognito'));
}

function runFlutterOnDevice(device, projectDir, t) {
  return spawnFlutterWithSpinner(['run', '-d', device.id], projectDir, t);
}

async function runReset(directory, options = {}) {
  const t = createTranslator(options.language || detectDefaultLanguage());
  const projectDir = path.resolve(directory || '.');

  if (!(await fs.pathExists(path.join(projectDir, 'pubspec.yaml')))) {
    throw new Error(t('reset.error.notFlutterProject'));
  }

  printCompactHeader(t);
  ui.intro(t('reset.title'));

  const [bundleId, packageName] = await Promise.all([
    readBundleId(projectDir),
    readPackageName(projectDir),
  ]);
  if (!bundleId && !packageName) {
    throw new Error(t('reset.error.noIdentifier'));
  }

  ui.log.message(`${kleur.dim('iOS bundle id:')} ${bundleId || kleur.dim('—')}`);
  ui.log.message(`${kleur.dim('Android package:')} ${packageName || kleur.dim('—')}`);

  const scanSpinner = ui.spinner();
  scanSpinner.start(t('reset.scanning'));
  const allDevices = await listFlutterDevices(projectDir);
  const devices = filterDevices(allDevices, options);
  scanSpinner.stop(t('reset.scanning'));

  if (devices.length === 0) {
    ui.log.warn(t('reset.warn.noDevices'));
    return;
  }

  const target = await pickDevice(devices, t);
  if (!target) {
    ui.log.warn(t('reset.warn.nothingSelected'));
    return;
  }

  const kind = classifyTarget(target);

  if (kind === 'ios-device') {
    const proceed = await ensureXcodeClosed(t);
    if (!proceed) {
      ui.outro(t('reset.outro.cancelled'));
      return;
    }
  }

  ui.log.step(kleur.bold(`${t('reset.resetting')}: ${target.name}`));

  let didReset = false;
  switch (kind) {
    case 'ios-simulator':
      if (!bundleId) {
        ui.log.error(t('reset.error.noBundleId'));
        break;
      }
      didReset = resetIosSimulator(target, bundleId, t);
      break;
    case 'ios-device':
      if (!bundleId) {
        ui.log.error(t('reset.error.noBundleId'));
        break;
      }
      didReset = resetIosDevice(target, bundleId, t);
      break;
    case 'android-emulator':
    case 'android-device':
      if (!packageName) {
        ui.log.error(t('reset.error.noPackageName'));
        break;
      }
      didReset = resetAndroid(target, packageName, t);
      break;
    case 'web':
      noticeWeb(t);
      break;
    default:
      ui.log.warn(`${t('reset.warn.unknownPlatform')}: ${kind}`);
  }

  if (!didReset) {
    ui.outro(t('reset.outro.skipped'));
    return;
  }

  // On Android, also clear the launcher cache so widget previews refresh.
  // Without this, the gallery keeps showing the gray placeholder even after
  // the app is reinstalled (the launcher caches preview drawables by
  // package + component name, which don't change between installs).
  if (
    (kind === 'android-emulator' || kind === 'android-device') &&
    options.clearLauncher !== false
  ) {
    const launcherPkg = detectAndroidLauncher(target);
    if (launcherPkg) {
      const proceedClear = await ui.confirm({
        message: t('reset.prompt.clearLauncherCache', { pkg: launcherPkg }),
        initialValue: true,
      });
      if (proceedClear) {
        clearAndroidLauncherCache(target, launcherPkg, t);
      }
    } else {
      ui.log.message(kleur.dim(t('reset.warn.launcherNotDetected')));
    }
  }

  if (options.reinstall === false) {
    ui.outro(t('reset.outro.uninstalledOnly'));
    return;
  }

  const proceed = await ui.confirm({
    message: t('reset.prompt.reinstallNow'),
    initialValue: true,
  });
  if (!proceed) {
    ui.outro(t('reset.outro.uninstalledOnly'));
    return;
  }

  ui.log.step(kleur.bold(t('reset.reinstalling')));
  await runFlutterOnDevice(target, projectDir, t);
}

module.exports = { runReset };
