/**
 * @fileoverview OrdoJS Component System - Modern component architecture
 * @author OrdoJS Framework Team
 */

import type { Props } from '../types/index.js';
import { signal, type EffectCleanup, type Signal } from './reactivity.js';

/**
 * Component lifecycle phases
 */
export enum ComponentLifecycle {
  CREATED = 'created',
  MOUNTED = 'mounted',
  UPDATED = 'updated',
  UNMOUNTED = 'unmounted',
  ERROR = 'error'
}

/**
 * Component context for dependency injection
 */
export interface ComponentContext {
  /** Parent component */
  parent?: ComponentInstance;
  /** Injected dependencies */
  injected: Map<string | symbol, any>;
  /** Provided dependencies */
  provided: Map<string | symbol, any>;
  /** Component registry */
  registry: ComponentRegistry;
}

/**
 * Component definition interface
 */
export interface ComponentDefinition<P extends Props = Props, S = any> {
  /** Component name */
  name: string;
  /** Props validation schema */
  props?: PropSchema<P>;
  /** Setup function */
  setup?: (props: P, context: SetupContext) => S | Promise<S>;
  /** Render function */
  render?: (state: S, props: P) => VNode | string;
  /** Lifecycle hooks */
  lifecycle?: Partial<LifecycleHooks>;
  /** Component styles */
  styles?: string | (() => string);
  /** Component metadata */
  meta?: ComponentMeta;
}

/**
 * Component metadata
 */
export interface ComponentMeta {
  /** Component version */
  version?: string;
  /** Component description */
  description?: string;
  /** Component tags */
  tags?: string[];
  /** Is component async */
  async?: boolean;
  /** Component dependencies */
  dependencies?: string[];
}

/**
 * Props validation schema
 */
export interface PropSchema<P = any> {
  [key: string]: PropDefinition<any>;
}

/**
 * Prop definition
 */
export interface PropDefinition<T = any> {
  /** Prop type */
  type: PropType<T>;
  /** Is required */
  required?: boolean;
  /** Default value */
  default?: T | (() => T);
  /** Validation function */
  validator?: (value: T) => boolean;
  /** Prop description */
  description?: string;
}

/**
 * Prop types
 */
export type PropType<T> =
  | 'string'
  | 'number'
  | 'boolean'
  | 'object'
  | 'array'
  | 'function'
  | ((value: any) => value is T);

/**
 * Setup context
 */
export interface SetupContext {
  /** Component props */
  props: Signal<Props>;
  /** Emit events */
  emit: (event: string, payload?: any) => void;
  /** Expose public API */
  expose: (api: Record<string, any>) => void;
  /** Access parent context */
  parent?: ComponentInstance;
  /** Inject dependency */
  inject: <T>(key: string | symbol, defaultValue?: T) => T;
  /** Provide dependency */
  provide: <T>(key: string | symbol, value: T) => void;
  /** Component slots */
  slots: Record<string, () => VNode[]>;
}

/**
 * Lifecycle hooks
 */
export interface LifecycleHooks {
  /** Before component creation */
  beforeCreate?: () => void;
  /** After component creation */
  created?: () => void;
  /** Before component mount */
  beforeMount?: () => void;
  /** After component mount */
  mounted?: () => void;
  /** Before component update */
  beforeUpdate?: () => void;
  /** After component update */
  updated?: () => void;
  /** Before component unmount */
  beforeUnmount?: () => void;
  /** After component unmount */
  unmounted?: () => void;
  /** Error handler */
  errorCaptured?: (error: Error, instance: ComponentInstance) => boolean | void;
}

/**
 * Virtual DOM node
 */
export interface VNode {
  /** Node type */
  type: string | ComponentDefinition;
  /** Node props */
  props?: Props;
  /** Child nodes */
  children?: VNode[];
  /** Node key */
  key?: string | number;
  /** Node ref */
  ref?: (el: Element | ComponentInstance | null) => void;
  /** Raw HTML */
  innerHTML?: string;
}

/**
 * Component instance
 */
