/**
 * SDK Iframe Core
 * Chạy trong hidden iframe riêng biệt
 *
 * Responsibilities:
 * - Receive data từ parent (preview mode)
 * - Fetch data từ API (live mode)
 * - Store answers securely
 * - Verify answers locally (fast feedback)
 * - Sync với server (background)
 * - Report results
 */
import { checkAnswer, sanitizeForClient, GAME_CODES } from '../kit/GameDataHandler';
import { getMockData } from '../client/MockData';
import { SDK_MESSAGE_TYPES, createSdkMessage, isSdkMessage, } from './types';
// =============================================================================
// SDK IFRAME CORE
// =============================================================================
export class SdkIframeCore {
    constructor(config = {}) {
        this.boundMessageHandler = null;
        this.mode = null;
        this.gameCode = null;
        this.assignmentId = null;
        this.studentId = null;
        this.apiBaseUrl = '';
        this.authToken = '';
        // Data storage
        this.originalItems = new Map(); // Có đáp án
        this.clientItems = new Map(); // Shuffled (cho Client)
        this.sanitizedItems = [];
        this.userAnswers = new Map();
        this.completedQuestions = new Set();
        this.isInitialized = false;
        this.parentOrigin = null;
        this.config = {
            debug: config.debug ?? false,
            allowedOrigins: config.allowedOrigins ?? ['*'],
        };
        this.setupMessageListener();
        this.log('info', 'SDK Iframe Core initialized');
    }
    // ==========================================================================
    // MESSAGE HANDLING
    // ==========================================================================
    setupMessageListener() {
        this.boundMessageHandler = this.handleMessage.bind(this);
        window.addEventListener('message', this.boundMessageHandler);
    }
    handleMessage(event) {
        // Validate origin
        if (!this.isAllowedOrigin(event.origin)) {
            this.log('warn', `Blocked message from unauthorized origin: ${event.origin}`);
            return;
        }
        const data = event.data;
        if (!isSdkMessage(data)) {
            return;
        }
        this.log('debug', `Received: ${data.type}`, data.payload);
        // Store parent origin for responses
        if (!this.parentOrigin) {
            this.parentOrigin = event.origin;
        }
        switch (data.type) {
            case SDK_MESSAGE_TYPES.SDK_INIT:
                this.handleInit(data.payload, data.request_id);
                break;
            case SDK_MESSAGE_TYPES.SDK_PUSH_DATA:
                this.handlePushData(data.payload, data.request_id);
                break;
            case SDK_MESSAGE_TYPES.SDK_CHECK_ANSWER:
                this.handleCheckAnswer(data.payload, data.request_id);
                break;
            case SDK_MESSAGE_TYPES.SDK_GET_RESULT:
                this.handleGetResult(data.request_id);
                break;
            case SDK_MESSAGE_TYPES.SDK_RETRY_SYNC:
                this.handleRetrySync(data.payload, data.request_id);
                break;
            default:
                this.log('warn', `Unknown message type: ${data.type}`);
        }
    }
    isAllowedOrigin(origin) {
        if (this.config.allowedOrigins.includes('*')) {
            return true;
        }
        return this.config.allowedOrigins.includes(origin);
    }
    // ==========================================================================
    // MESSAGE HANDLERS
    // ==========================================================================
    async handleInit(payload, requestId) {
        try {
            this.mode = payload.mode;
            this.gameCode = payload.game_code;
            this.assignmentId = payload.assignment_id ?? null;
            this.studentId = payload.student_id ?? null;
            this.apiBaseUrl = payload.api_base_url ?? '';
            this.authToken = payload.auth_token ?? '';
            // Validate game code
            if (!GAME_CODES[this.gameCode]) {
                throw new Error(`Invalid game code: ${this.gameCode}`);
            }
            this.isInitialized = true;
            // Send ready
            this.sendToParent(SDK_MESSAGE_TYPES.SDK_READY, {
                mode: this.mode,
                game_code: this.gameCode,
            }, requestId);
            this.log('info', `Initialized: mode=${this.mode}, game_code=${this.gameCode}`);
            // Mode handling
            if (this.mode === 'live' && this.assignmentId) {
                await this.fetchLiveData();
            }
            else if (this.mode === 'dev') {
                const mockData = getMockData(this.gameCode);
                if (mockData) {
                    this.log('info', `[Dev] Loaded mock data for ${this.gameCode}`);
                    this.processData(mockData.data);
                }
                else {
                    this.sendError('MOCK_NOT_FOUND', `No mock data found for ${this.gameCode}`, requestId);
                }
            }
        }
        catch (error) {
            this.sendError('INIT_ERROR', error.message, requestId);
        }
    }
    handlePushData(payload, requestId) {
        if (!this.isInitialized) {
            this.sendError('NOT_INITIALIZED', 'SDK not initialized. Call SDK_INIT first.', requestId);
            return;
        }
        if (this.mode !== 'preview') {
            this.sendError('INVALID_MODE', 'SDK_PUSH_DATA only allowed in preview mode', requestId);
            return;
        }
        try {
            this.processData(payload.data, payload.completed_question_ids);
        }
        catch (error) {
            this.sendError('DATA_ERROR', error.message, requestId);
        }
    }
    async handleCheckAnswer(payload, requestId) {
        if (!this.isInitialized || this.originalItems.size === 0) {
            this.sendError('NOT_READY', 'No data loaded', requestId);
            return;
        }
        const { question_id, choice, time_spent = 0 } = payload;
        // ===== RESOLVE CHOICE BY GAME CODE =====
        // Each game code has its own resolution logic
        const code = this.gameCode;
        const clientItem = this.clientItems.get(question_id);
        let answerValue = choice; // Default: use raw choice
        switch (code) {
            // ===== QUIZ VARIANTS (G001-G005): Client sends INDEX -> resolve to VALUE =====
            case 'G001': // Quiz Text-Text
            case 'G002': // Quiz Audio-Text
            case 'G003': // Quiz Text-Audio
            case 'G004': // Quiz Image-Text
            case 'G005': // Quiz Text-Image
                // Sanitizers store __shuffledOptions which contains resolved values (strings)
                if (clientItem && typeof choice === 'number') {
                    const shuffled = clientItem.__shuffledOptions;
                    if (Array.isArray(shuffled) && choice >= 0 && choice < shuffled.length) {
                        answerValue = shuffled[choice];
                        this.log('debug', `[${code}] Resolved: index ${choice} -> "${answerValue}"`);
                    }
                }
                break;
            // ===== SEQUENCE WORD VARIANTS (G110-G113) =====
            case 'G110': // Sequence Word
            case 'G111': // Sequence Word Audio
            case 'G112': // Sequence Word Audio
            case 'G113': // Sequence Word Audio
            // ===== SEQUENCE SENTENCE VARIANTS (G120-G123) =====
            case 'G120': // Sequence Sentence
            case 'G121': // Sequence Sentence Audio
            case 'G122': // Sequence Sentence Audio
            case 'G123': // Sequence Sentence Audio
                // Client gửi mảng chữ đã sắp xếp ["H","E","L","L","O"]
                // Không cần resolve - so trực tiếp với answer
                this.log('debug', `[${code}] Answer array: ${JSON.stringify(choice)}`);
                break;
            default:
                this.log('warn', `Unknown game code: ${code}, using raw choice`);
        }
        // Get original item
        const originalItem = this.originalItems.get(question_id);
        if (!originalItem) {
            this.sendError('QUESTION_NOT_FOUND', `Question ${question_id} not found`, requestId);
            return;
        }
        // =====================================================================
        // LOCAL VERIFY (Preview / Dev): Verify locally, return immediately
        // =====================================================================
        if (this.mode === 'preview' || this.mode === 'dev') {
            // Verify using RESOLVED VALUE (answerValue)
            const result = checkAnswer(this.gameCode, originalItem, answerValue);
            // Store answer
            const storedAnswer = {
                question_id,
                choice,
                answer_value: answerValue, // Store resolved value for retry
                correct: result.isCorrect,
                score: result.score,
                synced: true, // Local (preview/dev) = always synced
                time_spent,
            };
            this.userAnswers.set(question_id, storedAnswer);
            // Send result immediately
            this.sendToParent(SDK_MESSAGE_TYPES.SDK_ANSWER_RESULT, {
                question_id,
                correct: result.isCorrect,
                score: result.score,
                synced: true,
            }, requestId);
            this.log('info', `[Local] Answer checked locally (${this.mode}): ${question_id} = ${result.isCorrect}`);
            return;
        }
        // =====================================================================
        // LIVE MODE: Submit to server, wait for response, then return result
        // =====================================================================
        try {
            // Submit RESOLVED VALUE (answerValue) to server
            const serverResult = await this.submitAnswerToServer(question_id, answerValue, time_spent);
            // Store answer with server result
            const storedAnswer = {
                question_id,
                choice,
                answer_value: answerValue, // Store resolved value for retry
                correct: serverResult.correct,
                score: serverResult.score,
                synced: true,
                time_spent,
            };
            this.userAnswers.set(question_id, storedAnswer);
            // Send result from server
            this.sendToParent(SDK_MESSAGE_TYPES.SDK_ANSWER_RESULT, {
                question_id,
                correct: serverResult.correct,
                score: serverResult.score,
                synced: true,
            }, requestId);
            this.log('info', `[Live] Answer verified by server: ${question_id} = ${serverResult.correct}`);
        }
        catch (error) {
            // Server error - send error to game
            this.sendError('SUBMIT_ERROR', error.message || 'Failed to submit answer', requestId);
            this.log('error', `[Live] Failed to submit answer: ${question_id}`, error);
        }
    }
    /**
     * Submit answer to server and wait for result (Live mode)
     */
    async submitAnswerToServer(questionId, choice, timeSpent) {
        if (!this.apiBaseUrl || !this.assignmentId) {
            throw new Error('Missing apiBaseUrl or assignmentId');
        }
        const url = `${this.apiBaseUrl}/submissions/live/answer`;
        const headers = {
            'Content-Type': 'application/json',
        };
        if (this.authToken) {
            headers['Authorization'] = `Bearer ${this.authToken}`;
        }
        const response = await fetch(url, {
            method: 'POST',
            headers,
            body: JSON.stringify({
                assignment_id: this.assignmentId,
                student_id: this.studentId,
                question_id: questionId,
                selected_answer: choice,
                time_spent: timeSpent,
                is_timeout: false,
            }),
        });
        if (!response.ok) {
            const errorData = await response.json().catch(() => ({}));
            throw new Error(errorData.message || `Server error: ${response.status}`);
        }
        const result = await response.json();
        return {
            correct: result.correct ?? false,
            score: result.score ?? (result.correct ? 1 : 0),
        };
    }
    handleGetResult(requestId) {
        const details = Array.from(this.userAnswers.values()).map(answer => ({
            question_id: answer.question_id,
            choice: answer.choice,
            correct: answer.correct,
            synced: answer.synced,
            time_spent: answer.time_spent,
        }));
        const correct = details.filter(d => d.correct).length;
        const total = this.originalItems.size;
        const allSynced = details.every(d => d.synced);
        this.sendToParent(SDK_MESSAGE_TYPES.SDK_FINAL_RESULT, {
            score: total > 0 ? Math.round((correct / total) * 100) : 0,
            total,
            correct,
            wrong: total - correct,
            synced: allSynced,
            details,
        }, requestId);
    }
    async handleRetrySync(payload, requestId) {
        const answer = this.userAnswers.get(payload.question_id);
        if (!answer) {
            this.sendError('ANSWER_NOT_FOUND', `Answer for ${payload.question_id} not found`, requestId);
            return;
        }
        if (answer.synced) {
            // Already synced
            this.sendToParent(SDK_MESSAGE_TYPES.SDK_SYNC_STATUS, {
                question_id: payload.question_id,
                synced: true,
            }, requestId);
            return;
        }
        // Retry submit to server - use RESOLVED value, not raw choice
        try {
            const serverResult = await this.submitAnswerToServer(answer.question_id, answer.answer_value, // Use resolved value!
            answer.time_spent);
            // Update stored answer
            answer.correct = serverResult.correct;
            answer.score = serverResult.score;
            answer.synced = true;
            // Send success
            this.sendToParent(SDK_MESSAGE_TYPES.SDK_SYNC_STATUS, {
                question_id: payload.question_id,
                synced: true,
                server_result: serverResult,
            }, requestId);
        }
        catch (error) {
            this.sendError('RETRY_ERROR', error.message || 'Retry failed', requestId);
        }
    }
    // ==========================================================================
    // DATA PROCESSING
    // ==========================================================================
    processData(items, completedIds) {
        // Store original items (with answers)
        this.originalItems.clear();
        items.forEach(item => {
            if (item.id) {
                this.originalItems.set(item.id, item);
            }
        });
        // Track completed questions
        this.completedQuestions.clear();
        if (completedIds) {
            completedIds.forEach(q => this.completedQuestions.add(q.id));
        }
        // Sanitize for game (remove answers)
        this.sanitizedItems = sanitizeForClient(this.gameCode, items);
        // Store sanitized items for lookup (to map choice index -> value)
        this.clientItems.clear();
        this.sanitizedItems.forEach(item => {
            if (item.id)
                this.clientItems.set(item.id, item);
        });
        // Send data ready
        this.sendToParent(SDK_MESSAGE_TYPES.SDK_DATA_READY, {
            items: this.sanitizedItems,
            total_questions: items.length,
            completed_count: this.completedQuestions.size,
            resume_data: completedIds,
        });
        this.log('info', `Data processed: ${items.length} items, ${this.completedQuestions.size} completed`);
    }
    // ==========================================================================
    // API CALLS
    // ==========================================================================
    async fetchLiveData() {
        if (!this.apiBaseUrl || !this.assignmentId) {
            this.sendError('CONFIG_ERROR', 'Missing apiBaseUrl or assignmentId for live mode');
            return;
        }
        try {
            const url = `${this.apiBaseUrl}/games/${this.assignmentId}`;
            const headers = {
                'Content-Type': 'application/json',
            };
            if (this.authToken) {
                headers['Authorization'] = `Bearer ${this.authToken}`;
            }
            this.log('info', `Fetching live data: ${url}`);
            const response = await fetch(url, { headers });
            if (!response.ok) {
                throw new Error(`API Error: ${response.status}`);
            }
            const data = await response.json();
            // Extract items from response
            const items = data.data || data.items || data.questions || [];
            const completedIds = data.completed_question_ids || [];
            this.processData(items, completedIds);
        }
        catch (error) {
            this.log('error', 'Failed to fetch live data', error);
            this.sendError('FETCH_ERROR', error.message);
        }
    }
    // ==========================================================================
    // HELPERS
    // ==========================================================================
    sendToParent(type, payload, requestId) {
        const message = createSdkMessage(type, payload, requestId);
        // NOTE: Khi chạy local file://, event.origin có thể là "null" (string) => postMessage sẽ throw nếu dùng làm targetOrigin
        const origin = this.parentOrigin;
        const targetOrigin = (!origin || origin === 'null') ? '*' : origin;
        window.parent.postMessage(message, targetOrigin);
        this.log('debug', `Sent: ${type}`, payload);
    }
    sendError(code, message, requestId) {
        this.sendToParent(SDK_MESSAGE_TYPES.SDK_ERROR, {
            code,
            message,
        }, requestId);
        this.log('error', `Error: ${code} - ${message}`);
    }
    log(level, message, data) {
        if (!this.config.debug && level === 'debug')
            return;
        const prefix = '[SdkIframe]';
        switch (level) {
            case 'debug':
            case 'info':
                console.log(prefix, message, data ?? '');
                break;
            case 'warn':
                console.warn(prefix, message, data ?? '');
                break;
            case 'error':
                console.error(prefix, message, data ?? '');
                break;
        }
    }
    // ==========================================================================
    // CLEANUP
    // ==========================================================================
    destroy() {
        if (this.boundMessageHandler) {
            window.removeEventListener('message', this.boundMessageHandler);
            this.boundMessageHandler = null;
        }
        this.originalItems.clear();
        this.userAnswers.clear();
        this.log('info', 'SDK Iframe destroyed');
    }
}
// Auto-init khi script được load
if (typeof window !== 'undefined') {
    window.SdkIframe = new SdkIframeCore({
        debug: new URLSearchParams(window.location.search).get('debug') === 'true',
    });
}
//# sourceMappingURL=SdkIframeCore.js.map