import { html, render, type TemplateResult } from '@design.estate/dees-element';
import type {
  IViewDefinition,
  IViewActivationContext,
  IViewLifecycle,
  TDeesAppui
} from '../../interfaces/appconfig.js';

/**
 * Registry for managing views and their lifecycle
 *
 * Key features:
 * - View caching with hide/show pattern (not destroy/create)
 * - Async content loading support (lazy loading)
 * - View lifecycle hooks (onActivate, onDeactivate, canDeactivate)
 */
export class ViewRegistry {
  private views: Map<string, IViewDefinition> = new Map();
  private instances: Map<string, HTMLElement> = new Map();
  private currentViewId: string | null = null;
  private appui: TDeesAppui | null = null;

  /**
   * Set the appui reference for view activation context
   */
  public setAppuiRef(appui: TDeesAppui): void {
    this.appui = appui;
  }

  /**
   * Register a single view
   */
  public register(view: IViewDefinition): void {
    if (this.views.has(view.id)) {
      console.warn(`View with id "${view.id}" already registered. Overwriting.`);
    }
    this.views.set(view.id, view);
  }

  /**
   * Register multiple views
   */
  public registerAll(views: IViewDefinition[]): void {
    views.forEach((view) => this.register(view));
  }

  /**
   * Get a view definition by ID
   */
  public get(viewId: string): IViewDefinition | undefined {
    return this.views.get(viewId);
  }

  /**
   * Get all registered view IDs
   */
  public getViewIds(): string[] {
    return Array.from(this.views.keys());
  }

  /**
   * Get all views
   */
  public getAll(): IViewDefinition[] {
    return Array.from(this.views.values());
  }

  /**
   * Get route for a view
   */
  public getRoute(viewId: string): string {
    const view = this.views.get(viewId);
    return view?.route || view?.id || '';
  }

  /**
   * Find view by route (supports parameterized routes like 'settings/:section')
   */
  public findByRoute(route: string): { view: IViewDefinition; params: Record<string, string> } | undefined {
    for (const view of this.views.values()) {
      const viewRoute = view.route || view.id;
      const params = this.matchRoute(viewRoute, route);
      if (params !== null) {
        return { view, params };
      }
    }
    return undefined;
  }

  /**
   * Match a route pattern against an actual route
   * Returns params if matched, null otherwise
   */
  private matchRoute(pattern: string, route: string): Record<string, string> | null {
    const patternParts = pattern.split('/');
    const routeParts = route.split('/');

    // Check for optional trailing param (ends with ?)
    const hasOptionalParam = patternParts.length > 0 &&
      patternParts[patternParts.length - 1].endsWith('?');

    if (hasOptionalParam) {
      // Allow route to be shorter by 1
      if (routeParts.length < patternParts.length - 1 || routeParts.length > patternParts.length) {
        return null;
      }
    } else if (patternParts.length !== routeParts.length) {
      return null;
    }

    const params: Record<string, string> = {};

    for (let i = 0; i < patternParts.length; i++) {
      let part = patternParts[i];
      const isOptional = part.endsWith('?');
      if (isOptional) {
        part = part.slice(0, -1);
      }

      if (part.startsWith(':')) {
        // This is a parameter
        const paramName = part.slice(1);
        if (routeParts[i] !== undefined) {
          params[paramName] = routeParts[i];
        } else if (!isOptional) {
          return null;
        }
      } else if (routeParts[i] !== part) {
        return null;
      }
    }

    return params;
  }

  /**
   * Check if navigation away from current view is allowed
   */
  public async canLeaveCurrentView(): Promise<boolean | string> {
    if (!this.currentViewId) return true;

    const instance = this.instances.get(this.currentViewId);
    if (!instance) return true;

    const lifecycle = instance as unknown as IViewLifecycle;
    if (typeof lifecycle.canDeactivate === 'function') {
      return await lifecycle.canDeactivate();
    }

    return true;
  }

  /**
   * Activate a view - handles caching, lifecycle, and rendering
   */
  public async activateView(
    viewId: string,
    container: HTMLElement,
    params?: Record<string, string>
  ): Promise<HTMLElement | null> {
    const view = this.views.get(viewId);
    if (!view) {
      console.error(`View "${viewId}" not found in registry`);
      return null;
    }

    // Check if caching is enabled for this view (default: true)
    const shouldCache = view.cache !== false;

    // Deactivate current view
    if (this.currentViewId && this.currentViewId !== viewId) {
      await this.deactivateView(this.currentViewId);
    }

    // Check for cached instance
    let element = shouldCache ? this.instances.get(viewId) : undefined;

    if (element) {
      // Reuse cached instance - just show it
      element.style.display = '';
    } else {
      // Create new instance
      element = await this.createViewElement(view);
      if (!element) {
        console.error(`Failed to create element for view "${viewId}"`);
        return null;
      }

      // Add to container
      container.appendChild(element);

      // Cache if enabled
      if (shouldCache) {
        this.instances.set(viewId, element);
      }
    }

    this.currentViewId = viewId;

    // Call onActivate lifecycle hook
    await this.callOnActivate(element, viewId, params);

    return element;
  }

