// `gjsify self-update` — refresh the installed @gjsify/cli to a newer release.
//
// Walks `import.meta.url` to find this CLI's own package.json (works whether
// running from `lib/index.js` under Node or the published `dist/cli.gjs.mjs`
// bundle under GJS). Compares against the latest version on the npm registry
// (or the requested `--tag`); when an upgrade is needed, re-uses the existing
// `installPackages` + `linkGlobalBins` pipeline to lay down the new tree at
// the user-global XDG location.
//
// Limitation: only works when the current CLI is installed under
// `defaultGlobalLayout().prefix` (i.e. via `gjsify install -g` or via the
// `install.mjs` bootstrap). Installs from `npm install -g @gjsify/cli` land
// elsewhere and we don't try to chase them — we print a warning and exit.
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { fetchPackument } from '@gjsify/npm-registry';
import { installPackages } from '../utils/install-backend.js';
import { defaultGlobalLayout, linkGlobalBins } from '../utils/install-global.js';
const PACKAGE_NAME = '@gjsify/cli';
export const selfUpdateCommand = {
    command: 'self-update',
    description: `Update the installed ${PACKAGE_NAME} to the latest release (or pinned --tag).`,
    builder: (yargs) => yargs
        .option('check', {
        description: 'Only check whether a newer version is available; do not install.',
        type: 'boolean',
        default: false,
    })
        .option('force', {
        description: 'Reinstall even when the current version already matches the target tag.',
        type: 'boolean',
        default: false,
    })
        .option('tag', {
        description: 'npm dist-tag or pinned version to install (e.g. `latest`, `next`, `0.5.0`).',
        type: 'string',
        default: 'latest',
    })
        .option('skip-deps', {
        description: 'Update only the @gjsify/cli bundle itself; do not pull its runtime dependencies ' +
            '(rolldown, lightningcss, @gjsify/tsc, the native gi:// bridges, …). Faster, but can ' +
            'leave the on-disk runtime deps stale relative to the new bundle.',
        type: 'boolean',
        default: false,
    }),
    handler: async (args) => {
        const layout = defaultGlobalLayout();
        const installedPkgDir = join(layout.prefix, 'node_modules', PACKAGE_NAME);
        const installedPkgJson = join(installedPkgDir, 'package.json');
        const currentVersion = readCurrentVersion();
        const installedAtPrefix = existsSync(installedPkgJson);
        console.log(`Current ${PACKAGE_NAME}: v${currentVersion ?? '(unknown)'}`);
        if (!installedAtPrefix) {
            console.warn(`\nWarning: no @gjsify/cli install found under ${layout.prefix}.\n` +
                `self-update only manages installs created by install.mjs or \`gjsify install -g\`.\n` +
                `If you installed via \`npm install -g\`, remove that and use:\n` +
                `  curl -fsSL https://github.com/gjsify/gjsify/releases/latest/download/install.mjs -o /tmp/g.mjs && gjs -m /tmp/g.mjs && rm /tmp/g.mjs`);
        }
        console.log(`Fetching dist-tags for ${PACKAGE_NAME}@${args.tag} ...`);
        let packument;
        try {
            packument = await fetchPackument(PACKAGE_NAME);
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            console.error(`Failed to fetch packument: ${msg}`);
            process.exit(1);
            return;
        }
        const target = resolveTag(packument, args.tag);
        if (!target) {
            console.error(`Unknown dist-tag '${args.tag}' on ${PACKAGE_NAME}. ` +
                `Known tags: ${Object.keys(packument['dist-tags'] ?? {}).join(', ') || '(none)'}`);
            process.exit(1);
            return;
        }
        console.log(`Latest matching --tag ${args.tag}: v${target}`);
        if (currentVersion === target && !args.force) {
            console.log(`Already up to date (v${target}).`);
            if (!args.check)
                console.log(`Run with --force to reinstall anyway.`);
            return;
        }
        if (args.check) {
            console.log(currentVersion ? `Update available: v${currentVersion} → v${target}` : `Install required: → v${target}`);
            process.exit(1);
            return;
        }
        // `@gjsify/cli` ships two runtimes from one package: the Node `bin`
        // (`lib/index.js`, which imports its deps from `node_modules`) and the
        // self-contained GJS bundle (`dist/cli.gjs.mjs`, which inlines them).
        // The published `dependencies` therefore must cover everything `lib/**`
        // resolves at runtime PLUS the pieces only the GJS runtime loads from
        // disk (the `gi://` native typelib bridges via the launcher's
        // GI_TYPELIB_PATH, `rolldown`/`lightningcss` native addons, the
        // separate `@gjsify/tsc` bundle, `@gjsify/create-app`'s disk templates).
        //
        // By DEFAULT self-update now resolves that full production `dependencies`
        // tree (`skipDeps: false`) so the on-disk runtime deps move in lockstep
        // with the freshly-installed bundle instead of skewing behind it. This is
        // safe today because every `@gjsify/*` package — including the
        // `@gjsify/v8` that once 404'd → 406 and forced `skipDeps: true`
        // unconditionally — is now published to the public registry.
        //
        // `--skip-deps` restores the old fast path: fetch only the top-level
        // `@gjsify/cli` tarball and re-link the bin, leaving runtime deps untouched.
        const pullDeps = !args.skipDeps;
        const depNote = pullDeps ? ' + runtime dependencies' : ' (bundle only)';
        console.log(`Installing ${PACKAGE_NAME}@${target}${depNote} ...`);
        try {
            await installPackages({
                prefix: layout.prefix,
                specs: [`${PACKAGE_NAME}@${target}`],
                verbose: false,
                skipDeps: !pullDeps,
            });
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            console.error(`self-update: install failed — ${msg}`);
            console.error(`Re-run \`gjsify self-update\` to retry.`);
            process.exit(1);
            return;
        }
        const linked = linkGlobalBins([PACKAGE_NAME], layout);
        if (linked.length === 0) {
            console.warn('self-update: install completed but no bins were linked — package.json may be missing a `bin` field.');
        }
        else {
            for (const bin of linked) {
                console.log(`  • ${bin.link}  →  ${bin.target}`);
            }
        }
        console.log(`\nUpdated ${PACKAGE_NAME} to v${target}.`);
        if (pullDeps) {
            console.log('Runtime dependencies were resolved alongside the bundle.');
        }
        else {
            console.log('Bundle-only update (--skip-deps): on-disk runtime dependencies were left unchanged.');
        }
    },
};
/**
 * Resolve the CLI's own `package.json#version`. Walks up from
 * `import.meta.url` (the bundle file under GJS, or `lib/index.js` under
 * Node) until it finds a package.json with `name === '@gjsify/cli'`.
 */
