export type LegacyFetchHandler = (
  request: Request,
  ...arguments_: unknown[]
) => Response | Promise<Response>;

type DenoServeOptions = {
  hostname?: string;
  port?: number;
};

type CapturedDenoServer = {
  readonly finished: Promise<void>;
  readonly ref: () => void;
  readonly shutdown: () => Promise<void>;
  readonly unref: () => void;
};

const shimMarker = Symbol.for('tonline-erp.deno-shim');

const capturedHandlers = new Map<string, LegacyFetchHandler>();
let activeCaptureName: string | undefined;

function captureServe(
  handlerOrOptions: LegacyFetchHandler | DenoServeOptions,
  optionalHandler?: LegacyFetchHandler,
): CapturedDenoServer {
  const handler =
    typeof handlerOrOptions === 'function' ? handlerOrOptions : optionalHandler;

  if (!handler) {
    throw new TypeError('Deno.serve requires a fetch handler.');
  }
  if (!activeCaptureName) {
    throw new Error('Deno.serve was called outside a named capture.');
  }
  if (capturedHandlers.has(activeCaptureName)) {
    throw new Error(`Deno.serve handler already captured for "${activeCaptureName}".`);
  }

  capturedHandlers.set(activeCaptureName, handler);
  let resolveFinished: (() => void) | undefined;
  const finished = new Promise<void>((resolve) => {
    resolveFinished = resolve;
  });

  return Object.freeze({
    finished,
    ref: () => undefined,
    unref: () => undefined,
    shutdown: async () => {
      resolveFinished?.();
      resolveFinished = undefined;
    },
  });
}

export async function captureDenoServeHandler(
  name: string,
  loadEntrypoint: () => Promise<unknown>,
): Promise<LegacyFetchHandler> {
  const captureName = name.trim();
  if (!captureName) {
    throw new Error('A non-empty name is required to capture Deno.serve.');
  }
  if (activeCaptureName) {
    throw new Error(
      `Cannot capture "${captureName}" while "${activeCaptureName}" is loading.`,
    );
  }
  if (capturedHandlers.has(captureName)) {
    throw new Error(`Deno.serve handler already captured for "${captureName}".`);
  }

  activeCaptureName = captureName;
  try {
    await loadEntrypoint();
    const handler = capturedHandlers.get(captureName);
    if (!handler) {
      throw new Error(`Entrypoint "${captureName}" did not call Deno.serve.`);
    }
    return handler;
  } catch (error) {
    capturedHandlers.delete(captureName);
    throw error;
  } finally {
    activeCaptureName = undefined;
  }
}

function writeToStderr(data: Uint8Array): number {
  process.stderr.write(Buffer.from(data));
  return data.byteLength;
}

export function installDenoShim(): void {
  const current = (globalThis as Record<PropertyKey, unknown>).Deno as
    | Record<PropertyKey, unknown>
    | undefined;

  if (current?.[shimMarker] === true) return;
  if (current) {
    throw new Error('Refusing to replace an existing global Deno runtime.');
  }

  const denoShim = {
    [shimMarker]: true,
    env: Object.freeze({
      delete: (name: string) => delete process.env[name],
      get: (name: string) => process.env[name],
      has: (name: string) => process.env[name] !== undefined,
      set: (name: string, value: string) => {
        process.env[name] = value;
      },
      toObject: () => ({ ...process.env }),
    }),
    serve: captureServe,
    stderr: Object.freeze({ writeSync: writeToStderr }),
  };

  Object.defineProperty(globalThis, 'Deno', {
    configurable: false,
    enumerable: false,
    value: Object.freeze(denoShim),
    writable: false,
  });
}

export function getCapturedDenoServeHandler(name: string): LegacyFetchHandler {
  const handler = capturedHandlers.get(name);
  if (!handler) {
    throw new Error(
      `No Deno.serve handler was captured for "${name}". ` +
        'Run scripts/sync-legacy-runtime.ts first.',
    );
  }

  return handler;
}
