/**
 * PasteHandler - Handles paste events and content sanitization
 *
 * Extracted from the monolithic initEditor function to provide
 * focused, testable paste handling logic.
 */

import { DEFAULT_BLOCK_FORMATS } from '../config/constants';

export class PasteHandler {
  constructor() {
    // Pattern for classes to preserve during paste
    this.preserveClassPattern = /^has-.*(color|size)$/;
  }

  /**
   * Get the list of allowed HTML tags for pasting
   * @returns {Array<string>}
   */
  getAllowedTags() {
    const blockTags = DEFAULT_BLOCK_FORMATS.map(format => format.tag);
    const inlineTags = ['strong', 'em', 'u', 'br', 'a'];
    const listTags = ['ul', 'ol', 'li'];

    return [...blockTags, ...inlineTags, ...listTags];
  }

  /**
   * Handle paste event
   * @param {Object} editor - TinyMCE editor instance
   * @param {Event} event - Paste event
   */
  handlePaste(editor, event) {
    const clipboardData = event.clipboardData || window.clipboardData;
    const pastedData =
      clipboardData.getData('text/html') || clipboardData.getData('text/plain');

    if (!pastedData) return;

    event.preventDefault();

    const allowedTags = this.getAllowedTags();
    const cleanedContent = this.cleanPastedHTML(pastedData, allowedTags);

    editor.insertContent(cleanedContent);
  }

  /**
   * Handle post-paste processing to preserve specific classes
   * @param {Event} event - Post-process event
   */
  handlePostProcess(event) {
    const elementsWithClasses = event.node.querySelectorAll('[class]');

    elementsWithClasses.forEach(element => {
      const classList = element.className.split(' ');
      const preservedClasses = classList.filter(className =>
        this.preserveClassPattern.test(className)
      );

      if (preservedClasses.length > 0) {
        element.className = preservedClasses.join(' ');
      } else {
        element.removeAttribute('class');
      }
    });
  }

  /**
   * Clean pasted HTML content
   * @param {string} html - HTML content to clean
   * @param {Array<string>} allowedTags - Allowed HTML tags
   * @returns {string} Cleaned HTML
   */
  cleanPastedHTML(html, allowedTags) {
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = html;

    const cleanedContent = Array.from(tempDiv.childNodes)
      .map(child => this.cleanNode(child, allowedTags))
      .join('');

    // Wrap in paragraph if no block-level tags
    const hasBlockTags = allowedTags.some(
      tag =>
        ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'div', 'ul', 'ol'].includes(tag) &&
        cleanedContent.includes(`<${tag}>`)
    );

    if (!hasBlockTags && cleanedContent.trim()) {
      return `<p>${cleanedContent}</p>`;
    }

    return cleanedContent;
  }

  /**
   * Recursively clean a DOM node
   * @param {Node} node - Node to clean
   * @param {Array<string>} allowedTags - Allowed tags
   * @returns {string} Cleaned HTML string
   */
  cleanNode(node, allowedTags) {
    if (node.nodeType === Node.TEXT_NODE) {
      const text = this.removeHiddenChars(node.textContent);
      return text.replace(/\s+/g, ' ');
    }

    if (node.nodeType === Node.ELEMENT_NODE) {
      const tagName = node.tagName.toLowerCase();

      if (allowedTags.includes(tagName)) {
        const cleanChildren = Array.from(node.childNodes)
          .map(child => this.cleanNode(child, allowedTags))
          .filter(content => content !== '')
          .join('');

        // Preserve attributes for links
        if (tagName === 'a' && node.hasAttribute('href')) {
          const href = node.getAttribute('href');
          const target = node.hasAttribute('target')
            ? ` target="${node.getAttribute('target')}"`
            : '';
          const preservedClasses = this.preserveSpecificClasses(node);
          return `<${tagName} href="${href}"${target}${preservedClasses}>${cleanChildren}</${tagName}>`;
        }

        // Preserve specific classes for all allowed elements
        const preservedClasses = this.preserveSpecificClasses(node);
        return `<${tagName}${preservedClasses}>${cleanChildren}</${tagName}>`;
      } else {
        // Tag not allowed, return just the text content
        return Array.from(node.childNodes)
          .map(child => this.cleanNode(child, allowedTags))
          .join('');
      }
    }

    return '';
  }

  /**
   * Remove hidden/invisible characters from text
   * @param {string} text - Text to clean
   * @returns {string} Cleaned text
   */
  removeHiddenChars(text) {
    return text
      .replace(/\u00A0/g, ' ') // Non-breaking space
      .replace(/\u200B/g, '') // Zero-width space
      .replace(/\u200C/g, '') // Zero-width non-joiner
      .replace(/\u200D/g, '') // Zero-width joiner
      .replace(/\u2060/g, '') // Word joiner
      .replace(/\u00AD/g, '') // Soft hyphen
      .replace(/\uFEFF/g, '') // Zero-width no-break space
      .replace(/[\u2000-\u200F]/g, ' ') // Various Unicode spaces
      .replace(/[\u2028-\u202F]/g, ' '); // Line/paragraph separators
  }

  /**
   * Preserve specific classes that match the pattern
   * @param {Element} element - DOM element
   * @returns {string} Class attribute string or empty string
   */
  preserveSpecificClasses(element) {
    if (!element.hasAttribute('class')) return '';

    const classList = element.className.split(' ');
    const preservedClasses = classList.filter(className =>
      this.preserveClassPattern.test(className)
    );

    return preservedClasses.length > 0 ? ` class="${preservedClasses.join(' ')}"` : '';
  }

  /**
   * Attach paste handlers to an editor
   * @param {Object} editor - TinyMCE editor instance
   */
  attach(editor) {
    editor.on('paste', e => this.handlePaste(editor, e));
    editor.on('pastePostProcess', e => this.handlePostProcess(e));
  }
}

export default PasteHandler;
