import { html, type TemplateResult } from '@design.estate/dees-element';
import { WysiwygSelection } from './wysiwyg.selection.js';

export interface IFormatButton {
  command: string;
  icon: string;
  label: string;
  shortcut?: string;
  action?: () => void;
}

/**
 * Handles text formatting with smart toggle behavior:
 * - If selection contains ANY instance of a format, removes ALL instances
 * - If selection has no formatting, applies the format
 * - Works correctly with Shadow DOM using range-based operations
 */
export class WysiwygFormatting {
  static readonly formatButtons: IFormatButton[] = [
    { command: 'bold', icon: 'B', label: 'Bold', shortcut: '⌘B' },
    { command: 'italic', icon: 'I', label: 'Italic', shortcut: '⌘I' },
    { command: 'underline', icon: 'U', label: 'Underline', shortcut: '⌘U' },
    { command: 'strikeThrough', icon: 'S̶', label: 'Strikethrough' },
    { command: 'code', icon: '{ }', label: 'Inline Code' },
    { command: 'link', icon: '🔗', label: 'Link', shortcut: '⌘K' },
  ];

  static renderFormattingMenu(
    position: { x: number; y: number },
    onFormat: (command: string) => void
  ): TemplateResult {
    return html`
      <div 
        class="formatting-menu" 
        style="top: ${position.y}px; left: ${position.x}px;"
        @mousedown="${(e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); }}"
        @click="${(e: MouseEvent) => e.stopPropagation()}"
      >
        ${this.formatButtons.map(button => html`
          <button 
            class="format-button ${button.command}"
            @click="${() => onFormat(button.command)}"
            title="${button.label}${button.shortcut ? ` (${button.shortcut})` : ''}"
          >
            <span class="${button.command === 'code' ? 'code-icon' : ''}">${button.icon}</span>
          </button>
        `)}
      </div>
    `;
  }

  static applyFormat(command: string, value?: string, range?: Range, shadowRoots?: ShadowRoot[]): boolean {
    // If range is provided, use it directly (Shadow DOM case)
    // Otherwise fall back to window.getSelection()
    let workingRange: Range;
    
    if (range) {
      workingRange = range;
    } else {
      const selection = window.getSelection();
      if (!selection || selection.rangeCount === 0) return false;
      workingRange = selection.getRangeAt(0);
    }
    
    // Apply format based on command
    switch (command) {
      case 'bold':
        this.wrapSelection(workingRange, 'strong');
        break;
      
      case 'italic':
        this.wrapSelection(workingRange, 'em');
        break;
      
      case 'underline':
        this.wrapSelection(workingRange, 'u');
        break;
      
      case 'strikeThrough':
        this.wrapSelection(workingRange, 's');
        break;
      
      case 'code':
        this.wrapSelection(workingRange, 'code');
        break;
      
      case 'link':
        // Don't use prompt - return false to indicate we need async input
        if (!value) {
          return false;
        }
        this.wrapSelectionWithLink(workingRange, value);
        break;
    }
    
    // If we have shadow roots, use our Shadow DOM selection utility
    if (shadowRoots && shadowRoots.length > 0) {
      WysiwygSelection.setSelectionFromRange(workingRange);
    } else {
      // Regular selection restoration
      const selection = window.getSelection();
      if (selection) {
        selection.removeAllRanges();
        selection.addRange(workingRange);
      }
    }
    
    return true;
  }
  
  private static wrapSelection(range: Range, tagName: string): void {
    const selection = window.getSelection();
    if (!selection) return;
    
    // Check if ANY part of the selection contains this formatting
    const hasFormatting = this.selectionContainsTag(range, tagName);
    
    if (hasFormatting) {
      // Remove all instances of this tag from the selection
      this.removeTagFromSelection(range, tagName);
    } else {
      // Wrap selection with the tag
      const wrapper = document.createElement(tagName);
      try {
        // Extract and wrap contents
        const contents = range.extractContents();
        wrapper.appendChild(contents);
        range.insertNode(wrapper);
        
        // Select the wrapped content
        range.selectNodeContents(wrapper);
        selection.removeAllRanges();
        selection.addRange(range);
      } catch (e) {
        console.error('Failed to wrap selection:', e);
      }
    }
  }
  
  /**
   * Check if the selection contains or is within any instances of a tag
   */
  private static selectionContainsTag(range: Range, tagName: string): boolean {
    // First check: Are we inside a tag? (even if selection doesn't include the tag)
    let node: Node | null = range.startContainer;
    
    while (node && node !== range.commonAncestorContainer.ownerDocument) {
      if (node.nodeType === Node.ELEMENT_NODE) {
        const element = node as Element;
        if (element.tagName.toLowerCase() === tagName) {
          return true;
        }
      }
      node = node.parentNode;
    }
    
    // Also check the end container
    node = range.endContainer;
    
    while (node && node !== range.commonAncestorContainer.ownerDocument) {
      if (node.nodeType === Node.ELEMENT_NODE) {
        const element = node as Element;
        if (element.tagName.toLowerCase() === tagName) {
          return true;
        }
      }
      node = node.parentNode;
    }
    
    // Second check: Does the selection contain any complete tags?
    const tempDiv = document.createElement('div');
    const contents = range.cloneContents();
    tempDiv.appendChild(contents);
    const tags = tempDiv.getElementsByTagName(tagName);
    
    return tags.length > 0;
  }
  
