/**
 * @file
 * Batch operations for media items.
 */

(function (Drupal, drupalSettings, once) {
  'use strict';

  Drupal.ffmpegMedia = Drupal.ffmpegMedia || {};

  /**
   * Batch operations manager class.
   */
  Drupal.ffmpegMedia.BatchOperations = class BatchOperations {
    /**
     * Creates a batch operations manager.
     *
     * @param {HTMLElement} container - Container element.
     * @param {Object} options - Configuration options.
     */
    constructor(container, options = {}) {
      this.container = container;
      this.options = {
        selectionAttribute: 'data-media-id',
        onSelectionChange: options.onSelectionChange || (() => {}),
        onBatchAction: options.onBatchAction || (() => {}),
        ...options
      };

      this.actionDefinitions = this._getActionDefinitions();
      this.availableActions = this._resolveActions(
        Array.isArray(this.options.actions)
          ? this.options.actions
          : (Array.isArray(drupalSettings.ffmpegMediaBatch?.allowedActions)
            ? drupalSettings.ffmpegMediaBatch.allowedActions
            : [])
      );
      this.selectedItems = new Set();
      this.isSelectAllActive = false;
      this.isProcessing = false;
      this._csrfTokenPromise = null;
      this.menuId = 'ffmpeg-batch-menu-' + Math.random().toString(36).slice(2, 11);
      this._onSelectAllChange = null;
      this._onTriggerClick = null;
      this._onTriggerKeydown = null;
      this._onDocumentClick = null;
      this._onMenuKeydown = null;
      this._onMenuClick = null;
      this._onContainerChange = null;

      this._init();
    }

    /**
     * Initializes the batch operations UI.
     *
     * @private
     */
    _init() {
      this._createToolbar();
      if (!this.toolbar) {
        return;
      }
      this._bindEvents();
    }

    /**
     * Returns supported batch action definitions.
     *
     * @returns {Object<string, {label: string, icon: string}>} Action config.
     * @private
     */
    _getActionDefinitions() {
      return {
        transcode: {
          label: Drupal.t('Transcode selected'),
          icon: `
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <polygon points="23 7 16 12 23 17 23 7"/>
              <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
            </svg>
          `
        },
        requeue: {
          label: Drupal.t('Re-queue selected'),
          icon: `
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <polyline points="1 4 1 10 7 10"/>
              <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
            </svg>
          `
        },
        'delete-outputs': {
          label: Drupal.t('Delete outputs'),
          icon: `
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <polyline points="3 6 5 6 21 6"/>
              <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
            </svg>
          `
        }
      };
    }

    /**
     * Resolves configured actions against the supported action set.
     *
     * @param {Array<string>} actions - Requested action names.
     * @returns {Array<string>} Allowed actions in UI order.
     * @private
     */
    _resolveActions(actions) {
      const requestedActions = Array.isArray(actions) ? actions : [];

      return requestedActions.filter((action, index) => {
        return Object.prototype.hasOwnProperty.call(this.actionDefinitions, action)
          && requestedActions.indexOf(action) === index;
      });
    }

    /**
     * Builds the menu button markup for allowed actions.
     *
     * @returns {string} Action button markup.
     * @private
     */
    _buildActionButtonsMarkup() {
      return this.availableActions.map((action) => {
        const definition = this.actionDefinitions[action];
        return `
          <button type="button" class="ffmpeg-batch-action" data-action="${action}" role="menuitem">
            ${definition.icon}
            ${definition.label}
          </button>
        `;
      }).join('');
    }

    /**
     * Creates the batch toolbar.
     *
     * @private
     */
    _createToolbar() {
      const actionButtons = this._buildActionButtonsMarkup();
      if (!actionButtons) {
        this.toolbar = null;
        return;
      }

      this.toolbar = document.createElement('div');
      this.toolbar.className = 'ffmpeg-batch-toolbar';
      this.toolbar.innerHTML = `
        <div class="visually-hidden" role="status" aria-live="polite" aria-atomic="true" data-batch-live></div>
        <div class="ffmpeg-batch-toolbar__left">
          <label class="ffmpeg-batch-select-all">
            <input type="checkbox" class="ffmpeg-batch-select-all__input">
            <span class="ffmpeg-batch-select-all__label">${Drupal.t('Select all')}</span>
          </label>
          <span class="ffmpeg-batch-selected-count"></span>
        </div>
        <div class="ffmpeg-batch-toolbar__actions">
          <div class="ffmpeg-batch-dropdown">
            <button type="button"
              class="ffmpeg-batch-dropdown__trigger"
              disabled
              id="${this.menuId}-trigger"
              aria-haspopup="menu"
              aria-expanded="false"
              aria-controls="${this.menuId}">
              ${Drupal.t('Batch actions')}
              <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <polyline points="6 9 12 15 18 9"/>
              </svg>
            </button>
            <div class="ffmpeg-batch-dropdown__menu" id="${this.menuId}" role="menu" aria-labelledby="${this.menuId}-trigger" aria-hidden="true">
              ${actionButtons}
            </div>
          </div>
          <div class="ffmpeg-batch-profile-select" style="display: none;">
            <select class="ffmpeg-batch-profile">
              <option value="">${Drupal.t('Select profile')}</option>
            </select>
          </div>
        </div>
      `;

      this.container.insertBefore(this.toolbar, this.container.firstChild);

      // Get references.
      this.selectAllCheckbox = this.toolbar.querySelector('.ffmpeg-batch-select-all__input');
      this.selectedCountEl = this.toolbar.querySelector('.ffmpeg-batch-selected-count');
      this.dropdownTrigger = this.toolbar.querySelector('.ffmpeg-batch-dropdown__trigger');
      this.dropdownMenu = this.toolbar.querySelector('.ffmpeg-batch-dropdown__menu');
      this.profileSelect = this.toolbar.querySelector('.ffmpeg-batch-profile-select');
      this.liveRegion = this.toolbar.querySelector('[data-batch-live]');
      this._syncMenuItemTabState(false);
    }

    /**
     * Binds event handlers.
     *
     * @private
     */
    _bindEvents() {
      // Select all checkbox.
      this._onSelectAllChange = (e) => {
        this._toggleSelectAll(e.target.checked);
      };
      this.selectAllCheckbox.addEventListener('change', this._onSelectAllChange);

      // Dropdown toggle.
      this._onTriggerClick = () => {
        if (this.dropdownTrigger.disabled) {
          return;
        }
        this._setMenuOpen(!this._isMenuOpen(), 'first');
      };
      this.dropdownTrigger.addEventListener('click', this._onTriggerClick);

      // Keyboard support for trigger.
      this._onTriggerKeydown = (e) => {
        if (this.dropdownTrigger.disabled) {
          return;
        }

        if (e.key === 'ArrowDown') {
          e.preventDefault();
          this._setMenuOpen(true, 'first');
        }
        else if (e.key === 'ArrowUp') {
          e.preventDefault();
          this._setMenuOpen(true, 'last');
        }
      };
      this.dropdownTrigger.addEventListener('keydown', this._onTriggerKeydown);

      // Close dropdown on outside click.
      this._onDocumentClick = (e) => {
        if (!this.toolbar.contains(e.target)) {
          this._setMenuOpen(false);
        }
      };
      document.addEventListener('click', this._onDocumentClick);

      // Keyboard support for menu navigation.
      this._onMenuKeydown = (e) => {
        if (!this._isMenuOpen()) {
          return;
        }

        const items = this._getMenuItems();
        if (!items.length) {
          return;
        }

        const activeElement = document.activeElement;
        const index = items.indexOf(activeElement);

        if (e.key === 'Escape') {
          e.preventDefault();
          this._setMenuOpen(false, 'trigger');
          return;
        }

        if (e.key === 'Tab') {
          this._setMenuOpen(false);
          return;
        }

        if (e.key === 'ArrowDown') {
          e.preventDefault();
          const nextIndex = index >= 0 ? (index + 1) % items.length : 0;
          items[nextIndex].focus();
        }
        else if (e.key === 'ArrowUp') {
          e.preventDefault();
          const prevIndex = index >= 0 ? (index - 1 + items.length) % items.length : items.length - 1;
          items[prevIndex].focus();
        }
        else if (e.key === 'Home') {
          e.preventDefault();
          items[0].focus();
        }
        else if (e.key === 'End') {
          e.preventDefault();
          items[items.length - 1].focus();
        }
      };
      this.dropdownMenu.addEventListener('keydown', this._onMenuKeydown);

      // Action buttons (delegated).
      this._onMenuClick = (e) => {
        const btn = e.target.closest('.ffmpeg-batch-action');
        if (btn && this.dropdownMenu.contains(btn)) {
          const action = btn.dataset.action || '';
          void this._executeAction(action);
        }
      };
      this.dropdownMenu.addEventListener('click', this._onMenuClick);

      // Delegate checkbox events for items.
      this._onContainerChange = (e) => {
        if (e.target.matches('.ffmpeg-batch-item-checkbox')) {
          const mediaId = e.target.closest('[' + this.options.selectionAttribute + ']')
            ?.getAttribute(this.options.selectionAttribute);

          if (mediaId) {
            const item = e.target.closest('[' + this.options.selectionAttribute + ']');
            if (e.target.checked) {
              this.selectedItems.add(mediaId);
              if (item) {
                item.classList.add('selected');
              }
            }
            else {
              this.selectedItems.delete(mediaId);
              if (item) {
                item.classList.remove('selected');
              }
            }
            this._updateUI();
          }
        }
      };
      this.container.addEventListener('change', this._onContainerChange);
    }

    /**
     * Toggles select all state.
     *
     * @param {boolean} selectAll - Whether to select all.
     * @private
     */
    _toggleSelectAll(selectAll) {
      this.isSelectAllActive = selectAll;

      const items = this.container.querySelectorAll('[' + this.options.selectionAttribute + ']');
      items.forEach((item) => {
        const checkbox = item.querySelector('.ffmpeg-batch-item-checkbox');
        const mediaId = item.getAttribute(this.options.selectionAttribute);

        if (checkbox) {
          checkbox.checked = selectAll;
        }

        if (!mediaId) {
          return;
        }

        if (selectAll && checkbox) {
          this.selectedItems.add(mediaId);
          item.classList.add('selected');
        }
        else {
          this.selectedItems.delete(mediaId);
          item.classList.remove('selected');
        }
      });

      this._updateUI();
    }

    /**
     * Updates the toolbar UI.
     *
     * @private
     */
    _updateUI() {
      const count = this.selectedItems.size;

      // Update count display.
      if (count > 0) {
        this.selectedCountEl.textContent = Drupal.t('@count selected', { '@count': count });
        this.selectedCountEl.style.display = '';
      } else {
        this.selectedCountEl.style.display = 'none';
      }

      this.dropdownTrigger.disabled = this.isProcessing || count === 0;
      this.container.classList.toggle('selection-mode', count > 0);

      // Update select all checkbox state.
      const totalItems = this.container.querySelectorAll('[' + this.options.selectionAttribute + ']').length;
      this.selectAllCheckbox.checked = count === totalItems && count > 0;
      this.selectAllCheckbox.indeterminate = count > 0 && count < totalItems;

      this.options.onSelectionChange(Array.from(this.selectedItems));
    }

    /**
     * Executes a batch action.
     *
     * @param {string} action - Action name.
     * @private
     */
    async _executeAction(action) {
      const mediaIds = Array.from(this.selectedItems);

      if (this.isProcessing || mediaIds.length === 0 || !action) {
        return;
      }

      // Close dropdown.
      this._setMenuOpen(false, 'trigger');

      // Confirm destructive actions.
      if (action === 'delete-outputs') {
        const message = Drupal.t('Are you sure you want to delete outputs for @count media items?', { '@count': mediaIds.length });

        if (!confirm(message)) {
          return;
        }
      }

      // Execute action.
      this.options.onBatchAction(action, mediaIds);
      await this._performBatchAction(action, mediaIds);
    }

    /**
     * Performs the batch action via AJAX.
     *
     * @param {string} action - Action name.
     * @param {Array<string>} mediaIds - Media IDs.
     * @private
     */
    async _performBatchAction(action, mediaIds) {
      try {
        this._setProcessingState(true);
        this._showProgress(Drupal.t('Processing @count items...', { '@count': mediaIds.length }));
        this._announce(Drupal.t('Batch processing started for @count items.', {
          '@count': mediaIds.length
        }));

        const csrfToken = await this._getCsrfToken();

        const response = await fetch(this._getBatchUrl(), {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-CSRF-Token': csrfToken
          },
          body: JSON.stringify({
            action,
            media_ids: mediaIds
          })
        });

        const result = await this._parseJsonResponse(response);
        if (!response.ok) {
          throw new Error(result?.error || Drupal.t('Batch request failed.'));
        }

        if (result.success) {
          const processed = Number(result?.results?.processed || 0);
          const errors = Array.isArray(result?.results?.errors) ? result.results.errors : [];
          const hasErrors = errors.length > 0;

          if (hasErrors) {
            this._showToast(Drupal.t('Batch completed with @count errors.', {
              '@count': errors.length
            }), 'warning');

            this._showError(errors.slice(0, 3).join(' | '));
          }
          else {
            this._showSuccess(Drupal.t('Batch operation completed (@count processed).', {
              '@count': processed
            }));
          }

          // Clear selection.
          this.selectedItems.clear();
          this._toggleSelectAll(false);
          this._announce(Drupal.t('Batch processing finished. @count items processed.', {
            '@count': processed
          }));
        }
        else {
          this._showError(result.error || Drupal.t('Batch operation failed.'));
        }
      }
      catch (error) {
        this._showError(error?.message || Drupal.t('An error occurred while processing the batch operation.'));
        this._announce(Drupal.t('Batch processing failed.'));
        console.error('[FFmpeg Batch]', error);
      }
      finally {
        this._setProcessingState(false);
      }
    }

    /**
     * Returns whether menu is currently open.
     *
     * @returns {boolean} TRUE when menu is open.
     * @private
     */
    _isMenuOpen() {
      return this.dropdownMenu.classList.contains('ffmpeg-batch-dropdown__menu--open');
    }

    /**
     * Returns focusable menu items.
     *
     * @returns {Array<HTMLElement>} Focusable menu buttons.
     * @private
     */
    _getMenuItems() {
      return Array.from(this.dropdownMenu.querySelectorAll('.ffmpeg-batch-action:not([disabled])'))
        .filter((item) => item instanceof HTMLElement);
    }

    /**
     * Sets dropdown state.
     *
     * @param {boolean} isOpen - Whether the menu should be open.
     * @param {'none'|'first'|'last'|'trigger'} focusTarget - Where focus should move.
     * @private
     */
    _setMenuOpen(isOpen, focusTarget = 'none') {
      this.dropdownMenu.classList.toggle('ffmpeg-batch-dropdown__menu--open', isOpen);
      this.dropdownTrigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
      this.dropdownMenu.setAttribute('aria-hidden', isOpen ? 'false' : 'true');
      this._syncMenuItemTabState(isOpen);

      const items = this._getMenuItems();

      if (isOpen) {
        if (focusTarget === 'last' && items.length) {
          items[items.length - 1].focus();
        }
        else if (focusTarget === 'first' && items.length) {
          items[0].focus();
        }
      }
      else if (focusTarget === 'trigger') {
        this.dropdownTrigger.focus();
      }
    }

    /**
     * Synchronizes menuitem tab state.
     *
     * @param {boolean} isOpen - Whether menu is open.
     * @private
     */
    _syncMenuItemTabState(isOpen) {
      this.dropdownMenu.querySelectorAll('.ffmpeg-batch-action').forEach((item) => {
        if (!(item instanceof HTMLElement)) {
          return;
        }
        item.tabIndex = isOpen ? 0 : -1;
      });
    }

    /**
     * Toggles processing state.
     *
     * @param {boolean} isProcessing - Whether processing is active.
     * @private
     */
    _setProcessingState(isProcessing) {
      this.isProcessing = isProcessing;
      this.toolbar.classList.toggle('ffmpeg-batch-toolbar--busy', isProcessing);
      this.selectAllCheckbox.disabled = isProcessing;
      this.dropdownTrigger.disabled = isProcessing || this.selectedItems.size === 0;
      this._setMenuOpen(false);
      this._updateUI();
    }

    /**
     * Announces text for screen readers.
     *
     * @param {string} message - Message text.
     * @private
     */
    _announce(message) {
      if (!this.liveRegion || !message) {
        return;
      }
      this.liveRegion.textContent = '';
      window.setTimeout(() => {
        this.liveRegion.textContent = message;
      }, 25);
    }

    /**
     * Returns the batch endpoint URL.
     *
     * @returns {string} Batch endpoint URL.
     * @private
     */
    _getBatchUrl() {
      if (drupalSettings.ffmpegMediaBatch?.batchUrl) {
        return drupalSettings.ffmpegMediaBatch.batchUrl;
      }
      const basePath = drupalSettings.path?.baseUrl || '/';
      return `${basePath}ffmpeg-media/ajax/batch`;
    }

    /**
     * Resolves a CSRF token for header-protected routes.
     *
     * @returns {Promise<string>} CSRF token.
     * @private
     */
    _getCsrfToken() {
      if (drupalSettings.csrfToken) {
        return Promise.resolve(drupalSettings.csrfToken);
      }

      if (!this._csrfTokenPromise) {
        const basePath = drupalSettings.path?.baseUrl || '/';
        this._csrfTokenPromise = fetch(`${basePath}session/token`, {
          credentials: 'same-origin',
          headers: { 'X-Requested-With': 'XMLHttpRequest' }
        })
          .then((response) => {
            if (!response.ok) {
              throw new Error(Drupal.t('Failed to fetch CSRF token.'));
            }
            return response.text();
          })
          .then((token) => token.trim());
      }

      return this._csrfTokenPromise;
    }

    /**
     * Parses a JSON response safely.
     *
     * @param {Response} response - Fetch response.
     * @returns {Promise<Object>} Parsed JSON object.
     * @private
     */
    async _parseJsonResponse(response) {
      try {
        return await response.json();
      }
      catch (error) {
        return {
          success: false,
          error: Drupal.t('Unexpected server response.')
        };
      }
    }

    /**
     * Shows a progress message.
     *
     * @param {string} message - Progress message.
     * @private
     */
    _showProgress(message) {
      this._showToast(message, 'info');
    }

    /**
     * Shows a success message.
     *
     * @param {string} message - Success message.
     * @private
     */
    _showSuccess(message) {
      this._showToast(message, 'success');
    }

    /**
     * Shows an error message.
     *
     * @param {string} message - Error message.
     * @private
     */
    _showError(message) {
      this._showToast(message, 'error');
    }

    /**
     * Shows a toast notification.
     *
     * @param {string} message - Message text.
     * @param {string} type - Toast type.
     * @private
     */
    _showToast(message, type) {
      const toast = document.createElement('div');
      toast.className = `ffmpeg-toast ${type}`;
      toast.textContent = message;
      toast.setAttribute('role', 'status');
      toast.setAttribute('aria-live', 'polite');
      document.body.appendChild(toast);

      requestAnimationFrame(() => {
        toast.classList.add('visible');
      });

      setTimeout(() => {
        toast.classList.remove('visible');
        setTimeout(() => toast.remove(), 300);
      }, 5000);
    }

    /**
     * Adds checkboxes to all items.
     */
    addCheckboxesToItems() {
      if (!this.toolbar) {
        return;
      }

      const items = this.container.querySelectorAll('[' + this.options.selectionAttribute + ']');

      items.forEach((item) => {
        if (item.querySelector('.ffmpeg-batch-item-checkbox')) {
          return; // Already has checkbox.
        }

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'ffmpeg-batch-item-checkbox';
        checkbox.setAttribute('aria-label', Drupal.t('Select this media item'));

        const wrapper = document.createElement('label');
        wrapper.className = 'ffmpeg-batch-item-checkbox-wrapper';
        wrapper.appendChild(checkbox);

        item.insertBefore(wrapper, item.firstChild);
      });
    }

    /**
     * Gets selected media IDs.
     *
     * @returns {Array<string>} Selected media IDs.
     */
    getSelectedIds() {
      return Array.from(this.selectedItems);
    }

    /**
     * Clears selection.
     */
    clearSelection() {
      this.selectedItems.clear();
      this._toggleSelectAll(false);
      this._updateUI();
    }

    /**
     * Cleans up listeners and instance references.
     */
    destroy() {
      if (!this.toolbar) {
        return;
      }

      this._setMenuOpen(false);

      if (this._onSelectAllChange) {
        this.selectAllCheckbox.removeEventListener('change', this._onSelectAllChange);
      }
      if (this._onTriggerClick) {
        this.dropdownTrigger.removeEventListener('click', this._onTriggerClick);
      }
      if (this._onTriggerKeydown) {
        this.dropdownTrigger.removeEventListener('keydown', this._onTriggerKeydown);
      }
      if (this._onDocumentClick) {
        document.removeEventListener('click', this._onDocumentClick);
      }
      if (this._onMenuKeydown) {
        this.dropdownMenu.removeEventListener('keydown', this._onMenuKeydown);
      }
      if (this._onMenuClick) {
        this.dropdownMenu.removeEventListener('click', this._onMenuClick);
      }
      if (this._onContainerChange) {
        this.container.removeEventListener('change', this._onContainerChange);
      }
    }
  };

  /**
   * Drupal behavior for batch operations.
   */
  Drupal.behaviors.ffmpegMediaBatchOperations = {
    attach: function (context, settings) {
      once('ffmpeg-batch-operations', '[data-ffmpeg-batch-container]', context).forEach(function (element) {
        const batch = new Drupal.ffmpegMedia.BatchOperations(element, {
          selectionAttribute: element.dataset.selectionAttribute || 'data-media-id',
          actions: Array.isArray(settings.ffmpegMediaBatch?.allowedActions)
            ? settings.ffmpegMediaBatch.allowedActions
            : [],
          onSelectionChange: function (selectedIds) {
            // Trigger custom event for other scripts.
            element.dispatchEvent(new CustomEvent('ffmpeg:selectionChange', {
              detail: { selectedIds }
            }));
          },
          onBatchAction: function (action, mediaIds) {
            element.dispatchEvent(new CustomEvent('ffmpeg:batchAction', {
              detail: { action, mediaIds }
            }));
          }
        });

        if (!batch.toolbar) {
          return;
        }

        batch.addCheckboxesToItems();
        element.ffmpegBatchOperations = batch;
      });
    },

    detach: function (context, settings, trigger) {
      if (trigger === 'unload') {
        const elements = context.querySelectorAll ? context.querySelectorAll('[data-ffmpeg-batch-container]') : [];
        elements.forEach(function (element) {
          if (element.ffmpegBatchOperations) {
            element.ffmpegBatchOperations.destroy();
            delete element.ffmpegBatchOperations;
          }
        });
      }
    }
  };

})(Drupal, drupalSettings, once);