export interface ComponentInstance<P extends Props = Props, S = any> {
  /** Component ID */
  id: string;
  /** Component definition */
  definition: ComponentDefinition<P, S>;
  /** Component props */
  props: Signal<P>;
  /** Component state */
  state: S;
  /** Component context */
  context: ComponentContext;
  /** Current lifecycle phase */
  lifecycle: Signal<ComponentLifecycle>;
  /** DOM element */
  element: Element | null;
  /** Child components */
  children: ComponentInstance[];
  /** Event emitter */
  emit: (event: string, payload?: any) => void;
  /** Exposed API */
  exposed: Record<string, any>;
  /** Cleanup functions */
  cleanups: EffectCleanup[];
  /** Mount component */
  mount: (target: Element) => Promise<void>;
  /** Unmount component */
  unmount: () => Promise<void>;
  /** Update component */
  update: (newProps?: Partial<P>) => Promise<void>;
  /** Force re-render */
  forceUpdate: () => Promise<void>;
}

/**
 * Component registry
 */
export interface ComponentRegistry {
  /** Register component */
  register: (definition: ComponentDefinition) => void;
  /** Get component */
  get: (name: string) => ComponentDefinition | undefined;
  /** Unregister component */
  unregister: (name: string) => boolean;
  /** List all components */
  list: () => ComponentDefinition[];
}

/**
 * Component factory options
 */
export interface ComponentFactoryOptions {
  /** Global component registry */
  registry?: ComponentRegistry;
  /** Global error handler */
  errorHandler?: (error: Error, instance: ComponentInstance) => void;
  /** Development mode */
  devMode?: boolean;
}

/**
 * Component registry implementation
 */
class ComponentRegistryImpl implements ComponentRegistry {
  private components = new Map<string, ComponentDefinition>();

  register(definition: ComponentDefinition): void {
    if (this.components.has(definition.name)) {
      console.warn(`Component "${definition.name}" is already registered`);
    }
    this.components.set(definition.name, definition);
  }

  get(name: string): ComponentDefinition | undefined {
    return this.components.get(name);
  }

  unregister(name: string): boolean {
    return this.components.delete(name);
  }

  list(): ComponentDefinition[] {
    return Array.from(this.components.values());
  }
}

/**
 * Component instance implementation
 */
class ComponentInstanceImpl<P extends Props = Props, S = any> implements ComponentInstance<P, S> {
  public id: string;
  public props: Signal<P>;
  public state: S;
  public context: ComponentContext;
  public lifecycle: Signal<ComponentLifecycle>;
  public element: Element | null = null;
  public children: ComponentInstance[] = [];
  public exposed: Record<string, any> = {};
  public cleanups: EffectCleanup[] = [];

  private eventListeners = new Map<string, Set<(payload: any) => void>>();

  constructor(
    public definition: ComponentDefinition<P, S>,
    initialProps: P,
    parentContext?: ComponentContext
  ) {
    this.id = generateComponentId();
    this.props = signal(initialProps);
    this.lifecycle = signal(ComponentLifecycle.CREATED);

    this.context = {
      parent: parentContext?.parent,
      injected: new Map(parentContext?.injected),
      provided: new Map(),
      registry: parentContext?.registry || new ComponentRegistryImpl()
    };

    this.state = {} as S;
    this.initializeComponent();
  }

  emit = (event: string, payload?: any): void => {
    const listeners = this.eventListeners.get(event);
    if (listeners) {
      for (const listener of listeners) {
        try {
          listener(payload);
        } catch (error) {
          console.error(`Error in event listener for "${event}":`, error);
        }
      }
    }

    // Bubble to parent
    if (this.context.parent) {
      this.context.parent.emit(`child:${event}`, { source: this, payload });
    }
  };

  async mount(target: Element): Promise<void> {
    if (this.element) {
      throw new Error('Component is already mounted');
    }

    try {
      this.runLifecycleHook('beforeMount');

      // Create element
      this.element = await this.render();
      target.appendChild(this.element);

      this.lifecycle.set(ComponentLifecycle.MOUNTED);
      this.runLifecycleHook('mounted');
    } catch (error) {
      this.lifecycle.set(ComponentLifecycle.ERROR);
      this.handleError(error as Error);
    }
  }

  async unmount(): Promise<void> {
    if (!this.element) {
      return;
    }

    try {
      this.runLifecycleHook('beforeUnmount');

      // Unmount children
      for (const child of this.children) {
        await child.unmount();
      }

      // Remove element
      if (this.element.parentNode) {
        this.element.parentNode.removeChild(this.element);
      }
      this.element = null;

      // Cleanup effects
      for (const cleanup of this.cleanups) {
        cleanup();
      }
      this.cleanups = [];

      this.lifecycle.set(ComponentLifecycle.UNMOUNTED);
      this.runLifecycleHook('unmounted');
    } catch (error) {
      this.handleError(error as Error);
    }
  }

