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

/**
 * Normalize a raw style_formats entry into the shape this plugin needs.
 *
 * Providers emit style_formats in slightly different shapes — TheDock sends
 * `classes` as a space-separated string and may omit `selector`, while a
 * hand-rolled config might send `classes` as an array. Normalizing here lets the
 * dropdown work with or without TheDock, as long as some provider supplies
 * style_formats (via the config or the window.erteStyleFormats global).
 *
 * @param {Object} raw - Raw style format entry
 * @param {number} index - Position in the list (used to derive a fallback name)
 * @returns {Object|null}
 */
function normalizeStyle(raw, index) {
  if (!raw || typeof raw !== 'object') return null;

  let classes = raw.classes;
  if (typeof classes === 'string') {
    classes = classes.split(/\s+/).filter(Boolean);
  } else if (!Array.isArray(classes)) {
    classes = [];
  }

  // `name` is required: formatters register as `block-style-<name>` /
  // `inline-style-<name>`. Derive a stable one when a provider omits it.
  const name =
    raw.name ||
    classes[0] ||
    (raw.title ? String(raw.title).toLowerCase().replace(/\s+/g, '-') : `style-${index}`);

  return {
    ...raw,
    name,
    title: raw.title || name,
    classes,
    selector: raw.selector || BLOCK_SELECTORS,
  };
}

/**
 * Normalize a full style_formats array, dropping anything unusable.
 * @param {Array} styleFormats
 * @returns {Array}
 */
function normalizeStyles(styleFormats) {
  if (!Array.isArray(styleFormats)) return [];
  return styleFormats.map(normalizeStyle).filter(Boolean);
}

