/**
 * Optimistic State Manager
 *
 * Manages optimistic UI updates with version-based conflict resolution.
 * Implements the "Optimistic UI" pattern where client immediately updates
 * the UI before server confirms, then rolls back on conflicts.
 *
 * Key Features:
 * - Immediate UI feedback (no loading spinners)
 * - Version-based optimistic concurrency control
 * - Automatic conflict detection and rollback
 * - Pending operations queue with retry
 * - User-friendly conflict resolution
 *
 * Protocol:
 * 1. Client updates UI immediately (optimistic)
 * 2. Client sends update to server with version
 * 3. Server checks version and either:
 *    a) Accepts: version matches, returns new state with version++
 *    b) Rejects: version conflict, returns current server state
 * 4. Client resolves:
 *    a) On accept: commit optimistic state
 *    b) On conflict: rollback to server state, show conflict UI
 */

export class OptimisticStateManager {
    constructor() {
        /**
         * Pending operations queue
         * Map<componentId, PendingOperation[]>
         */
        this.pendingOperations = new Map();

        /**
         * Rollback snapshots
         * Map<componentId, StateSnapshot>
         */
        this.snapshots = new Map();

        /**
         * Conflict callbacks
         * Map<componentId, ConflictCallback>
         */
        this.conflictHandlers = new Map();

        /**
         * Optimistic operation counter
         */
        this.operationIdCounter = 0;
    }

    /**
     * Apply optimistic update
     *
     * Updates component state immediately without waiting for server confirmation.
     * Creates snapshot for potential rollback.
     *
     * @param {string} componentId - Component identifier
     * @param {Object} currentState - Current component state (with version)
     * @param {Function} optimisticUpdate - Function that returns optimistically updated state
     * @param {Object} metadata - Operation metadata (action, params)
     * @returns {Object} Optimistic state with incremented version
     */
    applyOptimisticUpdate(componentId, currentState, optimisticUpdate, metadata = {}) {
        // Create snapshot before applying optimistic update
        if (!this.snapshots.has(componentId)) {
            this.createSnapshot(componentId, currentState);
        }

        // Extract version from state
        const currentVersion = currentState.version || 1;

        // Apply optimistic update
        const optimisticState = optimisticUpdate(currentState);

        // Increment version optimistically
        const newState = {
            ...optimisticState,
            version: currentVersion + 1
        };

        // Create pending operation
        const operation = {
            id: this.generateOperationId(),
            componentId,
            metadata,
            expectedVersion: currentVersion,
            optimisticState: newState,
            timestamp: Date.now(),
            status: 'pending' // pending, confirmed, failed
        };

        // Add to pending operations queue
        this.addPendingOperation(componentId, operation);

        console.log(`[OptimisticUI] Applied optimistic update for ${componentId}`, {
            version: `${currentVersion} → ${currentVersion + 1}`,
            operationId: operation.id,
            pendingCount: this.getPendingOperationsCount(componentId)
        });

        return newState;
    }

    /**
     * Confirm operation success
     *
     * Called when server successfully processes the operation.
     * Commits the optimistic state and clears snapshot if no more pending operations.
     *
     * @param {string} componentId - Component identifier
     * @param {string} operationId - Operation ID to confirm
     * @param {Object} serverState - Server-confirmed state (may differ from optimistic)
     * @returns {Object} Final state (server state or optimistic if versions match)
     */
    confirmOperation(componentId, operationId, serverState) {
        const operation = this.getPendingOperation(componentId, operationId);

        if (!operation) {
            console.warn(`[OptimisticUI] Cannot confirm unknown operation: ${operationId}`);
            return serverState;
        }

        // Mark operation as confirmed
        operation.status = 'confirmed';

        // Remove from pending queue
        this.removePendingOperation(componentId, operationId);

        console.log(`[OptimisticUI] Confirmed operation ${operationId} for ${componentId}`, {
            pendingCount: this.getPendingOperationsCount(componentId)
        });

        // If no more pending operations, clear snapshot
        if (!this.hasPendingOperations(componentId)) {
            this.clearSnapshot(componentId);
        }

        return serverState;
    }

