import { LitElement, html } from "lit";
import { property } from "lit/decorators.js";
import { IRegion, regionManager } from "@uxland/regions";
import { PrimariaRegionHost } from "../../../api/api";
import { regionsProperty } from "@uxland/regions/region-decorator";

/**
 * PrimariaRegion Web Component
 *
 * A plug-and-play region component that allows dynamic region creation without
 * needing to extend PrimariaRegionHost or use the @region decorator manually.
 *
 * Renders in light DOM (no shadow-root) to allow region content to be properly
 * displayed in the parent's DOM tree.
 *
 * @example
 * ```html
 * <primaria-region name="diagnostics-region"></primaria-region>
 * ```
 *
 * This component internally handles:
 * - Creating a container with the appropriate ID
 * - Setting up the region definition
 * - Managing the region lifecycle
 */
export class PrimariaRegion extends PrimariaRegionHost(LitElement) {
  /**
   * The name of the region to create.
   * This will be used both as the region name in the region manager
   * and to generate the container ID.
   */
  @property({ type: String })
  name = "";
  /**
   * Rendering mode for the region.
   * - "multi" (default): all registered views are shown simultaneously (MultipleActiveAdapter).
   * - "single": only one view is shown at a time (SelectableAdapter via primaria-content-switcher).
   *   Each plugin activates its own view; the adapter deactivates the rest automatically.
   */
  @property({ type: String })
  mode: "multi" | "single" = "multi";
  /**
   * Render in light DOM instead of shadow DOM.
   * This allows the region content to be visible in the parent's DOM tree.
   */
  createRenderRoot() {
    return this;
  }

  /**
   * Override shadowRoot getter to return this (light DOM) instead of null.
   * The region mixin tries to find elements using shadowRoot.querySelector(),
   * so we need to make it work with light DOM.
   */
  get shadowRoot(): ShadowRoot | null {
    return this as any;
  }

  /**
   * The region instance created by the region host mixin.
   * Plugins can inject content into this region using the region manager.
   */
  region: IRegion | undefined;

  /**
   * Virtual constructor for this instance to hold its own region definition.
   * This prevents conflicts when multiple primaria-region instances exist.
   */
  private _instanceConstructor: any;

  constructor() {
    super();

    // Create a unique constructor object for this instance
    // This allows each instance to have its own region definition
    // without affecting other instances of the same component
    this._instanceConstructor = Object.create(this.constructor);

    // Override the constructor property to return our instance constructor
    Object.defineProperty(this, "constructor", {
      get: () => this._instanceConstructor,
      configurable: true,
    });
  }

  /**
   * Called when the component is connected to the DOM.
   * Sets up the region definition before the parent connectedCallback runs.
   */
  connectedCallback(): void {
    // First, call the base LitElement connectedCallback but NOT the mixin yet
    // We need to do this in a special order
    LitElement.prototype.connectedCallback.call(this);

    if (this.name) {
      const targetId = `${this.name}-container`;

      // Create the container element based on mode.
      // "single" uses primaria-content-switcher so the region mixin picks up
      // selectableAdapterFactory (already registered in UI bootstrapper), giving
      // SingleActiveAdapter behaviour: activating one view auto-deactivates the rest.
      const container =
        this.mode === "single" ? document.createElement("primaria-content-switcher") : document.createElement("div");
      container.id = targetId;
      if (this.mode !== "single") {
        (container as HTMLElement).style.cssText = "width: 100%; height: 100%; min-height: 1px";
      }
      this.appendChild(container);

      // Set the region metadata directly on the instance constructor
      this._instanceConstructor[regionsProperty] = {
        ...this._instanceConstructor[regionsProperty],
        region: { targetId, name: this.name },
      };

      // The mixin creates regions in the `updated()` lifecycle, not in connectedCallback
      // So we need to trigger the updated lifecycle or call createRegions directly
      // Let's check if the component has the create method from the mixin
      if ((this as any).create) {
        (this as any).create();
      }

      // Also manually call the mixin's connectedCallback if it exists
      const litProto = Object.getPrototypeOf(this);
      const mixinProto = Object.getPrototypeOf(litProto);

      if (mixinProto.connectedCallback) {
        mixinProto.connectedCallback.call(this);
      }
    }
  }

  /**
   * Mixin hook fired after `createRegions` finishes.
   *
   * Hydrates the freshly-created region with every view that has already been
   * registered for this region name via `regionManager.registerViewWithRegion`.
   * `registerViewWithRegion` only forwards to regions that exist at the moment
   * of the call, so a plugin that registers once at `initialize` would lose
   * its view every time the region is destroyed and re-created (e.g. when a
   * drawer that hosts the region is closed and reopened). Pulling from the
   * registry here makes injection transparent: any plugin can register once
   * and any region with that name auto-populates.
   */
  regionsCreated(_regions: unknown): void {
    if (!this.region || !this.name) return;
    const registered = regionManager.getRegisteredViews(this.name) ?? [];
    for (const { key, view } of registered) {
      if (this.region.containsView(key)) continue;
      this.region.addView(key, view).catch((e) => {
        console.warn(`primaria-region(${this.name}): failed to addView "${key}"`, e);
      });
    }
  }

  /**
   * Called when the component is removed from the DOM.
   *
   * The base mixin's `disconnectedCallback` removes the region from the manager
   * but does NOT clear `this.region`, so on a later reconnect the mixin's
   * `createRegions` sees `isNil(this.region) === false` and skips re-creating
   * the region. That breaks the "drawer opens, closes, opens again" flow:
   * the second open would render the host but never receive the plugin's
   * view. Clear the reference (and drop the orphan container) so the next
   * connect rebuilds everything from scratch.
   */
  disconnectedCallback(): void {
    super.disconnectedCallback();
    this.region = undefined;
    const targetId = `${this.name}-container`;
    this.querySelector(`#${targetId}`)?.remove();
  }

  /**
   * Called before the component updates.
   * Updates the region definition if the name changes.
   */
  protected willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
    super.willUpdate(changedProperties);

    // If the name changed after connection, update the region definition
    if (this.name && changedProperties.has("name")) {
      const targetId = `${this.name}-container`;
      this._instanceConstructor[regionsProperty] = {
        ...this._instanceConstructor[regionsProperty],
        region: { targetId, name: this.name },
      };
    }
  }

  /**
   * Renders nothing because we create the container div manually in connectedCallback.
   * This is necessary because the mixin needs the div to exist before it tries to create the region.
   */
  render() {
    // Don't render anything - the container is created in connectedCallback
    return html``;
  }
}