  async update(newProps?: Partial<P>): Promise<void> {
    if (newProps) {
      this.props.update(current => ({ ...current, ...newProps }));
    }

    try {
      this.runLifecycleHook('beforeUpdate');

      if (this.element) {
        const newElement = await this.render();
        if (this.element.parentNode) {
          this.element.parentNode.replaceChild(newElement, this.element);
        }
        this.element = newElement;
      }

      this.lifecycle.set(ComponentLifecycle.UPDATED);
      this.runLifecycleHook('updated');
    } catch (error) {
      this.handleError(error as Error);
    }
  }

  async forceUpdate(): Promise<void> {
    await this.update();
  }

  private async initializeComponent(): Promise<void> {
    try {
      this.runLifecycleHook('beforeCreate');

      // Validate props
      if (this.definition.props) {
        this.validateProps(this.props.value, this.definition.props);
      }

      // Setup component
      if (this.definition.setup) {
        const setupContext = this.createSetupContext();
        const result = await this.definition.setup(this.props.value, setupContext);
        if (result) {
          this.state = result;
        }
      }

      this.runLifecycleHook('created');
    } catch (error) {
      this.handleError(error as Error);
    }
  }

  private createSetupContext(): SetupContext {
    return {
      props: this.props,
      emit: this.emit,
      expose: (api: Record<string, any>) => {
        Object.assign(this.exposed, api);
      },
      parent: this.context.parent,
      inject: <T>(key: string | symbol, defaultValue?: T): T => {
        return this.context.injected.get(key) ?? defaultValue;
      },
      provide: <T>(key: string | symbol, value: T) => {
        this.context.provided.set(key, value);
        // Propagate to children
        for (const child of this.children) {
          child.context.injected.set(key, value);
        }
      },
      slots: {} // TODO: Implement slots
    };
  }

  private async render(): Promise<Element> {
    if (this.definition.render) {
      const vnode = this.definition.render(this.state, this.props.value);
      return this.renderVNode(vnode);
    }

    // Default render
    const div = document.createElement('div');
    div.className = `ordojs-component ordojs-${this.definition.name}`;
    div.textContent = `Component: ${this.definition.name}`;
    return div;
  }

  private renderVNode(vnode: VNode | string): Element {
    if (typeof vnode === 'string') {
      const div = document.createElement('div');
      div.textContent = vnode;
      return div;
    }

    if (typeof vnode.type === 'string') {
      // HTML element
      const element = document.createElement(vnode.type);

      // Set props as attributes
      if (vnode.props) {
        for (const [key, value] of Object.entries(vnode.props)) {
          if (key.startsWith('on') && typeof value === 'function') {
            // Event listener
            const eventName = key.slice(2).toLowerCase();
            element.addEventListener(eventName, value);
          } else if (key === 'className' || key === 'class') {
            element.className = String(value);
          } else if (key === 'style' && typeof value === 'object') {
            Object.assign(element.style, value);
          } else {
            element.setAttribute(key, String(value));
          }
        }
      }

      // Render children
      if (vnode.children) {
        for (const child of vnode.children) {
          element.appendChild(this.renderVNode(child));
        }
      }

      // Set innerHTML if provided
      if (vnode.innerHTML) {
        element.innerHTML = vnode.innerHTML;
      }

      return element;
    } else {
      // Component
      const childInstance = createComponent(vnode.type, vnode.props || {}, this.context);
      this.children.push(childInstance);

      const childElement = document.createElement('div');
      childInstance.mount(childElement).then(() => {
        // Component mounted
      });

      return childElement;
    }
  }

  private validateProps(props: P, schema: PropSchema<P>): void {
    for (const [key, definition] of Object.entries(schema)) {
      const value = props[key as keyof P];

      // Check required
      if (definition.required && (value === undefined || value === null)) {
        throw new Error(`Required prop "${key}" is missing`);
      }

      // Check type
      if (value !== undefined && !this.validatePropType(value, definition.type)) {
        throw new Error(`Prop "${key}" has invalid type`);
      }

      // Custom validator
      if (value !== undefined && definition.validator && !definition.validator(value)) {
        throw new Error(`Prop "${key}" failed validation`);
      }
    }
  }

