"use strict";
/**
 * OAuth Process Execution for CLIProxyAPI
 *
 * Handles the spawning and monitoring of CLIProxy OAuth process.
 * Separated from oauth-handler.ts for modularity.
 */
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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeOAuthProcess = exports.analyzeSuccessfulAuthExit = exports.extractLikelyAuthFailureFromStderr = exports.extractLikelyAuthFailureFromLogs = exports.extractLikelyOAuthAuthorizationUrl = exports.getKiroBuilderIdSelectionInput = exports.validateManualCallbackUrl = exports.getExpectedLocalCallback = exports.isLoopbackHost = void 0;
const child_process_1 = require("child_process");
const ui_1 = require("../../utils/ui");
const process_utils_1 = require("../../utils/process-utils");
const kiro_import_1 = require("./kiro-import");
const project_selection_handler_1 = require("../auth/project-selection-handler");
const environment_detector_1 = require("./environment-detector");
const token_manager_1 = require("./token-manager");
const device_code_handler_1 = require("../auth/device-code-handler");
const management_1 = require("../../management");
const auth_session_manager_1 = require("../auth/auth-session-manager");
const oauth_trace_1 = require("./oauth-trace");
const redactor_1 = require("./oauth-trace/redactor");
const sink_file_1 = require("./oauth-trace/sink-file");
const diagnose_failure_1 = require("./oauth-trace/diagnose-failure");
const path = __importStar(require("path"));
const config_manager_1 = require("../../utils/config-manager");
/**
 * Handle project selection prompt
 */
