/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { QwenDeviceFlow, openBrowserSecurely, shouldLaunchBrowser, OAuthError, OAuthErrorFactory, GracefulErrorHandler, RetryHandler, DebugLogger, } from '@vybestack/llxprt-code-core';
import { ClipboardService } from '../services/ClipboardService.js';
import { globalOAuthUI } from './global-oauth-ui.js';
var InitializationState;
(function (InitializationState) {
    InitializationState["NotStarted"] = "not-started";
    InitializationState["InProgress"] = "in-progress";
    InitializationState["Completed"] = "completed";
    InitializationState["Failed"] = "failed";
})(InitializationState || (InitializationState = {}));
export class QwenOAuthProvider {
    tokenStore;
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode line 5
     */
    name = 'qwen';
    deviceFlow;
    initializationState = InitializationState.NotStarted;
    initializationPromise;
    initializationError;
    errorHandler;
    retryHandler;
    logger;
    addItem;
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode lines 6-15
     *
     * Constructor completes synchronously - no async calls
     */
    constructor(tokenStore, addItem) {
        this.tokenStore = tokenStore;
        // Line 7: SET this.tokenStore = tokenStore
        this.tokenStore = tokenStore;
        this.retryHandler = new RetryHandler();
        this.errorHandler = new GracefulErrorHandler(this.retryHandler);
        this.logger = new DebugLogger('llxprt:auth:qwen');
        this.addItem = addItem;
        /**
         * @plan PLAN-20250823-AUTHFIXES.P16
         * @requirement REQ-004.2
         * Deprecation warning for missing TokenStore
         */
        if (!tokenStore) {
            console.warn(`DEPRECATION: ${this.name} OAuth provider created without TokenStore. ` +
                `Token persistence will not work. Please update your code.`);
        }
        // Lines 8-13: SET config
        const config = {
            clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
            authorizationEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
            tokenEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/token',
            scopes: ['openid', 'profile', 'email', 'model.completion'],
        };
        // Line 14: SET this.deviceFlow = new QwenDeviceFlow(config)
        this.deviceFlow = new QwenDeviceFlow(config);
        // DO NOT call initializeToken() - lazy initialization pattern
    }
    /**
     * Set the addItem callback for displaying messages in the UI
     */
    setAddItem(addItem) {
        this.addItem = addItem;
    }
    /**
     * Lazy initialization with proper state management
     * Ensures initialization only happens once and handles concurrent calls
     */
    async ensureInitialized() {
        // If already completed, return immediately
        if (this.initializationState === InitializationState.Completed) {
            return;
        }
        // If failed, allow retry by resetting to NotStarted
        if (this.initializationState === InitializationState.Failed) {
            this.initializationState = InitializationState.NotStarted;
            this.initializationPromise = undefined;
            this.initializationError = undefined;
        }
        // If not started, start initialization
        if (this.initializationState === InitializationState.NotStarted) {
            this.initializationState = InitializationState.InProgress;
            this.initializationPromise = this.initializeToken();
        }
        // Wait for initialization to complete (handles concurrent calls)
        if (this.initializationPromise) {
            try {
                await this.initializationPromise;
                this.initializationState = InitializationState.Completed;
            }
            catch (error) {
                this.initializationState = InitializationState.Failed;
                this.initializationError =
                    error instanceof OAuthError
                        ? error
                        : OAuthErrorFactory.fromUnknown(this.name, error, 'ensureInitialized');
                throw this.initializationError;
            }
        }
    }
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode lines 17-26
     */
    async initializeToken() {
        return this.errorHandler.handleGracefully(async () => {
            // Line 19: SET savedToken = AWAIT this.tokenStore.getToken('qwen')
            const savedToken = await this.tokenStore?.getToken('qwen');
            // Line 20: IF savedToken AND NOT this.isTokenExpired(savedToken)
            if (savedToken && !this.isTokenExpired(savedToken)) {
                // Line 21: RETURN
                return;
            }
        }, undefined, // No fallback needed - graceful failure is acceptable
        this.name, 'initializeToken');
    }
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode lines 32-57
     */
    async initiateAuth() {
        await this.ensureInitialized();
        return this.errorHandler.wrapMethod(async () => {
            // Line 33: SET deviceCodeResponse = AWAIT this.deviceFlow.initiateDeviceFlow()
            const deviceCodeResponse = await this.deviceFlow.initiateDeviceFlow();
            // Lines 34-35: SET authUrl
            const authUrl = deviceCodeResponse.verification_uri_complete ||
                `${deviceCodeResponse.verification_uri}?user_code=${deviceCodeResponse.user_code}`;
            // Try instance addItem first, fallback to global
            const addItem = this.addItem || globalOAuthUI.getAddItem();
            // Always show OAuth URL in the TUI first, before attempting browser (like Gemini does)
            const historyItem = {
                type: 'oauth_url',
                text: `Please visit the following URL to authorize with Qwen:\\n${authUrl}`,
                url: authUrl,
            };
            if (addItem) {
                addItem(historyItem, Date.now());
            }
            else {
                // Lines 37-38: PRINT
                console.log('\nQwen OAuth Authentication');
                console.log('─'.repeat(40));
                console.log('Please visit the following URL to authorize:');
                console.log(authUrl);
            }
            // Copy URL to clipboard with error handling
            try {
                await ClipboardService.copyToClipboard(authUrl);
            }
            catch (error) {
                // Clipboard copy is non-critical, continue without it
                this.logger.debug(() => `Failed to copy URL to clipboard: ${error instanceof Error ? error.message : String(error)}`);
            }
            // Line 40: IF shouldLaunchBrowser()
            let noBrowser = false;
            try {
                const { getEphemeralSetting } = await import('../runtime/runtimeSettings.js');
                noBrowser =
                    getEphemeralSetting('auth.noBrowser') ?? false;
            }
            catch {
                // Runtime not initialized (e.g., tests) — use default
            }
            if (shouldLaunchBrowser({ forceManual: noBrowser })) {
                // Line 41: PRINT
                if (addItem) {
                    addItem({ type: 'info', text: 'Opening browser for authentication...' }, Date.now());
                }
                else {
                    console.log('Opening browser for authentication...');
                }
                // Lines 42-46: TRY
                try {
                    await openBrowserSecurely(authUrl);
                }
                catch (error) {
                    // Line 45: PRINT - browser failure is not critical
                    if (addItem) {
                        addItem({
                            type: 'warning',
                            text: 'Failed to open browser automatically.',
                        }, Date.now());
                    }
                    else {
                        console.log('Failed to open browser automatically.');
                    }
                    this.logger.debug(() => `Browser launch error: ${error instanceof Error ? error.message : String(error)}`);
                }
            }
            if (addItem) {
                // Line 52: PRINT
                addItem({ type: 'info', text: 'Waiting for authorization...' }, Date.now());
            }
            else {
                console.log('─'.repeat(40));
                // Line 52: PRINT
                console.log('Waiting for authorization...\n');
            }
            // Line 54: SET token = AWAIT this.deviceFlow.pollForToken
            const token = await this.deviceFlow.pollForToken(deviceCodeResponse.device_code);
            // Line 56: PRINT
            if (addItem) {
                addItem({ type: 'info', text: 'Authentication successful!' }, Date.now());
            }
            else {
                console.log('Authentication successful!');
            }
            return token;
        }, this.name, 'initiateAuth')();
    }
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode lines 27-31
     */
    isTokenExpired(token) {
        // Line 28: SET now = Date.now() / 1000
        const now = Date.now() / 1000;
        // Line 29: SET buffer = 30
        const buffer = 30;
        // Line 30: RETURN token.expiry <= (now + buffer)
        return token.expiry <= now + buffer;
    }
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode lines 58-59
     */
    async getToken() {
        await this.ensureInitialized();
        return this.errorHandler.handleGracefully(async () => {
            // Line 59: RETURN AWAIT this.tokenStore.getToken('qwen')
            // Read-only - OAuthManager owns all refresh operations
            const token = (await this.tokenStore?.getToken('qwen')) || null;
            return token;
        }, null, // Return null on error
        this.name, 'getToken');
    }
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode lines 61-85
     */
    async refreshIfNeeded() {
        this.logger.debug(() => 'refreshIfNeeded() is deprecated — refresh is handled by OAuthManager');
        return null;
    }
    async refreshToken(currentToken) {
        await this.ensureInitialized();
        if (!currentToken.refresh_token) {
            return null;
        }
        try {
            // Skip actual refresh in test environment to avoid network calls
            if (process.env.NODE_ENV === 'test') {
                throw new Error('Simulated refresh failure in test environment');
            }
            const refreshedToken = await this.deviceFlow.refreshToken(currentToken.refresh_token);
            return {
                ...currentToken,
                ...refreshedToken,
                refresh_token: refreshedToken.refresh_token ?? currentToken.refresh_token,
            };
        }
        catch (error) {
            const refreshError = error instanceof OAuthError
                ? error
                : OAuthErrorFactory.authorizationExpired(this.name, {
                    originalError: error instanceof Error ? error.message : String(error),
                    operation: 'refreshToken',
                });
            this.logger.debug(() => `Token refresh failed: ${JSON.stringify(refreshError.toLogEntry())}`);
            return null;
        }
    }
    /**
     * @plan PLAN-20250823-AUTHFIXES.P05
     * @requirement REQ-001.1
     * @pseudocode lines 87-89
     */
    async logout(token) {
        await this.ensureInitialized();
        return this.errorHandler.handleGracefully(async () => {
            // Line 88: AWAIT this.tokenStore.removeToken('qwen')
            if (this.tokenStore && !token) {
                try {
                    await this.tokenStore.removeToken('qwen');
                }
                catch (error) {
                    throw OAuthErrorFactory.storageError(this.name, error instanceof Error ? error : undefined, {
                        operation: 'removeToken',
                    });
                }
            }
            // Line 89: PRINT "Successfully logged out from Qwen"
            if (!token) {
                const addItem = this.addItem || globalOAuthUI.getAddItem();
                if (addItem) {
                    addItem({ type: 'info', text: 'Successfully logged out from Qwen' }, Date.now());
                }
                else {
                    console.log('Successfully logged out from Qwen');
                }
            }
        }, undefined, // Always complete logout even if some steps fail
        this.name, 'logout');
    }
}
//# sourceMappingURL=qwen-oauth-provider.js.map