"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.HeadlessInstallWizard = exports.InstallWizard = exports.CannedAnswerProvider = exports.ReadlineAnswerProvider = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const readline = __importStar(require("readline"));
const InstallFlow_1 = require("./InstallFlow");
const InstallStateStore_1 = require("./InstallStateStore");
const InstallModules_1 = require("./InstallModules");
const AgentManager_1 = require("./AgentManager");
/**
 * Production answer provider — uses node:readline. Renders the question and
 * available defaults (bracketed) so the operator can press Enter to accept.
 */
class ReadlineAnswerProvider {
    rl;
    ownsInterface;
    constructor(rl) {
        if (rl) {
            this.rl = rl;
            this.ownsInterface = false;
        }
        else {
            this.rl = readline.createInterface({ input: process.stdin, output: process.stdout });
            this.ownsInterface = true;
        }
    }
    close() {
        if (this.ownsInterface) {
            this.rl.close();
        }
    }
    ask(prompt) {
        return new Promise((resolve) => {
            this.rl.question(prompt, (answer) => resolve(answer));
        });
    }
    async choice(question, options, defaultIndex = 0) {
        const lines = [question];
        for (let i = 0; i < options.length; i++) {
            const marker = i === defaultIndex ? '*' : ' ';
            lines.push(`  ${marker} ${i + 1}) ${options[i]}`);
        }
        lines.push(`Choice [${defaultIndex + 1}]: `);
        const raw = (await this.ask(lines.join('\n'))).trim();
        if (!raw)
            return defaultIndex;
        const parsed = Number.parseInt(raw, 10);
        if (Number.isNaN(parsed) || parsed < 1 || parsed > options.length) {
            // Re-ask once instead of silently picking default — gives the operator
            // a chance to fix a typo rather than getting an unexpected install.
            const retry = (await this.ask(`Invalid choice. Pick 1-${options.length} [${defaultIndex + 1}]: `)).trim();
            if (!retry)
                return defaultIndex;
            const retryParsed = Number.parseInt(retry, 10);
            if (Number.isNaN(retryParsed) || retryParsed < 1 || retryParsed > options.length) {
                return defaultIndex;
            }
            return retryParsed - 1;
        }
        return parsed - 1;
    }
    async text(question, defaultValue) {
        const suffix = defaultValue !== undefined && defaultValue !== '' ? ` [${defaultValue}]` : '';
        const raw = (await this.ask(`${question}${suffix}: `)).trim();
        if (!raw && defaultValue !== undefined)
            return defaultValue;
        return raw;
    }
    async yesNo(question, defaultYes = false) {
        const hint = defaultYes ? '[Y/n]' : '[y/N]';
        const raw = (await this.ask(`${question} ${hint}: `)).trim().toLowerCase();
        if (!raw)
            return defaultYes;
        if (raw === 'y' || raw === 'yes')
            return true;
        if (raw === 'n' || raw === 'no')
            return false;
        return defaultYes;
    }
}
exports.ReadlineAnswerProvider = ReadlineAnswerProvider;
/**
 * Test seam — feeds canned answers from a FIFO queue. Each call shifts one off.
 * When the queue is exhausted, throws WIZARD_TEST_OUT_OF_ANSWERS so tests can't
 * silently fall through to a default and mask bugs.
 */
class CannedAnswerProvider {
    queue;
    constructor(answers) {
        this.queue = [...answers];
    }
    next(kind) {
        if (this.queue.length === 0) {
            throw new Error(`WIZARD_TEST_OUT_OF_ANSWERS: wizard requested another ${kind} answer but the canned queue is empty`);
        }
        return this.queue.shift();
    }
    async choice(_question, _options, _defaultIndex) {
        const v = this.next('choice');
        if (typeof v !== 'number') {
            throw new Error(`WIZARD_TEST_WRONG_ANSWER_TYPE: expected number for choice, got ${typeof v}`);
        }
        return v;
    }
    async text(_question, _defaultValue) {
        const v = this.next('text');
        if (typeof v !== 'string') {
            throw new Error(`WIZARD_TEST_WRONG_ANSWER_TYPE: expected string for text, got ${typeof v}`);
        }
        return v;
    }
    async yesNo(_question, _defaultYes) {
        const v = this.next('yesNo');
        if (typeof v !== 'boolean') {
            throw new Error(`WIZARD_TEST_WRONG_ANSWER_TYPE: expected boolean for yesNo, got ${typeof v}`);
        }
        return v;
    }
    remaining() {
        return this.queue.length;
    }
}
exports.CannedAnswerProvider = CannedAnswerProvider;
/**
 * Interactive install wizard. Walks the operator through profile, host,
 * model order, Telegram bits (autonomous only), doctor toggle, and a final
 * confirm preview. Returns a WizardResult that the caller passes into
 * `new InstallFlow().run(options)`.
 *
 * Re-entry safe: if `<root>/.openlife/install-manifest.json` exists, the
 * wizard asks the operator whether to reinstall, repair, or abort BEFORE
 * touching any other question — so an accidental rerun doesn't clobber
 * a working install silently.
 */
