import { type IBlock } from './wysiwyg.types.js';
import { type IWysiwygComponent } from './wysiwyg.interfaces.js';

export class WysiwygDragDropHandler {
  private component: IWysiwygComponent;
  private draggedBlockId: string | null = null;
  private dragOverBlockId: string | null = null;
  private dragOverPosition: 'before' | 'after' | null = null;
  private dropIndicator: HTMLElement | null = null;
  private initialMouseY: number = 0;
  private initialBlockY: number = 0;
  private draggedBlockElement: HTMLElement | null = null;
  private draggedBlockHeight: number = 0;
  private draggedBlockContentHeight: number = 0;
  private draggedBlockMarginTop: number = 0;
  private lastUpdateTime: number = 0;
  private updateThrottle: number = 80; // milliseconds

  constructor(component: IWysiwygComponent) {
    this.component = component;
  }

  /**
   * Gets the current drag state
   */
  get dragState() {
    return {
      draggedBlockId: this.draggedBlockId,
      dragOverBlockId: this.dragOverBlockId,
      dragOverPosition: this.dragOverPosition
    };
  }

  /**
   * Handles drag start
   */
  handleDragStart(e: DragEvent, block: IBlock): void {
    if (!e.dataTransfer) return;
    
    this.draggedBlockId = block.id;
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', block.id);
    
    // Hide the default drag image
    const emptyImg = new Image();
    emptyImg.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';
    e.dataTransfer.setDragImage(emptyImg, 0, 0);
    
    // Store initial mouse position and block element
    this.initialMouseY = e.clientY;
    this.draggedBlockElement = this.component.editorContentRef.querySelector(`[data-block-id="${block.id}"]`);
    
    
    if (this.draggedBlockElement) {
      // Get the wrapper rect for measurements
      const rect = this.draggedBlockElement.getBoundingClientRect();
      this.initialBlockY = rect.top;
      
      // Get the inner block element for proper measurements
      const innerBlock = this.draggedBlockElement.querySelector('.block');
      if (innerBlock) {
        const innerRect = innerBlock.getBoundingClientRect();
        const computedStyle = window.getComputedStyle(innerBlock);
        this.draggedBlockMarginTop = parseInt(computedStyle.marginTop) || 0;
        this.draggedBlockContentHeight = innerRect.height;
      }
      
      // The drop indicator should match the wrapper height exactly
      // The wrapper already includes all the space the block occupies
      this.draggedBlockHeight = rect.height;
      
      console.log('Drag measurements:', {
        wrapperHeight: rect.height,
        marginTop: this.draggedBlockMarginTop,
        dropIndicatorHeight: this.draggedBlockHeight,
        contentHeight: this.draggedBlockContentHeight,
        blockId: block.id
      });
      
      // Create drop indicator
      this.createDropIndicator();
      
      // Set up drag event listeners
      document.addEventListener('dragover', this.handleGlobalDragOver);
      document.addEventListener('dragend', this.handleGlobalDragEnd);
    }
    
    // Update component state
    this.component.draggedBlockId = this.draggedBlockId;
    
    // Add dragging class after a small delay
    setTimeout(() => {
      if (this.draggedBlockElement) {
        this.draggedBlockElement.classList.add('dragging');
      }
      if (this.component.editorContentRef) {
        this.component.editorContentRef.classList.add('dragging');
      }
    }, 10);
  }

  /**
   * Handles drag end
   */
  handleDragEnd(): void {
    // Clean up visual state
    const allBlocks = this.component.editorContentRef.querySelectorAll('.block-wrapper');
    allBlocks.forEach((block: HTMLElement) => {
      block.classList.remove('dragging', 'move-up', 'move-down');
      block.style.removeProperty('--drag-offset');
      block.style.removeProperty('transform');
    });
    
    // Remove dragging class from editor
    if (this.component.editorContentRef) {
      this.component.editorContentRef.classList.remove('dragging');
    }
    
    // Reset drag state
    this.draggedBlockId = null;
    this.dragOverBlockId = null;
    this.dragOverPosition = null;
    this.draggedBlockElement = null;
    this.draggedBlockHeight = 0;
    this.draggedBlockContentHeight = 0;
    this.draggedBlockMarginTop = 0;
    this.initialBlockY = 0;
    
    // Update component state
    this.component.draggedBlockId = null;
    this.component.dragOverBlockId = null;
    this.component.dragOverPosition = null;
  }

