/**
 * @fileoverview OrdoJS Runtime - Minimal runtime components
 */

import type { ComponentData, HydrationData, Props } from '../types/index.js';

/**
 * Component instance for hydrated components
 */
interface ComponentInstance {
  id: string;
  name: string;
  element: Element;
  props: Props;
  state: Record<string, any>;
  eventListeners: Map<string, EventListener>;
  update: () => void;
  unmount: () => void;
}

/**
 * Minimal runtime for hydration and reactivity
 */
export class OrdoJSRuntime {
  private static instance: OrdoJSRuntime;
  private hydratedComponents: Map<string, ComponentInstance> = new Map();
  private componentConstructors: Map<string, Function> = new Map();

  static getInstance(): OrdoJSRuntime {
    if (!OrdoJSRuntime.instance) {
      OrdoJSRuntime.instance = new OrdoJSRuntime();
    }
    return OrdoJSRuntime.instance;
  }

  /**
   * Initialize the runtime
   */
  init(): void {
    // Auto-hydrate components on DOM ready
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => this.autoHydrate());
    } else {
      this.autoHydrate();
    }
  }

  /**
   * Register a component constructor for hydration
   */
  registerComponent(name: string, constructor: Function): void {
    this.componentConstructors.set(name, constructor);
  }

  /**
   * Auto-hydrate all components found in the DOM
   */
  autoHydrate(): void {
    // Find all elements with hydration data
    const hydrationScript = document.getElementById('ordojs-hydration-data');
    if (hydrationScript && hydrationScript.textContent) {
      try {
        const hydrationData: HydrationData = JSON.parse(hydrationScript.textContent);
        this.hydrateFromData(hydrationData);
      } catch (error) {
        console.error('Failed to parse hydration data:', error);
      }
    }

    // Find all components marked for hydration
    const componentElements = document.querySelectorAll('[data-ordojs-component]');
    componentElements.forEach(element => {
      const componentName = element.getAttribute('data-ordojs-component');
      const componentId = element.getAttribute('data-component-id');

      if (componentName && componentId && !this.hydratedComponents.has(componentId)) {
        this.hydrateComponent(element, componentName, componentId);
      }
    });
  }

  /**
   * Hydrate a component from hydration data
   */
  hydrateFromData(hydrationData: HydrationData): void {
    const element = document.querySelector(`[data-component-id="${hydrationData.componentId}"]`);
    if (element) {
      this.hydrateComponent(element, hydrationData.componentName, hydrationData.componentId, hydrationData);
    }
  }

  /**
   * Hydrate a specific component
   */
  hydrateComponent(
    element: Element,
    componentName: string,
    componentId: string,
    hydrationData?: HydrationData
  ): ComponentInstance | null {
    const constructor = this.componentConstructors.get(componentName);
    if (!constructor) {
      console.warn(`Component constructor for "${componentName}" not found`);
      return null;
    }

    // Extract props from element or hydration data
    const props = this.extractProps(element, hydrationData);

    // Create component instance
    const componentInstance = constructor(props) as ComponentInstance;
    componentInstance.id = componentId;
    componentInstance.name = componentName;
    componentInstance.element = element;

    // Initialize reactive state from hydration data
    if (hydrationData?.initialState) {
      this.initializeReactiveState(componentInstance, hydrationData.initialState);
    }

    // Attach event listeners
    this.attachEventListeners(element, componentInstance);

    // Store the hydrated component
    this.hydratedComponents.set(componentId, componentInstance);

    // Mark as hydrated
    element.setAttribute('data-ordojs-hydrated', 'true');

    return componentInstance;
  }

  /**
   * Extract props from element attributes or hydration data
   */
  private extractProps(element: Element, hydrationData?: HydrationData): Props {
    let props: Props = {};

    // Extract from hydration data first
    if (hydrationData?.props) {
      props = { ...hydrationData.props };
    }

    // Extract from data-props attribute
    const propsAttr = element.getAttribute('data-props');
    if (propsAttr) {
      try {
        const parsedProps = JSON.parse(propsAttr);
        props = { ...props, ...parsedProps };
      } catch (error) {
        console.warn('Failed to parse props from data-props attribute:', error);
      }
    }

    return props;
  }

  /**
   * Initialize reactive state from server data
   */
  private initializeReactiveState(component: ComponentInstance, initialState: Record<string, any>): void {
    // Initialize state properties with reactive setters
    for (const [key, value] of Object.entries(initialState)) {
      if (component.state && typeof component.state === 'object') {
        // Set initial value
        component.state[key] = value;

        // Make it reactive if the component has an update method
        if (component.update && typeof component.update === 'function') {
          let currentValue = value;
          Object.defineProperty(component.state, key, {
            get: () => currentValue,
            set: (newValue) => {
              if (currentValue !== newValue) {
                currentValue = newValue;
                component.update();
              }
            },
            enumerable: true,
            configurable: true
          });
        }
      }
    }
  }

  /**
   * Attach event listeners to hydrated components
   */
  private attachEventListeners(element: Element, component: ComponentInstance): void {
    // Find all elements with event handlers
    const eventElements = element.querySelectorAll('[data-event]');

    if (eventElements) {
      eventElements.forEach(eventElement => {
        const eventName = eventElement.getAttribute('data-event');
        if (!eventName) return;

        // Create event handler
        const handler = (event: Event) => {
          // Look for handler function in component
          const handlerName = `handle${eventName.charAt(0).toUpperCase() + eventName.slice(1)}`;
          if (component && typeof (component as any)[handlerName] === 'function') {
            (component as any)[handlerName](event);
          }
        };

        // Attach event listener
        eventElement.addEventListener(eventName, handler);

        // Store for cleanup
        if (!component.eventListeners) {
          component.eventListeners = new Map();
        }
        component.eventListeners.set(`${eventElement.id || 'element'}-${eventName}`, handler);
      });
    }

    // Handle interpolation updates
    this.setupInterpolationUpdates(element, component);
  }

  /**
   * Set up updates for interpolated content
   */
  private setupInterpolationUpdates(element: Element, component: ComponentInstance): void {
    const interpolationElements = element.querySelectorAll('[data-interpolation]');

    interpolationElements.forEach(interpElement => {
      const varName = interpElement.getAttribute('data-interpolation');
      if (!varName || !component.state) return;

      // Update interpolation when state changes
      const originalUpdate = component.update;
      component.update = () => {
        // Update interpolation content
        if (component.state[varName] !== undefined) {
          interpElement.textContent = String(component.state[varName]);
        }

        // Call original update if it exists
        if (originalUpdate && typeof originalUpdate === 'function') {
          originalUpdate.call(component);
        }
      };
    });
  }

  /**
   * Get a hydrated component by ID
   */
  getComponent(componentId: string): ComponentInstance | undefined {
    return this.hydratedComponents.get(componentId);
  }

  /**
   * Get all hydrated components
   */
  getAllComponents(): ComponentInstance[] {
    return Array.from(this.hydratedComponents.values());
  }

  /**
   * Unmount a component
   */
  unmountComponent(componentId: string): void {
    const component = this.hydratedComponents.get(componentId);
    if (!component) return;

    // Remove event listeners
    if (component.eventListeners) {
      component.eventListeners.forEach((listener, key) => {
        const [elementId, eventName] = key.split('-');
        const element = elementId === 'element'
          ? component.element
          : document.getElementById(elementId);

        if (element) {
          element.removeEventListener(eventName, listener);
        }
      });
      component.eventListeners.clear();
    }

    // Call component unmount if available
    if (component.unmount && typeof component.unmount === 'function') {
      component.unmount();
    }

    // Remove from registry
    this.hydratedComponents.delete(componentId);

    // Remove hydration marker
    component.element.removeAttribute('data-ordojs-hydrated');
  }

  /**
   * Unmount all components
   */
  unmountAll(): void {
    const componentIds = Array.from(this.hydratedComponents.keys());
    componentIds.forEach(id => this.unmountComponent(id));
  }
}