    /**
     * Handle version conflict
     *
     * Called when server rejects operation due to version mismatch.
     * Rolls back to snapshot and applies server state.
     *
     * @param {string} componentId - Component identifier
     * @param {string} operationId - Failed operation ID
     * @param {Object} serverState - Current server state
     * @param {Object} conflict - Conflict information
     * @returns {Object} Resolution result with rollback state and user notification
     */
    handleConflict(componentId, operationId, serverState, conflict = {}) {
        const operation = this.getPendingOperation(componentId, operationId);

        if (!operation) {
            console.warn(`[OptimisticUI] Cannot handle conflict for unknown operation: ${operationId}`);
            return { state: serverState, notification: null };
        }

        // Mark operation as failed
        operation.status = 'failed';

        // Get snapshot for rollback
        const snapshot = this.snapshots.get(componentId);

        console.warn(`[OptimisticUI] Version conflict detected for ${componentId}`, {
            operationId,
            expectedVersion: operation.expectedVersion,
            serverVersion: serverState.version,
            metadata: operation.metadata,
            pendingOperationsCount: this.getPendingOperationsCount(componentId)
        });

        // Clear all pending operations (cascade rollback)
        this.clearPendingOperations(componentId);

        // Clear snapshot
        this.clearSnapshot(componentId);

        // Call conflict handler if registered
        const conflictHandler = this.conflictHandlers.get(componentId);
        if (conflictHandler) {
            conflictHandler({
                operation,
                serverState,
                snapshotState: snapshot?.state,
                conflict
            });
        }

        // Create user notification
        const notification = {
            type: 'conflict',
            title: 'Update Conflict',
            message: 'Your changes conflicted with another update. The latest version has been loaded.',
            action: operation.metadata.action || 'unknown',
            canRetry: true,
            operation
        };

        return {
            state: serverState,
            notification
        };
    }

    /**
     * Create state snapshot for rollback
     *
     * @param {string} componentId - Component identifier
     * @param {Object} state - Current state to snapshot
     */
    createSnapshot(componentId, state) {
        this.snapshots.set(componentId, {
            state: JSON.parse(JSON.stringify(state)), // Deep clone
            timestamp: Date.now()
        });

        console.log(`[OptimisticUI] Created snapshot for ${componentId}`, {
            version: state.version
        });
    }

    /**
     * Clear snapshot after all operations confirmed
     *
     * @param {string} componentId - Component identifier
     */
    clearSnapshot(componentId) {
        const snapshot = this.snapshots.get(componentId);

        if (snapshot) {
            this.snapshots.delete(componentId);
            console.log(`[OptimisticUI] Cleared snapshot for ${componentId}`);
        }
    }

    /**
     * Get rollback snapshot
     *
     * @param {string} componentId - Component identifier
     * @returns {Object|null} Snapshot or null if none exists
     */
    getSnapshot(componentId) {
        const snapshot = this.snapshots.get(componentId);
        return snapshot ? snapshot.state : null;
    }

    /**
     * Add pending operation
     *
     * @param {string} componentId - Component identifier
     * @param {Object} operation - Operation object
     */
    addPendingOperation(componentId, operation) {
        if (!this.pendingOperations.has(componentId)) {
            this.pendingOperations.set(componentId, []);
        }

        this.pendingOperations.get(componentId).push(operation);
    }

    /**
     * Remove pending operation
     *
     * @param {string} componentId - Component identifier
     * @param {string} operationId - Operation ID to remove
     */
    removePendingOperation(componentId, operationId) {
        const operations = this.pendingOperations.get(componentId);

        if (!operations) {
            return;
        }

        const index = operations.findIndex(op => op.id === operationId);

        if (index !== -1) {
            operations.splice(index, 1);
        }

        // Clean up empty arrays
        if (operations.length === 0) {
            this.pendingOperations.delete(componentId);
        }
    }