class InstallWizard {
    root;
    answers;
    constructor(root = process.cwd(), answers = new ReadlineAnswerProvider()) {
        this.root = root;
        this.answers = answers;
    }
    async run() {
        const warnings = [];
        // 1. Pre-existing install detection
        const manifestPath = path.join(this.root, '.openlife', 'install-manifest.json');
        let preExistingAction;
        if (fs.existsSync(manifestPath)) {
            const idx = await this.answers.choice('Detected an existing OpenLife install at .openlife/install-manifest.json. What do you want to do?', ['abort (recommended)', 'reinstall (overwrite)', 'repair (re-run install over existing)'], 0);
            if (idx === 0) {
                return { ok: false, reason: 'user_aborted', detail: 'pre_existing_install_abort' };
            }
            preExistingAction = idx === 1 ? 'reinstall' : 'repair';
        }
        // 2. Profile selection — 3 user-visible options. `both` is an alias for
        // `autonomous` since autonomous already includes the framework layer
        // (systemInstaller.install() runs unconditionally in InstallFlow.run()).
        const profileIdx = await this.answers.choice('Which profile do you want to install?\n  framework — local interactive CLI only\n  autonomous — long-running daemon with Telegram gateway and governance\n  both — autonomous daemon plus framework CLI (recommended for full setup)', ['framework', 'autonomous', 'both'], 0);
        const profile = profileIdx === 0 ? 'framework' : 'autonomous';
        const installBoth = profileIdx === 2;
        if (installBoth) {
            warnings.push('INSTALLING_BOTH: autonomous profile selected (includes framework layer)');
        }
        // 3. Host selection — auto-detect, but always ask to confirm.
        const detected = (0, InstallFlow_1.detectHostFromEnv)();
        const hostOptions = [...InstallFlow_1.VALID_HOSTS];
        const defaultHostIdx = hostOptions.indexOf(detected || InstallFlow_1.DEFAULT_HOST);
        const hostHeader = detected
            ? `Which host CLI? (detected: ${detected})`
            : 'Which host CLI?';
        const hostIdx = await this.answers.choice(`${hostHeader}\n  claude-code — Anthropic Claude Code (fully supported)\n  gemini-cli — Google Gemini CLI (host-install not yet wired)\n  codex — OpenAI Codex CLI (host-install not yet wired)`, [...hostOptions], defaultHostIdx >= 0 ? defaultHostIdx : 0);
        const host = hostOptions[hostIdx];
        let validatedHost;
        try {
            validatedHost = (0, InstallFlow_1.validateHost)(host);
        }
        catch (err) {
            return { ok: false, reason: 'invalid_input', detail: err instanceof Error ? err.message : String(err) };
        }
        if (validatedHost !== 'claude-code') {
            warnings.push(`HOST_NOT_YET_SUPPORTED: ${validatedHost} host-specific install is a no-op for now; .openlife state will still be created`);
        }
        // 4. API keys (optional) — each prompt accepts paste or blank to skip.
        //    IMPORTANT: collect first, but only persist after final confirmation.
        //    An aborted wizard must not mutate .env or write pasted secrets.
        let savedApiKeyNames = [];
        const collectedApiKeys = {};
        const wantsApiKeys = await this.answers.yesNo('Configure LLM API keys now? (You can also set them later in .env)', true);
        if (wantsApiKeys) {
            collectedApiKeys.openai = await this.answers.text('OPENAI_API_KEY (paste or Enter to skip)', '');
            collectedApiKeys.anthropic = await this.answers.text('ANTHROPIC_API_KEY (paste or Enter to skip)', '');
            collectedApiKeys.gemini = await this.answers.text('GEMINI_API_KEY (paste or Enter to skip)', '');
            collectedApiKeys.openrouter = await this.answers.text('OPENROUTER_API_KEY (paste or Enter to skip)', '');
            savedApiKeyNames = (0, InstallModules_1.providedApiKeyEnvNames)(collectedApiKeys);
            if (savedApiKeyNames.length === 0) {
                warnings.push('NO_API_KEYS_PROVIDED: model providers will only work via OAuth CLIs or Ollama until keys are added to .env');
            }
        }
        else {
            warnings.push('SKIPPED_API_KEYS: configure providers later via .env or `openlife auth <provider>`');
        }
        // 5. OAuth pointer — informational only. The actual `openlife auth gemini`
        //    / `auth openai` flows are interactive subprocesses; running them inside
        //    the wizard would break the canned-answer test seam. We just signal the
        //    operator that the flag exists so they can opt-in after init completes.
        const wantsOAuth = await this.answers.yesNo('Plan to configure OAuth for Gemini or OpenAI CLI later?', false);
        if (wantsOAuth) {
            warnings.push('OAUTH_PENDING: after init completes, run `openlife auth gemini` and/or `openlife auth openai` to log in');
        }
        // 6. Model chain (optional)
        const modelsRaw = await this.answers.text('LLM model order (comma-separated provider/model chain, blank = defaults)', '');
        const modelOrder = modelsRaw
            ? modelsRaw.split(',').map((m) => m.trim()).filter(Boolean)
            : undefined;
        // 7. Telegram (autonomous only)
        if (profile === 'autonomous') {
            const hasTelegram = await this.answers.yesNo('Do you already have TELEGRAM_BOT_TOKEN and OPENLIFE_TELEGRAM_ALLOWED_USER_ID set in your .env?', false);
            if (!hasTelegram) {
                warnings.push('TELEGRAM_NOT_CONFIGURED: autonomous profile needs TELEGRAM_BOT_TOKEN and OPENLIFE_TELEGRAM_ALLOWED_USER_ID — set them in .env before running `openlife agent start`');
            }
        }
        // 8. Skip doctor?
        const skipDoctor = await this.answers.yesNo('Skip the system doctor run?', false);
        // 9. Confirm preview
        const previewLines = [
            'Review your choices:',
            `  profile      : ${profile}${installBoth ? ' (both — framework + autonomous)' : ''}`,
            `  host         : ${validatedHost}`,
            `  apiKeys      : ${savedApiKeyNames.length ? savedApiKeyNames.join(', ') : '(none saved)'}`,
            `  modelOrder   : ${modelOrder ? modelOrder.join(', ') : '(defaults)'}`,
            `  skipDoctor   : ${skipDoctor}`,
            preExistingAction ? `  preExisting  : ${preExistingAction}` : ''
        ].filter(Boolean);
        if (warnings.length) {
            previewLines.push('  warnings:');
            for (const w of warnings)
                previewLines.push(`    - ${w}`);
        }
        const confirmed = await this.answers.yesNo(`${previewLines.join('\n')}\nProceed with install?`, false);
        if (!confirmed) {
            return { ok: false, reason: 'user_aborted', detail: 'preview_not_confirmed' };
        }
        if (savedApiKeyNames.length > 0) {
            const saved = (0, InstallModules_1.saveApiKeysToEnv)(this.root, collectedApiKeys);
            savedApiKeyNames = saved.saved;
        }
        const options = {
            profile,
            host: validatedHost,
            skipDoctor,
            modelOrder
        };
        return {
            ok: true,
            options,
            preExistingAction,
            warnings: warnings.length ? warnings : undefined
        };
    }
}
exports.InstallWizard = InstallWizard;
class HeadlessInstallWizard {
    root;
    flow;
    state;
    constructor(root = process.cwd(), flow = new InstallFlow_1.InstallFlow(root), state = new InstallStateStore_1.InstallStateStore(root)) {
        this.root = root;
        this.flow = flow;
        this.state = state;
    }
    run(options) {
        const mode = this.resolveMode(options.mode);
        if (options.resume) {
            const loaded = this.state.load();
            if (!loaded) {
                return ['⚠️ Nenhum estado salvo para retomar em .openlife/install-state.json'];
            }
            return [
                `♻️ Retomando setup salvo`,
                `- mode: ${loaded.mode}`,
                `- currentStep: ${loaded.currentStep}`,
                `- completed: ${loaded.completedSteps.join(', ') || '(none)'}`,
                `- state: ${this.state.path()}`
            ];
        }
        const pre = (0, InstallModules_1.runPrecheck)();
        const preLines = pre.checks.map(c => `- ${c.ok ? '✅' : '❌'} ${c.name}: ${c.detail}`);
        const installChecklist = [];
        if (options.telegramToken) {
            const tokenValidation = (0, InstallModules_1.validateTelegramToken)(options.telegramToken);
            if (!tokenValidation.ok) {
                installChecklist.push(`❌ telegram_token: ${tokenValidation.detail}`);
                return ['📋 Install checklist', ...installChecklist, `❌ Telegram token inválido: ${tokenValidation.detail}`];
            }
            installChecklist.push(`✅ telegram_token: ${tokenValidation.detail}`);
            const conflict = (0, InstallModules_1.checkTelegram409Conflict)(this.root, options.telegramToken);
            if (!conflict.ok) {
                installChecklist.push(`❌ telegram_conflict: ${conflict.detail}`);
                return [
                    '📋 Install checklist',
                    ...installChecklist,
                    `❌ ${conflict.detail}`,
                    'Ações: pare a outra instância, use outro token, ou rode sem --telegram-token.'
                ];
            }
            installChecklist.push(`✅ telegram_conflict: ${conflict.detail}`);
            (0, InstallModules_1.saveTelegramConfig)(this.root, options.telegramToken, options.telegramChatId);
            installChecklist.push('✅ telegram_save: configuração salva em .env');
            const chatValidation = (0, InstallModules_1.validateTelegramChatId)(options.telegramToken, options.telegramChatId);
            if (!chatValidation.ok) {
                installChecklist.push(`❌ telegram_chat: ${chatValidation.detail}`);
                return ['📋 Install checklist', ...installChecklist, `❌ Telegram chat_id inválido: ${chatValidation.detail}`];
            }
            installChecklist.push(`✅ telegram_chat: ${chatValidation.detail}`);
            (0, InstallModules_1.writeTelegramLock)(this.root, options.telegramToken);
            installChecklist.push('✅ telegram_lock: lock de polling gravado');
        }
        else {
            installChecklist.push('ℹ️ telegram: não configurado nesta execução');
        }
        this.state.save({ mode, currentStep: 'run-install-flow', completedSteps: ['precheck'] });
        const profile = mode === 'autonomous' ? 'autonomous' : 'framework';
        const result = this.flow.run({
            profile,
            host: options.host,
            skipDoctor: options.skipDoctor,
            modelOrder: options.modelOrder
        });
        const chat = (0, InstallModules_1.chatSmoke)();
        let agentLines = [];
        if (mode === 'autonomous') {
            const manager = new AgentManager_1.AgentManager(this.root);
            let agents = manager.ensureDefault(options.defaultAgentName);
            if (options.additionalAgentName) {
                agents = manager.createAdditional(options.additionalAgentName);
            }
            agentLines = [
                '🤖 Agent manager',
                ...agents.map(a => `- ${a.isDefault ? '[default]' : '[extra]'} ${a.name} (${a.id})`)
            ];
        }
        this.state.save({
            mode,
            currentStep: 'completed',
            completedSteps: ['precheck', 'run-install-flow', 'chat-smoke', 'completed'],
            metadata: { reportPath: result.reportPath }
        });
        return [
            '📋 Install checklist',
            `- ${pre.ok ? '✅' : '❌'} precheck`,
            ...installChecklist.map(i => `- ${i}`),
            `- ✅ install_flow: concluído`,
            ...agentLines.length ? ['- ✅ agent_manager: aplicado (modo autônomo)'] : ['- ℹ️ agent_manager: não aplicável no modo CLI'],
            `- ${chat.ok ? '✅' : '❌'} chat_smoke`,
            '- ✅ state_save: install-state.json atualizado',
            '',
            '🩺 Precheck',
            ...preLines,
            ...this.flow.renderSummary(result),
            ...agentLines,
            `${chat.ok ? '✅' : '❌'} Chat smoke: ${chat.detail}`,
            `🧩 State file: ${this.state.path()}`
        ];
    }
    resolveMode(mode) {
        return String(mode || '').toLowerCase() === 'autonomous' ? 'autonomous' : 'cli';
    }
}
exports.HeadlessInstallWizard = HeadlessInstallWizard;
