/**
 * Change Tracker for collaborative screenplay editing
 * Records all changes with metadata and supports version history
 */

import { Doc, UndoManager, Transaction, encodeStateAsUpdate, applyUpdate } from 'yjs';

export type ChangeType = 'insert' | 'delete' | 'format' | 'move';

export interface DocumentChange {
  id: string;
  userId: string;
  userName: string;
  timestamp: Date;
  type: ChangeType;
  position: number;
  length: number;
  content?: string;
  accepted: boolean;
  metadata?: Record<string, any>;
}

export interface Snapshot {
  id: string;
  timestamp: Date;
  userId: string;
  userName: string;
  description?: string;
  state: Uint8Array;
  changes: DocumentChange[];
}

export interface ChangeDiff {
  additions: number;
  deletions: number;
  changes: DocumentChange[];
}

export class ChangeTracker {
  private doc: Doc;
  private changes: DocumentChange[] = [];
  private snapshots: Snapshot[] = [];
  private changeListeners: Set<(change: DocumentChange) => void> = new Set();
  private userId: string;
  private userName: string;
  private currentTransaction: Transaction | null = null;

  constructor(doc: Doc, userId: string, userName: string) {
    this.doc = doc;
    this.userId = userId;
    this.userName = userName;

    // Listen to document updates
    this.doc.on('update', (update, origin) => {
      if (origin !== 'snapshot-restore') {
        this.recordTransaction(update, origin);
      }
    });
  }

  /**
   * Record a change from a transaction
   */
  private recordTransaction(update: Uint8Array, origin: any): void {
    const change: DocumentChange = {
      id: this.generateChangeId(),
      userId: this.userId,
      userName: this.userName,
      timestamp: new Date(),
      type: this.detectChangeType(update),
      position: 0, // Would need to calculate from update
      length: update.length,
      accepted: true,
      metadata: {
        origin,
        updateSize: update.length,
      },
    };

    this.changes.push(change);
    this.changeListeners.forEach(listener => listener(change));
  }

  /**
   * Detect the type of change from the update
   */
  private detectChangeType(update: Uint8Array): ChangeType {
    // Simplified detection - in production would parse Yjs update format
    if (update.length > 100) {
      return 'insert';
    } else if (update.length < 10) {
      return 'format';
    }
    return 'insert';
  }

  /**
   * Generate unique change ID
   */
  private generateChangeId(): string {
    return `change-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * Record a manual change
   */
  recordChange(change: Omit<DocumentChange, 'id' | 'userId' | 'userName' | 'timestamp' | 'accepted'>): void {
    const fullChange: DocumentChange = {
      ...change,
      id: this.generateChangeId(),
      userId: this.userId,
      userName: this.userName,
      timestamp: new Date(),
      accepted: true,
    };

    this.changes.push(fullChange);
    this.changeListeners.forEach(listener => listener(fullChange));
  }

  /**
   * Get changes within a range
   */
  getChangesInRange(start: number, end: number): DocumentChange[] {
    return this.changes.filter((change, index) => {
      return index >= start && index < end;
    });
  }

  /**
   * Get all changes
   */
  getAllChanges(): DocumentChange[] {
    return [...this.changes];
  }

  /**
   * Accept a change
   */
  acceptChange(changeId: string): void {
    const change = this.changes.find(c => c.id === changeId);
    if (change) {
      change.accepted = true;
    }
  }

  /**
   * Reject a change
   */
  rejectChange(changeId: string): void {
    const change = this.changes.find(c => c.id === changeId);
    if (change) {
      change.accepted = false;
      // In production, would revert the change
    }
  }

  /**
   * Create a snapshot of the current document state
   */
  createSnapshot(description?: string): Snapshot {
    const state = encodeStateAsUpdate(this.doc);
    const snapshot: Snapshot = {
      id: this.generateSnapshotId(),
      timestamp: new Date(),
      userId: this.userId,
      userName: this.userName,
      description,
      state,
      changes: [...this.changes],
    };

    this.snapshots.push(snapshot);
    return snapshot;
  }

  /**
   * Generate unique snapshot ID
   */
  private generateSnapshotId(): string {
    return `snapshot-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * Restore a snapshot by replacing document state entirely
   */
  restoreSnapshot(snapshot: Snapshot): void {
    const newDoc = new Doc();
    applyUpdate(newDoc, snapshot.state, 'snapshot-restore');

    // Copy text content from snapshot doc
    const newText = newDoc.getText('main');
    const oldText = this.doc.getText('main');
    oldText.delete(0, oldText.length);
    oldText.insert(0, newText.toString());

    // Copy map content from snapshot doc
    const newMeta = newDoc.getMap('metadata');
    const oldMeta = this.doc.getMap('metadata');
    const metaJson = newMeta.toJSON();
    while (oldMeta.size > 0) {
      const key = Object.keys(oldMeta.toJSON())[0];
      oldMeta.delete(key!);
    }
    Object.entries(metaJson).forEach(([key, value]) => {
      oldMeta.set(key, value);
    });

    // Copy characters
    const newChars = newDoc.getMap('characters');
    const oldChars = this.doc.getMap('characters');
    const charsJson = newChars.toJSON();
    while (oldChars.size > 0) {
      const key = Object.keys(oldChars.toJSON())[0];
      oldChars.delete(key!);
    }
    Object.entries(charsJson).forEach(([key, value]) => {
      oldChars.set(key, value);
    });

    // Copy scenes
    const newScenes = newDoc.getMap('scenes');
    const oldScenes = this.doc.getMap('scenes');
    const scenesJson = newScenes.toJSON();
    while (oldScenes.size > 0) {
      const key = Object.keys(oldScenes.toJSON())[0];
      oldScenes.delete(key!);
    }
    Object.entries(scenesJson).forEach(([key, value]) => {
      oldScenes.set(key, value);
    });
  }

  /**
   * Get all snapshots
   */
  getSnapshots(): Snapshot[] {
    return [...this.snapshots];
  }

  /**
   * Generate diff between two snapshots
   */
  generateDiff(snapshot1: Snapshot, snapshot2: Snapshot): ChangeDiff {
    // In production, would use Yjs diffing algorithm
    const changes = snapshot2.changes.filter(
      change => change.timestamp > snapshot1.timestamp
    );

    const additions = changes.filter(c => c.type === 'insert').length;
    const deletions = changes.filter(c => c.type === 'delete').length;

    return {
      additions,
      deletions,
      changes,
    };
  }

  /**
   * Listen for new changes
   */
  onChange(callback: (change: DocumentChange) => void): void {
    this.changeListeners.add(callback);
  }

  /**
   * Remove change listener
   */
  removeChangeListener(callback: (change: DocumentChange) => void): void {
    this.changeListeners.delete(callback);
  }

  /**
   * Get change statistics
   */
  getStats(): { totalChanges: number; totalSnapshots: number; lastChangeAt: Date | null } {
    const lastChange = this.changes[this.changes.length - 1];
    return {
      totalChanges: this.changes.length,
      totalSnapshots: this.snapshots.length,
      lastChangeAt: lastChange?.timestamp ?? null,
    };
  }
}