    /**
     * Get specific pending operation
     *
     * @param {string} componentId - Component identifier
     * @param {string} operationId - Operation ID
     * @returns {Object|null} Operation or null if not found
     */
    getPendingOperation(componentId, operationId) {
        const operations = this.pendingOperations.get(componentId);

        if (!operations) {
            return null;
        }

        return operations.find(op => op.id === operationId) || null;
    }

    /**
     * Get all pending operations for component
     *
     * @param {string} componentId - Component identifier
     * @returns {Array<Object>} Array of pending operations
     */
    getPendingOperations(componentId) {
        return this.pendingOperations.get(componentId) || [];
    }

    /**
     * Get pending operations count
     *
     * @param {string} componentId - Component identifier
     * @returns {number} Number of pending operations
     */
    getPendingOperationsCount(componentId) {
        const operations = this.pendingOperations.get(componentId);
        return operations ? operations.length : 0;
    }

    /**
     * Check if component has pending operations
     *
     * @param {string} componentId - Component identifier
     * @returns {boolean} True if has pending operations
     */
    hasPendingOperations(componentId) {
        return this.getPendingOperationsCount(componentId) > 0;
    }

    /**
     * Clear all pending operations for component
     *
     * @param {string} componentId - Component identifier
     */
    clearPendingOperations(componentId) {
        this.pendingOperations.delete(componentId);
    }

    /**
     * Register conflict handler
     *
     * Allows components to provide custom conflict resolution UI.
     *
     * @param {string} componentId - Component identifier
     * @param {Function} handler - Conflict handler callback
     */
    registerConflictHandler(componentId, handler) {
        this.conflictHandlers.set(componentId, handler);
    }

    /**
     * Unregister conflict handler
     *
     * @param {string} componentId - Component identifier
     */
    unregisterConflictHandler(componentId) {
        this.conflictHandlers.delete(componentId);
    }

    /**
     * Generate unique operation ID
     *
     * @returns {string} Unique operation ID
     */
    generateOperationId() {
        return `op-${++this.operationIdCounter}-${Date.now()}`;
    }

    /**
     * Retry failed operation
     *
     * Allows user to retry operation after conflict resolution.
     *
     * @param {string} componentId - Component identifier
     * @param {Object} operation - Failed operation to retry
     * @param {Function} retryCallback - Callback to execute retry
     * @returns {Promise} Retry promise
     */
    async retryOperation(componentId, operation, retryCallback) {
        console.log(`[OptimisticUI] Retrying operation ${operation.id} for ${componentId}`);

        try {
            // Execute retry callback
            const result = await retryCallback(operation.metadata);

            console.log(`[OptimisticUI] Retry succeeded for ${operation.id}`);

            return result;

        } catch (error) {
            console.error(`[OptimisticUI] Retry failed for ${operation.id}:`, error);
            throw error;
        }
    }

    /**
     * Get debugging stats
     *
     * @returns {Object} Statistics about optimistic operations
     */
    getStats() {
        const stats = {
            total_components: this.pendingOperations.size,
            total_pending_operations: 0,
            total_snapshots: this.snapshots.size,
            components: {}
        };

        this.pendingOperations.forEach((operations, componentId) => {
            stats.total_pending_operations += operations.length;
            stats.components[componentId] = {
                pending: operations.length,
                has_snapshot: this.snapshots.has(componentId)
            };
        });

        return stats;
    }

    /**
     * Clear all state (for testing/debugging)
     */
    clear() {
        this.pendingOperations.clear();
        this.snapshots.clear();
        this.conflictHandlers.clear();
        this.operationIdCounter = 0;

        console.log('[OptimisticUI] Cleared all state');
    }
}

// Create singleton instance
export const optimisticStateManager = new OptimisticStateManager();

// Export for testing and direct use
export default optimisticStateManager;