  /**
   * Handles drag over
   */
  handleDragOver(e: DragEvent, block: IBlock): void {
    e.preventDefault();
    if (!e.dataTransfer || !this.draggedBlockId || this.draggedBlockId === block.id) return;
    
    e.dataTransfer.dropEffect = 'move';
    
    const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
    const midpoint = rect.top + rect.height / 2;
    
    this.dragOverBlockId = block.id;
    this.dragOverPosition = e.clientY < midpoint ? 'before' : 'after';
    
    // Update component state
    this.component.dragOverBlockId = this.dragOverBlockId;
    this.component.dragOverPosition = this.dragOverPosition;
    
    // The parent component already handles drag-over classes programmatically
  }

  /**
   * Handles drag leave
   */
  handleDragLeave(block: IBlock): void {
    if (this.dragOverBlockId === block.id) {
      this.dragOverBlockId = null;
      this.dragOverPosition = null;
      
      // Update component state
      this.component.dragOverBlockId = null;
      this.component.dragOverPosition = null;
      
      // The parent component already handles removing drag-over classes programmatically
    }
  }

  /**
   * Handles drop
   */
  handleDrop(e: DragEvent, targetBlock: IBlock): void {
    e.preventDefault();
    
    if (!this.draggedBlockId || this.draggedBlockId === targetBlock.id) return;
    
    // The parent component already has a handleDrop method that handles this programmatically
    // We'll delegate to that to ensure proper programmatic rendering
    this.component.handleDrop(e, targetBlock);
  }


  /**
   * Checks if a block is being dragged
   */
  isDragging(blockId: string): boolean {
    return this.draggedBlockId === blockId;
  }

  /**
   * Checks if a block has drag over state
   */
  isDragOver(blockId: string): boolean {
    return this.dragOverBlockId === blockId;
  }

  /**
   * Gets drag over CSS classes for a block
   */
  getDragOverClasses(blockId: string): string {
    if (!this.isDragOver(blockId)) return '';
    return this.dragOverPosition === 'before' ? 'drag-over-before' : 'drag-over-after';
  }


  /**
   * Creates the drop indicator element
   */
  private createDropIndicator(): void {
    this.dropIndicator = document.createElement('div');
    this.dropIndicator.className = 'drop-indicator';
    this.dropIndicator.style.display = 'none';
    this.component.editorContentRef.appendChild(this.dropIndicator);
  }

  /**
   * Handles global dragover to update dragged block position and move other blocks
   */
  private handleGlobalDragOver = (e: DragEvent): void => {
    e.preventDefault();
    
    if (!this.draggedBlockElement) return;
    
    // Calculate vertical offset from initial position
    const deltaY = e.clientY - this.initialMouseY;
    
    // Apply transform to move the dragged block vertically
    this.draggedBlockElement.style.transform = `translateY(${deltaY}px)`;
    
    // Throttle position updates to reduce stuttering
    const now = Date.now();
    if (now - this.lastUpdateTime < this.updateThrottle) {
      return;
    }
    this.lastUpdateTime = now;
    
    // Calculate which blocks should move
    this.updateBlockPositions(e.clientY);
  };

