import path from "node:path";
import { fileURLToPath } from "node:url";
import {
  acquireProcessRecord,
  processStartTime,
  readProcessRecord,
  releaseProcessRecord,
} from "./managed-process.mjs";

const repoRoot = path.resolve(
  path.dirname(fileURLToPath(import.meta.url)),
  ".."
);
export const buildCacheDir =
  process.env.HA_BUILD_CACHE_DIR ??
  path.join(repoRoot, "node_modules", ".cache");

const WORKFLOW_LOCK_TOKEN_ENV = "HA_WORKFLOW_LOCK_TOKEN";
const signalCleanups = new Set();
const cleanupSignals = ["SIGINT", "SIGTERM", "SIGHUP"];

const handleSignal = (signal) => {
  for (const cleanup of signalCleanups) {
    cleanup();
  }
  for (const cleanupSignal of cleanupSignals) {
    process.off(cleanupSignal, handleSignal);
  }
  process.kill(process.pid, signal);
};

const registerSignalCleanup = (cleanup) => {
  if (signalCleanups.size === 0) {
    for (const signal of cleanupSignals) {
      process.on(signal, handleSignal);
    }
  }
  signalCleanups.add(cleanup);
};

const unregisterSignalCleanup = (cleanup) => {
  signalCleanups.delete(cleanup);
  if (signalCleanups.size === 0) {
    for (const signal of cleanupSignals) {
      process.off(signal, handleSignal);
    }
  }
};

export const workflowLockFile = path.join(buildCacheDir, "ha-workflow.lock");

export const workflowLockEnv = (token) => ({
  ...process.env,
  [WORKFLOW_LOCK_TOKEN_ENV]: token,
});

export const describeOutputOwner = (owner) => {
  if (owner?.kind === "build") {
    return `frontend ${owner.modern ? "modern " : ""}build`;
  }
  if (owner?.kind === "dev") {
    return `dev server (${owner.suite ?? "app"})`;
  }
  return owner?.target ? `Gulp task ${owner.target}` : "another process";
};

const createLockTask = ({ file, inheritedTokenEnv, kind, label, target }) => {
  let exitToken;

  const cleanup = () => {
    if (!exitToken) {
      return;
    }
    releaseProcessRecord(file, exitToken);
    exitToken = undefined;
    process.off("exit", cleanup);
    unregisterSignalCleanup(cleanup);
  };
  const acquire = async () => {
    const inheritedToken = process.env[inheritedTokenEnv];
    if (inheritedToken) {
      if (readProcessRecord(file)?.token !== inheritedToken) {
        throw Error(
          `${label} lock ownership was lost before ${target} started.`
        );
      }
      exitToken = inheritedToken;
      process.once("exit", cleanup);
      registerSignalCleanup(cleanup);
      return;
    }

    const token = `${process.pid}-${Date.now()}-${Math.random()}`;
    const record = {
      pid: process.pid,
      startTime: processStartTime(process.pid),
      processGroup: false,
      kind,
      target,
      token,
    };
    const result = acquireProcessRecord(file, record);
    if (!result.acquired) {
      const pid = result.existing?.pid;
      throw Error(
        `Cannot run ${target}: ${describeOutputOwner(result.existing)} ` +
          `already owns ${label}${pid ? ` (pid ${pid})` : ""}.`
      );
    }

    exitToken = token;
    process.once("exit", cleanup);
    registerSignalCleanup(cleanup);
  };

  acquire.displayName = `lock-${label}:${target}`;
  return acquire;
};

export const createWorkflowLockTask = (target) =>
  createLockTask({
    file: workflowLockFile,
    inheritedTokenEnv: WORKFLOW_LOCK_TOKEN_ENV,
    kind: "output",
    label: "build and development workflow",
    target,
  });
