(function initQianchuanDispatchRuntime(root, factory) {
  const api = factory();
  if (typeof module === "object" && module.exports) module.exports = api;
  else root.MMWQianchuanDispatchRuntime = api;
})(typeof globalThis !== "undefined" ? globalThis : this, () => {
  "use strict";

  const INSTALLATION_ID_KEY = "mmw.qianchuanDispatch.installationId.v1";
  const HEARTBEAT_INTERVAL_MS = 15_000;
  const OBSERVATION_STALE_MS = 35_000;
  const MAX_BACKOFF_MS = 60_000;
  const GATE_DEDUP_MS = 5_000;
  const SUPPORTED_SCOPES = new Set([
    "overall_promotion", "live_room", "multiplier_product", "multiplier_live_room"
  ]);

  function createRuntime(options = {}) {
    const storage = options.storage;
    const cryptoApi = options.crypto || globalThis.crypto;
    const fetchApi = options.fetch || globalThis.fetch;
    const extensionId = normalizeIdentifier(options.extensionId, 160);
    const now = options.now || Date.now;
    const setTimer = options.setTimeout || setTimeout;
    const clearTimer = options.clearTimeout || clearTimeout;
    const AbortControllerApi = options.AbortController || globalThis.AbortController;
    const getGate = options.getGate;
    const isAuthUsable = typeof options.isAuthUsable === "function"
      ? options.isAuthUsable
      : (auth) => Boolean(auth?.token);
    const audit = typeof options.audit === "function" ? options.audit : () => {};
    const commanderEnabled = options.commanderEnabled === true;
    const contexts = new Map();
    const pageContexts = new Map();
    let gateCache = null;
    let gateInFlight = null;
    let installationIdPromise = null;

    if (!storage?.get || !storage?.set || !cryptoApi || typeof fetchApi !== "function" || !AbortControllerApi) {
      throw new Error("千川 dispatch runtime 依赖不可用");
    }
    if (!extensionId || typeof getGate !== "function") {
      throw new Error("千川 dispatch runtime 配置无效");
    }

    options.storageOnChanged?.addListener?.((changes, areaName) => {
      if (areaName !== "local" || !changes?.[options.authStorageKey || "pluginAuth"]) return;
      const change = changes[options.authStorageKey || "pluginAuth"];
      gateCache = null;
      const oldToken = String(change.oldValue?.token || "");
      const newToken = String(change.newValue?.token || "");
      if (!newToken || oldToken !== newToken || !isAuthUsable(change.newValue)) stopAll();
    });

    async function observe(message = {}, sender = {}) {
      const page = trustedPage(sender);
      if (!page) return { accepted: false, reason: "untrusted_page" };
      if (message.pageSupported !== true) {
        stopPage(page.key);
        return { accepted: false, reason: "unsupported_page" };
      }
      const observation = normalizeObservation(message);
      if (!observation) {
        stopPage(page.key);
        return { accepted: false, reason: "incomplete_observation" };
      }
      const gate = await currentGate();
      if (!gate?.ok || !gate.auth?.token || !gate.auth?.baseUrl) {
        stopPage(page.key);
        return { accepted: false, reason: "membership_required" };
      }

      const contextKey = `${page.key}:${observation.accountKey}:${observation.scope}`;
      const previousKey = pageContexts.get(page.key);
      if (previousKey && previousKey !== contextKey) stopContext(previousKey);
      pageContexts.set(page.key, contextKey);

      let context = contexts.get(contextKey);
      if (!context) {
        context = {
          contextKey,
          pageKey: page.key,
          observation,
          workerInstanceId: "",
          initializing: null,
          lastObservedAt: now(),
          lastSuccessAt: 0,
          nextAttemptAt: 0,
          failures: 0,
          timer: null,
          abortController: null,
          inFlight: null,
          stopped: false
        };
        contexts.set(contextKey, context);
        context.initializing = getWorkerId(page, observation).then((workerInstanceId) => {
          context.workerInstanceId = workerInstanceId;
          context.initializing = null;
        });
      } else {
        context.observation = observation;
        context.lastObservedAt = now();
        context.stopped = false;
      }
      if (context.initializing) await context.initializing;
      await heartbeat(context, gate);
      return { accepted: true };
    }

    async function heartbeat(context, knownGate = null) {
      if (!context || context.stopped || contexts.get(context.contextKey) !== context) return null;
      if (context.inFlight) return context.inFlight;
      if (now() - context.lastObservedAt > OBSERVATION_STALE_MS) {
        stopContext(context.contextKey);
        return null;
      }
      if (context.nextAttemptAt > now()) {
        schedule(context, context.nextAttemptAt - now());
        return null;
      }
      if (context.lastSuccessAt && now() - context.lastSuccessAt < HEARTBEAT_INTERVAL_MS) {
        schedule(context, HEARTBEAT_INTERVAL_MS - (now() - context.lastSuccessAt));
        return null;
      }
      context.inFlight = (async () => {
        const gate = knownGate || await currentGate();
        if (!gate?.ok || !gate.auth?.token || !gate.auth?.baseUrl) {
          stopContext(context.contextKey);
          return null;
        }
        const controller = new AbortControllerApi();
        context.abortController = controller;
        const requestTimer = setTimer(() => controller.abort(), 8_000);
        try {
          await relayRequest(
            gate,
            "/api/plugin/qianchuan/workers/heartbeat",
            {
              workerInstanceId: context.workerInstanceId,
              extensionId,
              accountKey: context.observation.accountKey,
              scope: context.observation.scope
            },
            controller.signal
          );
          context.failures = 0;
          context.lastSuccessAt = now();
          context.nextAttemptAt = context.lastSuccessAt + HEARTBEAT_INTERVAL_MS;
          schedule(context, HEARTBEAT_INTERVAL_MS);
          return true;
        } catch (error) {
          context.failures += 1;
          audit("heartbeat_failed", sanitizeAudit(error));
          if ([401, 403].includes(Number(error?.status || 0))) {
            stopContext(context.contextKey);
            return null;
          }
          const delay = boundedBackoff(context.failures);
          context.nextAttemptAt = now() + delay;
          schedule(context, delay);
          return null;
        } finally {
          clearTimer(requestTimer);
          if (context.abortController === controller) context.abortController = null;
        }
      })().finally(() => {
        context.inFlight = null;
      });
      return context.inFlight;
    }

    async function claimOne(worker) {
      return commanderRequest("/api/plugin/qianchuan/dispatch/claim", worker);
    }

    async function submitResult(result) {
      return commanderRequest("/api/plugin/qianchuan/dispatch/result", result, result?.dispatchId);
    }

    async function cancelSignal(payload) {
      return commanderRequest("/api/plugin/qianchuan/dispatch/cancel-signal", payload, payload?.dispatchId);
    }

    async function commanderRequest(path, body, dispatchId = "") {
      if (!commanderEnabled) {
        const error = new Error("dispatch commander 未启用");
        error.code = "COMMANDER_DISABLED";
        throw error;
      }
      const gate = await currentGate();
      if (!gate?.ok || !gate.auth?.token || !gate.auth?.baseUrl) {
        const error = new Error("dispatch 登录会话不可用");
        error.code = "AUTH_REQUIRED";
        throw error;
      }
      try {
        return await relayRequest(gate, path, body);
      } catch (error) {
        audit("dispatch_request_failed", sanitizeAudit(error, dispatchId));
        throw error;
      }
    }

    async function relayRequest(gate, path, body, signal) {
      const response = await fetchApi(`${normalizeBaseUrl(gate.auth.baseUrl)}${path}`, {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
          Authorization: `Bearer ${gate.auth.token}`
        },
        body: JSON.stringify(body),
        credentials: "omit",
        cache: "no-store",
        signal
      });
      const payload = await response.json().catch(() => ({}));
      if (!response.ok || payload?.success === false) {
        const error = new Error("千川 dispatch 请求失败");
        error.status = Number(response.status || 0);
        error.code = String(payload?.code || payload?.error?.code || "RELAY_FAILED").slice(0, 64);
        throw error;
      }
      return payload?.data || {};
    }

    async function currentGate() {
      if (gateCache && now() - gateCache.at < GATE_DEDUP_MS) return gateCache.value;
      if (!gateInFlight) {
        gateInFlight = Promise.resolve(getGate())
          .then((value) => {
            gateCache = { at: now(), value };
            return value;
          })
          .finally(() => {
            gateInFlight = null;
          });
      }
      return gateInFlight;
    }

    function schedule(context, delayMs) {
      if (context.stopped) return;
      if (context.timer) clearTimer(context.timer);
      context.timer = setTimer(() => {
        context.timer = null;
        void heartbeat(context);
      }, Math.max(0, Number(delayMs) || 0));
    }

    function stopPage(pageKey) {
      const contextKey = pageContexts.get(pageKey);
      if (contextKey) stopContext(contextKey);
    }

    function stopContext(contextKey) {
      const context = contexts.get(contextKey);
      if (!context) return;
      context.stopped = true;
      if (context.timer) clearTimer(context.timer);
      context.abortController?.abort();
      contexts.delete(contextKey);
      if (pageContexts.get(context.pageKey) === contextKey) pageContexts.delete(context.pageKey);
    }

    function stopAll() {
      for (const key of [...contexts.keys()]) stopContext(key);
    }

    async function getWorkerId(page, observation) {
      if (!installationIdPromise) {
        installationIdPromise = (async () => {
          const saved = await storage.get(INSTALLATION_ID_KEY);
          if (validUuid(saved[INSTALLATION_ID_KEY])) return saved[INSTALLATION_ID_KEY];
          const installationId = randomUuid(cryptoApi);
          await storage.set({ [INSTALLATION_ID_KEY]: installationId });
          return installationId;
        })();
      }
      return deriveWorkerId(cryptoApi, await installationIdPromise, {
        tabId: page.tabId,
        documentId: page.documentId,
        accountKey: observation.accountKey,
        scope: observation.scope
      });
    }

    return Object.freeze({ observe, stopAll, claimOne, submitResult, cancelSignal });
  }

  function trustedPage(sender) {
    if (!Number.isInteger(sender?.tab?.id) || sender.tab.id <= 0 || sender.frameId !== 0) return null;
    const documentId = normalizeIdentifier(sender.documentId, 160);
    if (!documentId) return null;
    try {
      const url = new URL(String(sender.url || sender.tab.url || ""));
      if (url.protocol !== "https:" || url.hostname !== "qianchuan.jinritemai.com") return null;
    } catch {
      return null;
    }
    return { key: `${sender.tab.id}:${documentId}`, tabId: sender.tab.id, documentId };
  }

  function normalizeObservation(message) {
    const accountKey = normalizeIdentifier(message.accountKey, 128);
    const scope = String(message.scope || "").trim().toLowerCase();
    if (!accountKey || !SUPPORTED_SCOPES.has(scope)) return null;
    return {
      accountKey,
      scope,
      accountName: String(message.accountName || "").trim().slice(0, 160)
    };
  }

  function normalizeIdentifier(value, maxLength) {
    const text = String(value || "").trim().slice(0, maxLength);
    return /^[A-Za-z0-9_.:@-]+$/.test(text) ? text : "";
  }

  function normalizeBaseUrl(value) {
    return String(value || "").trim().replace(/\/+$/, "");
  }

  function boundedBackoff(failures) {
    return Math.min(MAX_BACKOFF_MS, 5_000 * (2 ** Math.max(0, Number(failures || 1) - 1)));
  }

  function sanitizeAudit(error, dispatchId = "") {
    return {
      status: Number(error?.status || 0),
      code: String(error?.code || "RELAY_FAILED").slice(0, 64),
      dispatch: String(dispatchId || "").slice(0, 12)
    };
  }

  function validUuid(value) {
    return /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(String(value || ""));
  }

  function randomUuid(cryptoApi) {
    if (typeof cryptoApi.randomUUID === "function") return cryptoApi.randomUUID();
    const bytes = new Uint8Array(16);
    cryptoApi.getRandomValues(bytes);
    bytes[6] = (bytes[6] & 0x0f) | 0x40;
    bytes[8] = (bytes[8] & 0x3f) | 0x80;
    const hex = [...bytes].map((value) => value.toString(16).padStart(2, "0"));
    return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
  }

  async function deriveWorkerId(cryptoApi, installationId, context) {
    if (!cryptoApi.subtle?.digest) throw new Error("千川 Worker 身份摘要能力不可用");
    const fields = [
      String(installationId),
      String(context.tabId),
      String(context.documentId),
      String(context.accountKey),
      String(context.scope)
    ];
    const canonical = fields.map((value) => `${value.length}:${value}`).join("\0");
    const bytes = new TextEncoder().encode(`mmw:qianchuan-dispatch-worker:v1\0${canonical}`);
    const digest = new Uint8Array(await cryptoApi.subtle.digest("SHA-256", bytes));
    return `qcdw-${[...digest.slice(0, 16)].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
  }

  return Object.freeze({
    createRuntime,
    boundedBackoff,
    sanitizeAudit,
    constants: Object.freeze({
      INSTALLATION_ID_KEY,
      HEARTBEAT_INTERVAL_MS,
      GATE_DEDUP_MS,
      MAX_BACKOFF_MS
    })
  });
});
