import { InstalledPackage, IScopedLogger } from '@camstack/types';
import { AddonManifest } from './addon-manifest.js';
/**
 * Addon install source mode (CAMSTACK_INSTALL_SOURCE env var):
 * - 'npm'     — download from npm registry (production)
 * - 'symlink' — symlink to local workspace packages/ (dev hot-reload)
 * - 'local'   — copy dist/ from local workspace packages/ (dev, no hot-reload)
 *
 * If not set: auto-detect — uses 'symlink' if in monorepo, 'npm' otherwise.
 */
export type InstallSource = "npm" | "symlink" | "local";
export interface AddonInstallerConfig {
    /** Directory where addons are stored (e.g., {dataDir}/addons) */
    readonly addonsDir: string;
    /** npm registry URL */
    readonly registry?: string;
    /** Workspace packages directory (e.g., /path/to/camstack-server/packages) */
    readonly workspacePackagesDir?: string;
    /** Install source mode. Default: auto-detect */
    readonly installSource?: InstallSource;
}
export type { InstalledPackage };
export declare class AddonInstaller {
    readonly addonsDir: string;
    private readonly registry;
    readonly workspaceDir: string | undefined;
    readonly installSource: InstallSource;
    private readonly logger;
    /**
     * Central manifest of installed addons. Tracks version + source +
     * timestamps + last-backup pointer per package, written atomically
     * after every install/uninstall. Replaces the legacy per-addon
     * `.install-source` markers (which we still write for backward
     * compatibility with code that reads them directly — see fs-utils
     * `getAddonInstallSource`).
     */
    readonly manifest: AddonManifest;
    constructor(config: AddonInstallerConfig, logger?: IScopedLogger);
    /**
     * Required addon packages that must be installed for the server to function.
     *
     * TODO (kernel-cleanup Point 5 — future discussion): this static list
     * is the one remaining place where the kernel carries knowledge of
     * addon package names. It is NOT domain logic (the kernel never
     * dispatches based on these ids), but it is a deployment manifest
     * that today lives in kernel source. The agreed direction is to
     * externalize it via the deployment itself — the Docker image / npm
     * bundle / host package can pre-install the desired addon set and
     * the kernel stops referencing package names at all. Until that
     * change lands, the list stays here so fresh installs can bootstrap
     * without manual npm work.
     */
    static readonly REQUIRED_PACKAGES: readonly string[];
    /**
     * Agent-only packages: core infra + cluster-capable addons.
     * Same TODO as REQUIRED_PACKAGES above — candidate for deployment-
     * time externalization.
     *
     * `decoder-ffmpeg` removed (2026-04-18) — node-av is the single
     * decoder path. ffmpeg emitted native-resolution JPEGs that blew
     * motion-wasm's WASM memory (`offset is out of bounds` at 3840×2160).
     * node-av already downsamples to ≤640 width.
     */
    static readonly AGENT_PACKAGES: readonly string[];
    /**
     * Ensure all required packages are installed in the addons directory.
     *
     * Bootstrap-only — prefers the workspace/bundle copy when available
     * (offline-first, faster, container-self-contained). On-demand
     * installs from the admin UI go through `install()` instead, which
     * defaults to npm in production.
     *
     * @param packages — optional custom package list (default: REQUIRED_PACKAGES)
     */
    ensureRequiredPackages(packages?: readonly string[]): Promise<void>;
    /**
     * Install a single addon — on-demand installer used by AddonPackageService
     * (admin UI "Install addon" / "Reinstall" buttons).
     *
     * Always npm by default — even in production where the workspace is
     * present at `/app/packages/`. The user pressing "Reinstall" from
     * the admin UI typically wants the LATEST version from the registry,
     * not the version that happened to be bundled with the container.
     *
     * The only exception is `installSource === 'symlink'` (dev mode) —
     * developers iterating on an addon want the workspace copy reflected
     * immediately. Everyone else (production, CI, end users) goes to npm.
     */
    install(packageName: string, version?: string): Promise<{
        name: string;
        version: string;
    }>;
    /**
     * Install addon from a local workspace package.
     * In 'symlink' mode: creates a symlink to the package directory.
     * In 'local' mode: copies package.json + dist/ to addons dir.
     */
    private installLocal;
    private installSymlink;
    private installCopy;
    /**
     * Install addon from npm (download tgz, then extract). Tries the
     * registry HTTPS API first (works inside packaged Electron — no need
     * for `npm` on PATH); falls back to `npm pack` for parity with the
     * legacy path when the registry call fails (private registries with
     * unusual auth, offline mode, etc).
     */
    installFromNpm(packageName: string, version?: string): Promise<{
        name: string;
        version: string;
    }>;
    /**
     * Resolve `{packageName, version?}` against the configured registry
     * over HTTPS and download the tarball into `tmpDir`. Returns the path
     * to the downloaded `.tgz`.
     *
     * Versioning rules (mirrors `npm pack` semantics):
     *   - `version === undefined` → uses `dist-tags.latest`
     *   - exact semver string → looked up in `versions[version]`
     *   - dist-tag name (e.g. `next`, `beta`) → resolved via `dist-tags`
     *
     * Range specs (`^1.0.0`, `~1.2`, `>=2`) are NOT supported here — the
     * caller should resolve those upstream. `npm pack` handles them, so
     * the legacy fallback path remains the right answer for those.
     */
    private downloadNpmTarball;
    /** Install addon from a tgz file (uploaded or downloaded) */
    installFromTgz(tgzPath: string): Promise<{
        name: string;
        version: string;
    }>;
    /**
     * Apply an update to an installed addon, backing up the current
     * version first so the caller can roll back atomically on failure.
     *
     * Steps:
     *   1. Rename current install dir → {addonsDir}/.backups/{name}/{ts}/
     *   2. Run a fresh install (npm or local depending on installSource)
     *   3. Record `lastBackupDir` in the manifest entry
     *
     * The post-install caller (kernel.restartAddon + health-check) is
     * responsible for verifying the new version actually works and for
     * triggering `rollbackAddon()` if the health check fails.
     *
     * Throws if there is no installed version to update — caller should
     * prefer plain `install()` for first-time installs.
     */
    applyUpdate(packageName: string, version?: string): Promise<{
        name: string;
        version: string;
        backupDir: string;
    }>;
    /**
     * Roll back an addon to its most recent backup. Used after an update
     * passes installation but fails its post-restart health check.
     *
     * Returns the version that was restored, or `null` if there was no
     * backup to roll back to.
     */
    rollbackAddon(packageName: string): Promise<string | null>;
    /**
     * Drop the recorded backup for an addon (e.g. after a successful
     * post-update health check confirms the new version is healthy).
     * Frees disk space without affecting the running install.
     */
    clearBackup(packageName: string): void;
    uninstall(packageName: string): Promise<void>;
    listInstalled(): InstalledPackage[];
    isInstalled(packageName: string): boolean;
    getInstalledPackage(packageName: string): InstalledPackage | null;
    private findLocalPackage;
    /** Ensure a package is built (dist/ exists and page bundles present) */
    private ensureBuilt;
    /** Check if page bundles declared in package.json are missing from dist/ */
    private isDistIncomplete;
}
//# sourceMappingURL=addon-installer.d.ts.map