  /**
   * Updates block positions based on cursor position
   */
  private updateBlockPositions(mouseY: number): void {
    const blocks = Array.from(this.component.editorContentRef.querySelectorAll('.block-wrapper')) as HTMLElement[];
    const draggedIndex = blocks.findIndex(b => b.getAttribute('data-block-id') === this.draggedBlockId);
    
    if (draggedIndex === -1) return;
    
    // Reset all transforms first (except the dragged block)
    blocks.forEach(block => {
      if (block.getAttribute('data-block-id') !== this.draggedBlockId) {
        block.classList.remove('move-up', 'move-down');
        block.style.removeProperty('--drag-offset');
      }
    });
    
    // Calculate where the dragged block should be inserted
    let newIndex = blocks.length; // Default to end
    
    for (let i = 0; i < blocks.length; i++) {
      if (i === draggedIndex) continue;
      
      const block = blocks[i];
      const rect = block.getBoundingClientRect();
      const blockTop = rect.top;
      
      // Check if mouse is above this block's middle
      if (mouseY < blockTop + (rect.height * 0.5)) {
        newIndex = i;
        break;
      }
    }
    
    
    // Apply transforms to move blocks out of the way
    for (let i = 0; i < blocks.length; i++) {
      if (i === draggedIndex) continue;
      
      const block = blocks[i];
      
      // Determine if this block needs to move
      if (draggedIndex < newIndex) {
        // Dragging down: blocks between original and new position move up
        if (i > draggedIndex && i < newIndex) {
          block.classList.add('move-up');
          block.style.setProperty('--drag-offset', `${this.draggedBlockHeight}px`);
        }
      } else if (draggedIndex > newIndex) {
        // Dragging up: blocks between new and original position move down
        if (i >= newIndex && i < draggedIndex) {
          block.classList.add('move-down');
          block.style.setProperty('--drag-offset', `${this.draggedBlockHeight}px`);
        }
      }
    }
    
    // Update drop indicator position
    this.updateDropIndicator(blocks, newIndex, draggedIndex);
  }

  /**
   * Updates the drop indicator position
   */
  private updateDropIndicator(blocks: HTMLElement[], targetIndex: number, draggedIndex: number): void {
    if (!this.dropIndicator || !this.draggedBlockElement) return;
    
    this.dropIndicator.style.display = 'block';
    
    const containerRect = this.component.editorContentRef.getBoundingClientRect();
    let topPosition = 0;
    
    // Build array of visual block positions (excluding dragged block)
    const visualBlocks: { index: number, top: number, bottom: number }[] = [];
    
    for (let i = 0; i < blocks.length; i++) {
      if (i === draggedIndex) continue; // Skip the dragged block
      
      const block = blocks[i];
      const rect = block.getBoundingClientRect();
      let top = rect.top - containerRect.top;
      let bottom = rect.bottom - containerRect.top;
      
      // Account for any transforms
      const transform = window.getComputedStyle(block).transform;
      if (transform && transform !== 'none') {
        const matrix = new DOMMatrix(transform);
        const yOffset = matrix.m42;
        top += yOffset;
        bottom += yOffset;
      }
      
      visualBlocks.push({ index: i, top, bottom });
    }
    
    // Sort by visual position
    visualBlocks.sort((a, b) => a.top - b.top);
    
    // Adjust targetIndex to account for excluded dragged block
    let adjustedTargetIndex = targetIndex;
    if (targetIndex > draggedIndex) {
      adjustedTargetIndex--; // Reduce by 1 since dragged block is not in visualBlocks
    }
    
    // Calculate drop position
    // Get the margin that will be applied based on the dragged block type
    let blockMargin = 16; // default margin
    if (this.draggedBlockElement) {
      const draggedBlock = this.component.blocks.find(b => b.id === this.draggedBlockId);
      if (draggedBlock) {
        const blockType = draggedBlock.type;
        if (blockType === 'heading-1' || blockType === 'heading-2' || blockType === 'heading-3') {
          blockMargin = 24;
        } else if (blockType === 'code' || blockType === 'quote') {
          blockMargin = 20;
        }
      }
    }
    
    if (adjustedTargetIndex === 0) {
      // Insert at the very top - no margin needed for first block
      topPosition = 0;
    } else if (adjustedTargetIndex >= visualBlocks.length) {
      // Insert at the end
      const lastBlock = visualBlocks[visualBlocks.length - 1];
      if (lastBlock) {
        topPosition = lastBlock.bottom;
        // Add margin that will be applied to the dropped block
        topPosition += blockMargin;
      }
    } else {
      // Insert between blocks
      const blockBefore = visualBlocks[adjustedTargetIndex - 1];
      if (blockBefore) {
        topPosition = blockBefore.bottom;
        // Add margin that will be applied to the dropped block
        topPosition += blockMargin;
      }
    }
    
    // Set the indicator height to match the dragged block
    this.dropIndicator.style.height = `${this.draggedBlockHeight}px`;
    
    // Set position
    this.dropIndicator.style.top = `${Math.max(0, topPosition)}px`;
    
    console.log('Drop indicator update:', {
      targetIndex,
      adjustedTargetIndex,
      draggedIndex,
      topPosition,
      height: this.draggedBlockHeight,
      blockMargin,
      visualBlocks: visualBlocks.map(b => ({ index: b.index, top: b.top, bottom: b.bottom }))
    });
  }

