

const IMPORT_JOB_TIMEOUT_MS = 180 * 60 * 1000;
const QUOTE_JOB_TIMEOUT_MS = 60 * 60 * 1000;
const DEFAULT_CANCEL_TIMEOUT_MS = 15 * 1000;
const CANCELLATION_ERROR_CODE_ALLOWLIST = new Set([
  "BROWSER_CLOSE_FAILED",
  "BROWSER_KILL_FAILED",
  "PROCESS_TERMINATION_FAILED",
  "SERVICE_CLOSED",
  "EACCES",
  "EPERM",
  "ESRCH",
  "ETIMEDOUT",
  "CANCEL_TIMEOUT",
  "CANCEL_FAILED",
]);

export class JobExecutionTimeoutError extends Error {
  constructor(timeoutMs) {
    super("Przekroczono limit czasu zadania.");
    this.name = "JobExecutionTimeoutError";
    this.code = "JOB_TIMEOUT";
    this.timeoutMs = timeoutMs;
  }
}

export function getJobTimeoutMs(kind) {
  return String(kind || "").startsWith("portfolio")
    ? IMPORT_JOB_TIMEOUT_MS
    : QUOTE_JOB_TIMEOUT_MS;
}

function getTimerApi(timers) {
  const setTimeoutFunction = timers?.setTimeout || globalThis.setTimeout;
  const clearTimeoutFunction = timers?.clearTimeout || globalThis.clearTimeout;
  if (typeof setTimeoutFunction !== "function" || typeof clearTimeoutFunction !== "function") {
    throw new TypeError("Brak prawidłowego API timerów.");
  }
  return {
    setTimeout: setTimeoutFunction,
    clearTimeout: clearTimeoutFunction,
  };
}

function safeCancellationFailureCode(error) {
  const candidate = String(error?.code || error?.name || "ERROR")
    .toUpperCase()
    .replace(/[^A-Z0-9_-]/g, "_")
    .slice(0, 48);
  return CANCELLATION_ERROR_CODE_ALLOWLIST.has(candidate) ? candidate : "CANCEL_FAILED";
}




export async function runWithDeadline({
  timeoutMs,
  cancelTimeoutMs = DEFAULT_CANCEL_TIMEOUT_MS,
  run,
  cancel = async () => {},
  onTimeout = async () => {},
  timers = null,
} = {}) {
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
    throw new TypeError("timeoutMs musi być dodatnią liczbą całkowitą.");
  }
  if (!Number.isSafeInteger(cancelTimeoutMs) || cancelTimeoutMs <= 0) {
    throw new TypeError("cancelTimeoutMs musi być dodatnią liczbą całkowitą.");
  }
  if (typeof run !== "function") throw new TypeError("run musi być funkcją.");
  if (typeof cancel !== "function") throw new TypeError("cancel musi być funkcją.");
  if (typeof onTimeout !== "function") throw new TypeError("onTimeout musi być funkcją.");

  const timerApi = getTimerApi(timers);
  let timerHandle = null;
  let finished = false;
  let timedOut = false;
  let timeoutError = null;
  let cancellationPromise = Promise.resolve();
  let rejectDeadline;

  const deadlinePromise = new Promise((resolve, reject) => {
    void resolve;
    rejectDeadline = reject;
  });
  const runPromise = Promise.resolve().then(run);
  
  
  runPromise.catch(() => {});

  timerHandle = timerApi.setTimeout(() => {
    if (finished || timedOut) return;
    timedOut = true;
    timeoutError = new JobExecutionTimeoutError(timeoutMs);
    cancellationPromise = (async () => {
      try {
        
        
        Promise.resolve(onTimeout(timeoutError)).catch(() => {
          timeoutError.statusUpdateFailed = true;
        });
      } catch {
        
        timeoutError.statusUpdateFailed = true;
      }
      let cancelTimer = null;
      const cancelPromise = Promise.resolve().then(() => cancel(timeoutError));
      cancelPromise.catch(() => {});
      const cancelDeadline = new Promise((resolve) => {
        cancelTimer = timerApi.setTimeout(() => resolve({ timedOut: true }), cancelTimeoutMs);
        cancelTimer?.unref?.();
      });
      const cancelResult = await Promise.race([
        cancelPromise.then(
          () => ({ status: "completed" }),
          (error) => ({ status: "rejected", error }),
        ),
        cancelDeadline,
      ]);
      if (cancelTimer !== null) timerApi.clearTimeout(cancelTimer);
      if (cancelResult?.timedOut) {
        timeoutError.cancellationFailed = true;
        timeoutError.cancellationTimedOut = true;
        timeoutError.cancellationErrorCode = "CANCEL_TIMEOUT";
      } else if (cancelResult?.status === "rejected") {
        
        
        timeoutError.cancellationFailed = true;
        timeoutError.cancellationErrorCode = safeCancellationFailureCode(cancelResult.error);
      }
    })();
    
    rejectDeadline(timeoutError);
  }, timeoutMs);
  timerHandle?.unref?.();

  try {
    return await Promise.race([runPromise, deadlinePromise]);
  } catch (error) {
    if (error === timeoutError) {
      await cancellationPromise;
      throw timeoutError;
    }
    throw error;
  } finally {
    finished = true;
    if (timerHandle !== null) timerApi.clearTimeout(timerHandle);
  }
}




export function createSafeSerialQueue({ onRejected = async () => {} } = {}) {
  if (typeof onRejected !== "function") throw new TypeError("onRejected musi być funkcją.");
  let tail = Promise.resolve();
  let pendingCount = 0;
  let blocked = false;
  let blockedReasonCode = "";

  function createBlockedError() {
    const error = new Error("Kolejka jest zablokowana po nieudanym zamknięciu poprzedniego zadania.");
    error.name = "JobQueueBlockedError";
    error.code = "QUEUE_BLOCKED";
    error.reasonCode = blockedReasonCode || "CANCEL_FAILED";
    return error;
  }

  function enqueue(task, context = null) {
    if (typeof task !== "function") {
      return Promise.resolve({
        ok: false,
        error: new TypeError("task musi być funkcją."),
      });
    }
    pendingCount += 1;
    const scheduled = tail.then(() => {
      if (blocked) throw createBlockedError();
      return Promise.resolve().then(task);
    });
    const observed = scheduled.then(
      (value) => ({ ok: true, value }),
      async (error) => {
        if (error?.cancellationFailed === true) {
          blocked = true;
          blockedReasonCode = safeCancellationFailureCode({
            code: error.cancellationErrorCode || "CANCEL_FAILED",
          });
        }
        try {
          await onRejected(error, context);
        } catch {
          
        }
        return { ok: false, error };
      },
    ).finally(() => {
      pendingCount -= 1;
    });
    
    tail = observed.then(() => undefined, () => undefined);
    return observed;
  }

  
  
  
  function block(reasonCode) {
    blocked = true;
    blockedReasonCode = safeCancellationFailureCode({ code: reasonCode || "CANCEL_FAILED" });
  }

  return Object.freeze({
    enqueue,
    block,
    whenIdle: () => tail,
    getPendingCount: () => pendingCount,
    isBlocked: () => blocked,
    getBlockedReasonCode: () => blockedReasonCode,
  });
}

export const JOB_TIMEOUTS = Object.freeze({
  importMs: IMPORT_JOB_TIMEOUT_MS,
  quoteMs: QUOTE_JOB_TIMEOUT_MS,
  cancelMs: DEFAULT_CANCEL_TIMEOUT_MS,
});
