/**
 * studiograph deploy — automated deployment via provider APIs
 *
 * Subcommands:
 *   studiograph deploy railway   — create a full Railway project via GraphQL API
 */
import { Command } from 'commander';
import { intro, outro, password, confirm, log, select, spinner } from '@clack/prompts';
import { spawnSync } from 'child_process';
import { writeFileSync } from 'fs';
import { join } from 'path';
import { Workspace } from '../../core/workspace.js';
import { loadUserConfig, saveUserConfig } from '../../core/user-config.js';
import { MODEL_PROVIDERS } from '../setup-wizard.js';
import { provisionRequiredRailwaySecrets } from '../railway-provisioning.js';
import { pinStartCommand, readOwnVersion } from '../railway-pin.js';
// ── helpers ──────────────────────────────────────────────────────────────────
function openBrowser(url) {
    const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
    spawnSync(cmd, [url], { stdio: 'ignore' });
}
/** Extract a Railway project ID from a dashboard URL or bare UUID. */
function parseProjectId(input) {
    const clean = input.split('?')[0];
    const urlMatch = clean.match(/\/project\/([a-f0-9-]+)/);
    if (urlMatch)
        return urlMatch[1];
    if (/^[a-f0-9-]{36}$/.test(clean))
        return clean;
    return null;
}
// ── Railway GraphQL client ──────────────────────────────────────────────────
const RAILWAY_API = 'https://backboard.railway.app/graphql/v2';
async function railwayQuery(token, query, variables = {}) {
    const res = await fetch(RAILWAY_API, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ query, variables }),
    });
    if (!res.ok) {
        throw new Error(`Railway API error: ${res.status} ${res.statusText}`);
    }
    const json = await res.json();
    if (json.errors?.length) {
        throw new Error(`Railway API: ${json.errors.map((e) => e.message).join(', ')}`);
    }
    return json.data;
}
// ── deploy command group ──────────────────────────────────────────────────────
export const deployCommand = new Command('deploy')
    .description('Deploy Studiograph to a hosting platform');
