/**
 * Individual setup step functions for the GUI `setup-step` CLI subcommand.
 *
 * Each step reads/writes `setup-state.json` for idempotency and outputs
 * JSON to stdout so the Electron GUI can parse results.
 *
 * Deterministic helpers (config building, identity-memory writing, photo
 * download, GitHub username probe) live in `./setup-shared.ts` and are
 * shared with the interactive CLI flow in `./index.ts` so the two
 * entry points cannot drift.
 */
import { ensureConfigDir, saveConfig } from "../config/index.js";
import { installBuiltinSkills } from "../skills/install.js";
import { loadSetupState, updateSetupState } from "./setup-state.js";
import { loadProvisioningSpec } from "./provisioning-spec.js";
import { provisionEthWallet } from "./provision-eth-wallet.js";
import { hasLocalGoogleRefreshToken } from "../google/token-file.js";
import { applyServerGoogleOAuthTokensForSetup } from "./setup-credentials.js";
import { installDaemon as installVisionclawDaemon } from "../service/daemon.js";
import { generateAndSetWallpaper } from "./generate-wallpaper.js";
import { checkGhAuthenticated, checkGhInstalled } from "./macos-permissions.js";
import { configureMacMiniDisplayResolution } from "./prepare-mac.js";
import { spawnGhAuthLoginWithBrowser } from "./gh-auth-browser.js";
import { applyWorkspaceProfilePhoto, authenticateGithubWithPat, buildVisionClawConfig, downloadProfilePhotos, resolveGithubUsername, writeIdentityMemory, } from "./setup-shared.js";
import { CLAUDE_SONNET_MODEL } from "../config/models.js";
function ok(message, data) {
    return { ok: true, message, data };
}
function getSetupCode(context) {
    return context.setupCode;
}
const stepHandlers = {
    "load-spec": (spec) => {
        if (!spec.agentName)
            throw new Error("agentName is required");
        if (!spec.telegram.botToken)
            throw new Error("telegram.botToken is required");
        if (!spec.aws.region)
            throw new Error("aws.region is required");
        return Promise.resolve(ok(`Spec validated: ${spec.agentName}`));
    },
    "provision-workspace": (spec) => {
        const state = loadSetupState();
        if (state.gmail && state.gmailPassword) {
            return Promise.resolve(ok("Workspace already provisioned", { email: state.gmail, password: state.gmailPassword }));
        }
        const bundleDeliveredPassword = spec.gmailPassword?.trim();
        if (!spec.googleActivated || !bundleDeliveredPassword || !spec.emailUsername) {
            throw new Error("Google Workspace account must be provisioned and activated on the onboarding server before client setup. " +
                "Complete Google provisioning/activation in the onboarding server, regenerate the spec, then retry setup.");
        }
        const email = `${spec.emailUsername}@${spec.workspaceDomain}`;
        updateSetupState({ gmail: email, gmailPassword: bundleDeliveredPassword });
        return Promise.resolve(ok(`Workspace account activated on server (${email})`, {
            email,
            password: bundleDeliveredPassword,
            existed: true,
            source: "server-activated",
        }));
    },
    "google-oauth": async (_spec, context) => {
        const state = loadSetupState();
        if (state.googleOAuthComplete) {
            return ok("Google OAuth already complete");
        }
        // If the onboarding service delivered OAuth tokens via heartbeat bundle,
        // the runtime-credentials applier has already written google-tokens.json.
        // Skip the interactive OAuth flow in that case.
        if (hasLocalGoogleRefreshToken()) {
            updateSetupState({ googleOAuthComplete: true });
            return ok("Google OAuth tokens already present; skipping interactive OAuth");
        }
        const setupCode = getSetupCode(context);
        if (setupCode) {
            const applied = await applyServerGoogleOAuthTokensForSetup(setupCode);
            if (applied) {
                updateSetupState({ googleOAuthComplete: true });
                return ok("Google OAuth tokens delivered by server; skipping interactive OAuth");
            }
        }
        return ok("Google OAuth tokens are managed by the onboarding server and are not available locally yet. " +
            "Complete Google OAuth in the onboarding server profile wizard; tokens will be delivered by setup credentials or runtime heartbeat.", { source: "server-managed", status: "pending_oauth" });
    },
    "github-auth": async (spec) => {
        if (!spec.github.enabled) {
            return ok("GitHub auth not enabled — skipped");
        }
        const state = loadSetupState();
        if (state.githubAuthComplete) {
            return ok("GitHub already authenticated", { username: state.githubUsername });
        }
        const ghInstalled = await checkGhInstalled();
        if (!ghInstalled)
            throw new Error("GitHub CLI (gh) is not installed");
        const ghAuthed = await checkGhAuthenticated();
        if (ghAuthed) {
            const username = resolveGithubUsername();
            updateSetupState({ githubAuthComplete: true, githubUsername: username });
            return ok(`GitHub authenticated as ${username ?? "unknown"}`, { username });
        }
        const githubPat = spec.github.pat?.trim();
        if (githubPat) {
            const auth = authenticateGithubWithPat(githubPat);
            if (!auth.ok)
                throw new Error(auth.error ?? "GitHub PAT auth failed");
            updateSetupState({ githubAuthComplete: true, githubUsername: auth.username });
            return ok(`GitHub authenticated via PAT as ${auth.username ?? "unknown"}`, { username: auth.username, via: "pat" });
        }
        const result = spawnGhAuthLoginWithBrowser();
        if (result.status !== 0)
            throw new Error("gh auth login --web failed");
        const authedAfter = await checkGhAuthenticated();
        if (!authedAfter)
            throw new Error("GitHub CLI auth did not complete successfully");
        const username = resolveGithubUsername();
        updateSetupState({ githubAuthComplete: true, githubUsername: username });
        return ok(`GitHub authenticated as ${username ?? "unknown"}`, { username });
    },
    "cloudflare-tunnel": (_spec) => {
        return Promise.resolve(ok("Tunnel configuration is now delivered via runtime credentials — skipped"));
    },
    "eth-wallet": (_spec) => {
        const state = loadSetupState();
        if (state.ethAddress && state.ethPrivateKey && state.ethMnemonic) {
            return Promise.resolve(ok("ETH wallet already generated", {
                address: state.ethAddress,
                privateKey: state.ethPrivateKey,
                mnemonic: state.ethMnemonic,
            }));
        }
        const wallet = provisionEthWallet();
        updateSetupState({
            ethAddress: wallet.address,
            ethPrivateKey: wallet.privateKey,
            ethMnemonic: wallet.mnemonic,
        });
        return Promise.resolve(ok(`ETH wallet generated: ${wallet.address}`, {
            address: wallet.address,
            privateKey: wallet.privateKey,
            mnemonic: wallet.mnemonic,
        }));
    },
    "save-config": (spec) => {
        const state = loadSetupState();
        if (state.configSaved) {
            return Promise.resolve(ok("Config already saved"));
        }
        ensureConfigDir();
        // GUI flow has no model picker — pin to the default. The interactive
        // CLI flow lets the operator choose; both paths share everything else
        // via `buildVisionClawConfig`.
        const config = buildVisionClawConfig({
            spec,
            state,
            gmail: state.gmail ?? `${spec.emailUsername}@${spec.workspaceDomain}`,
            model: CLAUDE_SONNET_MODEL,
        });
        saveConfig(config);
        installBuiltinSkills();
        writeIdentityMemory({
            gmail: config.gmail,
            gmailPassword: config.gmailPassword,
            systemPassword: config.systemPassword,
            wallet: {
                address: state.ethAddress ?? "",
                privateKey: state.ethPrivateKey ?? "",
                mnemonic: state.ethMnemonic ?? "",
            },
            // The GUI `save-config` step does not materialise photos itself —
            // that's the `bot-identity` step's job. Pass an empty list so the
            // memory file gets the "no photos available" placeholder; if the
            // operator runs steps out of order the GUI is responsible for
            // re-running save-config later.
            profilePhotos: [],
            githubUsername: state.githubUsername ?? undefined,
        });
        const quickRef = [];
        quickRef.push("To start agent: visionclaw daemon start --debug");
        quickRef.push("To install auto-start on login: visionclaw daemon install --debug");
        quickRef.push("To check agent logs: visionclaw daemon logs");
        updateSetupState({ configSaved: true });
        return Promise.resolve(ok(`Config saved for ${config.agentName}`, { gmail: config.gmail, quickRef }));
    },
    // Telegram bot name and profile photo are now set by the onboarding admin
    // when the agent is created. This step only downloads photos for local
    // storage and sets the Google Workspace profile photo.
    "bot-identity": async (spec) => {
        const state = loadSetupState();
        if (state.botIdentityComplete) {
            return ok("Bot identity already configured");
        }
        const botName = spec.telegram.botName.trim();
        const photoSources = spec.telegram.profilePhotoSources?.filter(Boolean) ?? [];
        // Silent reporter — the GUI surfaces a single structured result, not
        // a stream of console messages. Per-photo failures are still
        // swallowed inside `downloadProfilePhotos`.
        const savedPhotos = await downloadProfilePhotos(photoSources);
        if (state.gmail) {
            await applyWorkspaceProfilePhoto(spec, state.gmail, savedPhotos);
        }
        updateSetupState({ botIdentityComplete: true });
        return ok(`Bot identity set: ${botName}`, { photos: savedPhotos.length });
    },
    "display-resolution": (_spec) => {
        if (process.platform !== "darwin") {
            return Promise.resolve(ok("Not macOS — skipped"));
        }
        configureMacMiniDisplayResolution();
        return Promise.resolve(ok("Display resolution step complete"));
    },
    "generate-wallpaper": (spec) => {
        if (process.platform !== "darwin") {
            return Promise.resolve(ok("Not macOS — skipped"));
        }
        const wallpaperPath = generateAndSetWallpaper(spec.agentName);
        return Promise.resolve(ok(`Wallpaper generated and set: ${wallpaperPath}`, { path: wallpaperPath }));
    },
    "install-service": async (spec) => {
        if (!spec.installService) {
            return ok("Daemon install skipped (disabled in spec)");
        }
        if (process.platform !== "darwin") {
            return ok("Not macOS — skipped");
        }
        if (process.env.VISIONCLAW_ELECTRON === "1") {
            return ok("Electron host handles daemon installation");
        }
        await installVisionclawDaemon();
        return ok("LaunchAgent installed");
    },
};
export const SETUP_STEP_IDS = Object.keys(stepHandlers);
export const __testStepHandlers = stepHandlers;
/**
 * Run a single setup step by ID.
 * Outputs JSON `{ ok, message, data? }` to stdout.
 * Exits with code 0 on success, 1 on failure.
 */
export async function runSetupStepCli(args) {
    const stepIdx = args.indexOf("--step");
    const stepId = stepIdx !== -1 && stepIdx + 1 < args.length ? args[stepIdx + 1] : undefined;
    if (!stepId) {
        console.error("Usage: visionclaw setup-step --step <stepId> --spec <path>");
        process.exit(1);
    }
    const specIdx = args.indexOf("--spec");
    const specPath = specIdx !== -1 && specIdx + 1 < args.length ? args[specIdx + 1] : undefined;
    if (!specPath) {
        console.error("Usage: visionclaw setup-step --step <stepId> --spec <path>");
        process.exit(1);
    }
    if (!SETUP_STEP_IDS.includes(stepId)) {
        console.log(JSON.stringify({ ok: false, message: `Unknown step: ${stepId}` }));
        process.exit(1);
    }
    const handler = stepHandlers[stepId];
    try {
        ensureConfigDir();
        const { spec, setupCode } = await loadProvisioningSpec(specPath);
        const result = await handler(spec, { setupCode });
        console.log(JSON.stringify(result));
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        console.log(JSON.stringify({ ok: false, message }));
        process.exit(1);
    }
}
//# sourceMappingURL=setup-steps.js.map