/**
 * DsComponentBase - Base class for all design system components
 *
 * Provides standardized:
 * - Component lifecycle (connectedCallback, disconnectedCallback, attributeChangedCallback)
 * - Standard attributes (variant, size, disabled, loading, aria-* attributes)
 * - Standard methods (focus(), blur())
 * - Theme change handling
 * - Accessibility features (WCAG 2.1 AA)
 * - Event emission patterns (ds-* namespaced events)
 *
 * All Web Components should extend this class to ensure API consistency.
 *
 * Usage:
 *   class DsButton extends DsComponentBase {
 *     static get observedAttributes() {
 *       return [...super.observedAttributes(), 'type'];
 *     }
 *   }
 */

import StylesheetManager from '../core/stylesheet-manager.js';

export class DsComponentBase extends HTMLElement {
  /**
   * Standard observed attributes all components should support
   * Subclasses should extend this list with component-specific attributes
   */
  static get observedAttributes() {
    return [
      // State management
      'disabled',
      'loading',
      // Accessibility
      'aria-label',
      'aria-disabled',
      'aria-expanded',
      'aria-hidden',
      'aria-pressed',
      'aria-selected',
      'aria-invalid',
      'aria-describedby',
      'aria-labelledby',
      // Focus management
      'tabindex'
    ];
  }

