/**
 * CRDT Document Manager
 * Coordinates Yjs document lifecycle, persistence, and sync
 */

import { Doc, Text, Map as YMap, UndoManager, applyUpdate } from 'yjs';
import { WebSocketConnection, WebSocketConnectionManager } from './websocket-connection';
import { createScreenplayDoc, getOrCreateSharedTypes, ScreenplayMetadata } from './yjs-document';

export interface CRDTDocumentManager {
  initialize(projectId: string, serverUrl: string, authToken: string): Promise<Doc>;
  getText(type: string): Text;
  getMetadata(): ScreenplayMetadata;
  getProvider(): any; // WebSocketProvider
  applyRemoteUpdate(update: Uint8Array, origin: string): void;
  getUndoManager(): UndoManager;
  destroy(): void;
}

export class CRDTDocument implements CRDTDocumentManager {
  private doc: Doc | null = null;
  private connection: WebSocketConnectionManager | null = null;
  private undoManager: UndoManager | null = null;
  private projectId: string | null = null;

  async initialize(
    projectId: string,
    serverUrl: string,
    authToken: string
  ): Promise<Doc> {
    this.projectId = projectId;

    // Create Yjs document
    this.doc = createScreenplayDoc(projectId, {});

    // Initialize WebSocket connection
    this.connection = new WebSocketConnection({
      serverUrl,
      documentName: `project-${projectId}`,
      authToken,
      reconnectInterval: 1000,
      maxReconnectInterval: 30000,
    });

    // Connect to WebSocket server
    await this.connection.connect();

    // Get the provider to access the synced document
    const provider = this.connection.getProvider();
    
    // Sync local document with remote state
    // Yjs WebSocketProvider handles this automatically on connect
    
    // Initialize undo manager (single instance handles both undo and redo)
    const sharedTypes = getOrCreateSharedTypes(this.doc);
    this.undoManager = new UndoManager([sharedTypes.text], {
      captureTimeout: 1000,
    });

    return this.doc;
  }

  getText(type: string = 'main'): Text {
    if (!this.doc) {
      throw new Error('Document not initialized. Call initialize() first.');
    }
    return this.doc.getText(type);
  }

  getMetadata(): ScreenplayMetadata {
    if (!this.doc) {
      throw new Error('Document not initialized. Call initialize() first.');
    }
    const meta = this.doc.getMap<ScreenplayMetadata>('metadata');
    return meta.toJSON() as ScreenplayMetadata;
  }

  getProvider(): any {
    if (!this.connection) {
      throw new Error('Connection not initialized. Call initialize() first.');
    }
    return this.connection.getProvider();
  }

  applyRemoteUpdate(update: Uint8Array, origin: string): void {
    if (!this.doc) {
      throw new Error('Document not initialized.');
    }
    
    // Apply the update to the document
    // Yjs handles the CRDT merge automatically
    this.doc.transact(() => {
      applyUpdate(this.doc!, update);
    }, origin);
  }

  getUndoManager(): UndoManager {
    if (!this.undoManager) {
      throw new Error('Document not initialized. Call initialize() first.');
    }
    return this.undoManager;
  }

  destroy(): void {
    if (this.undoManager) {
      this.undoManager.destroy();
      this.undoManager = null;
    }

    if (this.connection) {
      this.connection.disconnect();
      this.connection = null;
    }

    if (this.doc) {
      this.doc.destroy();
      this.doc = null;
    }

    this.projectId = null;
  }
}
