import type { HostGlobalState, RuntimeAction } from "../sdk/protocol";

const HOST_TAB_CHANNEL = "wasmeld-console:host-state";
const HOST_TAB_PROTOCOL_VERSION = 1;

type HostTabMessage =
  | {
      channel: typeof HOST_TAB_CHANNEL;
      version: typeof HOST_TAB_PROTOCOL_VERSION;
      type: "hello";
      sender: string;
    }
  | {
      channel: typeof HOST_TAB_CHANNEL;
      version: typeof HOST_TAB_PROTOCOL_VERSION;
      type: "state";
      sender: string;
      sequence: number;
      state: HostGlobalState;
    };

export type TabChannel = {
  postMessage: (message: unknown) => void;
  addEventListener: (type: "message", listener: (event: MessageEvent<unknown>) => void) => void;
  removeEventListener: (type: "message", listener: (event: MessageEvent<unknown>) => void) => void;
  close: () => void;
};

export type HostTabSyncOptions = {
  getState: () => HostGlobalState;
  applyState: (state: HostGlobalState) => void;
  channel?: TabChannel;
  tabId?: string;
};

/**
 * Replicates Host-owned control-plane state between same-origin browser tabs.
 *
 * Every tab may publish; there is deliberately no leader election. Sequence
 * numbers are scoped to a sender and suppress stale delivery, while consumers
 * suppress their own Solid broadcast effect when applying a remote snapshot.
 * Page-local state never enters this channel.
 */
export class HostTabSync {
  readonly #channel: TabChannel;
  readonly #tabId: string;
  readonly #getState: () => HostGlobalState;
  readonly #applyState: (state: HostGlobalState) => void;
  readonly #latestSequence = new Map<string, number>();
  #sequence = 0;
  #closed = false;

  constructor(options: HostTabSyncOptions) {
    this.#channel = options.channel ?? new BroadcastChannel(HOST_TAB_CHANNEL);
    this.#tabId = options.tabId ?? crypto.randomUUID();
    this.#getState = options.getState;
    this.#applyState = options.applyState;
    this.#channel.addEventListener("message", this.#receive);
    this.#send({
      channel: HOST_TAB_CHANNEL,
      version: HOST_TAB_PROTOCOL_VERSION,
      type: "hello",
      sender: this.#tabId,
    });
  }

  publish(state: HostGlobalState): void {
    this.#send({
      channel: HOST_TAB_CHANNEL,
      version: HOST_TAB_PROTOCOL_VERSION,
      type: "state",
      sender: this.#tabId,
      sequence: ++this.#sequence,
      state,
    });
  }

  close(): void {
    if (this.#closed) return;
    this.#closed = true;
    this.#channel.removeEventListener("message", this.#receive);
    this.#channel.close();
  }

  readonly #receive = (event: MessageEvent<unknown>) => {
    const message = event.data;
    if (!isHostTabMessage(message) || message.sender === this.#tabId) return;

    if (message.type === "hello") {
      this.publish(this.#getState());
      return;
    }

    const latest = this.#latestSequence.get(message.sender) ?? 0;
    if (message.sequence <= latest) return;
    this.#latestSequence.set(message.sender, message.sequence);
    this.#applyState(message.state);
  };

  #send(message: HostTabMessage): void {
    if (!this.#closed) this.#channel.postMessage(message);
  }
}

export function createBrowserHostTabSync(
  options: Omit<HostTabSyncOptions, "channel">,
): HostTabSync | null {
  if (typeof BroadcastChannel === "undefined") return null;
  return new HostTabSync(options);
}

function isHostTabMessage(value: unknown): value is HostTabMessage {
  if (!isRecord(value)) return false;
  if (
    value.channel !== HOST_TAB_CHANNEL ||
    value.version !== HOST_TAB_PROTOCOL_VERSION ||
    typeof value.sender !== "string"
  ) {
    return false;
  }
  if (value.type === "hello") return true;
  return (
    value.type === "state" &&
    Number.isSafeInteger(value.sequence) &&
    (value.sequence as number) > 0 &&
    isHostGlobalState(value.state)
  );
}

function isHostGlobalState(value: unknown): value is HostGlobalState {
  if (!isRecord(value)) return false;
  return (
    (value.snapshot === null || isRecord(value.snapshot)) &&
    isConnection(value.connection) &&
    typeof value.apiBase === "string" &&
    (value.runtimeAction === null || isRuntimeAction(value.runtimeAction)) &&
    (value.serviceAction === null || typeof value.serviceAction === "string") &&
    (value.deploymentAction === null || typeof value.deploymentAction === "string")
  );
}

function isConnection(value: unknown): value is HostGlobalState["connection"] {
  return value === "connecting" || value === "online" || value === "offline";
}

function isRuntimeAction(value: unknown): value is RuntimeAction {
  return value === "start" || value === "stop" || value === "restart";
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}