  /**
   * Initialize component
   * Subclasses should call super.constructor()
   */
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    // Initialize standard properties
    this._disabled = false;
    this._loading = false;
    this._initialized = false;
    this._cleanup = [];
    this._themeObserver = null;
    this._resizeObserver = null;
  }

  /**
   * Called when component is inserted into DOM
   * Loads stylesheets, syncs attributes, and renders
   */
  async connectedCallback() {
    try {
      // Attach stylesheets
      await StylesheetManager.attachStyles(this.shadowRoot);

      // Sync HTML attributes to JavaScript properties
      this._syncAttributesToProperties();

      // Initialize theme observer for dark/light mode changes
      this._initializeThemeObserver();

      // Allow subclass to setup event listeners
      this.setupEventListeners?.();

      // Initial render
      this._initialized = true;
      this.render?.();

      // Emit connected event for testing/debugging
      this.emit('ds-component-connected', {
        component: this.constructor.name
      });
    } catch (error) {
      console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
      this.emit('ds-component-error', { error: error.message });
    }
  }

  /**
   * Called when component is removed from DOM
   * Cleanup event listeners and observers
   */
  disconnectedCallback() {
    // Allow subclass to cleanup
    this.cleanupEventListeners?.();

    // Remove theme observer
    if (this._themeObserver) {
      window.removeEventListener('theme-changed', this._themeObserver);
      this._themeObserver = null;
    }

    // Disconnect resize observer if present
    if (this._resizeObserver) {
      this._resizeObserver.disconnect();
      this._resizeObserver = null;
    }

    // Cleanup all tracked listeners
    this._cleanup.forEach(({ element, event, handler }) => {
      element.removeEventListener(event, handler);
    });
    this._cleanup = [];
  }

  /**
   * Called when observed attributes change
   * Subclasses can override but should call super.attributeChangedCallback()
   */
  attributeChangedCallback(name, oldValue, newValue) {
    if (!this._initialized || oldValue === newValue) return;

    // Handle standard attributes
    switch (name) {
      case 'disabled':
        this._disabled = newValue !== null;
        this._updateAccessibility();
        break;
      case 'loading':
        this._loading = newValue !== null;
        break;
      case 'aria-label':
      case 'aria-disabled':
      case 'aria-expanded':
      case 'aria-hidden':
      case 'aria-pressed':
      case 'aria-selected':
      case 'aria-invalid':
        this._updateAccessibility();
        break;
      case 'tabindex':
        // Update tabindex if changed
        this.setAttribute('tabindex', newValue || '0');
        break;
    }

    // Re-render component
    this.render?.();
  }

  /**
   * Sync HTML attributes to JavaScript properties
   * @private
   */
  _syncAttributesToProperties() {
    this._disabled = this.hasAttribute('disabled');
    this._loading = this.hasAttribute('loading');

    // Ensure accessible tabindex
    if (!this.hasAttribute('tabindex')) {
      this.setAttribute('tabindex', this._disabled ? '-1' : '0');
    } else if (this._disabled && this.getAttribute('tabindex') !== '-1') {
      this.setAttribute('tabindex', '-1');
    }
  }

  /**
   * Initialize theme observer to listen for dark/light mode changes
   * @private
   */
  _initializeThemeObserver() {
    this._themeObserver = () => {
      // Re-render when theme changes
      this.render?.();
    };

    window.addEventListener('theme-changed', this._themeObserver);
  }

  /**
   * Update accessibility attributes based on component state
   * @private
   */
  _updateAccessibility() {
    // Update aria-disabled to match disabled state
    this.setAttribute('aria-disabled', this._disabled);

    // Ensure proper tab order when disabled
    if (this._disabled) {
      this.setAttribute('tabindex', '-1');
    } else if (this.getAttribute('tabindex') === '-1') {
      this.setAttribute('tabindex', '0');
    }
  }

  /**
   * Standard properties with getters/setters
   */

  get disabled() { return this._disabled; }
  set disabled(value) {
    this._disabled = !!value;
    value ? this.setAttribute('disabled', '') : this.removeAttribute('disabled');
  }

  get loading() { return this._loading; }
  set loading(value) {
    this._loading = !!value;
    value ? this.setAttribute('loading', '') : this.removeAttribute('loading');
  }

  get ariaLabel() { return this.getAttribute('aria-label'); }
  set ariaLabel(value) {
    value ? this.setAttribute('aria-label', value) : this.removeAttribute('aria-label');
  }

  get ariaDescribedBy() { return this.getAttribute('aria-describedby'); }
  set ariaDescribedBy(value) {
    value ? this.setAttribute('aria-describedby', value) : this.removeAttribute('aria-describedby');
  }

  /**
   * Standard methods for focus management
   */

  focus(options) {
    // Find first focusable element in shadow DOM
    const focusable = this.shadowRoot.querySelector('button, input, [tabindex]');
    if (focusable) {
      focusable.focus(options);
    }
  }

  blur() {
    const focused = this.shadowRoot.activeElement;
    if (focused && typeof focused.blur === 'function') {
      focused.blur();
    }
  }

  /**
   * Emit custom event (ds-* namespaced)
   * @param {string} eventName - Event name (without 'ds-' prefix)
   * @param {object} detail - Event detail object
   * @returns {boolean} Whether event was not prevented
   */
  emit(eventName, detail = {}) {
    const event = new CustomEvent(`ds-${eventName}`, {
      detail,
      composed: true,        // Bubble out of shadow DOM
      bubbles: true,         // Standard bubbling
      cancelable: true       // Allow preventDefault()
    });

    return this.dispatchEvent(event);
  }

  /**
   * Add event listener with automatic cleanup
   * Listener is automatically removed in disconnectedCallback()
   * @param {HTMLElement} element - Element to listen on
   * @param {string} event - Event name
   * @param {Function} handler - Event handler
   * @param {object} [options] - Event listener options
   */
  addEventListener(element, event, handler, options = false) {
    element.addEventListener(event, handler, options);
    this._cleanup.push({ element, event, handler });
  }

  /**
   * Render method stub - override in subclass
   * Called on initialization and on attribute changes
   */
  render() {
    // Override in subclass
  }

  /**
   * Setup event listeners - override in subclass
   * Called in connectedCallback after render
   */
  setupEventListeners() {
    // Override in subclass
  }

  /**
   * Cleanup event listeners - override in subclass
   * Called in disconnectedCallback
   */
  cleanupEventListeners() {
    // Override in subclass
  }

  /**
   * Get computed CSS variable value
   * @param {string} varName - CSS variable name (with or without --)
   * @returns {string} CSS variable value
   */
  getCSSVariable(varName) {
    const name = varName.startsWith('--') ? varName : `--${varName}`;
    return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
  }

  /**
   * Check if component is in dark mode
   * @returns {boolean}
   */
  isDarkMode() {
    return document.documentElement.classList.contains('dark') ||
           window.matchMedia('(prefers-color-scheme: dark)').matches;
  }

  /**
   * Debounce function execution
   * @param {Function} fn - Function to debounce
   * @param {number} delay - Delay in milliseconds
   * @returns {Function} Debounced function
   */
  debounce(fn, delay = 300) {
    let timeoutId;
    return (...args) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => fn.apply(this, args), delay);
    };
  }

  /**
   * Throttle function execution
   * @param {Function} fn - Function to throttle
   * @param {number} limit - Time limit in milliseconds
   * @returns {Function} Throttled function
   */
  throttle(fn, limit = 300) {
    let inThrottle;
    return (...args) => {
      if (!inThrottle) {
        fn.apply(this, args);
        inThrottle = true;
        setTimeout(() => (inThrottle = false), limit);
      }
    };
  }

  /**
   * Wait for an event
   * @param {string} eventName - Event name to wait for
   * @param {number} [timeout] - Optional timeout in milliseconds
   * @returns {Promise} Resolves with event detail
   */
  waitForEvent(eventName, timeout = null) {
    return new Promise((resolve, reject) => {
      const handler = (e) => {
        this.removeEventListener(eventName, handler);
        clearTimeout(timeoutId);
        resolve(e.detail);
      };

      this.addEventListener(eventName, handler);

      let timeoutId;
      if (timeout) {
        timeoutId = setTimeout(() => {
          this.removeEventListener(eventName, handler);
          reject(new Error(`Event '${eventName}' did not fire within ${timeout}ms`));
        }, timeout);
      }
    });
  }

  /**
   * Get HTML structure for rendering in shadow DOM
   * Useful for preventing repeated string concatenation
   * @param {string} html - HTML template
   * @param {object} [data] - Data for template interpolation
   * @returns {string} Rendered HTML
   */
  renderTemplate(html, data = {}) {
    return html.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] ?? match);
  }

  /**
   * Static helper to create component with attributes
   * @param {object} attrs - Attributes to set
   * @returns {HTMLElement} Component instance
   */
  static create(attrs = {}) {
    const element = document.createElement(this.name.replace(/([A-Z])/g, '-$1').toLowerCase());
    Object.entries(attrs).forEach(([key, value]) => {
      if (value === true) {
        element.setAttribute(key, '');
      } else if (value !== false && value !== null && value !== undefined) {
        element.setAttribute(key, value);
      }
    });
    return element;
  }
}

// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
  module.exports = { DsComponentBase };
}

// Make available globally
if (typeof window !== 'undefined') {
  window.DsComponentBase = DsComponentBase;
}
