type RuntimeListener = (payload: unknown) => void;
type PendingPayload = unknown;

import {
  noteBrowserCommandReceived,
  noteBrowserShellStateReceived,
  resolveBootState
} from './boot-state';

function tryParseJsonPayload(value: unknown): unknown
{
  if (typeof value !== 'string')
  {
    return value;
  }

  try
  {
    return JSON.parse(value);
  }
  catch
  {
    return value;
  }
}

function getUnrealBridgeHandle(): Record<string, unknown> | null
{
  const Candidate = (window as Record<string, unknown>).ue as Record<string, unknown> | undefined;
  if (!Candidate || typeof Candidate !== 'object')
  {
    return null;
  }

  const HyperTwistBridge = Candidate.hypertwist;
  return HyperTwistBridge && typeof HyperTwistBridge === 'object'
    ? HyperTwistBridge as Record<string, unknown>
    : null;
}

const CommandListeners = new Set<RuntimeListener>();
const ShellStateListeners = new Set<RuntimeListener>();
let LastCommandPayload: PendingPayload | undefined;
let LastShellStatePayload: PendingPayload | undefined;
let bHasLastCommandPayload = false;
let bHasLastShellStatePayload = false;

function dispatchToListeners(Listeners: Set<RuntimeListener>, Payload: unknown): void
{
  Listeners.forEach((Listener) => Listener(Payload));
}

function postEnvelope(Type: string, Payload: unknown): void
{
  const Envelope = {
    type: Type,
    payload: Payload,
    emittedAtUtc: new Date().toISOString()
  };

  const SerializedEnvelope = JSON.stringify(Envelope);
  const UnrealBridgeHandle = getUnrealBridgeHandle();
  let bDeliveredToUnreal = false;

  if (UnrealBridgeHandle && typeof UnrealBridgeHandle.notifyEnvelope === 'function')
  {
    (UnrealBridgeHandle.notifyEnvelope as (EnvelopeJson: string) => void)(SerializedEnvelope);
    bDeliveredToUnreal = true;
  }

  if (Type === 'hypertwist-state'
    && UnrealBridgeHandle
    && typeof UnrealBridgeHandle.notifyState === 'function')
  {
    (UnrealBridgeHandle.notifyState as (StateJson: string) => void)(JSON.stringify(Payload));
    bDeliveredToUnreal = true;
  }

  if (bDeliveredToUnreal)
  {
    return;
  }

  window.parent?.postMessage(Envelope, '*');
}

window.addEventListener('message', (Event) =>
{
  const Envelope = Event.data;
  if (!Envelope || typeof Envelope !== 'object')
  {
    return;
  }

  const TypedEnvelope = Envelope as Record<string, unknown>;
  if (TypedEnvelope.type === 'hypertwist-command')
  {
    dispatchToListeners(CommandListeners, tryParseJsonPayload(TypedEnvelope.payload));
  }
  else if (TypedEnvelope.type === 'hypertwist-shell-state')
  {
    dispatchToListeners(ShellStateListeners, tryParseJsonPayload(TypedEnvelope.payload));
  }
});

export const UEBridge = {
  onCommand(Listener: RuntimeListener): () => void
  {
    CommandListeners.add(Listener);
    if (bHasLastCommandPayload)
    {
      Listener(LastCommandPayload);
    }
    return () => CommandListeners.delete(Listener);
  },

  onShellState(Listener: RuntimeListener): () => void
  {
    ShellStateListeners.add(Listener);
    if (bHasLastShellStatePayload)
    {
      Listener(LastShellStatePayload);
    }
    return () => ShellStateListeners.delete(Listener);
  },

  receiveCommand(Payload: unknown): void
  {
    LastCommandPayload = tryParseJsonPayload(Payload);
    bHasLastCommandPayload = true;
    noteBrowserCommandReceived();
    dispatchToListeners(CommandListeners, LastCommandPayload);
  },

  setShellState(Payload: unknown): void
  {
    LastShellStatePayload = tryParseJsonPayload(Payload);
    bHasLastShellStatePayload = true;
    noteBrowserShellStateReceived();
    dispatchToListeners(ShellStateListeners, LastShellStatePayload);
  },

  sendState(Payload: unknown): void
  {
    postEnvelope('hypertwist-state', Payload);
  },

  sendCommandResult(Payload: unknown): void
  {
    postEnvelope('hypertwist-command-result', Payload);
  },

  notifyRuntimeReady(Payload: unknown): void
  {
    const BootState = resolveBootState();
    BootState.runtimeStatusId = 'runtime-ready';

    const UnrealBridgeHandle = getUnrealBridgeHandle();
    if (UnrealBridgeHandle && typeof UnrealBridgeHandle.notifyRuntimeReady === 'function')
    {
      (UnrealBridgeHandle.notifyRuntimeReady as (PayloadJson: string) => void)(JSON.stringify(Payload));
      return;
    }

    postEnvelope('hypertwist-runtime-ready', Payload);
  }
};
