/**
 * npm CLI update notifier — thin wrapper around `update-notifier`.
 *
 * Shows a non-blocking banner on CLI startup when a newer version of
 * the published package is available on the npm registry. User stays
 * in control: the CLI never auto-installs anything.
 *
 * Brand awareness:
 *   The CLI ships under two brand-specific package names
 *   (`@loxia-labs/loxia-autopilot-one`, `@loxia-labs/loxia-onbuzz`).
 *   `update-notifier` reads the package name from the runtime
 *   package.json so each brand's notifier checks the right registry
 *   entry automatically.
 *
 * Kill-switch:
 *   - LOXIA_DISABLE_UPDATE_CHECK=1   — our env var (preferred)
 *   - NO_UPDATE_NOTIFIER=1           — package's own opt-out
 *   - CI=true                        — most CIs set this; we honour it
 *
 * Dependency tolerance:
 *   `update-notifier` is dynamically imported. If the package isn't
 *   installed (e.g. dev tree without `npm install`), the function is
 *   a silent no-op — the CLI MUST still boot.
 *
 * Network tolerance:
 *   The check is a background fetch with cached result. We never await
 *   the network — `update-notifier` schedules a child-process fetch
 *   and writes the result to ~/.config/configstore for the NEXT run.
 *   That means: the FIRST time a user is offline, no banner. Subsequent
 *   runs use the cached version-comparison until the next check window.
 */

import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';

/**
 * Env vars that disable the check. All are case-sensitive string
 * comparisons against `'1'` / `'true'` to match common conventions.
 */
const DISABLE_ENVS = Object.freeze([
  'LOXIA_DISABLE_UPDATE_CHECK',
  'NO_UPDATE_NOTIFIER',
  'CI',                       // standard CI marker
]);

/** How often update-notifier should re-check the npm registry. */
const UPDATE_CHECK_INTERVAL_MS = 1000 * 60 * 60 * 24; // 24h

/**
 * @returns {boolean} true if any kill-switch env is set truthy.
 */
export function isUpdateCheckDisabled(env = process.env) {
  for (const key of DISABLE_ENVS) {
    const v = env[key];
    if (v && v !== '0' && v.toLowerCase() !== 'false') return true;
  }
  return false;
}

/**
 * Load the package.json this CLI is running from (the installed one,
 * not the one in src/utilities of a dev tree). Returned object always
 * has at least `name` and `version`.
 *
 * @returns {{ name: string, version: string } | null}
 */
export function loadPackageJson({
  startDir = dirname(fileURLToPath(import.meta.url)),
  fs = { readFileSync },
} = {}) {
  // Walk up from this file looking for package.json with a `bin`
  // entry — that's the published CLI package, not a nested dep.
  let dir = startDir;
  const ROOT_LIMIT = 8;
  for (let i = 0; i < ROOT_LIMIT; i++) {
    try {
      const raw = fs.readFileSync(join(dir, 'package.json'), 'utf8');
      const pkg = JSON.parse(raw);
      if (pkg.bin || pkg.name?.startsWith('@loxia-labs/')) {
        return { name: pkg.name, version: pkg.version };
      }
    } catch { /* keep walking */ }
    const parent = dirname(dir);
    if (parent === dir) break;
    dir = parent;
  }
  return null;
}

/**
 * Run the update check and (if a newer version is on the registry)
 * print a banner. Never throws. Never blocks. Safe to call exactly
 * once at CLI entry.
 *
 * Returns the underlying notifier instance for tests, or null when
 * disabled / package missing / pkg.json unreadable.
 */
export async function runUpdateCheck({
  env = process.env,
  pkg = loadPackageJson(),
  importImpl = (specifier) => import(specifier),
  logger = console,
} = {}) {
  if (isUpdateCheckDisabled(env)) return null;
  if (!pkg) return null;

  let mod;
  try {
    mod = await importImpl('update-notifier');
  } catch {
    // Package not installed in this tree. Silent no-op.
    return null;
  }

  const updateNotifier = mod.default || mod;
  if (typeof updateNotifier !== 'function') return null;

  try {
    const notifier = updateNotifier({
      pkg,
      updateCheckInterval: UPDATE_CHECK_INTERVAL_MS,
      shouldNotifyInNpmScript: false,
    });
    // notify() is itself a no-op when there's no update; safe to call
    // unconditionally.
    notifier.notify?.({
      defer: false,            // print before the CLI does its work
      isGlobal: true,          // CLI ships as a global binary
      message:
        `Update available {currentVersion} → {latestVersion}\n` +
        `Run \`npm i -g {packageName}\` to update.`,
    });
    return notifier;
  } catch (err) {
    // Any failure (network, configstore write, etc.) is non-fatal.
    logger?.debug?.('[updateNotifier] check skipped:', err.message);
    return null;
  }
}
