/**
 * Integration tests for WebSocket + Yjs CRDT sync
 * Tests that two app instances can sync text changes via WebSocket
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { Doc, Text, applyUpdate, encodeStateAsUpdate } from 'yjs';
import { createScreenplayDoc, getOrCreateSharedTypes } from './yjs-document';

describe('Integration: Two-instance sync', () => {
  let doc1: Doc;
  let doc2: Doc;
  let text1: Text;
  let text2: Text;

  beforeEach(() => {
    // Create two separate Yjs documents
    doc1 = createScreenplayDoc('project-1', {
      title: 'Test Screenplay',
      author: 'Test Author',
    });
    
    doc2 = new Doc();
    
    // Get text instances
    text1 = doc1.getText('main');
    text2 = doc2.getText('main');
  });

  it('should sync initial text from doc1 to doc2', () => {
    // Insert text in doc1
    text1.insert(0, 'Hello World');

    // Encode doc1 state and apply to doc2
    const update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);

    expect(text2.toString()).toBe('Hello World');
  });

  it('should sync concurrent edits correctly', () => {
    // Both documents start with same base
    text1.insert(0, 'Hello');
    const initialUpdate = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, initialUpdate);

    // Capture deltas for concurrent edits
    const doc1Updates: Uint8Array[] = [];
    const doc2Updates: Uint8Array[] = [];
    
    doc1.on('update', (update: Uint8Array) => doc1Updates.push(update));
    doc2.on('update', (update: Uint8Array) => doc2Updates.push(update));

    // Doc1 adds suffix (captures delta)
    text1.insert(5, ' World');
    
    // Doc2 adds different suffix (captures delta, simulating concurrency)
    text2.insert(5, '!');

    // Apply doc1's delta to doc2
    doc1Updates.forEach(update => applyUpdate(doc2, update));
    
    // Apply doc2's delta to doc1
    doc2Updates.forEach(update => applyUpdate(doc1, update));

    // Both should converge to the same state (Yjs CRDT resolves concurrent inserts)
    expect(text1.toString()).toBe(text2.toString());
    // Contains both concurrent edits
    expect(text1.toString()).toContain('Hello');
    expect(text1.toString()).toContain('World');
    expect(text1.toString()).toContain('!');
  });

  it('should handle delete operations across instances', () => {
    // Setup: both have "Hello World"
    text1.insert(0, 'Hello World');
    let update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);

    // Doc1 deletes "World" (positions 6-10, keeping the space at position 5)
    text1.delete(6, 5);
    update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);

    expect(text1.toString()).toBe('Hello ');
    expect(text2.toString()).toBe('Hello ');
  });

  it('should sync metadata changes', () => {
    // Update metadata in doc1
    const meta1 = doc1.getMap('metadata');
    meta1.set('title', 'Updated Title');
    meta1.set('version', 2);

    // Sync to doc2
    const update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);

    const meta2 = doc2.getMap('metadata');
    expect(meta2.get('title')).toBe('Updated Title');
    expect(meta2.get('version')).toBe(2);
  });

  it('should handle multi-step sync', () => {
    // Step 1: Initial sync
    text1.insert(0, 'A');
    let update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);
    expect(text2.toString()).toBe('A');

    // Step 2: More edits
    text1.insert(1, 'B');
    update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);
    expect(text2.toString()).toBe('AB');

    // Step 3: Even more edits
    text1.insert(2, 'C');
    update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);
    expect(text2.toString()).toBe('ABC');

    // Step 4: Reverse sync (doc2 to doc1)
    text2.insert(0, 'X');
    update = encodeStateAsUpdate(doc2);
    applyUpdate(doc1, update);
    expect(text1.toString()).toBe('XABC');
  });

  it('should preserve document structure during sync', () => {
    // Setup document with multiple shared types
    const shared1 = getOrCreateSharedTypes(doc1);
    
    // Add text
    shared1.text.insert(0, 'Screenplay content');
    
    // Add metadata
    shared1.metadata.set('title', 'My Script');
    shared1.metadata.set('author', 'Writer');
    
    // Add character
    shared1.characters.set('char1', {
      id: 'char1',
      name: 'John',
      shortName: 'J',
    });

    // Sync entire document
    const update = encodeStateAsUpdate(doc1);
    applyUpdate(doc2, update);

    // Verify all data synced
    const shared2 = getOrCreateSharedTypes(doc2);
    expect(shared2.text.toString()).toBe('Screenplay content');
    expect(shared2.metadata.get('title')).toBe('My Script');
    expect(shared2.metadata.get('author')).toBe('Writer');
    expect(shared2.characters.get('char1')?.name).toBe('John');
  });
});