/**
 * Hydrator class for mounting to pre-rendered HTML
 */
export class OrdoJSHydrator {
  private runtime: OrdoJSRuntime;

  constructor() {
    this.runtime = OrdoJSRuntime.getInstance();
  }

  /**
   * Hydrate a specific component
   */
  hydrateComponent(element: Element, componentData: ComponentData): ComponentInstance | null {
    const componentName = element.getAttribute('data-ordojs-component');
    const componentId = element.getAttribute('data-component-id');

    if (!componentName || !componentId) {
      console.warn('Element missing required hydration attributes');
      return null;
    }

    return this.runtime.hydrateComponent(element, componentName, componentId, {
      componentName,
      componentId,
      props: componentData.props,
      initialState: componentData.state || {},
      version: '1.0'
    });
  }

  /**
   * Attach event listeners to hydrated components
   */
  attachEventListeners(element: Element, handlers: Record<string, Function>): void {
    Object.entries(handlers).forEach(([eventName, handler]) => {
      const eventElements = element.querySelectorAll(`[data-event="${eventName}"]`);
      eventElements.forEach(eventElement => {
        eventElement.addEventListener(eventName, handler as EventListener);
      });
    });
  }

  /**
   * Initialize reactive state
   */
  initializeReactiveState(component: ComponentInstance): void {
    // This is handled by the runtime's initializeReactiveState method
    // This method is kept for API compatibility
  }
}