async function handleProjectSelection(output, state, options, authProcess, log) {
    const defaultProjectId = (0, project_selection_handler_1.parseDefaultProject)(output) || '';
    if (state.parsedProjects.length > 0 && !options.isCLI) {
        log(`Requesting project selection from UI (session: ${state.sessionId})`);
        const prompt = {
            sessionId: state.sessionId,
            provider: options.provider,
            projects: state.parsedProjects,
            defaultProjectId,
            supportsAll: output.includes('ALL'),
        };
        try {
            const selectedId = await (0, project_selection_handler_1.requestProjectSelection)(prompt);
            const response = selectedId || '';
            log(`User selected: ${response || '(default)'}`);
            authProcess.stdin?.write(response + '\n');
        }
        catch {
            log('Project selection failed, using default');
            authProcess.stdin?.write('\n');
        }
    }
    else {
        log('CLI mode or no projects, auto-selecting default');
        authProcess.stdin?.write('\n');
    }
}
function resolveAuthFlowType(options) {
    return options.authFlowType || management_1.OAUTH_FLOW_TYPES[options.provider] || 'authorization_code';
}
function isLoopbackHost(hostname) {
    const normalized = hostname.replace(/^\[|\]$/g, '').toLowerCase();
    return (normalized === '127.0.0.1' ||
        normalized === 'localhost' ||
        normalized === '::1' ||
        normalized === '0:0:0:0:0:0:0:1');
}
exports.isLoopbackHost = isLoopbackHost;
function getExpectedLocalCallback(authUrl) {
    try {
        const parsedAuthUrl = new URL(authUrl);
        const redirectUriRaw = parsedAuthUrl.searchParams.get('redirect_uri');
        if (!redirectUriRaw) {
            return null;
        }
        const redirectUri = new URL(redirectUriRaw);
        if (!isLoopbackHost(redirectUri.hostname)) {
            return null;
        }
        return {
            origin: redirectUri.origin,
            pathname: redirectUri.pathname,
            state: parsedAuthUrl.searchParams.get('state'),
        };
    }
    catch {
        return null;
    }
}
exports.getExpectedLocalCallback = getExpectedLocalCallback;
function validateManualCallbackUrl(callbackUrl, authUrl) {
    let parsedCallback;
    try {
        parsedCallback = new URL(callbackUrl);
    }
    catch {
        return 'Invalid callback URL format';
    }
    if (!parsedCallback.searchParams.get('code')) {
        return 'Invalid callback URL: missing code parameter';
    }
    const expectedCallback = getExpectedLocalCallback(authUrl);
    if (!expectedCallback) {
        return 'Unable to determine the expected local callback target';
    }
    if (!isLoopbackHost(parsedCallback.hostname)) {
        return 'Callback URL must target the local OAuth callback server';
    }
    if (parsedCallback.origin !== expectedCallback.origin ||
        parsedCallback.pathname !== expectedCallback.pathname) {
        return 'Callback URL does not match the expected local OAuth callback target';
    }
    if (expectedCallback.state) {
        const callbackState = parsedCallback.searchParams.get('state');
        if (callbackState !== expectedCallback.state) {
            return 'Callback URL state does not match the active OAuth session';
        }
    }
    return null;
}
exports.validateManualCallbackUrl = validateManualCallbackUrl;
function getKiroBuilderIdSelectionInput(output) {
    const promptMatch = /Select login method/i.exec(output);
    if (!promptMatch || promptMatch.index === undefined) {
        return null;
    }
    const promptWindow = output.slice(promptMatch.index, promptMatch.index + 600);
    const optionMatch = /(?:^|\n)\s*(\d+)\s*[\).:-]?\s*.*\bBuilder ID\b/im.exec(promptWindow);
    if (!optionMatch) {
        return null;
    }
    return `${optionMatch[1]}\n`;
}
exports.getKiroBuilderIdSelectionInput = getKiroBuilderIdSelectionInput;
function extractLikelyOAuthAuthorizationUrl(output) {
    const urls = Array.from(output.matchAll(/https?:\/\/[^\s]+/g), (match) => match[0]);
    let selectedUrl = null;
    let selectedScore = 0;
    for (const url of urls) {
        try {
            const parsed = new URL(url);
            let score = 0;
            if (parsed.searchParams.has('redirect_uri'))
                score += 4;
            if (parsed.searchParams.has('state'))
                score += 2;
            if (parsed.searchParams.has('code_challenge'))
                score += 1;
            if (parsed.pathname.includes('/authorize'))
                score += 1;
            if (isLoopbackHost(parsed.hostname))
                score -= 3;
            if (score >= selectedScore && score > 0) {
                selectedUrl = url;
                selectedScore = score;
            }
        }
        catch {
            continue;
        }
    }
    return selectedUrl;
}
exports.extractLikelyOAuthAuthorizationUrl = extractLikelyOAuthAuthorizationUrl;
async function promptManualCallbackUrl(displayName, state, timeoutMs) {
    const readline = await Promise.resolve().then(() => __importStar(require('readline')));
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    return new Promise((resolve) => {
        let settled = false;
        let timeout = null;
        const finish = (value) => {
            if (settled) {
                return;
            }
            settled = true;
            if (timeout) {
                clearTimeout(timeout);
            }
            state.cancelManualCallbackPrompt = null;
            resolve(value);
        };
        state.cancelManualCallbackPrompt = () => {
            if (!settled) {
                rl.close();
                finish(null);
            }
        };
        rl.on('close', () => {
            finish(null);
        });
        console.log('');
        console.log((0, ui_1.info)(`${displayName} is waiting for the OAuth callback.`));
        console.log('Paste the full callback URL after you finish the login in your browser.');
        rl.question('> ', (answer) => {
            rl.close();
            finish(answer.trim() || null);
        });
        timeout = setTimeout(() => {
            if (!settled) {
                console.log('');
                console.log((0, ui_1.fail)('Timed out waiting for callback URL'));
                rl.close();
            }
        }, timeoutMs);
    });
}
async function replayManualCallback(oauthConfig, authProcess, authUrl, verbose, state, timeoutMs) {
    if (!authUrl.includes('http://') && !authUrl.includes('https://')) {
        return false;
    }
    const callbackUrl = await promptManualCallbackUrl(oauthConfig.displayName, state, timeoutMs);
    if (!callbackUrl) {
        console.log((0, ui_1.info)('Cancelled'));
        (0, process_utils_1.killWithEscalation)(authProcess);
        return true;
    }
    const validationError = validateManualCallbackUrl(callbackUrl, authUrl);
    if (validationError) {
        console.log((0, ui_1.fail)(validationError));
        (0, process_utils_1.killWithEscalation)(authProcess);
        return true;
    }
    console.log((0, ui_1.info)('Replaying callback to the local auth server...'));
    try {
        const response = await fetch(callbackUrl);
        if (!response.ok && response.status >= 400) {
            console.log((0, ui_1.fail)(`OAuth callback failed with status ${response.status}`));
            (0, process_utils_1.killWithEscalation)(authProcess);
            return true;
        }
        console.log((0, ui_1.ok)('Callback submitted. Waiting for token exchange...'));
    }
    catch (error) {
        if (verbose) {
            console.log((0, ui_1.fail)(`Failed to replay callback: ${error.message}`));
        }
        else {
            console.log((0, ui_1.fail)('Failed to replay callback to the local auth server'));
        }
        (0, process_utils_1.killWithEscalation)(authProcess);
    }
    return true;
}
/**
 * Handle stdout data from OAuth process
 */
