/**
 * studiograph admin restore — counterpart to `admin backup`.
 *
 * Decrypts the .sgbackup bundle, expands the workspace contents into
 * a target directory, and writes two sidecar files into the PARENT of
 * the target:
 *
 *   1. `.env.restore` — the Railway env vars (master key, JWT secret)
 *      the operator captured at backup time. NOT auto-applied to
 *      Railway — restore prints instructions for pasting them into the
 *      Railway dashboard. Sidecar lives outside the workspace so it
 *      doesn't get accidentally git-committed when the operator later
 *      re-deploys.
 *   2. `RESTORE-README.md` — step-by-step instructions for finishing
 *      the recovery: create a new Railway service, paste env vars,
 *      update workspace.json's `railway` block with the new IDs, run
 *      `studiograph redeploy`.
 *
 * Refuses to overwrite an existing `.studiograph/` in the target dir.
 * That's the disaster-recovery contract: restore is destructive enough
 * that we want an explicit empty target.
 */
import { Command } from 'commander';
import { intro, outro, log, spinner, password, isCancel } from '@clack/prompts';
import { execFileSync } from 'child_process';
import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync, mkdtempSync, rmSync, } from 'fs';
import { join, resolve, dirname, basename } from 'path';
import { tmpdir } from 'os';
import { openBundle } from '../../crypto-bundle.js';
export const adminRestoreCommand = new Command('restore')
    .description('Decrypt a .sgbackup bundle and expand it into a target directory')
    .argument('<bundle>', 'Path to the .sgbackup file produced by `admin backup`')
    .option('--into <dir>', 'Target directory for the restored workspace (default: ./<workspace-name>)')
    .option('--passphrase <value>', 'Bundle passphrase (DO NOT use on shared shell history — prefer --passphrase-stdin)')
    .option('--passphrase-stdin', 'Read the bundle passphrase from stdin (single line)')
    .option('--force', 'Overwrite an existing .studiograph/ in the target directory')
    .action(async (bundlePath, opts) => {
    intro('Studiograph workspace restore');
    const absBundle = resolve(bundlePath);
    if (!existsSync(absBundle) || !statSync(absBundle).isFile()) {
        outro(`Not a file: ${absBundle}`);
        process.exit(1);
        return;
    }
    // Passphrase resolution. Flag → stdin → interactive. Refuse on no-TTY
    // without an explicit source (matches feedback_cli_non_interactive.md).
    let passphrase;
    if (opts.passphrase) {
        passphrase = opts.passphrase;
    }
    else if (opts.passphraseStdin) {
        passphrase = await readPassphraseStdin();
    }
    else if (process.stdout.isTTY) {
        const input = await password({
            message: 'Bundle passphrase:',
            validate: (v) => (v.length > 0 ? undefined : 'Required'),
        });
        if (isCancel(input)) {
            outro('Cancelled');
            process.exit(0);
            return;
        }
        passphrase = String(input);
    }
    else {
        outro('No TTY. Pass --passphrase-stdin and pipe the passphrase, or use --passphrase.');
        process.exit(1);
        return;
    }
    // Decrypt + expand into a temp dir first so we can read the manifest
    // before we know where the workspace lands. Avoids creating the
    // target directory until we know the bundle is legit.
    const tmpRoot = mkdtempSync(join(tmpdir(), 'sg-restore-'));
    try {
        const spin = spinner();
        spin.start('Decrypting bundle...');
        const ciphertext = readFileSync(absBundle);
        let plaintext;
        try {
            plaintext = openBundle(ciphertext, passphrase);
        }
        catch (err) {
            spin.stop('Decryption failed');
            outro(err instanceof Error ? err.message : String(err));
            process.exit(1);
            return;
        }
        spin.stop('Decrypted');
        const tarPath = join(tmpRoot, 'bundle.tar.gz');
        writeFileSync(tarPath, plaintext, { mode: 0o600 });
        const stagePath = join(tmpRoot, 'stage');
        mkdirSync(stagePath, { mode: 0o700 });
        spin.start('Expanding tar...');
        execFileSync('tar', ['-xzf', tarPath, '-C', stagePath], { stdio: 'pipe' });
        spin.stop('Expanded');
        // Read the manifest to determine target directory.
        const manifestPath = join(stagePath, 'manifest.json');
        if (!existsSync(manifestPath)) {
            outro('Bundle is missing manifest.json — was this produced by `admin backup`?');
            process.exit(1);
            return;
        }
        const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
        if (manifest.format_version !== 1) {
            outro(`Bundle format version ${manifest.format_version} is not supported by this CLI.`);
            process.exit(1);
            return;
        }
        const defaultDir = manifest.workspace_name
            .toLowerCase()
            .replace(/[^a-z0-9]+/g, '-')
            .replace(/^-+|-+$/g, '') || 'workspace';
        const targetDir = resolve(opts.into ?? defaultDir);
        const targetStudio = join(targetDir, '.studiograph');
        if (existsSync(targetStudio) && !opts.force) {
            outro(`Refusing to overwrite ${targetStudio}. Pass --force to clobber, or pick a different --into.`);
            process.exit(1);
            return;
        }
        if (existsSync(targetStudio) && opts.force) {
            log.warn(`--force: removing existing ${targetStudio}`);
            rmSync(targetStudio, { recursive: true, force: true });
        }
        mkdirSync(targetDir, { recursive: true, mode: 0o700 });
        spin.start('Moving workspace contents into place...');
        // The staged tar contains: manifest.json + everything that was at
        // the workspace root. Copy everything except manifest.json into
        // targetDir. We use `cp -R` rather than rename to handle the
        // already-exists case cleanly.
        execFileSync('sh', ['-c', `cp -R "${stagePath}/." "${targetDir}/" && rm -f "${targetDir}/manifest.json"`], { stdio: 'pipe' });
        spin.stop('Workspace files restored');
        // Sidecars in the PARENT of targetDir so they don't get committed
        // into the workspace by accident.
        const parentDir = dirname(targetDir);
        const envPath = join(parentDir, '.env.restore');
        writeFileSync(envPath, renderEnvFile(manifest, basename(targetDir)), { encoding: 'utf-8', mode: 0o600 });
        const readmePath = join(parentDir, 'RESTORE-README.md');
        writeFileSync(readmePath, renderReadme(manifest, basename(targetDir)), { encoding: 'utf-8', mode: 0o600 });
        log.success(`Restored workspace into ${targetDir}`);
        log.message(`Sidecars written:\n` +
            `  ${envPath}        (paste these into Railway → Variables)\n` +
            `  ${readmePath}    (step-by-step recovery instructions)`);
        outro('Restore complete — open RESTORE-README.md for the next steps.');
    }
    finally {
        rmSync(tmpRoot, { recursive: true, force: true });
    }
});
async function readPassphraseStdin() {
    return new Promise((resolveFn, rejectFn) => {
        let buf = '';
        process.stdin.setEncoding('utf-8');
        process.stdin.on('data', (chunk) => (buf += chunk));
        process.stdin.on('end', () => resolveFn(buf.replace(/\r?\n$/, '')));
        process.stdin.on('error', rejectFn);
    });
}
function renderEnvFile(manifest, workspaceDir) {
    return (`# Studiograph restore sidecar — created ${new Date().toISOString()}\n` +
        `# Source workspace: ${manifest.workspace_name} (backed up ${manifest.created_at})\n` +
        `# Restored into:    ./${workspaceDir}\n` +
        `#\n` +
        `# Paste these into Railway → your new service → Variables. Then\n` +
        `# update ${workspaceDir}/.studiograph/workspace.json's "railway" block\n` +
        `# with the new project/service/environment IDs from the dashboard URL\n` +
        `# (old IDs below are not portable across Railway accounts).\n` +
        `#\n` +
        `# Old Railway IDs (for reference only):\n` +
        `#   project_id:     ${manifest.railway?.project_id ?? '<not set>'}\n` +
        `#   service_id:     ${manifest.railway?.service_id ?? '<not set>'}\n` +
        `#   environment_id: ${manifest.railway?.environment_id ?? '<not set>'}\n` +
        `\n` +
        `STUDIOGRAPH_MASTER_KEY=${manifest.env.STUDIOGRAPH_MASTER_KEY}\n` +
        `STUDIOGRAPH_JWT_SECRET=${manifest.env.STUDIOGRAPH_JWT_SECRET}\n`);
}
function renderReadme(manifest, workspaceDir) {
    return `# Restoring \`${manifest.workspace_name}\`

This workspace was decrypted from a \`.sgbackup\` bundle on ${new Date().toISOString().slice(0, 10)}.
Source backup timestamp: \`${manifest.created_at}\`.

The workspace files are now in \`./${workspaceDir}/\`. Two sidecars sit beside
this README:

- \`.env.restore\` — the Railway env vars you'll need (master key + JWT secret).
- This file.

## What to do next

1. **Spin up a fresh Railway service.** Either via \`studiograph deploy railway\`
   from \`./${workspaceDir}/\`, or manually in the Railway dashboard. You want a
   brand-new project so the IDs are fresh.

2. **Paste the env vars.** Copy \`STUDIOGRAPH_MASTER_KEY\` and
   \`STUDIOGRAPH_JWT_SECRET\` from \`.env.restore\` into the new service's
   Variables. **These MUST match exactly** — without them, the restored
   \`secrets.db\` cannot decrypt and the restored auth state cannot validate
   tokens.

3. **Update \`workspace.json\`.** The old Railway IDs in the manifest do NOT
   transfer to your new service. Open
   \`${workspaceDir}/.studiograph/workspace.json\` and replace the \`railway\`
   block with the IDs from your new service's dashboard URL:

   \`\`\`
   railway.com/project/<PROJECT_ID>/service/<SERVICE_ID>?environmentId=<ENV_ID>
   \`\`\`

4. **Redeploy.**
   \`\`\`
   cd ${workspaceDir}
   npx -y studiograph@<latest> redeploy
   \`\`\`

5. **Verify.** \`pull --remote\` from the new server. Confirm your users,
   secrets, and folder access all came across.

## After restore is complete

- Delete \`.env.restore\` from this directory once Railway has the values.
- Delete the original \`.sgbackup\` bundle from any shared/transferred location
  (it contains bcrypt password hashes, even though they're inside an
  encrypted envelope).
- Rotate the backup passphrase if it was typed on a machine you don't trust.
`;
}
//# sourceMappingURL=restore.js.map