/**
 * @file
 * Base utilities and shared helpers for EB UI module.
 *
 * This file provides foundational utilities used by both the YAML editor
 * and grid-based editors (eb_aggrid). These helpers handle DOM manipulation,
 * AJAX requests, and common UI patterns.
 */

((Drupal, drupalSettings) => {
  'use strict';

  /**
   * Initialize the ebUi namespace.
   *
   * @type {Object}
   */
  Drupal.ebUi = Drupal.ebUi || {};

  /**
   * Cache for fetched bundles by entity type.
   *
   * @type {Object}
   */
  Drupal.ebUi.bundleCache = {};

  /**
   * In-flight bundle fetch promises to prevent duplicate requests.
   *
   * @type {Object}
   */
  Drupal.ebUi.bundleFetchPromises = {};

  /**
   * Error categories for grouping validation errors.
   *
   * @type {Object}
   */
  Drupal.ebUi.ERROR_CATEGORIES = {
    'does not exist': {
      Bundle: 'Missing Bundles',
      Field: 'Missing Fields',
      'Entity type': 'Entity Type Issues',
      Menu: 'Menu Issues',
      default: 'Missing Dependencies'
    },
    'already exists': 'Duplicate Entries',
    required: 'Required Fields',
    Required: 'Required Fields',
    invalid: 'Invalid Values',
    Invalid: 'Invalid Values',
    default: 'Validation Errors'
  };

  /**
   * Escape HTML entities to prevent XSS attacks.
   *
   * This is a fallback for when Drupal.checkPlain is not available.
   * Always prefer Drupal.checkPlain when available.
   *
   * @param {string} text
   *   The text to escape.
   *
   * @return {string}
   *   The escaped text safe for HTML insertion.
   */
  Drupal.ebUi.escapeHtml = (text) => {
    if (text === null || text === undefined) {
      return '';
    }
    if (typeof Drupal.checkPlain === 'function') {
      return Drupal.checkPlain(String(text));
    }
    const div = document.createElement('div');
    div.textContent = String(text);
    return div.innerHTML;
  };

  /**
   * Create an element with safe text content.
   *
   * @param {string} tagName
   *   The HTML tag name.
   * @param {string} text
   *   The text content (will be escaped).
   * @param {Object} attributes
   *   Optional attributes to set.
   *
   * @return {Element}
   *   The created element.
   */
  Drupal.ebUi.createElement = (tagName, text, attributes) => {
    const element = document.createElement(tagName);
    if (text !== undefined && text !== null) {
      element.textContent = String(text);
    }
    if (attributes) {
      Object.keys(attributes).forEach((key) => {
        element.setAttribute(key, attributes[key]);
      });
    }
    return element;
  };

  /**
   * Reset a select element with a placeholder option.
   *
   * Safely clears and resets a select element without using innerHTML.
   *
   * @param {HTMLSelectElement} selectElement
   *   The select element to reset.
   * @param {string} placeholderText
   *   The placeholder option text.
   */
  Drupal.ebUi.resetSelectWithPlaceholder = (selectElement, placeholderText) => {
    while (selectElement.firstChild) {
      selectElement.removeChild(selectElement.firstChild);
    }
    const placeholder = Drupal.ebUi.createElement('option', placeholderText, { value: '' });
    selectElement.appendChild(placeholder);
  };

  /**
   * Get CSRF token for AJAX requests.
   *
   * Checks both ebUi and ebAggrid settings for the token.
   *
   * @return {string}
   *   The CSRF token from drupalSettings.
   */
  Drupal.ebUi.getCsrfToken = () => {
    const settings = drupalSettings.ebUi || drupalSettings.ebAggrid || {};
    return settings.csrfToken ?? '';
  };

  /**
   * Get headers for POST AJAX requests including CSRF token.
   *
   * @return {Object}
   *   Headers object with Content-Type, X-Requested-With, and X-CSRF-Token.
   */
  Drupal.ebUi.getPostHeaders = () => ({
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
    'X-CSRF-Token': Drupal.ebUi.getCsrfToken()
  });

  /**
   * Wrapper for fetch API with standard JSON handling and error handling.
   *
   * @param {string} url
   *   The URL to fetch.
   * @param {Object} options
   *   Optional fetch options (method, body, headers, etc.).
   *
   * @return {Promise}
   *   Promise that resolves with parsed JSON or rejects with error.
   */
  Drupal.ebUi.fetchJson = (url, options = {}) => {
    const defaultOptions = {
      method: 'GET',
      headers: { 'X-Requested-With': 'XMLHttpRequest' }
    };
    const mergedOptions = { ...defaultOptions, ...options };

    if (mergedOptions.method === 'POST' && !mergedOptions.headers['X-CSRF-Token']) {
      mergedOptions.headers = { ...mergedOptions.headers, ...Drupal.ebUi.getPostHeaders() };
    }

    return fetch(url, mergedOptions)
      .then((response) => {
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }
        return response.json();
      });
  };

  /**
   * Fetch bundles for an entity type via AJAX.
   *
   * Results are cached to avoid redundant requests.
   *
   * @param {string} entityType
   *   The entity type ID.
   *
   * @return {Promise}
   *   Promise that resolves with bundle values.
   */
  Drupal.ebUi.fetchBundlesForEntityType = (entityType) => {
    if (Drupal.ebUi.bundleFetchPromises[entityType]) {
      return Drupal.ebUi.bundleFetchPromises[entityType];
    }

    if (Drupal.ebUi.bundleCache[entityType]) {
      return Promise.resolve(Drupal.ebUi.bundleCache[entityType]);
    }

    const promise = Drupal.ebUi.fetchJson(`/eb/api/bundles/${encodeURIComponent(entityType)}`)
      .then((result) => {
        delete Drupal.ebUi.bundleFetchPromises[entityType];

        if (result.success && result.bundles) {
          const values = [''];
          // Bundles come as an object keyed by bundle ID, not an array.
          Object.keys(result.bundles).forEach((bundleId) => {
            values.push(bundleId);
          });
          Drupal.ebUi.bundleCache[entityType] = values;
          return values;
        }

        return [''];
      })
      .catch((error) => {
        delete Drupal.ebUi.bundleFetchPromises[entityType];
        console.error(`Error fetching bundles for ${entityType}:`, error);
        return [''];
      });

    Drupal.ebUi.bundleFetchPromises[entityType] = promise;
    return promise;
  };

  /**
   * Group validation errors by category for display.
   *
   * @param {Array} errors
   *   Array of error message strings.
   *
   * @return {Object}
   *   Errors grouped by category.
   */
  Drupal.ebUi.groupValidationErrors = (errors) => {
    const grouped = {};
    const errorCategories = Drupal.ebUi.ERROR_CATEGORIES;

    errors.forEach((error) => {
      let category = 'Other';
      let message = error;
      let context = '';

      const contextMatch = error.match(/^(.+?)\s*\[([^\]]+)\]$/);
      if (contextMatch) {
        message = contextMatch[1];
        context = contextMatch[2];
      }

      let foundCategory = false;

      Object.keys(errorCategories).forEach((pattern) => {
        if (foundCategory || pattern === 'default') {
          return;
        }

        if (message.includes(pattern)) {
          const categoryConfig = errorCategories[pattern];

          if (typeof categoryConfig === 'string') {
            category = categoryConfig;
            foundCategory = true;
          } else if (typeof categoryConfig === 'object') {
            Object.keys(categoryConfig).forEach((subPattern) => {
              if (foundCategory || subPattern === 'default') {
                return;
              }

              if (message.includes(subPattern)) {
                category = categoryConfig[subPattern];
                foundCategory = true;
              }
            });

            if (!foundCategory && categoryConfig.default) {
              category = categoryConfig.default;
              foundCategory = true;
            }
          }
        }
      });

      if (!foundCategory) {
        category = errorCategories.default || 'Validation Errors';
      }

      if (!grouped[category]) {
        grouped[category] = [];
      }

      grouped[category].push({
        original: error,
        message,
        context
      });
    });

    return grouped;
  };

})(Drupal, drupalSettings);