export default function(editor) {
  let customStyles = [];
  // Set in onSetup so initStyles() can refresh the button when styles arrive
  // after the toolbar has already rendered.
  let refreshButton = null;

  editor.ui.registry.addMenuButton("tdstyles", {
    text: "Styles",
    tooltip: "Custom Styles",
    onSetup: (api) => {
      const updateButtonText = () => {
        // Disable the button when there are no custom styles, so a site running
        // this plugin without any style provider doesn't show a dead dropdown.
        api.setEnabled(customStyles.length > 0);

        const selection = editor.selection;
        const selectedNode = selection.getNode();
        const parents = editor.dom.getParents(selectedNode);
        
        // First check for block styles using formatter
        const activeBlockStyle = customStyles.find(style => 
          editor.formatter.match('block-style-' + style.name)
        );
        if (activeBlockStyle) {
          api.setText(activeBlockStyle.title);
          return;
        }
        
        // Then check for block styles using classes
        const blockElm = parents.find(parent => editor.dom.is(parent));
        if (blockElm) {
          const activeBlockStyleByClass = customStyles.find(style => 
            style.classes.some(className => editor.dom.hasClass(blockElm, className))
          );
          if (activeBlockStyleByClass) {
            api.setText(activeBlockStyleByClass.title);
            return;
          }
        }
        
        // Then check for inline styles
        const activeInlineStyle = customStyles.find(style => 
          editor.formatter.match('inline-style-' + style.name)
        );
        if (activeInlineStyle) {
          api.setText(activeInlineStyle.title);
          return;
        }
        
        // If no active style, show default text
        api.setText('Custom Styles');
      };

      // Let initStyles() refresh the button once styles are loaded.
      refreshButton = updateButtonText;

      // Update on selection change
      editor.on('NodeChange', updateButtonText);

      // Initial update
      updateButtonText();

      // Cleanup
      return () => {
        refreshButton = null;
        editor.off('NodeChange', updateButtonText);
      };
    },
    fetch: (callback) => {
      const items = customStyles.map(style => {
        const selection = editor.selection;
        const selectedNode = selection.getNode();
        const parents = editor.dom.getParents(selectedNode);
        
        let indicator = '';
        
        // First check if block element has the style classes
        const blockElm = parents.find(parent => editor.dom.is(parent));
        if (blockElm) {
          const hasBlockClass = style.classes.some(className => 
            editor.dom.hasClass(blockElm, className)
          );
          if (hasBlockClass) {
            indicator = '● ';  // Show active for block
          }
        }
        
        // If block is not active, check for inline
        if (!indicator) {
          const isInlineActive = editor.formatter.match('inline-style-' + style.name);
          if (isInlineActive) {
            indicator = '◉ ';  // Show active for inline
          }
        }
        
        return {
          type: 'menuitem',
          text: indicator + style.title,
          onAction: () => {
            editor.execCommand('applyCustomStyle', style);
          },
        };
      });
      callback(items);
    }
  });

  // Custom command to apply appropriate format based on selection
  editor.addCommand('applyCustomStyle', function (style) {
    const selection = editor.selection;
    const selectedNode = selection.getNode();
    let selectedContent = selection.getContent({ format: 'text' });

    // If no selection, get the word at cursor position
    if (!selectedContent.trim()) {
      const cursorPosition = selection.getRng();
      const textNode = cursorPosition.startContainer;
      
      if (textNode.nodeType === 3) { // Text node
        const text = textNode.textContent;
        const offset = cursorPosition.startOffset;
        
        // Find word boundaries
        const wordStart = text.lastIndexOf(' ', offset - 1) + 1;
        const wordEnd = text.indexOf(' ', offset);
        const word = text.substring(
          wordStart,
          wordEnd === -1 ? text.length : wordEnd
        ).trim();
        
        if (word) {
          // Create a new range for the word
          const range = editor.dom.createRng();
          range.setStart(textNode, wordStart);
          range.setEnd(textNode, wordEnd === -1 ? text.length : wordEnd);
          selection.setRng(range);
          selectedContent = word;
        }
      }
    }

    // Get all parent elements and find the first one that matches our selector
    const parents = editor.dom.getParents(selectedNode);
    const blockElm = parents.find(parent => editor.dom.is(parent, BLOCK_SELECTORS));

    if (!blockElm) return;

    const blockText = blockElm.textContent;
    const isBlockSelection = selectedContent.trim() === blockText.trim();
    const formatName = isBlockSelection ? 'block-style-' + style.name : 'inline-style-' + style.name;

    // Check if the style is already applied
    if (editor.formatter.match(formatName)) {
      // If it's active, just remove it
      editor.formatter.remove(formatName);
      if (!isBlockSelection) {
        // Get all spans in the selection
        const spans = editor.dom.select('span', selection.getNode());
        spans.forEach(span => {
          // If the span has no classes, remove it
          if (!span.className) {
            editor.dom.remove(span, true);
          }
        });
      }
    } else {
      // If it's not active, first remove any existing custom styles
      customStyles.forEach(s => {
        if (isBlockSelection) {
          editor.formatter.remove('block-style-' + s.name);
        } else {
          editor.formatter.remove('inline-style-' + s.name);
        }
      });
      // Then apply the new style
      if (isBlockSelection) {
        editor.formatter.apply(formatName);
      } else {
        // Check if the block already has this class
        const hasBlockClass = style.classes.some(className => 
          editor.dom.hasClass(blockElm, className)
        );
        if (!hasBlockClass) {
          editor.formatter.apply(formatName);
        }
      }
    }
  });

  // Register the TinyMCE formatters for the current custom styles. Safe to call
  // repeatedly — formatter.register overrides an existing name — so it runs both
  // on `init` and again from initStyles() if styles arrive/refresh afterwards.
  const registerFormatters = () => {
    customStyles.forEach(style => {
      // Register each style with its specific selector and class
      editor.formatter.register('block-style-' + style.name, {
        selector: style.selector,
        classes: style.classes,
        exact: style.exact,
        deep: style.deep,
        split: style.split,
        remove: style.remove,
        group: 'custom-styles' // This ensures only one style can be active at a time
      });
      editor.formatter.register('inline-style-' + style.name, {
        inline: 'span',
        classes: style.classes,
        group: 'custom-styles' // This ensures only one style can be active at a time
      });
    });
  };

  // Register the custom styles
  editor.on('init', registerFormatters);

  return {
    /**
     * Initialize the custom styles dropdown.
     * @param {Array} style_formats - style_formats from the editor config or a
     *   provider global. Shapes are normalized so it works with or without
     *   TheDock.
     */
    initStyles: (style_formats) => {
      customStyles = normalizeStyles(style_formats);

      // Styles can arrive after the editor's own `init` handler has run, so
      // (re)register formatters and refresh the button state now.
      if (editor.initialized) {
        registerFormatters();
      }
      if (refreshButton) {
        refreshButton();
      }
    }
  };
}