/**
 * @file
 * Auto-refresh functionality for FFmpeg Media UI dashboard.
 *
 * Provides live updates for active conversions, queue status, and statistics.
 */

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

  /**
   * Dashboard auto-refresh behavior.
   *
   * @type {Drupal~behavior}
   */
  Drupal.behaviors.ffmpegMediaUiDashboardRefresh = {
    attach: function (context) {
      once('ffmpeg-dashboard-refresh', '[data-ffmpeg-dashboard]', context).forEach(function (dashboard) {
        var settings = drupalSettings.ffmpegMediaUi || {};
        var ajaxUrl = settings.dashboardAjaxUrl;
        var configuredInterval = Number(settings.refreshInterval);
        var refreshInterval = Number.isFinite(configuredInterval) && configuredInterval >= 0
          ? configuredInterval
          : 5000;

        // Respect "0 disables refresh" from UI settings.
        if (!ajaxUrl || refreshInterval === 0) {
          return;
        }

        var refresher = new DashboardRefresher(dashboard, ajaxUrl, refreshInterval);
        refresher.start();

        // Store reference for cleanup.
        dashboard._ffmpegRefresher = refresher;
      });
    },

    detach: function (context, settings, trigger) {
      if (trigger === 'unload') {
        var dashboards = context.querySelectorAll
          ? context.querySelectorAll('[data-ffmpeg-dashboard]')
          : [];

        dashboards.forEach(function (dashboard) {
          if (dashboard._ffmpegRefresher) {
            dashboard._ffmpegRefresher.stop();
            delete dashboard._ffmpegRefresher;
          }
        });
      }
    }
  };

  /**
   * Dashboard refresher class.
   *
   * @param {HTMLElement} dashboard - The dashboard container element.
   * @param {string} ajaxUrl - The URL for AJAX updates.
   * @param {number} interval - Base refresh interval in milliseconds.
   */
  function DashboardRefresher(dashboard, ajaxUrl, interval) {
    this.dashboard = dashboard;
    this.ajaxUrl = ajaxUrl;
    this.baseInterval = interval;
    this.timer = null;
    this.isRefreshing = false;
    this._lastRefresh = null;
    this._prefersReducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    this.ensureMetaElements();
    this._lastUpdatedNode = this.dashboard.querySelector('[data-ffmpeg-dashboard-last-updated]');
    this._refreshStateNode = this.dashboard.querySelector('[data-ffmpeg-dashboard-refresh-state]');
  }

  /**
   * Gets the authoritative queue total for dashboard display.
   *
   * Prefer the deduplicated queued_media count when present; fall back to the
   * backend queue total for older responses.
   *
   * @param {Object} queueStatus - Queue status payload from the server.
   * @returns {number}
   *   The authoritative queued item count.
   */
  function getQueueTotal(queueStatus) {
    if (!queueStatus) {
      return 0;
    }

    var queuedMedia = Number(queueStatus.queued_media);
    if (Number.isFinite(queuedMedia)) {
      return Math.max(0, queuedMedia);
    }

    var totalCount = Number(queueStatus.total_count);
    return Number.isFinite(totalCount) ? Math.max(0, totalCount) : 0;
  }

  /**
   * Ensures dashboard refresh meta UI exists (theme-override safe).
   */
  DashboardRefresher.prototype.ensureMetaElements = function () {
    var header = this.dashboard.querySelector('.ffmpeg-dashboard-header');
    if (!header) {
      return;
    }

    var meta = header.querySelector('.ffmpeg-dashboard-meta');
    if (!meta) {
      meta = document.createElement('div');
      meta.className = 'ffmpeg-dashboard-meta';
      header.appendChild(meta);
    }

    if (!meta.querySelector('[data-ffmpeg-dashboard-refresh-state]')) {
      var refreshState = document.createElement('span');
      refreshState.className = 'ffmpeg-dashboard-meta__item ffmpeg-dashboard-meta__item--refresh';
      refreshState.setAttribute('data-ffmpeg-dashboard-refresh-state', '');
      refreshState.setAttribute('data-state', 'idle');
      refreshState.textContent = Drupal.t('Live updates enabled');
      meta.appendChild(refreshState);
    }

    if (!meta.querySelector('[data-ffmpeg-dashboard-last-updated]')) {
      var updatedLabel = document.createElement('span');
      updatedLabel.className = 'ffmpeg-dashboard-meta__item ffmpeg-dashboard-meta__item--updated';
      updatedLabel.appendChild(document.createTextNode(Drupal.t('Last update:') + ' '));

      var updatedTime = document.createElement('time');
      updatedTime.setAttribute('data-ffmpeg-dashboard-last-updated', '');
      updatedTime.textContent = Drupal.t('just now');
      updatedLabel.appendChild(updatedTime);

      meta.appendChild(updatedLabel);
    }
  };

  /**
   * Starts the auto-refresh timer.
   */
  DashboardRefresher.prototype.start = function () {
    if (this.baseInterval <= 0) {
      this.setRefreshState('disabled');
      this.syncLiveRegionFromDom();
      return;
    }

    var self = this;
    this.setRefreshState('idle');
    this.syncLiveRegionFromDom();
    this.updateLastUpdated();

    this.timer = setInterval(function () {
      var hasActive = self.hasActiveConversions();

      // Refresh every 5s when active conversions exist, every 30s otherwise.
      if (hasActive || self.shouldPeriodicRefresh()) {
        self.refresh();
      }
    }, this.baseInterval);
  };

  /**
   * Stops the auto-refresh timer.
   */
  DashboardRefresher.prototype.stop = function () {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  };

  /**
   * Checks if there are active conversions displayed.
   *
   * @returns {boolean} True if there are active conversions.
   */
  DashboardRefresher.prototype.hasActiveConversions = function () {
    var activeSection = this.dashboard.querySelector('[data-active-conversions]');
    if (!activeSection) {
      return false;
    }

    var tbody = activeSection.querySelector('[data-active-tbody]');
    return tbody && tbody.children.length > 0;
  };

  /**
   * Determines if a periodic refresh should occur (every 30 seconds).
   *
   * @returns {boolean} True if periodic refresh should occur.
   */
  DashboardRefresher.prototype.shouldPeriodicRefresh = function () {
    if (!this._lastRefresh) {
      this._lastRefresh = Date.now();
      return true;
    }

    var elapsed = Date.now() - this._lastRefresh;
    if (elapsed >= 30000) {
      this._lastRefresh = Date.now();
      return true;
    }

    return false;
  };

  /**
   * Fetches updated data and refreshes the display.
   */
  DashboardRefresher.prototype.refresh = function () {
    if (this.isRefreshing) {
      return;
    }

    var self = this;
    this.isRefreshing = true;
    this.dashboard.setAttribute('aria-busy', 'true');
    this.setRefreshState('loading');

    fetch(this.ajaxUrl, {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'X-Requested-With': 'XMLHttpRequest'
      },
      credentials: 'same-origin'
    })
      .then(function (response) {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(function (data) {
        self.updateDashboard(data);
        self._lastRefresh = Date.now();
        self.updateLastUpdated();
        self.setRefreshState('idle');
      })
      .catch(function (error) {
        // Keep current values visible and announce degraded refresh state.
        console.warn('FFmpeg dashboard refresh failed:', error.message);
        self.setRefreshState('error');
        self.announceRefreshFailure();
      })
      .finally(function () {
        self.isRefreshing = false;
        self.dashboard.setAttribute('aria-busy', 'false');
      });
  };

  /**
   * Updates the dashboard with new data.
   *
   * @param {Object} data - The response data from the server.
   */
  DashboardRefresher.prototype.updateDashboard = function (data) {
    // Update queue status.
    if (data.queue_status) {
      this.updateValue('[data-stat="queue_normal"]', data.queue_status.normal_count);
      this.updateValue('[data-stat="queue_large"]', data.queue_status.large_count);
      this.updateValue('[data-stat="queue_total"]', getQueueTotal(data.queue_status));
    }

    // Update conversion stats.
    if (data.stats) {
      this.updateValue('[data-stat="total"]', data.stats.total);
      this.updateValue('[data-stat="completed"]', data.stats.completed);
      this.updateValue('[data-stat="failed"]', data.stats.failed);
      this.updateValue('[data-stat="pending"]', data.stats.pending);
      this.updateValue('[data-stat="converting"]', data.stats.converting);
      this.updateValue('[data-stat="success_rate"]', data.stats.success_rate + '%');
    }

    // Update 24h stats.
    if (data.stats_24h) {
      this.updateValue('[data-stat="24h_completed"]', data.stats_24h.completed);
      this.updateValue('[data-stat="24h_failed"]', data.stats_24h.failed);
      this.updateValue('[data-stat="24h_success_rate"]', data.stats_24h.success_rate + '%');

      var avgTime = data.stats_24h.average_time;
      this.updateValue('[data-stat="24h_avg_time"]', avgTime ? Math.round(avgTime) + 's' : 'N/A');
    }

    // Update active conversions.
    if (data.active_conversions) {
      this.updateActiveConversions(data.active_conversions);
    }

    this.updateLiveRegion(data);
  };

  /**
   * Updates a single value element with animation.
   *
   * @param {string} selector - The data attribute selector.
   * @param {string|number} value - The new value.
   */
  DashboardRefresher.prototype.updateValue = function (selector, value) {
    var element = this.dashboard.querySelector(selector);
    if (!element) {
      return;
    }

    var currentValue = element.textContent.trim();
    var newValue = String(value);

    if (currentValue !== newValue) {
      element.textContent = newValue;

      if (!this._prefersReducedMotion) {
        element.classList.add('ffmpeg-value-updated');
        setTimeout(function () {
          element.classList.remove('ffmpeg-value-updated');
        }, 1000);
      }
    }
  };

  /**
   * Updates the active conversions table.
   *
   * @param {Array} conversions - Array of active conversion data.
   */
  DashboardRefresher.prototype.updateActiveConversions = function (conversions) {
    var activeSection = this.dashboard.querySelector('[data-active-conversions]');
    if (!activeSection) {
      return;
    }

    var normalizedConversions = Array.isArray(conversions) ? conversions : [];

    // Update count badge.
    var countBadge = activeSection.querySelector('[data-active-count]');
    if (countBadge) {
      countBadge.textContent = normalizedConversions.length;
    }

    var table = activeSection.querySelector('[data-active-table]');
    var tbody = activeSection.querySelector('[data-active-tbody]');
    if (!table || !tbody) {
      return;
    }

    // If no active conversions, show empty state.
    if (normalizedConversions.length === 0) {
      tbody.innerHTML = '';
      this.showEmptyState(activeSection, table);
      return;
    }

    this.hideEmptyState(activeSection, table);

    // Update existing rows and add new ones (table diffing).
    var existingRows = new Map();
    Array.from(tbody.querySelectorAll('tr[data-media-id]')).forEach(function (row) {
      existingRows.set(row.dataset.mediaId, row);
    });

    var fragment = document.createDocumentFragment();
    var self = this;
    normalizedConversions.forEach(function (conversion) {
      var mediaId = String(conversion.media_id);
      var row = existingRows.get(mediaId);

      if (row) {
        self.updateConversionRow(row, conversion);
      }
      else {
        row = self.createConversionRow(conversion);
        self.markRowAsNew(row);
      }

      if (row) {
        fragment.appendChild(row);
      }

      existingRows.delete(mediaId);
    });

    // Remove rows for completed/removed conversions.
    existingRows.forEach(function (row) {
      row.remove();
    });

    // Re-append in server order.
    tbody.appendChild(fragment);
  };

  /**
   * Updates the dashboard live region for screen readers.
   *
   * @param {Object} data - The response data from the server.
   */
  DashboardRefresher.prototype.updateLiveRegion = function (data) {
    var liveRegion = this.dashboard.querySelector('[data-ffmpeg-dashboard-live]');
    if (!liveRegion) {
      return;
    }

    var pending = getQueueTotal(data.queue_status);
    var active = 0;
    if (Array.isArray(data.active_conversions)) {
      active = data.active_conversions.length;
    }
    else if (
      data.active_conversions &&
      Number.isFinite(Number(data.active_conversions.count))
    ) {
      active = Number(data.active_conversions.count);
    }

    var message = Drupal.t('Queued items: @pending. Active conversions: @active.', {
      '@pending': pending,
      '@active': active
    });

    if (liveRegion.textContent !== message) {
      liveRegion.textContent = message;
    }
  };

  /**
   * Syncs live region with current DOM values before first AJAX refresh.
   */
  DashboardRefresher.prototype.syncLiveRegionFromDom = function () {
    var queueTotalNode = this.dashboard.querySelector('[data-stat="queue_total"]');
    var activeCountNode = this.dashboard.querySelector('[data-active-count]');
    var queueTotal = queueTotalNode ? Number(queueTotalNode.textContent.trim()) : 0;
    var activeCount = activeCountNode ? Number(activeCountNode.textContent.trim()) : 0;

    this.updateLiveRegion({
      queue_status: { queued_media: Number.isFinite(queueTotal) ? Math.max(0, queueTotal) : 0 },
      active_conversions: { count: Math.max(0, Number.isFinite(activeCount) ? activeCount : 0) },
    });
  };

  /**
   * Updates the "last updated" UI badge.
   */
  DashboardRefresher.prototype.updateLastUpdated = function () {
    if (!this._lastUpdatedNode) {
      return;
    }

    var now = new Date();
    this._lastUpdatedNode.dateTime = now.toISOString();
    this._lastUpdatedNode.textContent = now.toLocaleTimeString([], {
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit'
    });
  };

  /**
   * Updates the refresh state badge.
   *
   * @param {string} state - idle, loading, error, or disabled.
   */
  DashboardRefresher.prototype.setRefreshState = function (state) {
    if (!this._refreshStateNode) {
      return;
    }

    this._refreshStateNode.setAttribute('data-state', state);

    var labelMap = {
      idle: Drupal.t('Live updates enabled'),
      loading: Drupal.t('Live updates in progress'),
      error: Drupal.t('Live updates degraded'),
      disabled: Drupal.t('Live updates disabled')
    };

    this._refreshStateNode.textContent = labelMap[state] || labelMap.idle;
  };

  /**
   * Announces refresh failure in the ARIA live region.
   */
  DashboardRefresher.prototype.announceRefreshFailure = function () {
    var liveRegion = this.dashboard.querySelector('[data-ffmpeg-dashboard-live]');
    if (!liveRegion) {
      return;
    }

    liveRegion.textContent = Drupal.t('Live update failed. Showing last known values.');
  };

  /**
   * Updates a single conversion row.
   *
   * @param {HTMLElement} row - The table row element.
   * @param {Object} conversion - The conversion data.
   */
  DashboardRefresher.prototype.updateConversionRow = function (row, conversion) {
    var percent = Number(conversion.percent) || 0;
    var normalizedStatus = this.normalizeStatus(conversion.status);
    var statusLabel = conversion.status_label || conversion.status || '';
    var stepText = conversion.current_step || '-';

    var statusBadge = row.querySelector('.status-badge');
    if (statusBadge) {
      statusBadge.className = 'status-badge status-badge--' + normalizedStatus;
      statusBadge.textContent = statusLabel;
    }

    // Update progress bar.
    var progressBar = row.querySelector('.progress-bar');
    var progressFill = row.querySelector('[data-progress]');
    var progressText = row.querySelector('[data-progress-text]');

    if (progressBar) {
      progressBar.setAttribute('aria-valuenow', String(percent));
      progressBar.setAttribute('aria-valuetext', percent + '%');
    }

    if (progressFill) {
      progressFill.style.width = percent + '%';
    }

    if (progressText) {
      progressText.textContent = percent + '%';
    }

    // Update step.
    var stepCell = row.querySelector('[data-step]');
    if (stepCell) {
      stepCell.textContent = stepText;
    }

    // Update duration.
    var durationCell = row.querySelector('[data-duration]');
    if (durationCell) {
      durationCell.textContent = conversion.duration || '-';
    }

    // Update remaining time.
    var remainingCell = row.querySelector('[data-remaining]');
    if (remainingCell) {
      remainingCell.textContent = conversion.estimated_remaining || '-';
    }

    var previousPercent = row.dataset.percent ? Number(row.dataset.percent) : null;
    var previousStatus = row.dataset.status || null;
    var previousStep = row.dataset.step || null;
    var percentDelta = previousPercent !== null ? Math.abs(percent - previousPercent) : null;

    row.dataset.percent = String(percent);
    row.dataset.status = normalizedStatus;
    row.dataset.step = stepText;

    var shouldMarkUpdated =
      (previousStatus && previousStatus !== normalizedStatus) ||
      (previousStep && previousStep !== stepText) ||
      (percentDelta !== null && percentDelta >= 5);

    if (shouldMarkUpdated) {
      this.markRowAsUpdated(row);
    }
  };

  /**
   * Creates a conversion row element.
   *
   * @param {Object} conversion - The conversion data.
   * @returns {HTMLTableRowElement} The created row.
   */
  DashboardRefresher.prototype.createConversionRow = function (conversion) {
    var row = document.createElement('tr');
    row.dataset.mediaId = String(conversion.media_id);

    var percent = Number(conversion.percent) || 0;
    var mediaName = conversion.media_name || Drupal.t('Unknown');
    var statusLabel = conversion.status_label || conversion.status || '';
    var normalizedStatus = this.normalizeStatus(conversion.status);

    // Media cell.
    var mediaCell = document.createElement('td');
    if (conversion.media_url) {
      var link = document.createElement('a');
      link.href = conversion.media_url;
      link.textContent = mediaName;
      mediaCell.appendChild(link);
    }
    else {
      mediaCell.textContent = mediaName;
    }

    var idSpan = document.createElement('span');
    idSpan.className = 'ffmpeg-media-id';
    idSpan.textContent = '#' + conversion.media_id;
    mediaCell.appendChild(document.createTextNode(' '));
    mediaCell.appendChild(idSpan);

    // Status cell.
    var statusCell = document.createElement('td');
    var statusBadge = document.createElement('span');
    statusBadge.className = 'status-badge status-badge--' + normalizedStatus;
    statusBadge.textContent = statusLabel;
    statusCell.appendChild(statusBadge);

    // Progress cell.
    var progressCell = document.createElement('td');
    var progressBar = document.createElement('div');
    progressBar.className = 'progress-bar';
    progressBar.setAttribute('role', 'progressbar');
    progressBar.setAttribute('aria-valuemin', '0');
    progressBar.setAttribute('aria-valuemax', '100');
    progressBar.setAttribute('aria-valuenow', String(percent));
    progressBar.setAttribute('aria-valuetext', percent + '%');
    var progressFill = document.createElement('div');
    progressFill.className = 'progress-bar__fill';
    progressFill.style.width = percent + '%';
    progressFill.setAttribute('data-progress', '');
    var progressText = document.createElement('span');
    progressText.className = 'progress-bar__text';
    progressText.setAttribute('data-progress-text', '');
    progressText.textContent = percent + '%';
    progressBar.appendChild(progressFill);
    progressBar.appendChild(progressText);
    progressCell.appendChild(progressBar);

    // Step cell.
    var stepCell = document.createElement('td');
    stepCell.className = 'ffmpeg-step-cell';
    stepCell.setAttribute('data-step', '');
    stepCell.textContent = conversion.current_step || '-';

    // Duration cell.
    var durationCell = document.createElement('td');
    durationCell.setAttribute('data-duration', '');
    durationCell.textContent = conversion.duration || '-';

    // Remaining cell.
    var remainingCell = document.createElement('td');
    remainingCell.setAttribute('data-remaining', '');
    remainingCell.textContent = conversion.estimated_remaining || '-';

    row.appendChild(mediaCell);
    row.appendChild(statusCell);
    row.appendChild(progressCell);
    row.appendChild(stepCell);
    row.appendChild(durationCell);
    row.appendChild(remainingCell);

    row.dataset.percent = String(percent);
    row.dataset.status = normalizedStatus;
    row.dataset.step = conversion.current_step || '-';

    return row;
  };

  /**
   * Normalizes status for use in CSS class names.
   *
   * @param {string} status - Status string.
   * @returns {string} Normalized status.
   */
  DashboardRefresher.prototype.normalizeStatus = function (status) {
    var allowed = ['pending', 'converting', 'completed', 'failed'];
    return allowed.indexOf(status) !== -1 ? status : 'unknown';
  };

  /**
   * Marks a row as newly added.
   *
   * @param {HTMLTableRowElement} row - The row element.
   */
  DashboardRefresher.prototype.markRowAsNew = function (row) {
    if (this._prefersReducedMotion) {
      return;
    }

    row.classList.add('ffmpeg-row--new');
    this.setRowBadge(row, 'new', Drupal.t('New'));

    var self = this;
    setTimeout(function () {
      row.classList.remove('ffmpeg-row--new');
      self.clearRowBadge(row, 'new');
    }, 4000);
  };

  /**
   * Marks a row as updated.
   *
   * @param {HTMLTableRowElement} row - The row element.
   */
  DashboardRefresher.prototype.markRowAsUpdated = function (row) {
    if (this._prefersReducedMotion) {
      return;
    }

    row.classList.remove('ffmpeg-row--updated');
    void row.offsetWidth;
    row.classList.add('ffmpeg-row--updated');
    this.setRowBadge(row, 'updated', Drupal.t('Updated'));

    var self = this;
    setTimeout(function () {
      row.classList.remove('ffmpeg-row--updated');
      self.clearRowBadge(row, 'updated');
    }, 2500);
  };

  /**
   * Adds or updates a row badge.
   *
   * @param {HTMLTableRowElement} row - The row element.
   * @param {string} type - Badge type ("new" or "updated").
   * @param {string} label - Badge label.
   */
  DashboardRefresher.prototype.setRowBadge = function (row, type, label) {
    var mediaCell = row.querySelector('td');
    if (!mediaCell) {
      return;
    }

    var badge = mediaCell.querySelector('.ffmpeg-row-badge');
    if (!badge) {
      badge = document.createElement('span');
      badge.className = 'ffmpeg-row-badge';
      mediaCell.appendChild(badge);
    }

    badge.classList.remove('ffmpeg-row-badge--new', 'ffmpeg-row-badge--updated');
    badge.classList.add('ffmpeg-row-badge--' + type);
    badge.textContent = label;
  };

  /**
   * Clears a row badge if it matches the given type.
   *
   * @param {HTMLTableRowElement} row - The row element.
   * @param {string} type - Badge type.
   */
  DashboardRefresher.prototype.clearRowBadge = function (row, type) {
    var mediaCell = row.querySelector('td');
    if (!mediaCell) {
      return;
    }

    var badge = mediaCell.querySelector('.ffmpeg-row-badge');
    if (badge && badge.classList.contains('ffmpeg-row-badge--' + type)) {
      badge.remove();
    }
  };

  /**
   * Shows the empty state for active conversions.
   *
   * @param {HTMLElement} section - The active conversions section.
   * @param {HTMLElement} table - The active conversions table.
   */
  DashboardRefresher.prototype.showEmptyState = function (section, table) {
    if (table) {
      table.classList.add('is-hidden');
    }
    var emptyState = section.querySelector('[data-active-empty]');
    if (emptyState) {
      emptyState.classList.add('is-visible');
      emptyState.setAttribute('aria-hidden', 'false');
    }
  };

  /**
   * Hides the empty state if present.
   *
   * @param {HTMLElement} section - The active conversions section.
   * @param {HTMLElement} table - The active conversions table.
   */
  DashboardRefresher.prototype.hideEmptyState = function (section, table) {
    var emptyState = section.querySelector('[data-active-empty]');
    if (emptyState) {
      emptyState.classList.remove('is-visible');
      emptyState.setAttribute('aria-hidden', 'true');
    }

    if (table) {
      table.classList.remove('is-hidden');
    }
  };

})(Drupal, drupalSettings, once);