  /**
   * Remove all instances of a tag from the selection
   */
  private static removeTagFromSelection(range: Range, tagName: string): void {
    const selection = window.getSelection();
    if (!selection) return;
    
    // Special handling: Check if we need to expand the selection to include parent tags
    let expandedRange = range.cloneRange();
    
    // Check if start is inside a tag
    let startNode: Node | null = range.startContainer;
    let startTag: Element | null = null;
    while (startNode && startNode !== range.commonAncestorContainer.ownerDocument) {
      if (startNode.nodeType === Node.ELEMENT_NODE && (startNode as Element).tagName.toLowerCase() === tagName) {
        startTag = startNode as Element;
        break;
      }
      startNode = startNode.parentNode;
    }
    
    // Check if end is inside a tag
    let endNode: Node | null = range.endContainer;
    let endTag: Element | null = null;
    while (endNode && endNode !== range.commonAncestorContainer.ownerDocument) {
      if (endNode.nodeType === Node.ELEMENT_NODE && (endNode as Element).tagName.toLowerCase() === tagName) {
        endTag = endNode as Element;
        break;
      }
      endNode = endNode.parentNode;
    }
    
    // Expand range to include the tags if needed
    if (startTag) {
      expandedRange.setStartBefore(startTag);
    }
    if (endTag) {
      expandedRange.setEndAfter(endTag);
    }
    
    // Extract the contents using the expanded range
    const fragment = expandedRange.extractContents();
    
    // Process the fragment to remove tags
    const processedFragment = this.removeTagsFromFragment(fragment, tagName);
    
    // Insert the processed content back
    expandedRange.insertNode(processedFragment);
    
    // Restore selection to match the original selection intent
    // Find the text nodes that correspond to the original selection
    const textNodes: Node[] = [];
    const walker = document.createTreeWalker(
      processedFragment,
      NodeFilter.SHOW_TEXT,
      null
    );
    
    let node;
    while (node = walker.nextNode()) {
      textNodes.push(node);
    }
    
    if (textNodes.length > 0) {
      const newRange = document.createRange();
      newRange.setStart(textNodes[0], 0);
      newRange.setEnd(textNodes[textNodes.length - 1], textNodes[textNodes.length - 1].textContent?.length || 0);
      selection.removeAllRanges();
      selection.addRange(newRange);
    }
  }
  
  /**
   * Remove all instances of a tag from a document fragment
   */
  private static removeTagsFromFragment(fragment: DocumentFragment, tagName: string): DocumentFragment {
    const tempDiv = document.createElement('div');
    tempDiv.appendChild(fragment);
    
    // Find all instances of the tag
    const tags = tempDiv.getElementsByTagName(tagName);
    
    // Convert to array to avoid live collection issues
    const tagArray = Array.from(tags);
    
    // Unwrap each tag
    tagArray.forEach(tag => {
      const parent = tag.parentNode;
      if (parent) {
        // Move all children out of the tag
        while (tag.firstChild) {
          parent.insertBefore(tag.firstChild, tag);
        }
        // Remove the empty tag
        parent.removeChild(tag);
      }
    });
    
    // Create a new fragment from the processed content
    const newFragment = document.createDocumentFragment();
    while (tempDiv.firstChild) {
      newFragment.appendChild(tempDiv.firstChild);
    }
    
    return newFragment;
  }
  
  private static wrapSelectionWithLink(range: Range, url: string): void {
    const selection = window.getSelection();
    if (!selection) return;
    
    // First remove any existing links in the selection
    if (this.selectionContainsTag(range, 'a')) {
      this.removeTagFromSelection(range, 'a');
      // Re-get the range after modification
      if (selection.rangeCount > 0) {
        range = selection.getRangeAt(0);
      }
    }
    
    const link = document.createElement('a');
    link.href = url;
    link.target = '_blank';
    link.rel = 'noopener noreferrer';
    
    try {
      const contents = range.extractContents();
      link.appendChild(contents);
      range.insertNode(link);
      
      // Select the link
      range.selectNodeContents(link);
      selection.removeAllRanges();
      selection.addRange(range);
    } catch (e) {
      console.error('Failed to create link:', e);
    }
  }

  static getSelectionCoordinates(...shadowRoots: ShadowRoot[]): { x: number, y: number } | null {
    // Get selection info using the new utility that handles Shadow DOM
    const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots);
    
    console.log('getSelectionCoordinates - selectionInfo:', selectionInfo);
    
    if (!selectionInfo) {
      console.log('No selection info available');
      return null;
    }

    // Create a range from the selection info to get bounding rect
    const range = WysiwygSelection.createRangeFromInfo(selectionInfo);
    const rect = range.getBoundingClientRect();
    
    console.log('Range rect:', rect);
    
    if (rect.width === 0 && rect.height === 0) {
      console.log('Rect width and height are 0, trying different approach');
      // Sometimes the rect is collapsed, let's try getting the caret position
      if ('caretPositionFromPoint' in document) {
        const selection = window.getSelection();
        if (selection && selection.rangeCount > 0) {
          const range = selection.getRangeAt(0);
          const tempSpan = document.createElement('span');
          tempSpan.textContent = '\u200B'; // Zero-width space
          range.insertNode(tempSpan);
          const spanRect = tempSpan.getBoundingClientRect();
          tempSpan.remove();
          
          if (spanRect.width > 0 || spanRect.height > 0) {
            const coords = {
              x: spanRect.left,
              y: Math.max(45, spanRect.top - 45)
            };
            console.log('Used span trick for coords:', coords);
            return coords;
          }
        }
      }
      return null;
    }

    const coords = {
      x: rect.left + (rect.width / 2),
      y: Math.max(45, rect.top - 45) // Position above selection, but ensure it's not negative
    };
    
    console.log('Returning coords:', coords);
    return coords;
  }
}