  private validatePropType(value: any, type: PropType<any>): boolean {
    if (typeof type === 'function') {
      return type(value);
    }

    switch (type) {
      case 'string':
        return typeof value === 'string';
      case 'number':
        return typeof value === 'number';
      case 'boolean':
        return typeof value === 'boolean';
      case 'object':
        return typeof value === 'object' && value !== null;
      case 'array':
        return Array.isArray(value);
      case 'function':
        return typeof value === 'function';
      default:
        return true;
    }
  }

  private runLifecycleHook(hook: keyof LifecycleHooks): void {
    if (hook === 'errorCaptured') {
      return; // errorCaptured is handled separately
    }

    const hookFn = this.definition.lifecycle?.[hook] as (() => void) | undefined;
    if (hookFn) {
      try {
        hookFn.call(this);
      } catch (error) {
        this.handleError(error as Error);
      }
    }
  }

  private handleError(error: Error): void {
    console.error(`Error in component "${this.definition.name}":`, error);

    // Try error boundary
    if (this.definition.lifecycle?.errorCaptured) {
      const handled = this.definition.lifecycle.errorCaptured(error, this as any);
      if (handled) return;
    }

    // Bubble to parent
    if (this.context.parent?.definition.lifecycle?.errorCaptured) {
      const handled = this.context.parent.definition.lifecycle.errorCaptured(error, this as any);
      if (handled) return;
    }

    // Global error handler would be called here
    throw error;
  }
}

/**
 * Generate unique component ID
 */
function generateComponentId(): string {
  return `ordojs-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

/**
 * Create component instance
 */
export function createComponent<P extends Props = Props, S = any>(
  definition: ComponentDefinition<P, S>,
  props: P,
  context?: ComponentContext
): ComponentInstance<P, S> {
  return new ComponentInstanceImpl(definition, props, context);
}

/**
 * Define a component
 */
export function defineComponent<P extends Props = Props, S = any>(
  definition: ComponentDefinition<P, S>
): ComponentDefinition<P, S> {
  return definition;
}

/**
 * Create virtual DOM node
 */
export function h(
  type: string | ComponentDefinition,
  props?: Props,
  ...children: (VNode | string)[]
): VNode {
  return {
    type,
    props,
    children: children.flat().map(child =>
      typeof child === 'string' ? { type: 'text', props: { textContent: child } } as VNode : child
    )
  };
}

/**
 * Fragment component for multiple root nodes
 */
export const Fragment = defineComponent({
  name: 'Fragment',
  render: (_, props) => {
    return h('div', { style: { display: 'contents' } }, ...(props.children || []));
  }
});

/**
 * Component factory
 */
export class ComponentFactory {
  private registry: ComponentRegistry;
  private options: ComponentFactoryOptions;

  constructor(options: ComponentFactoryOptions = {}) {
    this.registry = options.registry || new ComponentRegistryImpl();
    this.options = {
      devMode: false,
      ...options
    };
  }

  /**
   * Register component
   */
  register(definition: ComponentDefinition): void {
    this.registry.register(definition);
  }

  /**
   * Create component instance
   */
  create<P extends Props = Props>(
    nameOrDefinition: string | ComponentDefinition<P>,
    props: P
  ): ComponentInstance<P> {
    const definition = typeof nameOrDefinition === 'string'
      ? this.registry.get(nameOrDefinition)
      : nameOrDefinition;

    if (!definition) {
      throw new Error(`Component "${nameOrDefinition}" not found`);
    }

    const context: ComponentContext = {
      injected: new Map(),
      provided: new Map(),
      registry: this.registry
    };

    return createComponent(definition as ComponentDefinition<P>, props, context);
  }

  /**
   * Get registry
   */
  getRegistry(): ComponentRegistry {
    return this.registry;
  }
}

/**
 * Default component factory
 */
export const componentFactory = new ComponentFactory();

/**
 * Global component registration
 */
export function registerComponent(definition: ComponentDefinition): void {
  componentFactory.register(definition);
}

/**
 * Create component from name
 */
export function createComponentByName<P extends Props = Props>(
  name: string,
  props: P
): ComponentInstance<P> {
  return componentFactory.create(name, props);
}
