(function (root, factory) {
  "use strict";

  const api = Object.freeze(factory());
  if (typeof module === "object" && module.exports) module.exports = api;
  if (root) root.MbnPlayoutSafety = api;
})(typeof globalThis === "object" ? globalThis : this, function () {
  "use strict";

  const connectionStates = new Set([
    "disabled", "dry-run-ready", "disconnected", "connecting", "connected",
    "reconnecting", "disconnecting", "faulted", "outcome-unknown", "disposed"
  ]);

  function normalizeConnectionState(value) {
    const normalized = String(value || "unavailable")
      .trim()
      .toLocaleLowerCase("en-US")
      .replace(/[\s_]+/g, "-");
    return connectionStates.has(normalized) ? normalized : "unavailable";
  }

  function responseTimeoutFromNative(value) {
    const operationTimeout = Number(value);
    if (!Number.isFinite(operationTimeout) || operationTimeout < 100 || operationTimeout > 300000) {
      return 15000;
    }
    return Math.min(305000, Math.max(5100, Math.trunc(operationTimeout) + 5000));
  }

  function commandBlockReason(snapshot, command) {
    if (!snapshot || snapshot.pending) return "busy";
    if (snapshot.outcomeUnknown) return "outcome-unknown";
    if (!snapshot.commandAvailable) return "unavailable";
    if (snapshot.refreshFaulted && command !== "take-out") return "refresh-fault";

    const onAir = snapshot.engineState === "PROGRAM" || Boolean(snapshot.onAirCode);
    const prepared = snapshot.engineState === "PREPARED" || Boolean(snapshot.preparedCode);
    if (command === "next" && !onAir) return "next-requires-on-air";
    if (command === "take-in" && !prepared) return "take-in-requires-prepare";
    if (command === "take-out" && !onAir && !prepared) return "nothing-to-take-out";
    return null;
  }

  function legacyTakeInShortcutAction(snapshot) {
    if (!snapshot || snapshot.pending) return "blocked";
    if (!snapshot.takeInAllowed) return "take-in-locked";
    if (snapshot.engineState === "PROGRAM" || snapshot.onAirCode) return "next-required";
    if (snapshot.engineState === "PREPARED" || snapshot.preparedCode) return "take-in";
    return "prepare-then-take-in";
  }

  function matchesPendingCommand(pending, payload) {
    return Boolean(
      pending && payload &&
      payload.requestId === pending.requestId &&
      payload.command === pending.command);
  }

  function canClearCommandError(error, sessionQuarantined) {
    if (sessionQuarantined || !error) return !sessionQuarantined;
    return error.outcomeUnknown !== true && error.code !== "WEB_TIMEOUT" &&
      error.nonClearable !== true;
  }

  function playlistSnapshotLocked(snapshot) {
    return Boolean(snapshot && (
      snapshot.pending ||
      snapshot.outcomeUnknown ||
      snapshot.engineState === "PREPARED" ||
      snapshot.engineState === "PROGRAM" ||
      snapshot.preparedCode ||
      snapshot.onAirCode));
  }

  function shouldClearRefreshError(error, refreshFaulted) {
    return !refreshFaulted && error?.isRefreshFault === true;
  }

  function normalizeRefreshBudget(payload) {
    const completed = Number.isSafeInteger(payload?.refreshCompletedCount) &&
      payload.refreshCompletedCount >= 0 &&
      payload.refreshCompletedCount <= 2147483647
      ? payload.refreshCompletedCount
      : 0;
    const maximum = Number.isSafeInteger(payload?.refreshMaximumCount) &&
      payload.refreshMaximumCount >= 0 &&
      payload.refreshMaximumCount <= 1000000
      ? payload.refreshMaximumCount
      : null;
    return {
      completed,
      maximum,
      limitReached: maximum !== null && completed >= maximum &&
        payload?.refreshLimitReached === true &&
        payload?.refreshActive !== true &&
        payload?.playCompletionPending !== true &&
        payload?.refreshFaulted !== true
    };
  }

  function formatRefreshBudgetState(payload, nextLabel = "WAIT") {
    const budget = normalizeRefreshBudget(payload);
    const count = budget.maximum === null
      ? String(budget.completed)
      : `${budget.completed}/${budget.maximum}`;
    if (payload?.refreshFaulted === true) return "FAULT";
    if (budget.limitReached) return `CAPPED · ${count}`;
    if (payload?.refreshActive === true) return `ACTIVE · ${count} · ${nextLabel}`;
    return budget.maximum === null && budget.completed === 0
      ? "STOPPED"
      : `STOPPED · ${count}`;
  }

  function isNewerDatabaseStatusSequence(latestSequence, incomingSequence) {
    const latest = Number.isSafeInteger(latestSequence) && latestSequence >= 0
      ? latestSequence
      : 0;
    return Number.isSafeInteger(incomingSequence) &&
      incomingSequence > 0 && incomingSequence > latest;
  }

  function resolveStoredCatalogIndex(catalog, builderKey, cutCode) {
    if (!Array.isArray(catalog)) return -1;
    const exact = catalog.findIndex(item =>
      item && item.reachable !== false && item.builderKey === builderKey);
    if (exact >= 0) return exact;
    const code = String(cutCode || "");
    return catalog.findIndex(item => {
      if (!item || item.reachable === false) return false;
      const aliases = Array.isArray(item.aliases) ? item.aliases : [String(item.code || "")];
      return aliases.includes(code);
    });
  }

  return {
    normalizeConnectionState,
    responseTimeoutFromNative,
    commandBlockReason,
    legacyTakeInShortcutAction,
    matchesPendingCommand,
    canClearCommandError,
    playlistSnapshotLocked,
    shouldClearRefreshError,
    normalizeRefreshBudget,
    formatRefreshBudgetState,
    isNewerDatabaseStatusSequence,
    resolveStoredCatalogIndex
  };
});
