/**
 * @plan:PLAN-20250214-CREDPROXY.P29
 * @plan:PLAN-20250214-CREDPROXY.P27
 * @requirement R17.4, R17.5, R18.3-R18.5, R19.2
 * @pseudocode analysis/pseudocode/009-proxy-oauth-adapter.md
 */
export class ProxyOAuthAdapter {
    socketClient;
    constructor(socketClient) {
        this.socketClient = socketClient;
    }
    async login(provider, bucket) {
        const initiate = await this.socketClient.request('oauth_initiate', {
            provider,
            bucket,
        });
        const data = initiate.data;
        const sessionId = String(data.session_id ?? data.sessionId ?? '');
        const flowType = this.normalizeFlowType(data);
        try {
            if (flowType === 'pkce_redirect') {
                return await this.handlePkceRedirect(sessionId, data);
            }
            if (flowType === 'device_code') {
                const pending = this.handleDeviceCode(sessionId, data);
                pending.catch(() => {
                    // Attach a no-op rejection handler to avoid unhandled rejection warnings
                    // before callers attach their own .catch/.rejects handlers.
                });
                return await pending;
            }
            if (flowType === 'browser_redirect') {
                const pending = this.handleBrowserRedirect(sessionId, data);
                pending.catch(() => {
                    // Attach a no-op rejection handler to avoid unhandled rejection warnings
                    // before callers attach their own .catch/.rejects handlers.
                });
                return await pending;
            }
            throw new Error(`Unknown flow type: ${String(data.flow_type ?? data.mode ?? 'unknown')}`);
        }
        catch (error) {
            if (sessionId !== '') {
                try {
                    await this.socketClient.request('oauth_cancel', {
                        session_id: sessionId,
                    });
                }
                catch {
                    // best-effort cancellation
                }
            }
            throw error;
        }
    }
    async handlePkceRedirect(sessionId, data) {
        void data;
        const rawCode = await new Promise((resolve, reject) => {
            let settled = false;
            const cleanup = () => {
                process.stdin.removeListener('data', onData);
                process.stdin.removeListener('end', onEnd);
                process.stdin.removeListener('close', onClose);
                process.stdin.removeListener('error', onError);
            };
            const settleResolve = (value) => {
                if (settled) {
                    return;
                }
                settled = true;
                cleanup();
                resolve(value);
            };
            const settleReject = (error) => {
                if (settled) {
                    return;
                }
                settled = true;
                cleanup();
                reject(error);
            };
            const onData = (chunk) => {
                settleResolve(String(chunk));
            };
            const onEnd = () => {
                settleReject(new Error('Authorization cancelled — stdin closed without providing a code'));
            };
            const onClose = () => {
                settleReject(new Error('Authorization cancelled — stdin closed without providing a code'));
            };
            const onError = (error) => {
                settleReject(error);
            };
            process.stdin.on('data', onData);
            process.stdin.once('end', onEnd);
            process.stdin.once('close', onClose);
            process.stdin.once('error', onError);
            process.stdin.resume();
        });
        const code = rawCode.trim();
        if (code === '') {
            throw new Error('Authorization cancelled — no code provided');
        }
        const exchange = await this.socketClient.request('oauth_exchange', {
            session_id: sessionId,
            code,
        });
        return exchange.data;
    }
    handleDeviceCode(sessionId, data) {
        const promise = (async () => {
            let intervalMs = data.pollIntervalMs ?? 5000;
            for (;;) {
                await this.wait(intervalMs);
                const poll = await this.socketClient.request('oauth_poll', {
                    session_id: sessionId,
                });
                const pollData = poll.data;
                if (pollData.status === 'complete') {
                    return pollData;
                }
                if (pollData.status === 'error') {
                    throw new Error(`Authentication failed: ${String(pollData.error)}`);
                }
                if (typeof pollData.pollIntervalMs === 'number') {
                    intervalMs = pollData.pollIntervalMs;
                }
            }
        })();
        void promise.catch(() => { });
        return promise;
    }
    handleBrowserRedirect(sessionId, data) {
        const promise = (async () => {
            void data;
            let intervalMs = data.pollIntervalMs ?? 2000;
            for (;;) {
                await this.wait(intervalMs);
                const poll = await this.socketClient.request('oauth_poll', {
                    session_id: sessionId,
                });
                const pollData = poll.data;
                if (pollData.status === 'complete') {
                    return pollData;
                }
                if (pollData.status === 'error') {
                    throw new Error(`Authentication failed: ${String(pollData.error)}`);
                }
                if (typeof pollData.pollIntervalMs === 'number') {
                    intervalMs = pollData.pollIntervalMs;
                }
            }
        })();
        void promise.catch(() => { });
        return promise;
    }
    async refresh(provider, bucket) {
        const reply = await this.socketClient.request('refresh_token', {
            provider,
            bucket,
        });
        return reply.data;
    }
    async cancel(sessionId) {
        await this.socketClient.request('oauth_cancel', { session_id: sessionId });
    }
    normalizeFlowType(data) {
        if (typeof data.flow_type === 'string' && data.flow_type !== '') {
            return data.flow_type;
        }
        switch (data.mode) {
            case 'pkce':
                return 'pkce_redirect';
            case 'device_code':
                return 'device_code';
            case 'browser_redirect':
                return 'browser_redirect';
            default:
                return data.mode ?? '';
        }
    }
    async wait(ms) {
        await new Promise((resolve) => {
            setTimeout(resolve, ms);
        });
    }
}
//# sourceMappingURL=proxy-oauth-adapter.js.map