import type { WidgetTheme } from "./public-contracts.js";

import type { AccessibilityCapabilities } from "./accessibility/types.js";
import type { WidgetApiClient } from "./api.js";
import { ConversationController } from "./conversation.js";
import { resetConversationSession } from "./session.js";
import { SurfaceView, type SurfaceKind } from "./surface-view.js";

export interface AkintuSurfaceConfiguration {
  accessibility: AccessibilityCapabilities;
  apiClient: WidgetApiClient;
  context?: string | undefined;
  sessionId: string;
  sessionVersion?: number | undefined;
  theme: WidgetTheme;
}

export interface AkintuSurfaceElement extends HTMLElement {
  configure(configuration: AkintuSurfaceConfiguration): void;
}

/**
 * Creates a browser-only custom element class for one surface variant. Keeping
 * HTMLElement lookup inside this factory makes importing the package safe in
 * Node and Next.js server rendering environments.
 */
export function createSurfaceElementClass(
  kind: SurfaceKind,
): CustomElementConstructor {
  return class AkintuSurfaceElementImplementation
    extends HTMLElement
    implements AkintuSurfaceElement
  {
    private readonly view: SurfaceView;
    private controller: ConversationController | undefined;
    private configuration: AkintuSurfaceConfiguration | undefined;

    constructor() {
      super();
      // Open mode preserves style isolation while keeping the rendered surface
      // inspectable for integrators, browser tests, and support diagnostics.
      const shadowRoot = this.attachShadow({ mode: "open" });
      this.view = new SurfaceView(shadowRoot, kind);
      this.view.setHandlers({
        onAbort: () => this.controller?.abort(),
        onConfirmAction: () => {
          void this.controller?.confirmPendingAction();
        },
        onCancelAction: () => {
          void this.controller?.cancelPendingAction();
        },
        onCompleteExternalAction: (event) => {
          void this.controller?.completeExternalAction(event);
        },
        onSelectBookingSlot: (slot) => {
          this.controller?.selectBookingSlot(slot);
        },
        onSubmitPlaybookFields: (step, values) => {
          void this.controller?.submitPlaybookFields(step, values);
        },
        onSubmitBookingDetails: (details) => {
          void this.controller?.prepareCalendlyBooking(details);
        },
        onFeedback: (resolved) => {
          void this.controller?.submitFeedback(resolved);
        },
        onResetConversation: () => {
          resetConversationSession({
            sessionVersion: this.configuration?.sessionVersion,
          });
          window.location.reload();
        },
        onSend: (message) => {
          void this.controller?.sendMessage(message);
        },
      });
    }

    configure(configuration: AkintuSurfaceConfiguration): void {
      // Reconfiguration replaces the request owner instead of allowing two
      // controllers to update the same Shadow DOM concurrently.
      this.controller?.destroy();
      this.configuration = configuration;
      this.view.setCapabilities(configuration.accessibility);
      this.view.setTheme(configuration.theme);
      const isReducedMotion =
        window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ??
        false;
      this.controller = new ConversationController({
        apiClient: configuration.apiClient,
        context: configuration.context,
        sessionId: configuration.sessionId,
        isReducedMotion,
        onStateChange: (state) => this.view.update(state),
      });
      void this.controller.loadHistory();
    }

    disconnectedCallback(): void {
      // Both fetch streaming and Web Speech work can outlive a DOM node unless
      // they are cancelled explicitly during custom-element teardown.
      this.controller?.destroy();
      this.view.destroy();
    }
  };
}

export function isAkintuSurfaceElement(
  element: Element,
): element is AkintuSurfaceElement {
  return "configure" in element && typeof element.configure === "function";
}