// ── studiograph deploy railway ────────────────────────────────────────────────
deployCommand
    .command('railway')
    .description('Deploy Studiograph to Railway (creates a project, or adds a service to an existing one)')
    .option('-n, --name <name>', 'Railway project name (ignored when --project is given)')
    .option('-p, --project <project>', 'Existing Railway project ID or dashboard URL — add this service to it instead of creating a new project')
    .option('-s, --service-name <name>', 'Railway service name (default: workspace name slug)')
    .option('--provider <provider>', 'AI provider (skips the interactive picker, e.g. anthropic/google/openai)')
    .option('--model <model>', 'Model id (skips the interactive picker)')
    .option('--api-key <key>', 'Model API key (or set STUDIOGRAPH_DEPLOY_API_KEY); skips the password prompt')
    .action(async (opts) => {
    const workspace = new Workspace();
    if (!workspace.isWorkspace()) {
        console.error('Not a Studiograph workspace. Run this from your workspace root.');
        process.exit(1);
        return;
    }
    intro('Deploy Studiograph to Railway');
    // ── Step 1: Collect credentials ────────────────────────────────────────
    log.step('Step 1 — Credentials');
    const userConfig = loadUserConfig();
    let railwayToken = userConfig.railway_token || '';
    if (railwayToken) {
        log.info('Using saved Railway token from ~/.studiograph/user.json');
    }
    else {
        const input = await password({
            message: 'Railway API token (create at https://railway.com/account/tokens)',
            validate: (v) => v.trim() ? undefined : 'Required',
        });
        if (typeof input === 'symbol') {
            outro('Cancelled');
            process.exit(0);
        }
        railwayToken = input.trim();
    }
    // Verify the token
    const s = spinner();
    s.start('Verifying Railway token...');
    try {
        const data = await railwayQuery(railwayToken.trim(), `query { projects { edges { node { id name } } } }`);
        const count = data.projects.edges.length;
        s.stop(`Authenticated (${count} existing project${count === 1 ? '' : 's'})`);
    }
    catch (err) {
        s.stop('Railway authentication failed');
        log.error(err instanceof Error ? err.message : 'Unknown error');
        outro('Check your token and try again.');
        process.exit(1);
        return;
    }
    // Auto-save token for future use
    if (!userConfig.railway_token) {
        userConfig.railway_token = railwayToken.trim();
        saveUserConfig(userConfig);
        log.info('Token saved to ~/.studiograph/user.json');
    }
    // ── Step 2: Workspace info ────────────────────────────────────────────
    log.step('Step 2 — Workspace configuration');
    const config = await workspace.loadConfig();
    // Provider: always prompt, defaulting to whatever's set in workspace
    // config, user config, or MODEL_PROVIDER env (in that order). Lets users
    // override at deploy time without env-var gymnastics — important when
    // bouncing between providers for QA. No silent fallback to 'anthropic'.
    const explicitProvider = config.model_provider || userConfig.model_provider || process.env.MODEL_PROVIDER;
    const providerDefault = (explicitProvider && explicitProvider in MODEL_PROVIDERS)
        ? explicitProvider
        : undefined;
    let providerId;
    if (opts.provider) {
        if (!(opts.provider in MODEL_PROVIDERS)) {
            outro(`Unknown --provider "${opts.provider}". Valid: ${Object.keys(MODEL_PROVIDERS).join(', ')}`);
            process.exit(1);
            return;
        }
        providerId = opts.provider;
        log.info(`Provider: ${MODEL_PROVIDERS[providerId].name} (from --provider)`);
    }
    else {
        const providerChoice = await select({
            message: 'AI provider:',
            options: Object.keys(MODEL_PROVIDERS).map(key => ({
                value: key,
                label: MODEL_PROVIDERS[key].name,
            })),
            initialValue: providerDefault,
        });
        if (typeof providerChoice === 'symbol') {
            outro('Cancelled');
            process.exit(0);
            return;
        }
        providerId = providerChoice;
    }
    const providerInfo = MODEL_PROVIDERS[providerId];
    // Model: same — always prompt with config default. If the configured
    // model_id matches one of this provider's known models, preselect it;
    // otherwise leave unselected so a Google-default-but-Claude-listed
    // user.json doesn't preselect a stale Claude model id.
    const explicitModelId = config.model_id || userConfig.model_id || process.env.MODEL_ID;
    const modelDefault = providerInfo.models.some(m => m.id === explicitModelId)
        ? explicitModelId
        : undefined;
    let modelId;
    if (opts.model) {
        modelId = opts.model;
        log.info(`Model: ${modelId} (from --model)`);
    }
    else {
        const modelChoice = await select({
            message: `${providerInfo.name} model:`,
            options: providerInfo.models.map(m => ({
                value: m.id,
                label: m.name,
                hint: m.hint,
            })),
            initialValue: modelDefault,
        });
        if (typeof modelChoice === 'symbol') {
            outro('Cancelled');
            process.exit(0);
            return;
        }
        modelId = modelChoice;
    }
    let modelApiKey;
    const apiKeyFromFlag = opts.apiKey?.trim() || process.env.STUDIOGRAPH_DEPLOY_API_KEY?.trim();
    if (apiKeyFromFlag) {
        modelApiKey = apiKeyFromFlag;
        log.info('API key: supplied non-interactively');
    }
    else {
        const entered = await password({
            message: `${providerInfo.name} API key (required for AI agent)`,
            validate: (v) => v.trim() ? undefined : 'Required',
        });
        if (typeof entered === 'symbol') {
            outro('Cancelled');
            process.exit(0);
        }
        modelApiKey = entered;
    }
    const projectName = opts.name
        || (config.team_name ? `${config.team_name} — Studiograph` : 'Studiograph');
    // Pin to the exact CLI version, prerelease or not. Pre-A7 the
    // non-prerelease branch used unpinned `npx --yes studiograph`, which
    // would resolve to whatever @latest happens to be at Railway boot
    // time — a recipe for version drift. See cli-owner-audit §6.2.
    const pkgVersion = readOwnVersion();
    const teamName = config.team_name || 'Studiograph';
    const npxCmd = `npx --yes studiograph@${pkgVersion}`;
    const startCommand = `sh -c 'cd /workspace && [ -f .studiograph/workspace.json ] || ${npxCmd} init "${teamName}" . && ${npxCmd} serve'`;
    // Service name: a tenant-style slug of the team name, so several tenants
    // can coexist as distinct services inside one project. Overridable via
    // --service-name. Falls back to 'studiograph' for an empty slug.
    const slug = teamName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
    const serviceName = opts.serviceName?.trim() || slug || 'studiograph';
    // ── Step 3: Create or reuse the Railway project ─────────────────────────
    let projectId;
    let envId;
    let projectDisplayName;
    if (opts.project) {
        // Multi-tenant pattern: add a new service to an existing project rather
        // than creating a fresh one. Resolves the project's production env.
        const parsed = parseProjectId(opts.project);
        if (!parsed) {
            outro('Invalid --project. Pass a Railway project ID or dashboard URL.');
            process.exit(1);
            return;
        }
        log.step('Step 3 — Using existing Railway project');
        s.start('Resolving project + environment...');
        const projData = await railwayQuery(railwayToken.trim(), `
        query($id: String!) {
          project(id: $id) {
            name
            environments { edges { node { id name } } }
          }
        }
      `, { id: parsed });
        projectId = parsed;
        projectDisplayName = projData.project.name;
        const envEdges = projData.project.environments.edges;
        const prodEnv = envEdges.find((e) => e.node.name === 'production') ?? envEdges[0];
        if (!prodEnv) {
            s.stop('No environments found');
            outro('That project has no environments.');
            process.exit(1);
            return;
        }
        envId = prodEnv.node.id;
        s.stop(`Project: ${projectDisplayName} · env: ${prodEnv.node.name}`);
    }
    else {
        log.step('Step 3 — Creating Railway project');
        s.start('Creating project...');
        const projectData = await railwayQuery(railwayToken.trim(), `
        mutation($input: ProjectCreateInput!) {
          projectCreate(input: $input) { id name }
        }
      `, { input: { name: projectName } });
        projectId = projectData.projectCreate.id;
        projectDisplayName = projectData.projectCreate.name;
        s.stop(`Project created: ${projectDisplayName}`);
        // Get the default environment ID
        s.start('Reading environment...');
        const envData = await railwayQuery(railwayToken.trim(), `
        query($id: String!) {
          project(id: $id) {
            environments { edges { node { id name } } }
          }
        }
      `, { id: projectId });
        envId = envData.project.environments.edges[0].node.id;
        s.stop(`Environment: ${envData.project.environments.edges[0].node.name}`);
    }
    // Create service
    s.start(`Creating service "${serviceName}"...`);
    const serviceData = await railwayQuery(railwayToken.trim(), `
      mutation($input: ServiceCreateInput!) {
        serviceCreate(input: $input) { id name }
      }
    `, { input: { projectId, name: serviceName } });
    const serviceId = serviceData.serviceCreate.id;
    s.stop(`Service created: ${serviceName}`);
    // Configure start command via the shared pin helper. Failure propagates —
    // a fresh deploy whose start command never gets written is a broken
    // deploy; don't continue past it.
    s.start('Configuring start command...');
    await pinStartCommand((q, v) => railwayQuery(railwayToken.trim(), q, v), { serviceId, environmentId: envId }, pkgVersion, { startCommand });
    s.stop(`Start command set: ${startCommand}`);
    // Create volume
    s.start('Attaching volume at /workspace...');
    await railwayQuery(railwayToken.trim(), `
      mutation($input: VolumeCreateInput!) {
        volumeCreate(input: $input) { id name }
      }
    `, { input: { projectId, serviceId, mountPath: '/workspace' } });
    s.stop('Volume attached at /workspace');
    // Set environment variables. Phase 12c (2026-05-12) retired the
    // generic MODEL_API_KEY env in favour of per-provider env vars;
    // the deploy script writes ANTHROPIC_API_KEY / OPENAI_API_KEY / etc.
    // based on the chosen provider. The agent's resolver checks the
    // encrypted store first (Phase 6) and falls back to env, so a fresh
    // deploy chats via the env value until an owner sets the per-provider
    // key in the store via the Settings UI.
    const providerEnvKey = `${String(providerId).toUpperCase()}_API_KEY`;
    s.start('Setting environment variables...');
    await railwayQuery(railwayToken.trim(), `
      mutation($input: VariableCollectionUpsertInput!) {
        variableCollectionUpsert(input: $input)
      }
    `, {
        input: {
            projectId,
            environmentId: envId,
            serviceId,
            variables: {
                [providerEnvKey]: modelApiKey.trim(),
                MODEL_PROVIDER: String(providerId),
                MODEL_ID: modelId,
                PORT: '3001',
            },
        },
    });
    s.stop('Environment variables set');
    // Phase 2: provision STUDIOGRAPH_JWT_SECRET + STUDIOGRAPH_MASTER_KEY
    // on every fresh service. Idempotent — does not overwrite existing
    // values, so calling deploy.ts twice on the same project keeps the
    // first-set values stable.
    s.start('Provisioning auth + master-key secrets...');
    const provisioned = await provisionRequiredRailwaySecrets((q, v) => railwayQuery(railwayToken.trim(), q, v), { projectId, serviceId, environmentId: envId });
    if (provisioned.length > 0) {
        s.stop(`Provisioned: ${provisioned.join(', ')}`);
    }
    else {
        s.stop('Auth + master-key already provisioned');
    }
    // Generate a Railway domain
    s.start('Generating domain...');
    let domain = '';
    try {
        const domainData = await railwayQuery(railwayToken.trim(), `
        mutation($input: CustomDomainCreateInput!) {
          customDomainCreate(input: $input) { id domain }
        }
      `, { input: { projectId, environmentId: envId, serviceId } });
        domain = domainData.customDomainCreate?.domain || '';
    }
    catch {
        try {
            const domainData = await railwayQuery(railwayToken.trim(), `
          mutation($input: ServiceDomainCreateInput!) {
            serviceDomainCreate(input: $input) { id domain }
          }
        `, { input: { serviceId, environmentId: envId } });
            domain = domainData.serviceDomainCreate?.domain || '';
        }
        catch {
            // Non-fatal — user can add a domain manually
        }
    }
    if (domain) {
        s.stop(`Domain: https://${domain}`);
    }
    else {
        s.stop('Domain: generate one in Railway dashboard (Settings > Networking > Generate Domain)');
    }
    // Connect the Docker image — triggers the first deploy
    s.start('Connecting node:20 image and deploying...');
    await railwayQuery(railwayToken.trim(), `
      mutation($id: String!, $input: ServiceConnectInput!) {
        serviceConnect(id: $id, input: $input) { id }
      }
    `, { id: serviceId, input: { image: 'node:20' } });
    s.stop('Deploy triggered (node:20)');
    // Save Railway IDs to workspace config (per-workspace, not global)
    config.railway = { project_id: projectId, service_id: serviceId, environment_id: envId };
    const wsConfigPath = join(process.cwd(), '.studiograph', 'workspace.json');
    writeFileSync(wsConfigPath, JSON.stringify(config, null, 2), 'utf-8');
    // ── Summary ─────────────────────────────────────────────────────────────
    const baseUrl = domain ? `https://${domain}` : 'https://<your-railway-domain>';
    log.message('\nDeployment summary:\n' +
        `  Project:    ${projectDisplayName}\n` +
        `  Service:    ${serviceName}\n` +
        `  Volume:     /workspace\n` +
        (domain ? `  URL:        https://${domain}\n` : '') +
        '\nNext steps:\n' +
        '  1. Wait for the first deploy to complete in the Railway dashboard.\n' +
        '     The start command auto-runs `studiograph init` on the empty volume,\n' +
        '     then `studiograph serve`. No manual init needed.\n' +
        `  2. Open ${baseUrl} in a browser. Because auth.db is empty, the\n` +
        '     login page shows a setup form — enter an email + password to\n' +
        '     create the first workspace owner.\n' +
        '  3. Log in. The onboarding overlay will start the agent interview.\n' +
        `  4. From this machine, run \`studiograph admin token issue ${baseUrl}\`\n` +
        '     to mint a CLI token using the owner credentials from step 2. The\n' +
        '     token persists to ~/.studiograph/user.json; subsequent pull/push\n' +
        '     runs are non-interactive.\n');
    const dashboardUrl = `https://railway.com/project/${projectId}`;
    const open = await confirm({ message: 'Open Railway dashboard in browser?' });
    if (open) {
        openBrowser(dashboardUrl);
    }
    else {
        log.info(`Dashboard: ${dashboardUrl}`);
    }
    outro('Railway project created — check the dashboard for deploy progress.');
});
//# sourceMappingURL=deploy.js.map