function readCurrentVersion() {
    try {
        // Escape hatch for tests: point GJSIFY_CLI_PACKAGE_JSON at a synthetic
        // package.json to override the upward-walk discovery.  When set to a
        // file whose `name` does not equal PACKAGE_NAME the function returns
        // null, which is the correct production behaviour for "version unknown".
        const override = process.env.GJSIFY_CLI_PACKAGE_JSON;
        if (override) {
            const pkg = JSON.parse(readFileSync(override, 'utf-8'));
            if (pkg.name === PACKAGE_NAME && typeof pkg.version === 'string') {
                return pkg.version;
            }
            return null;
        }
        const here = fileURLToPath(import.meta.url);
        let dir = dirname(resolve(here));
        for (let i = 0; i < 8 && dir !== dirname(dir); i++) {
            const candidate = join(dir, 'package.json');
            if (existsSync(candidate)) {
                const pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
                if (pkg.name === PACKAGE_NAME && typeof pkg.version === 'string') {
                    return pkg.version;
                }
            }
            dir = dirname(dir);
        }
    }
    catch {
        /* not in a recognizable layout */
    }
    return null;
}
function resolveTag(packument, tag) {
    const distTags = (packument['dist-tags'] ?? {});
    if (distTags[tag])
        return distTags[tag];
    // Allow pinned versions via `--tag 0.5.0`
    if (packument.versions && typeof packument.versions === 'object') {
        if (packument.versions[tag])
            return tag;
    }
    return null;
}
