/**
 * olam memory mode <local|cloud> — flip the workspace's agent-memory
 * source between the host-process (Phase A) and a remote agentmemory
 * deployment (Phase C).
 *
 * Reads + rewrites `.olam/config.yaml`'s `memory:` block via the schema
 * defined in C1. Idempotent: re-running the same mode is a noop.
 *
 *   mode local              → memory.mode = "local"; ensures host-process running
 *   mode cloud [--url ...]  → prompts for URL + bearer secret, writes both to
 *                              config + ~/.olam/cloud-memory-secret, stops the
 *                              local host-process. Operator must re-create
 *                              active worlds for the new env to take effect.
 *
 * Plan reference: docs/plans/olam-agent-memory-distributed/phase-c-tasks.md C3
 */
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import * as readline from 'node:readline/promises';
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
import { MemorySchema } from '@olam/core/src/config/schema.js';
import { printError, printSuccess, printInfo, printHeader, printWarning } from '../../output.js';
import { writeCloudMemorySecret, CLOUD_MEMORY_SECRET_PATH } from '../../lib/memory-secret.js';
import { runMemoryStart } from './start.js';
import { runMemoryStop } from './stop.js';
const CONFIG_REL = '.olam/config.yaml';
function locateConfig(cwd) {
    const absPath = join(cwd, CONFIG_REL);
    if (!existsSync(absPath)) {
        throw new Error(`No ${CONFIG_REL} at ${cwd}. Run \`olam init\` in your workspace root first.`);
    }
    return { absPath };
}
function readConfigYaml(absPath) {
    const raw = readFileSync(absPath, 'utf-8');
    const parsed = (parseYaml(raw) ?? {});
    if (typeof parsed !== 'object' || parsed === null) {
        throw new Error(`${absPath} is not a YAML object`);
    }
    return { raw, parsed };
}
function writeConfigYaml(absPath, parsed) {
    // `yaml`'s default stringify is reasonable for round-trip; we don't
    // preserve every comment edge case but the major shape + keys survive.
    const out = stringifyYaml(parsed, { aliasDuplicateObjects: false });
    writeFileSync(absPath, out, 'utf-8');
}
async function defaultPromptText(question) {
    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
    try {
        return (await rl.question(question)).trim();
    }
    finally {
        rl.close();
    }
}
/**
 * Same shape as defaultPromptText for now — terminals don't natively mask
 * input via readline. Operators wanting a masked prompt can pipe via stdin
 * or pass `--secret` (NOT recommended; argv leaks via ps). The choice is
 * deferred to OQ4: "no Keychain integration in v1; document the limitation"
 * (design rubric S5).
 */
