// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { ipcRenderer } from 'electron';
import { serialize, deserialize } from 'node:v8';
import { createLogger } from '../logging/log.std.ts';
import { runTaskWithTimeout } from '../textsecure/TaskWithTimeout.std.ts';
import { explodePromise } from '../util/explodePromise.std.ts';
import { missingCaseError } from '../util/missingCaseError.std.ts';

const log = createLogger('channels');

const SQL_READ_KEY = 'sql-channel:read';
const SQL_WRITE_KEY = 'sql-channel:write';
const SQL_REMOVE_DB_KEY = 'sql-channel:remove-db';
let activeJobCount = 0;
let resolveShutdown: (() => void) | undefined;
let shutdownPromise: Promise<void> | null = null;

export enum AccessType {
  Read = 'Read',
  Write = 'Write',
}

export async function ipcInvoke<T>(
  access: AccessType,
  name: string,
  args: ReadonlyArray<unknown>
): Promise<T> {
  if (shutdownPromise && name !== 'close') {
    throw new Error(
      `Rejecting SQL channel job (${access}, ${name}); ` +
        'application is shutting down'
    );
  }

  let channel: string;
  if (access === AccessType.Read) {
    channel = SQL_READ_KEY;
  } else if (access === AccessType.Write) {
    channel = SQL_WRITE_KEY;
  } else {
    throw missingCaseError(access);
  }

  activeJobCount += 1;
  return runTaskWithTimeout(async () => {
    try {
      const result = await ipcRenderer.invoke(channel, name, serialize(args));
      if (!result.ok) {
        throw result.error;
      }
      return deserialize(result.value);
    } finally {
      activeJobCount -= 1;
      if (activeJobCount === 0) {
        resolveShutdown?.();
      }
    }
  }, `SQL channel call (${access}, ${name})`);
}

export async function doShutdown(): Promise<void> {
  log.info(
    `data.shutdown: shutdown requested. ${activeJobCount} jobs outstanding`
  );

  if (shutdownPromise) {
    return shutdownPromise;
  }

  // No outstanding jobs, return immediately
  if (activeJobCount === 0) {
    return;
  }

  ({ promise: shutdownPromise, resolve: resolveShutdown } =
    explodePromise<void>());

  try {
    await shutdownPromise;
  } finally {
    log.info('data.shutdown: process complete');
  }
}

export async function removeDB(): Promise<void> {
  return ipcRenderer.invoke(SQL_REMOVE_DB_KEY);
}
