"use strict";
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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.collaborationService = exports.CollaborationService = void 0;
const Y = __importStar(require("yjs"));
const y_indexeddb_1 = require("y-indexeddb");
const awarenessProtocol = __importStar(require("y-protocols/awareness"));
/**
 * Collaboration Service
 * Manages Y.js documents and real-time collaboration
 */
class CollaborationService {
    constructor() {
        this.documents = new Map();
        this.providers = new Map();
        this.persistences = new Map();
        this.awareness = new Map();
        this.changeHistory = new Map();
        this.io = null;
        this.userColors = [
            '#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',
            '#6C5CE7', '#FDCB6E', '#00B894', '#E17055', '#74B9FF'
        ];
        this.activeUsers = new Map(); // documentId -> Set of userIds
        this.userSessions = new Map(); // socketId -> user info
        this.userPresence = new Map(); // documentId -> userId -> presence data
        this.sectionAwareness = new Map(); // section -> userId -> awareness data
        this.initializeService();
    }
    /**
     * Initialize collaboration service
     */
    initializeService() {
        console.log('🤝 Collaboration Service initialized');
    }
    /**
     * Set Socket.io server instance
     */
    setSocketServer(io) {
        this.io = io;
        this.setupSocketHandlers();
    }
    /**
     * Set up Socket.io event handlers
     */
    setupSocketHandlers() {
        if (!this.io)
            return;
        this.io.on('connection', (socket) => {
            // Handle collaboration events
            socket.on('collaboration:join', async (data) => {
                await this.handleJoinDocument(socket, data);
            });
            socket.on('collaboration:leave', async (data) => {
                await this.handleLeaveDocument(socket, data);
            });
            socket.on('collaboration:update', async (data) => {
                await this.handleDocumentUpdate(socket, data);
            });
            socket.on('collaboration:cursor', async (data) => {
                await this.handleCursorUpdate(socket, data);
            });
            socket.on('collaboration:awareness', async (data) => {
                await this.handleAwarenessUpdate(socket, data);
            });
            socket.on('disconnect', () => {
                this.handleUserDisconnect(socket);
            });
        });
    }
    /**
     * Create or get a Y.js document for a task
     */
    getTaskDocument(taskId) {
        if (this.documents.has(taskId)) {
            return this.documents.get(taskId);
        }
        const doc = new Y.Doc();
        // Initialize task structure
        const taskData = doc.getMap('task');
        taskData.set('id', taskId);
        taskData.set('title', new Y.Text());
        taskData.set('description', new Y.Text());
        taskData.set('status', 'pending');
        taskData.set('priority', 'medium');
        taskData.set('assignees', new Y.Array());
        taskData.set('tags', new Y.Array());
        taskData.set('subtasks', new Y.Array());
        taskData.set('comments', new Y.Array());
        taskData.set('attachments', new Y.Array());
        taskData.set('customFields', new Y.Map());
        taskData.set('metadata', new Y.Map());
        // Set up persistence (IndexedDB)
        const persistence = new y_indexeddb_1.IndexeddbPersistence(`task-${taskId}`, doc);
        this.persistences.set(taskId, persistence);
        // Set up awareness
        const awareness = new awarenessProtocol.Awareness(doc);
        this.awareness.set(taskId, awareness);
        // Store document
        this.documents.set(taskId, doc);
        // Set up change observers
        this.setupChangeObservers(taskId, doc);
        return doc;
    }
    /**
     * Create or get a Y.js document for a project
     */
    getProjectDocument(projectId) {
        const docId = `project-${projectId}`;
        if (this.documents.has(docId)) {
            return this.documents.get(docId);
        }
        const doc = new Y.Doc();
        // Initialize project structure
        const projectData = doc.getMap('project');
        projectData.set('id', projectId);
        projectData.set('name', new Y.Text());
        projectData.set('description', new Y.Text());
        projectData.set('tasks', new Y.Array());
        projectData.set('members', new Y.Array());
        projectData.set('settings', new Y.Map());
        projectData.set('metadata', new Y.Map());
        // Set up persistence
        const persistence = new y_indexeddb_1.IndexeddbPersistence(docId, doc);
        this.persistences.set(docId, persistence);
        // Set up awareness
        const awareness = new awarenessProtocol.Awareness(doc);
        this.awareness.set(docId, awareness);
        // Store document
        this.documents.set(docId, doc);
        // Set up change observers
        this.setupChangeObservers(docId, doc);
        return doc;
    }
    /**
     * Set up change observers for a document
     */
    setupChangeObservers(documentId, doc) {
        // Observe all changes
        doc.on('update', (update, origin) => {
            this.handleDocumentChange(documentId, update, origin);
        });
        // Observe specific fields for detailed tracking
        const data = doc.getMap(documentId.startsWith('project-') ? 'project' : 'task');
        data.observe((event) => {
            event.changes.keys.forEach((change, key) => {
                if (change.action === 'add' || change.action === 'update') {
                    this.trackChange(documentId, key, change.oldValue, data.get(key));
                }
                else if (change.action === 'delete') {
                    this.trackChange(documentId, key, change.oldValue, undefined);
                }
            });
        });
    }
    /**
     * Handle document changes
     */
    handleDocumentChange(documentId, update, origin) {
        // Broadcast update to all connected clients in the document room
        if (this.io) {
            this.io.to(`doc:${documentId}`).emit('collaboration:sync', {
                documentId,
                update: Array.from(update),
                origin
            });
        }
        // Log the change
        console.log(`📝 Document ${documentId} updated by ${origin || 'system'}`);
    }
    /**
     * Track individual field changes
     */
    trackChange(documentId, field, oldValue, newValue) {
        const change = {
            documentId,
            type: documentId.startsWith('project-') ? 'project' : 'task',
            field,
            operation: oldValue === undefined ? 'add' : newValue === undefined ? 'delete' : 'update',
            userId: 'system', // Will be updated with actual user ID from awareness
            timestamp: new Date(),
            oldValue,
            newValue
        };
        // Store in history
        if (!this.changeHistory.has(documentId)) {
            this.changeHistory.set(documentId, []);
        }
        this.changeHistory.get(documentId).push(change);
        // Emit change event
        if (this.io) {
            this.io.to(`doc:${documentId}`).emit('collaboration:change', change);
        }
    }
    /**
     * Handle user joining a document
     */
    async handleJoinDocument(socket, data) {
        const { documentId, documentType, userId, userInfo } = data;
        // Join document room
        socket.join(`doc:${documentId}`);
        // Get or create document
        const doc = documentType === 'task'
            ? this.getTaskDocument(documentId)
            : this.getProjectDocument(documentId);
        // Add user to active users
        if (!this.activeUsers.has(documentId)) {
            this.activeUsers.set(documentId, new Set());
        }
        this.activeUsers.get(documentId).add(userId);
        // Update awareness with user info
        const awareness = this.awareness.get(documentId);
        if (awareness) {
            awareness.setLocalStateField('user', {
                ...userInfo,
                color: userInfo.color || this.getUserColor(userId)
            });
        }
        // Send current document state
        const state = Y.encodeStateAsUpdate(doc);
        socket.emit('collaboration:init', {
            documentId,
            state: Array.from(state),
            users: Array.from(this.activeUsers.get(documentId) || [])
        });
        // Notify others of new user
        socket.to(`doc:${documentId}`).emit('collaboration:user-joined', {
            documentId,
            userId,
            userInfo
        });
        console.log(`👤 User ${userId} joined document ${documentId}`);
    }
    /**
     * Handle user leaving a document
     */
    async handleLeaveDocument(socket, data) {
        const { documentId, userId } = data;
        // Leave document room
        socket.leave(`doc:${documentId}`);
        // Remove from active users
        const activeUsers = this.activeUsers.get(documentId);
        if (activeUsers) {
            activeUsers.delete(userId);
            if (activeUsers.size === 0) {
                this.activeUsers.delete(documentId);
            }
        }
        // Update awareness
        const awareness = this.awareness.get(documentId);
        if (awareness) {
            awareness.setLocalStateField('user', null);
        }
        // Notify others
        socket.to(`doc:${documentId}`).emit('collaboration:user-left', {
            documentId,
            userId
        });
        console.log(`👤 User ${userId} left document ${documentId}`);
    }
    /**
     * Handle document update from client
     */
    async handleDocumentUpdate(socket, data) {
        const { documentId, update, userId } = data;
        // Get document
        const doc = this.documents.get(documentId);
        if (!doc) {
            socket.emit('collaboration:error', {
                message: `Document ${documentId} not found`
            });
            return;
        }
        // Apply update
        Y.applyUpdate(doc, new Uint8Array(update), userId);
        // The update will trigger the observer, which will broadcast to other clients
        console.log(`✏️ Document ${documentId} updated by user ${userId}`);
    }
    /**
     * Handle cursor position update
     */
    async handleCursorUpdate(socket, data) {
        const { documentId, userId, cursor } = data;
        // Update awareness with cursor position
        const awareness = this.awareness.get(documentId);
        if (awareness) {
            const userState = awareness.getLocalState();
            if (userState?.user) {
                awareness.setLocalStateField('cursor', cursor);
            }
        }
        // Broadcast cursor position to others in the document
        socket.to(`doc:${documentId}`).emit('collaboration:cursor-update', {
            documentId,
            userId,
            cursor
        });
    }
    /**
     * Handle awareness update
     */
    async handleAwarenessUpdate(socket, data) {
        const { documentId, userId, awareness: awarenessData } = data;
        // Update awareness
        const awareness = this.awareness.get(documentId);
        if (awareness) {
            Object.keys(awarenessData).forEach(key => {
                awareness.setLocalStateField(key, awarenessData[key]);
            });
        }
        // Broadcast awareness update
        socket.to(`doc:${documentId}`).emit('collaboration:awareness-update', {
            documentId,
            userId,
            awareness: awarenessData
        });
    }
    /**
     * Handle user disconnect
     */
    handleUserDisconnect(socket) {
        // Remove user from all documents they were in
        // This would require tracking socket to user/document mapping
        console.log(`🔌 User disconnected from collaboration`);
    }
    /**
     * Get user color for collaboration
     */
    getUserColor(userId) {
        // Generate consistent color based on user ID
        const hash = userId.split('').reduce((acc, char) => {
            return char.charCodeAt(0) + ((acc << 5) - acc);
        }, 0);
        return this.userColors[Math.abs(hash) % this.userColors.length];
    }
    /**
     * Get change history for a document
     */
    getChangeHistory(documentId, limit) {
        const history = this.changeHistory.get(documentId) || [];
        return limit ? history.slice(-limit) : history;
    }
    /**
     * Get active users for a document
     */
    getActiveUsers(documentId) {
        return Array.from(this.activeUsers.get(documentId) || []);
    }
    /**
     * Get active users with full information
     */
    getActiveUsersWithInfo(documentId) {
        const presenceMap = this.userPresence.get(documentId);
        if (!presenceMap)
            return [];
        return Array.from(presenceMap.values()).map(presence => ({
            id: presence.id,
            name: presence.name,
            email: presence.email,
            avatar: presence.avatar,
            color: presence.color,
            cursor: presence.cursor
        }));
    }
    /**
     * Get section awareness data
     */
    getSectionAwareness(section) {
        const sectionMap = this.sectionAwareness.get(section);
        if (!sectionMap)
            return [];
        const now = new Date();
        const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);
        // Filter for recent activity and return
        return Array.from(sectionMap.values()).filter(awareness => awareness.lastActivity > fiveMinutesAgo);
    }
    /**
     * Update user typing status
     */
    updateUserTypingStatus(documentId, userId, isTyping, field) {
        const presenceMap = this.userPresence.get(documentId);
        if (!presenceMap || !presenceMap.has(userId))
            return;
        const presence = presenceMap.get(userId);
        presence.isTyping = isTyping;
        presence.typingField = field;
        presence.lastActivity = new Date();
        // Broadcast typing status
        if (this.io) {
            this.io.to(`doc:${documentId}`).emit('collaboration:typing-status', {
                documentId,
                userId,
                isTyping,
                field
            });
        }
    }
    /**
     * Apply external changes to a document
     */
    applyExternalChange(documentId, field, value, userId = 'system') {
        const doc = this.documents.get(documentId);
        if (!doc)
            return;
        const data = doc.getMap(documentId.startsWith('project-') ? 'project' : 'task');
        doc.transact(() => {
            if (value instanceof Array) {
                const yArray = data.get(field);
                if (yArray) {
                    yArray.delete(0, yArray.length);
                    yArray.insert(0, value);
                }
            }
            else if (typeof value === 'object' && value !== null) {
                const yMap = data.get(field);
                if (yMap) {
                    yMap.forEach((_, key) => yMap.delete(key));
                    Object.entries(value).forEach(([k, v]) => yMap.set(k, v));
                }
            }
            else if (typeof value === 'string' && (field === 'title' || field === 'description' || field === 'name')) {
                const yText = data.get(field);
                if (yText) {
                    yText.delete(0, yText.length);
                    yText.insert(0, value);
                }
            }
            else {
                data.set(field, value);
            }
        }, userId);
    }
    /**
     * Create a snapshot of the current document state
     */
    createSnapshot(documentId) {
        const doc = this.documents.get(documentId);
        if (!doc)
            return null;
        const data = doc.getMap(documentId.startsWith('project-') ? 'project' : 'task');
        const snapshot = {};
        data.forEach((value, key) => {
            if (value instanceof Y.Text) {
                snapshot[key] = value.toString();
            }
            else if (value instanceof Y.Array) {
                snapshot[key] = value.toArray();
            }
            else if (value instanceof Y.Map) {
                const mapData = {};
                value.forEach((v, k) => {
                    mapData[k] = v;
                });
                snapshot[key] = mapData;
            }
            else {
                snapshot[key] = value;
            }
        });
        return snapshot;
    }
    /**
     * Restore document from a snapshot
     */
    restoreFromSnapshot(documentId, snapshot, userId = 'system') {
        Object.entries(snapshot).forEach(([field, value]) => {
            this.applyExternalChange(documentId, field, value, userId);
        });
    }
    /**
     * Clean up resources for a document
     */
    cleanupDocument(documentId) {
        // Close persistence
        const persistence = this.persistences.get(documentId);
        if (persistence) {
            persistence.destroy();
            this.persistences.delete(documentId);
        }
        // Clear awareness
        this.awareness.delete(documentId);
        // Clear history
        this.changeHistory.delete(documentId);
        // Clear active users
        this.activeUsers.delete(documentId);
        // Remove document
        this.documents.delete(documentId);
        console.log(`🧹 Cleaned up document ${documentId}`);
    }
    /**
     * Get statistics for a document
     */
    getDocumentStats(documentId) {
        const doc = this.documents.get(documentId);
        const history = this.changeHistory.get(documentId) || [];
        const users = this.activeUsers.get(documentId) || new Set();
        return {
            activeUsers: users.size,
            changeCount: history.length,
            lastModified: history.length > 0 ? history[history.length - 1].timestamp : undefined,
            size: doc ? Y.encodeStateAsUpdate(doc).length : 0
        };
    }
    /**
     * Create a detailed snapshot with metadata
     */
    createDetailedSnapshot(documentId, name, description, userId = 'system') {
        const snapshot = this.createSnapshot(documentId);
        if (!snapshot)
            return null;
        return {
            id: `snapshot-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
            name,
            description,
            timestamp: new Date(),
            userId,
            data: snapshot,
            size: JSON.stringify(snapshot).length,
            isAutomatic: false
        };
    }
    /**
     * Get change history with enhanced filtering
     */
    getDetailedChangeHistory(documentId, options = {}) {
        const history = this.changeHistory.get(documentId) || [];
        let filtered = [...history];
        // Apply filters
        if (options.field) {
            filtered = filtered.filter(change => change.field === options.field);
        }
        if (options.userId) {
            filtered = filtered.filter(change => change.userId === options.userId);
        }
        if (options.since) {
            filtered = filtered.filter(change => change.timestamp >= options.since);
        }
        if (options.until) {
            filtered = filtered.filter(change => change.timestamp <= options.until);
        }
        // Sort by timestamp (newest first)
        filtered.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
        // Apply limit
        if (options.limit) {
            filtered = filtered.slice(0, options.limit);
        }
        return filtered;
    }
    /**
     * Get version comparison data
     */
    compareVersions(documentId, versionA, versionB) {
        const differences = [];
        const allFields = new Set([
            ...Object.keys(versionA),
            ...Object.keys(versionB)
        ]);
        allFields.forEach(field => {
            const valueA = versionA[field];
            const valueB = versionB[field];
            if (valueA === undefined && valueB !== undefined) {
                differences.push({ field, type: 'added', oldValue: undefined, newValue: valueB });
            }
            else if (valueA !== undefined && valueB === undefined) {
                differences.push({ field, type: 'removed', oldValue: valueA, newValue: undefined });
            }
            else if (JSON.stringify(valueA) !== JSON.stringify(valueB)) {
                differences.push({ field, type: 'modified', oldValue: valueA, newValue: valueB });
            }
        });
        return differences;
    }
    /**
     * Validate rollback operation
     */
    validateRollback(documentId, targetSnapshot) {
        const warnings = [];
        const currentData = this.createSnapshot(documentId);
        const affectedFields = [];
        const conflictingUsers = [];
        if (!currentData || !targetSnapshot) {
            return {
                isValid: false,
                warnings: ['Invalid snapshot data'],
                affectedFields: [],
                conflictingUsers: []
            };
        }
        // Find affected fields
        const differences = this.compareVersions(documentId, currentData, targetSnapshot);
        affectedFields.push(...differences.map(diff => diff.field));
        // Check for active users
        const activeUsers = this.getActiveUsersWithInfo(documentId);
        if (activeUsers.length > 1) {
            conflictingUsers.push(...activeUsers.map(user => user.name));
            warnings.push(`${activeUsers.length} users are currently editing this document`);
        }
        // Check for significant changes
        if (affectedFields.length > 10) {
            warnings.push('This rollback will affect many fields');
        }
        // Check for recent activity
        const recentChanges = this.getDetailedChangeHistory(documentId, {
            since: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
            limit: 100
        });
        if (recentChanges.length > 20) {
            warnings.push('This will undo significant recent activity');
        }
        return {
            isValid: true,
            warnings,
            affectedFields,
            conflictingUsers
        };
    }
    /**
     * Create automatic snapshot before major operations
     */
    createAutoSnapshot(documentId, operation, userId = 'system') {
        return this.createDetailedSnapshot(documentId, `Auto: Before ${operation}`, `Automatic snapshot created before ${operation}`, userId);
    }
}
exports.CollaborationService = CollaborationService;
// Export singleton instance
exports.collaborationService = new CollaborationService();
//# sourceMappingURL=collaborationService.js.map