async function defaultPromptSecret(question) {
    return defaultPromptText(`${question} (will appear on-screen; use stdin to pipe) `);
}
export async function runMemoryModeLocal(deps = {}) {
    const cwd = deps.cwd ?? process.cwd();
    const cfg = locateConfig(cwd);
    const { parsed } = readConfigYaml(cfg.absPath);
    const currentMode = parsed.memory?.mode ?? 'local';
    if (currentMode === 'local') {
        // Phase B substrate semantics: `mode local` ensures the Docker container
        // is running. Mechanism shifted from host-process spawn → docker container
        // (memory-service-as-docker-peripheral Phase B B8). Intent — "the memory
        // service runs locally rather than against a remote URL" — is preserved.
        printInfo('memory.mode', 'already local — ensuring Docker container is up');
        const rc = (deps.runStart ?? runMemoryStart)();
        await rc;
        return { outcome: 'noop', mode: 'local' };
    }
    // Flip: drop the cloud block (operator can re-cloud later via mode cloud)
    parsed.memory = { mode: 'local' };
    writeConfigYaml(cfg.absPath, parsed);
    printSuccess(`flipped .olam/config.yaml memory.mode → local`);
    printInfo('container', 'starting (Docker substrate)');
    const rc = await (deps.runStart ?? runMemoryStart)();
    if (rc !== 0) {
        printWarning('local Docker container failed to start. Config flipped but service is down — fix the underlying issue and run `olam memory start`.');
    }
    return { outcome: 'flipped-to-local' };
}
export async function runMemoryModeCloud(deps = {}) {
    const cwd = deps.cwd ?? process.cwd();
    const cfg = locateConfig(cwd);
    const { parsed } = readConfigYaml(cfg.absPath);
    const currentMem = (parsed.memory ?? {});
    const currentMode = currentMem.mode ?? 'local';
    // Resolve URL: --url flag wins, else interactive prompt.
    let url = deps.url;
    if (!url) {
        const askText = deps.promptText ?? defaultPromptText;
        url = (await askText('Cloud agentmemory URL (e.g. https://agentmemory.<you>.workers.dev): ')).trim();
    }
    if (!url) {
        throw new Error('cloud URL is required');
    }
    // Validate via the C1 schema before writing.
    const candidate = { mode: 'cloud', cloud: { url } };
    const parsedMem = MemorySchema.safeParse(candidate);
    if (!parsedMem.success) {
        const msg = parsedMem.error.issues.map((i) => i.message).join('; ');
        throw new Error(`invalid memory.cloud config: ${msg}`);
    }
    // Resolve secret: --secret flag wins, else interactive prompt. The
    // secret is the bearer the operator already configured on the remote
    // service (Cloudflare wrangler secret or equivalent). We persist it to
    // ~/.olam/cloud-memory-secret 0600 so B1's env-resolver can inject it
    // into worlds at spawn (C4 wires the read path).
    let secret = deps.secret;
    if (!secret) {
        const askSecret = deps.promptSecret ?? defaultPromptSecret;
        secret = await askSecret('Cloud agentmemory bearer secret (64-hex):');
    }
    if (!secret) {
        throw new Error('cloud secret is required');
    }
    // Idempotency check: if already cloud-mode with the same URL, just refresh secret.
    const sameUrl = currentMode === 'cloud' && currentMem.cloud?.url === url;
    if (sameUrl) {
        printInfo('memory.mode', `already cloud (url=${url}) — refreshing secret only`);
        (deps.writeSecret ?? ((v) => writeCloudMemorySecret(v)))(secret);
        return { outcome: 'noop', mode: 'cloud' };
    }
    // Stop local host-process first — fail-soft if it wasn't running.
    printInfo('host-process', 'stopping (was running for local mode)');
    await (deps.runStop ?? runMemoryStop)();
    // Persist the secret BEFORE updating the config so worlds spawned
    // between the writes see at most a transient "cloud but no secret"
    // state rather than the inverse (config flipped, secret missing).
    (deps.writeSecret ?? ((v) => writeCloudMemorySecret(v)))(secret);
    printSuccess(`wrote cloud bearer secret to ${CLOUD_MEMORY_SECRET_PATH} (0600)`);
    parsed.memory = {
        mode: 'cloud',
        cloud: {
            url,
            secret_ref: '~/.olam/cloud-memory-secret',
        },
    };
    writeConfigYaml(cfg.absPath, parsed);
    printSuccess(`flipped .olam/config.yaml memory.mode → cloud (${url})`);
    printWarning('Worlds spawned BEFORE this flip still have the old env. Re-create them ' +
        '(olam destroy + olam create) to pick up the new AGENTMEMORY_URL + secret.');
    return { outcome: 'flipped-to-cloud', url };
}
export function registerMemoryMode(cmd) {
    const mode = cmd
        .command('mode <target>')
        .description('Flip agent-memory source between local (host-process) and cloud (remote agentmemory deployment)')
        .option('--url <url>', 'Cloud URL (skips the interactive prompt; mode cloud only)')
        .option('--secret <value>', 'Cloud bearer secret (skips interactive prompt; argv-visible — prefer stdin)');
    mode.action(async (target, opts) => {
        printHeader(`olam memory mode ${target}`);
        try {
            if (target === 'local') {
                await runMemoryModeLocal();
            }
            else if (target === 'cloud') {
                await runMemoryModeCloud({
                    ...(opts.url !== undefined ? { url: opts.url } : {}),
                    ...(opts.secret !== undefined ? { secret: opts.secret } : {}),
                });
            }
            else {
                printError(`mode must be 'local' or 'cloud' (got: ${target})`);
                process.exitCode = 1;
            }
        }
        catch (err) {
            printError(err instanceof Error ? err.message : String(err));
            process.exitCode = 1;
        }
    });
}
//# sourceMappingURL=mode.js.map