  /**
   * Deactivate a view (hide and call lifecycle hook)
   */
  private async deactivateView(viewId: string): Promise<void> {
    const instance = this.instances.get(viewId);
    if (!instance) return;

    // Call onDeactivate lifecycle hook
    const lifecycle = instance as unknown as IViewLifecycle;
    if (typeof lifecycle.onDeactivate === 'function') {
      await lifecycle.onDeactivate();
    }

    // Hide the element
    instance.style.display = 'none';
  }

  /**
   * Create a view element from its definition (supports async content)
   */
  private async createViewElement(view: IViewDefinition): Promise<HTMLElement | null> {
    let content = view.content;

    // Handle async content (lazy loading)
    if (typeof content === 'function' &&
        !(content.prototype instanceof HTMLElement) &&
        content.constructor.name === 'AsyncFunction') {
      try {
        content = await (content as () => Promise<string | (new () => HTMLElement) | (() => TemplateResult)>)();
      } catch (error) {
        console.error(`Failed to load async content for view "${view.id}":`, error);
        return null;
      }
    }

    let element: HTMLElement;

    if (typeof content === 'string') {
      // Tag name string
      element = document.createElement(content);
    } else if (typeof content === 'function') {
      // Check if it's a class constructor or template function
      if (content.prototype instanceof HTMLElement) {
        // Element class constructor
        element = new (content as new () => HTMLElement)();
      } else {
        // Template function - wrap in a container and use Lit's render
        const wrapper = document.createElement('div');
        wrapper.className = 'view-content-wrapper';
        wrapper.style.cssText = 'display: contents;';
        const template = (content as () => TemplateResult)();
        render(template, wrapper);
        element = wrapper;
      }
    } else {
      console.error(`Invalid content type for view "${view.id}"`);
      return null;
    }

    // Add view ID as data attribute for debugging
    element.dataset.viewId = view.id;

    return element;
  }

  /**
   * Call onActivate lifecycle hook on a view element
   */
  private async callOnActivate(
    element: HTMLElement,
    viewId: string,
    params?: Record<string, string>
  ): Promise<void> {
    const lifecycle = element as unknown as IViewLifecycle;
    if (typeof lifecycle.onActivate === 'function') {
      const context: IViewActivationContext = {
        appui: this.appui!,
        viewId,
        params,
      };
      await lifecycle.onActivate(context);
    }
  }

  /**
   * Legacy method - renders view without caching
   * @deprecated Use activateView instead
   */
  public renderView(viewId: string, container: HTMLElement): HTMLElement | null {
    const view = this.views.get(viewId);
    if (!view) {
      console.error(`View "${viewId}" not found in registry`);
      return null;
    }

    // For legacy compatibility, clear container
    container.innerHTML = '';

    let element: HTMLElement;
    const content = view.content;

    if (typeof content === 'string') {
      element = document.createElement(content);
    } else if (typeof content === 'function') {
      if ((content as any).prototype instanceof HTMLElement) {
        element = new (content as new () => HTMLElement)();
      } else {
        const wrapper = document.createElement('div');
        wrapper.className = 'view-content-wrapper';
        wrapper.style.cssText = 'display: contents;';
        const template = (content as () => TemplateResult)();
        render(template, wrapper);
        element = wrapper;
      }
    } else {
      console.error(`Invalid content type for view "${viewId}"`);
      return null;
    }

    container.appendChild(element);
    this.instances.set(viewId, element);
    this.currentViewId = viewId;

    return element;
  }

  /**
   * Get currently active view ID
   */
  public getCurrentViewId(): string | null {
    return this.currentViewId;
  }

  /**
   * Get cached instance of a view
   */
  public getInstance(viewId: string): HTMLElement | undefined {
    return this.instances.get(viewId);
  }

  /**
   * Clear a specific cached instance
   */
  public clearInstance(viewId: string): void {
    const instance = this.instances.get(viewId);
    if (instance && instance.parentNode) {
      instance.parentNode.removeChild(instance);
    }
    this.instances.delete(viewId);
    if (this.currentViewId === viewId) {
      this.currentViewId = null;
    }
  }

  /**
   * Clear all instances
   */
  public clearInstances(): void {
    for (const [viewId, instance] of this.instances) {
      if (instance.parentNode) {
        instance.parentNode.removeChild(instance);
      }
    }
    this.instances.clear();
    this.currentViewId = null;
  }

  /**
   * Unregister a view
   */
  public unregister(viewId: string): boolean {
    this.clearInstance(viewId);
    return this.views.delete(viewId);
  }

  /**
   * Clear the registry
   */
  public clear(): void {
    this.views.clear();
    this.clearInstances();
  }

  /**
   * Check if a view is registered
   */
  public has(viewId: string): boolean {
    return this.views.has(viewId);
  }

  /**
   * Get the number of registered views
   */
  public get size(): number {
    return this.views.size;
  }
}