async function handleStdout(output, state, options, authProcess, log) {
    log(`stdout: ${output.trim()}`);
    state.accumulatedOutput += output;
    const flowType = resolveAuthFlowType(options);
    const isDeviceCodeFlow = flowType === 'device_code';
    if (options.provider === 'kiro' &&
        options.kiroMethod === 'aws' &&
        !state.kiroMethodSelectionHandled &&
        state.accumulatedOutput.includes('Select login method')) {
        const builderIdSelection = getKiroBuilderIdSelectionInput(state.accumulatedOutput);
        if (builderIdSelection) {
            state.kiroMethodSelectionHandled = true;
            authProcess.stdin?.write(builderIdSelection);
            log(`Auto-selected Kiro Builder ID flow (${builderIdSelection.trim()})`);
        }
    }
    // Parse project list when available
    if ((0, project_selection_handler_1.isProjectList)(state.accumulatedOutput) && state.parsedProjects.length === 0) {
        state.parsedProjects = (0, project_selection_handler_1.parseProjectList)(state.accumulatedOutput);
        log(`Parsed ${state.parsedProjects.length} projects`);
    }
    // Handle project selection prompt (Authorization Code flows only - Device Code has no stdin pipe)
    if (!isDeviceCodeFlow && !state.projectPromptHandled && (0, project_selection_handler_1.isProjectSelectionPrompt)(output)) {
        state.projectPromptHandled = true;
        await handleProjectSelection(output, state, options, authProcess, log);
    }
    // Handle Device Code Flow: parse and display user code
    if (isDeviceCodeFlow && !state.deviceCodeDisplayed) {
        // Parse device/user code from various formats:
        // "Enter code: XXXX-YYYY" or "code XXXX-YYYY" or "user code: XXXX-YYYY"
        const codeMatch = state.accumulatedOutput.match(/(?:enter\s+)?(?:user\s+)?code[:\s]+["']?([A-Z0-9]{4,8}[-\s]?[A-Z0-9]{4,8})["']?/i);
        const urlMatch = state.accumulatedOutput.match(/(https?:\/\/[^\s]+device[^\s]*)/i);
        if (codeMatch) {
            state.userCode = codeMatch[1].toUpperCase();
            state.deviceCodeDisplayed = true;
            log(`Parsed device code: ${state.userCode}`);
            const verificationUrl = urlMatch?.[1] || 'https://github.com/login/device';
            // Emit device code event for WebSocket broadcast to UI
            const deviceCodePrompt = {
                sessionId: state.sessionId,
                provider: options.provider,
                userCode: state.userCode,
                verificationUrl,
                expiresAt: Date.now() + device_code_handler_1.DEVICE_CODE_TIMEOUT_MS,
            };
            device_code_handler_1.deviceCodeEvents.emit('deviceCode:received', deviceCodePrompt);
            // Display device code prominently in CLI
            console.log('');
            console.log('  ╔══════════════════════════════════════════════════════╗');
            console.log(`  ║  Enter this code: ${state.userCode.padEnd(35)}║`);
            console.log('  ╚══════════════════════════════════════════════════════╝');
            console.log('');
            console.log((0, ui_1.info)(`Open: ${verificationUrl}`));
            console.log('');
            // Update step display for device code flow
            process.stdout.write('\x1b[1A\x1b[2K');
            (0, environment_detector_1.showStep)(2, 4, 'ok', 'Device code received');
            (0, environment_detector_1.showStep)(3, 4, 'progress', 'Waiting for authorization...');
        }
    }
    // Detect callback server / browser (for Authorization Code flows only)
    if (!isDeviceCodeFlow &&
        !state.browserOpened &&
        (output.includes('listening') || output.includes('http'))) {
        process.stdout.write('\x1b[1A\x1b[2K');
        (0, environment_detector_1.showStep)(2, 4, 'ok', `Callback server listening on port ${options.callbackPort}`);
        (0, environment_detector_1.showStep)(3, 4, 'progress', 'Opening browser...');
        state.browserOpened = true;
    }
    // Display OAuth URL for all modes (enables VS Code terminal URL detection popup)
    if (!isDeviceCodeFlow && !state.urlDisplayed) {
        const authUrl = extractLikelyOAuthAuthorizationUrl(state.accumulatedOutput);
        if (authUrl) {
            console.log('');
            console.log((0, ui_1.info)(`${options.oauthConfig.displayName} OAuth URL:`));
            console.log(`    ${authUrl}`);
            console.log('');
            state.urlDisplayed = true;
            if (options.manualCallback && !state.manualCallbackPrompted) {
                state.manualCallbackPrompted = true;
                await replayManualCallback(options.oauthConfig, authProcess, authUrl, options.verbose, state, 10 * 60 * 1000);
            }
        }
    }
}
/** Display OAuth URL from stderr if in headless mode */
function displayUrlFromStderr(output, state, oauthConfig) {
    const authUrl = extractLikelyOAuthAuthorizationUrl(output);
    if (authUrl) {
        console.log('');
        console.log((0, ui_1.info)(`${oauthConfig.displayName} OAuth URL:`));
        console.log(`    ${authUrl}`);
        console.log('');
        state.urlDisplayed = true;
    }
}
const ANSI_ESCAPE_REGEX = /\x1b\[[0-9;]*m/g;
function extractLikelyAuthFailureFromLogs(provider, logData) {
    if (!logData.trim()) {
        return null;
    }
    const normalizedLines = logData
        .split('\n')
        .map((line) => line.replace(ANSI_ESCAPE_REGEX, '').trim())
        .filter(Boolean)
        .map((line) => {
        const messageIndex = line.indexOf('msg="');
        if (messageIndex >= 0) {
            const message = line
                .slice(messageIndex + 5)
                .replace(/"$/, '')
                .trim();
            if (message) {
                return message;
            }
        }
        return line;
    });
    const providerPatterns = {
        ghcp: [
            /github copilot authentication failed:\s*(.+)/i,
            /failed to verify copilot access[^:]*:\s*(.+)/i,
        ],
        kiro: [
            /kiro idc authentication failed:\s*(.+)/i,
            /kiro authentication failed:\s*(.+)/i,
            /login failed:\s*(.+)/i,
            /failed to register client:\s*(.+)/i,
        ],
    };
    const prioritizedPatterns = [
        ...(providerPatterns[provider] || []),
        /^authentication failed:\s*(.+)/i,
        /^failed to save auth:\s*(.+)/i,
    ];
    for (let i = normalizedLines.length - 1; i >= 0; i--) {
        const line = normalizedLines[i];
        for (const pattern of prioritizedPatterns) {
            const match = line.match(pattern);
            if (match?.[1]?.trim()) {
                return match[1].trim().slice(0, 240);
            }
        }
    }
    return null;
}
exports.extractLikelyAuthFailureFromLogs = extractLikelyAuthFailureFromLogs;
function extractLikelyAuthFailureFromStderr(provider, stderrData) {
    return extractLikelyAuthFailureFromLogs(provider, stderrData);
}
exports.extractLikelyAuthFailureFromStderr = extractLikelyAuthFailureFromStderr;
function analyzeSuccessfulAuthExit(options) {
    const tokenSnapshot = (0, token_manager_1.findNewTokenSnapshot)(options.currentTokenFiles, options.knownTokenFiles, options.expectedAccountId);
    const failureReason = extractLikelyAuthFailureFromLogs(options.provider, [options.stdoutData, options.stderrData].filter(Boolean).join('\n'));
    return { tokenSnapshot, failureReason };
}
exports.analyzeSuccessfulAuthExit = analyzeSuccessfulAuthExit;
/** Handle token not found after successful process exit */
async function handleTokenNotFound(provider, callbackPort, tokenDir, nickname, expectedAccountId, verbose, failureReason, trace) {
    console.log('');
    if (failureReason) {
        // Sanitize internal URLs/paths from failure reason to avoid leaking infrastructure details
        const sanitizedReason = failureReason
            .replace(/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)[^\s]*/gi, '[internal-url]')
            .replace(/\/(?:root|home|opt|tmp|var)\/[^\s]*/g, '[path]');
        console.log((0, ui_1.fail)('Authentication failed before a usable token was saved'));
        console.log(`    ${sanitizedReason}`);
        console.log('');
        console.log(`Try: ccs ${provider} --auth --verbose`);
        return null;
    }
    // Kiro-specific: Try auto-import from Kiro IDE
    if (provider === 'kiro') {
        console.log((0, ui_1.warn)('Callback redirected to Kiro IDE. Attempting to import token...'));
        const result = await (0, kiro_import_1.tryKiroImport)(tokenDir, verbose);
        if (result.success) {
            const providerInfo = result.provider ? ` (Provider: ${result.provider})` : '';
            console.log((0, ui_1.ok)(`Imported Kiro token from IDE${providerInfo}`));
            return (0, token_manager_1.registerAccountFromToken)(provider, tokenDir, nickname, verbose, expectedAccountId);
        }
        console.log((0, ui_1.fail)(`Auto-import failed: ${result.error}`));
        console.log('');
        console.log('To manually import from Kiro IDE:');
        console.log('  1. Ensure you are logged into Kiro IDE');
        console.log('  2. Run: ccs kiro --import');
        return null;
    }
    // Branch-specific diagnosis when trace recorder is wired (Phase 7).
    if (trace) {
        const diagnosis = (0, diagnose_failure_1.diagnoseFailure)(trace.snapshot());
        if (diagnosis.branchId !== 'UNKNOWN') {
            console.log((0, ui_1.fail)('Authentication failed'));
            const lines = (0, diagnose_failure_1.formatErrorMessage)(diagnosis, {
                verbose,
                platform: process.platform,
                callbackPort,
                provider,
            });
            for (const line of lines)
                console.log(line);
            return null;
        }
    }
    console.log((0, ui_1.fail)('Token not found after authentication'));
    console.log('');
    console.log('The browser showed success but callback was not received.');
    console.log('');
    console.log('Common causes:');
    console.log('  1. OAuth session timed out (sessions expire after ~10 minutes)');
    console.log('  2. Callback server could not receive the redirect');
    console.log('  3. Browser did not redirect to localhost properly');
    if (process.platform === 'win32') {
        console.log('');
        console.log('On Windows, this usually means:');
        console.log('  1. Windows Firewall blocked the callback');
        console.log('  2. Antivirus software blocked the connection');
        console.log('');
        console.log('Try running as Administrator:');
        console.log(`  netsh advfirewall firewall add rule name="CCS OAuth" dir=in action=allow protocol=TCP localport=${callbackPort}`);
    }
    console.log('');
    console.log('If you copied the OAuth URL to a different browser:');
    console.log('  - Complete authentication within the timeout window');
    console.log('  - Ensure you are on the same machine (localhost callback)');
    console.log('  - Copy the entire URL including all parameters');
    console.log('');
    console.log(`Try: ccs ${provider} --auth --verbose`);
    return null;
}
/** Handle process exit with error */
function handleProcessError(code, state, headless) {
    console.log('');
    console.log((0, ui_1.fail)(`CLIProxy auth exited with code ${code}`));
    if (state.stderrData && !state.urlDisplayed) {
        console.log(`    ${state.stderrData.trim().split('\n')[0]}`);
    }
    if (headless && !state.urlDisplayed) {
        console.log('');
        console.log((0, ui_1.info)('No OAuth URL was displayed. Try with --verbose for details.'));
    }
}
/**
 * Execute OAuth process and wait for completion
 */
function executeOAuthProcess(options) {
    const { provider, binaryPath, args, tokenDir, oauthConfig, callbackPort, headless, verbose, nickname, expectedAccountId, } = options;
    const log = (msg) => {
        if (verbose)
            console.error(`[auth] ${msg}`);
    };
    return new Promise((resolve) => {
        const flowType = resolveAuthFlowType(options);
        const isDeviceCodeFlow = flowType === 'device_code';
        const knownTokenFiles = (0, token_manager_1.listProviderTokenSnapshots)(provider, tokenDir);
        // Device-code flows can usually inherit stdin, but Kiro's default AWS flow now
        // prints an intermediate Builder ID vs IDC selector that CCS auto-answers.
        const stdinMode = isDeviceCodeFlow &&
            process.stdin.isTTY &&
            !(provider === 'kiro' && options.kiroMethod === 'aws')
            ? 'inherit'
            : 'pipe';
        const authProcess = (0, child_process_1.spawn)(binaryPath, args, {
            stdio: [stdinMode, 'pipe', 'pipe'],
            env: { ...process.env, CLI_PROXY_AUTH_DIR: tokenDir },
        });
        // H7: Mutable ref for stdin keepalive interval (set later, needed in cleanup)
        let stdinKeepalive = null;
        // Mutable ref so cleanup/handleCancel can flush trace even though `trace`
        // is assigned after them. DEFERRED: per-event syscall throttling.
        let traceRef = null;
        // H5: Signal handling - properly kill child process on SIGINT/SIGTERM
        // H8: Also clear stdinKeepalive interval to prevent memory leak
        const cleanup = () => {
            if (stdinKeepalive)
                clearInterval(stdinKeepalive);
            if (traceRef) {
                try {
                    traceRef.record(oauth_trace_1.OAuthTracePhase.Cancelled, { reason: 'signal' });
                    void traceRef.flush();
                }
                catch {
                    // never block shutdown on trace errors
                }
            }
            if (authProcess && authProcess.exitCode === null) {
                (0, process_utils_1.killWithEscalation)(authProcess);
            }
        };
        process.on('SIGINT', cleanup);
        process.on('SIGTERM', cleanup);
        const state = {
            stderrData: '',
            urlDisplayed: false,
            browserOpened: false,
            projectPromptHandled: false,
            accumulatedOutput: '',
            parsedProjects: [],
            sessionId: (0, project_selection_handler_1.generateSessionId)(),
            deviceCodeDisplayed: false,
            userCode: null,
            kiroMethodSelectionHandled: false,
            manualCallbackPrompted: false,
            cancelManualCallbackPrompt: null,
        };
        // OAuth trace recorder — opt-in file sink via CCS_OAUTH_LOG_FILE=1
        const fileSink = process.env['CCS_OAUTH_LOG_FILE'] === '1'
            ? (0, sink_file_1.createFileSink)({ dir: path.join((0, config_manager_1.getCcsDir)(), 'logs') })
            : undefined;
        const trace = (0, oauth_trace_1.createOAuthTraceRecorder)({
            sessionId: state.sessionId,
            provider,
            verbose,
            fileSink,
        });
        traceRef = trace; // wire late-binding ref for cleanup/handleCancel
        trace.record(oauth_trace_1.OAuthTracePhase.BinarySpawn, {
            callbackPort,
            headless,
            manualCallback: !!options.manualCallback,
            flowType,
        });
        // Register session for cancellation support
        (0, auth_session_manager_1.registerAuthSession)(state.sessionId, provider);
        (0, auth_session_manager_1.attachProcessToSession)(state.sessionId, authProcess);
        // Listen for external cancel signal
        const handleCancel = (cancelledSessionId) => {
            if (cancelledSessionId === state.sessionId && authProcess && authProcess.exitCode === null) {
                log('Session cancelled externally');
                if (traceRef) {
                    try {
                        traceRef.record(oauth_trace_1.OAuthTracePhase.Cancelled, { reason: 'external' });
                        void traceRef.flush();
                    }
                    catch {
                        // never block shutdown on trace errors
                    }
                }
                (0, process_utils_1.killWithEscalation)(authProcess);
            }
        };
        auth_session_manager_1.authSessionEvents.on('session:cancelled', handleCancel);
        const startTime = Date.now();
        // H7: Stdin keepalive for Authorization Code flows
        // CLIProxyAPIPlus has a 15-second timer that prompts for manual URL paste.
        // If the user completes browser auth after this timer fires but before the
        // non-blocking check, the prompt blocks forever on stdin.
        // Workaround: Send newline every 16s to skip the manual prompt and continue polling.
        if (!isDeviceCodeFlow && stdinMode === 'pipe') {
            stdinKeepalive = setInterval(() => {
                if (authProcess.stdin && !authProcess.stdin.destroyed) {
                    authProcess.stdin.write('\n');
                    log('Sent stdin keepalive (skip manual URL prompt)');
                }
            }, 16000);
        }
        authProcess.stdout?.on('data', async (data) => {
            const out = data.toString();
            const wasUrlDisplayed = state.urlDisplayed;
            const wasBrowserOpened = state.browserOpened;
            await handleStdout(out, state, options, authProcess, log);
            if (!wasUrlDisplayed && state.urlDisplayed) {
                trace.record(oauth_trace_1.OAuthTracePhase.AuthUrlDisplayed, {});
            }
            if (!wasBrowserOpened && state.browserOpened) {
                trace.record(oauth_trace_1.OAuthTracePhase.BrowserOpened, { port: callbackPort });
                // CLIProxyAPI emits "Callback server listening" when bind succeeds; treat as
                // best-available signal that the loopback path is reachable. This is a heuristic.
                trace.record(oauth_trace_1.OAuthTracePhase.CallbackObservedHeuristic, { port: callbackPort });
            }
            // Capture redacted snippet (cap at 200 chars; high-frequency lines accepted as data).
            const snippet = (0, redactor_1.redactString)(out.trim().slice(0, 200));
            if (snippet)
                trace.record(oauth_trace_1.OAuthTracePhase.BinaryStdout, { snippet });
        });
        authProcess.stderr?.on('data', async (data) => {
            const output = data.toString();
            state.stderrData += output;
            log(`stderr: ${output.trim()}`);
            const snippet = (0, redactor_1.redactString)(output.trim().slice(0, 200));
            if (snippet)
                trace.record(oauth_trace_1.OAuthTracePhase.BinaryStderr, { snippet });
            if (headless && !state.urlDisplayed) {
                displayUrlFromStderr(output, state, oauthConfig);
            }
            if (options.manualCallback && !state.manualCallbackPrompted) {
                const authUrl = extractLikelyOAuthAuthorizationUrl(output) ?? output.match(/https?:\/\/[^\s]+/)?.[0];
                if (authUrl) {
                    state.manualCallbackPrompted = true;
                    await replayManualCallback(options.oauthConfig, authProcess, authUrl, options.verbose, state, 10 * 60 * 1000);
                }
            }
        });
        // Show waiting message after delay
        setTimeout(() => {
            if (isDeviceCodeFlow) {
                // Device Code Flow: show polling message
                if (!state.deviceCodeDisplayed) {
                    // Code not yet displayed, show generic waiting message
                    (0, environment_detector_1.showStep)(3, 4, 'progress', 'Waiting for device code...');
                }
                (0, environment_detector_1.showStep)(4, 4, 'progress', 'Polling for authorization...');
                console.log('');
                console.log((0, ui_1.info)('Complete the login in your browser. This page will update automatically.'));
            }
            else {
                // Authorization Code Flow: show callback server message
                if (!state.browserOpened) {
                    process.stdout.write('\x1b[1A\x1b[2K');
                    (0, environment_detector_1.showStep)(2, 4, 'ok', `Callback server ready (port ${callbackPort})`);
                    (0, environment_detector_1.showStep)(3, 4, 'ok', 'Browser opened');
                    state.browserOpened = true;
                }
                (0, environment_detector_1.showStep)(4, 4, 'progress', 'Waiting for OAuth callback...');
                console.log('');
                console.log((0, ui_1.info)('Complete the login in your browser. This page will update automatically.'));
            }
            if (!verbose)
                console.log((0, ui_1.info)('If stuck, try: ccs ' + provider + ' --auth --verbose'));
        }, 2000);
        // Timeout handling
        // Device code flows need longer timeout to match CLIProxy binary's polling window (60 attempts × 5s = 300s)
        const timeoutMs = options.manualCallback
            ? 10 * 60 * 1000
            : headless || isDeviceCodeFlow
                ? 300000
                : 120000;
        const timeout = setTimeout(() => {
            // H7: Clear stdin keepalive interval
            if (stdinKeepalive)
                clearInterval(stdinKeepalive);
            state.cancelManualCallbackPrompt?.();
            // H5: Remove signal handlers before killing process
            process.removeListener('SIGINT', cleanup);
            process.removeListener('SIGTERM', cleanup);
            auth_session_manager_1.authSessionEvents.removeListener('session:cancelled', handleCancel);
            (0, auth_session_manager_1.unregisterAuthSession)(state.sessionId);
            (0, project_selection_handler_1.cancelProjectSelection)(state.sessionId);
            trace.record(oauth_trace_1.OAuthTracePhase.Timeout, { timeoutMs });
            void trace.flush();
            (0, process_utils_1.killWithEscalation)(authProcess);
            console.log('');
            console.log((0, ui_1.fail)(`OAuth timed out after ${timeoutMs / 60000} minutes`));
            for (const line of (0, environment_detector_1.getTimeoutTroubleshooting)(provider, callbackPort ?? null)) {
                console.log(line);
            }
            resolve(null);
        }, timeoutMs);
        authProcess.on('exit', async (code) => {
            clearTimeout(timeout);
            // H7: Clear stdin keepalive interval
            if (stdinKeepalive)
                clearInterval(stdinKeepalive);
            state.cancelManualCallbackPrompt?.();
            // H5: Remove signal handlers to prevent memory leaks
            process.removeListener('SIGINT', cleanup);
            process.removeListener('SIGTERM', cleanup);
            auth_session_manager_1.authSessionEvents.removeListener('session:cancelled', handleCancel);
            (0, auth_session_manager_1.unregisterAuthSession)(state.sessionId);
            (0, project_selection_handler_1.cancelProjectSelection)(state.sessionId);
            const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
            trace.record(oauth_trace_1.OAuthTracePhase.BinaryExit, {
                code,
                stderrTail: (0, redactor_1.redactString)(state.stderrData.trim().split('\n').pop() ?? ''),
            });
            if (code === 0) {
                const exitAnalysis = analyzeSuccessfulAuthExit({
                    provider,
                    knownTokenFiles,
                    currentTokenFiles: (0, token_manager_1.listProviderTokenSnapshots)(provider, tokenDir),
                    expectedAccountId,
                    stdoutData: state.accumulatedOutput,
                    stderrData: state.stderrData,
                });
                if (exitAnalysis.tokenSnapshot) {
                    trace.record(oauth_trace_1.OAuthTracePhase.TokenFileAppeared, {});
                    await trace.flush();
                    console.log('');
                    console.log((0, ui_1.ok)(`Authentication successful (${elapsed}s)`));
                    // Emit device code completion event for UI
                    if (isDeviceCodeFlow && state.deviceCodeDisplayed) {
                        device_code_handler_1.deviceCodeEvents.emit('deviceCode:completed', state.sessionId);
                    }
                    resolve((0, token_manager_1.registerAccountFromToken)(provider, tokenDir, nickname, verbose, expectedAccountId));
                }
                else {
                    // Emit device code failure event for UI
                    if (isDeviceCodeFlow && state.deviceCodeDisplayed) {
                        device_code_handler_1.deviceCodeEvents.emit('deviceCode:failed', {
                            sessionId: state.sessionId,
                            error: exitAnalysis.failureReason || 'Token not found after authentication',
                        });
                    }
                    trace.record(oauth_trace_1.OAuthTracePhase.TokenFileMissing, {});
                    await trace.flush();
                    // Try auto-import for Kiro, show error for others
                    const account = await handleTokenNotFound(provider, callbackPort, tokenDir, nickname, expectedAccountId, verbose, exitAnalysis.failureReason || undefined, trace);
                    resolve(account);
                }
            }
            else {
                // Emit device code failure event for UI
                if (isDeviceCodeFlow && state.deviceCodeDisplayed) {
                    device_code_handler_1.deviceCodeEvents.emit('deviceCode:failed', {
                        sessionId: state.sessionId,
                        error: `Auth process exited with code ${code}`,
                    });
                }
                handleProcessError(code, state, headless);
                resolve(null);
            }
        });
        authProcess.on('error', (error) => {
            clearTimeout(timeout);
            // H7: Clear stdin keepalive interval
            if (stdinKeepalive)
                clearInterval(stdinKeepalive);
            state.cancelManualCallbackPrompt?.();
            // H5: Remove signal handlers to prevent memory leaks
            process.removeListener('SIGINT', cleanup);
            process.removeListener('SIGTERM', cleanup);
            auth_session_manager_1.authSessionEvents.removeListener('session:cancelled', handleCancel);
            (0, auth_session_manager_1.unregisterAuthSession)(state.sessionId);
            (0, project_selection_handler_1.cancelProjectSelection)(state.sessionId);
            trace.record(oauth_trace_1.OAuthTracePhase.Error, {}, error);
            void trace.flush();
            console.log('');
            console.log((0, ui_1.fail)(`Failed to start auth process: ${error.message}`));
            resolve(null);
        });
    });
}
exports.executeOAuthProcess = executeOAuthProcess;
//# sourceMappingURL=oauth-process.js.map