  /**
   * Handles global drag end
   */
  private handleGlobalDragEnd = (): void => {
    // Clean up event listeners
    document.removeEventListener('dragover', this.handleGlobalDragOver);
    document.removeEventListener('dragend', this.handleGlobalDragEnd);
    
    // Remove drop indicator
    if (this.dropIndicator) {
      this.dropIndicator.remove();
      this.dropIndicator = null;
    }
    
    // Trigger the actual drop if we have a dragged block
    if (this.draggedBlockId) {
      // Small delay to ensure transforms are applied
      requestAnimationFrame(() => {
        this.performDrop();
        // Call the regular drag end handler after drop
        this.handleDragEnd();
      });
    } else {
      // Call the regular drag end handler
      this.handleDragEnd();
    }
  };

  /**
   * Performs the actual drop operation
   */
  private performDrop(): void {
    if (!this.draggedBlockId) return;
    
    // Get the visual order of blocks based on their positions
    const blockElements = Array.from(this.component.editorContentRef.querySelectorAll('.block-wrapper')) as HTMLElement[];
    const draggedElement = blockElements.find(el => el.getAttribute('data-block-id') === this.draggedBlockId);
    
    if (!draggedElement) return;
    
    
    // Create an array of blocks with their visual positions
    const visualOrder = blockElements.map(el => {
      const id = el.getAttribute('data-block-id');
      const rect = el.getBoundingClientRect();
      const centerY = rect.top + rect.height / 2;
      return { id, centerY, element: el };
    });
    
    // Sort by visual Y position
    visualOrder.sort((a, b) => a.centerY - b.centerY);
    
    // Get the new order of block IDs
    const newBlockIds = visualOrder.map(item => item.id).filter(id => id !== null);
    
    // Find the original block data
    const originalBlocks = [...this.component.blocks];
    const draggedBlock = originalBlocks.find(b => b.id === this.draggedBlockId);
    
    if (!draggedBlock) return;
    
    // Check if order actually changed
    const oldOrder = originalBlocks.map(b => b.id);
    const orderChanged = !newBlockIds.every((id, index) => id === oldOrder[index]);
    
    if (!orderChanged) {
      return;
    }
    
    // Reorder blocks based on visual positions
    const newBlocks = newBlockIds.map(id => originalBlocks.find(b => b.id === id)!).filter(Boolean);
    
    // Update blocks
    this.component.blocks = newBlocks;
    
    // Re-render blocks programmatically
    this.component.renderBlocksProgrammatically();
    
    // Update value
    this.component.updateValue();
    
    // Focus the moved block after a delay
    setTimeout(() => {
      if (draggedBlock.type !== 'divider') {
        this.component.blockOperations.focusBlock(draggedBlock.id);
      }
    }, 100);
  }
}