import { ipcRenderer } from "electron";

import { RunCommandParams, RunCommandResult } from "./main/main-desktop-autofill.service";
import { AutofillCommand } from "./models/autofill-command";
import {
  AutofillIpcChannelControl,
  AutofillIpcChannelIncoming,
  AutofillIpcChannelOutgoing,
  AutofillIpcDefinitionMap,
  AutofillIpcRequest,
  AutofillIpcResponse,
} from "./models/autofill-ipc-channels";
import { CompletionCallback, IpcListener } from "./models/ipc-handler.type";

export const DesktopAutofillPreload = {
  runCommand: <C extends AutofillCommand>(
    params: RunCommandParams<C>,
  ): Promise<RunCommandResult<C>> =>
    ipcRenderer.invoke(AutofillIpcChannelControl.RunCommand, params),

  listenerReady: () => ipcRenderer.send("autofill.listenerReady"),

  /**
   * Returns the native handle of the Bitwarden app window, for native prompts
   * that must attach themselves to a window. Resolves to null when there is no
   * window, e.g. while the app is starting up.
   */
  getAppWindowHandle: (): Promise<Uint8Array | null> =>
    ipcRenderer.invoke(AutofillIpcChannelControl.GetAppWindowHandle),

  /**
   * Signals the main process whether native credential sync is enabled. The main process only
   * registers the native OS credential provider and starts the autofill IPC server once this is
   * called with `true`, so the flag-gating decision stays in the renderer where the feature flag
   * is evaluated. Resolves to whether native autofill is running in the main process.
   */
  setEnabled: (enabled: boolean): Promise<boolean> =>
    ipcRenderer.invoke(AutofillIpcChannelControl.SetEnabled, enabled),

  listenCancelRequest: makeListener(AutofillIpcChannelIncoming.CancelRequest),

  listenLockStatus: makeListener(
    AutofillIpcChannelIncoming.LockStatus,
    AutofillIpcChannelOutgoing.LockStatus,
  ),

  listenNativeStatus: makeListener(AutofillIpcChannelIncoming.NativeStatus),

  listenPasskeyRegistration: makeListener(
    AutofillIpcChannelIncoming.PasskeyRegistration,
    AutofillIpcChannelOutgoing.PasskeyRegistration,
  ),
  listenPasskeyAssertion: makeListener(
    AutofillIpcChannelIncoming.PasskeyAssertion,
    AutofillIpcChannelOutgoing.PasskeyAssertion,
  ),

  listenPasskeyAssertionWithoutUserInterface: makeListener(
    AutofillIpcChannelIncoming.PasskeyAssertionWithoutUserInterface,
    AutofillIpcChannelOutgoing.PasskeyAssertion,
  ),
};

function makeListener<K extends AutofillIpcChannelIncoming>(
  incomingChannel: K,
  outgoingChannel?: AutofillIpcDefinitionMap[K]["outgoing"],
) {
  return (fn: IpcListener<AutofillIpcRequest<K>, AutofillIpcResponse<K>>) => {
    ipcRenderer.on(
      incomingChannel,
      (
        _event,
        data: {
          clientId: number;
          sequenceNumber: number;
          request: AutofillIpcRequest<K>;
        },
      ) => {
        const { clientId, sequenceNumber, request } = data;
        const completeCallback: CompletionCallback<AutofillIpcResponse<K>> | undefined =
          outgoingChannel
            ? (error, response) => {
                if (error) {
                  ipcRenderer.send(AutofillIpcChannelOutgoing.Error, {
                    clientId,
                    sequenceNumber,
                    error: error.message,
                  });
                  return;
                }

                ipcRenderer.send(outgoingChannel, {
                  clientId,
                  sequenceNumber,
                  response,
                });
              }
            : undefined;
        fn(clientId, sequenceNumber, request, completeCallback);
      },
    );
  };
}
