/* global Audio, CSS, CustomEvent, Element, HTMLElement, MutationObserver, URL, clearInterval, clearTimeout, document, fetch, window */
(function () {
  "use strict";

  var RUNTIME_KEY = "__wrnexusCaptchaRuntime";
  var existingRuntime = window[RUNTIME_KEY];

  if (existingRuntime && typeof existingRuntime.scan === "function") {
    window.__wrnexusRuntimes = window.__wrnexusRuntimes || {};
    window.__wrnexusRuntimes.captcha = {
      mount: existingRuntime.scan,
      unmount: existingRuntime.unmount || function () {},
    };
    existingRuntime.scan(document);
    return;
  }

  var states = new WeakMap();
  var scriptPromises = new Map();
  var instanceCounter = 0;

  function bool(value, fallback) {
    if (value === undefined || value === null || value === "") return fallback;
    return value === true || value === "true" || value === "1";
  }

  function numberInRange(value, fallback, minimum, maximum) {
    var parsed = Number(value);
    if (!Number.isFinite(parsed)) return fallback;
    return Math.max(minimum, Math.min(maximum, Math.round(parsed)));
  }

  function normalizeSize(value, compact) {
    if (compact) return "compact";
    var normalized = String(value || "normal")
      .trim()
      .toLowerCase();
    if (normalized === "compact" || normalized === "small" || normalized === "sm") return "compact";
    if (normalized === "big" || normalized === "large" || normalized === "lg") return "big";
    return "normal";
  }

  function commaList(value) {
    return String(value || "")
      .split(",")
      .map(function (item) {
        return item.trim().toLowerCase();
      })
      .filter(Boolean);
  }

  function text(root, selector, value) {
    var element = root.querySelector(selector);
    if (element) element.textContent = value == null ? "" : String(value);
  }

  function show(element, visible) {
    if (!element) return;
    element.hidden = !visible;
  }

  function setBusy(root, busy) {
    root.setAttribute("aria-busy", busy ? "true" : "false");
  }

  function config(root) {
    var data = root.dataset;
    var responseField = data.captchaResponseField || data.captchaName || "wrn-captcha-response";
    var compact = bool(data.captchaCompact, false);
    var size = normalizeSize(data.captchaSize, compact);

    return {
      provider: data.captchaProvider || "self-hosted",
      siteKey: data.captchaSiteKey || "",
      type: data.captchaType || "alphanumeric",
      action: data.captchaAction || "form-submit",
      presentation: data.captchaPresentation || "visual",
      difficulty: data.captchaDifficulty || "normal",
      disturbance: numberInRange(data.captchaDisturbance, 50, 25, 75),
      imageStyle: data.captchaImageStyle || "random",
      allowedStyles: commaList(data.captchaAllowedStyles),
      excludedStyles: commaList(data.captchaExcludedStyles),
      randomizeStyle: bool(data.captchaRandomizeStyle, false),
      locale: data.captchaLocale || "en",
      size: size,
      endpoint: data.captchaEndpoint || "/__wrnexus/captcha/challenge",
      verifyEndpoint: data.captchaVerifyEndpoint || "/__wrnexus/captcha/verify",
      responseField: responseField,
      autoLoad: bool(data.captchaAutoLoad, true),
      autoVerify: bool(data.captchaAutoVerify, false),
      showVerify: bool(data.captchaShowVerify, true),
      showRefresh: bool(data.captchaShowRefresh, true),
      showAudio: bool(data.captchaShowAudio, true),
      showListen: bool(data.captchaShowListen, true),
      showStatus: bool(data.captchaShowStatus, true),
      disabled: bool(data.captchaDisabled, false),
      required: bool(data.captchaRequired, true),
      resetOnError: bool(data.captchaResetOnError, true),
      compact: compact,
      requiredMessage: data.captchaRequiredMessage || "Please complete the security check.",
      incorrectMessage: data.captchaIncorrectMessage || "That answer was not correct. Try again.",
      expiredMessage: data.captchaExpiredMessage || "This challenge expired. Load a new one.",
      networkMessage:
        data.captchaNetworkMessage || "The verification service is unavailable. Try again.",
    };
  }

  function eventDetail(state, extra) {
    var challenge = state.challenge;
    return {
      component: "Captcha",
      provider: state.config.provider,
      type: challenge && challenge.type ? challenge.type : state.config.type,
      action: state.config.action,
      disturbance: state.config.disturbance,
      imageStyle:
        challenge && challenge.metadata && challenge.metadata.imageStyle
          ? challenge.metadata.imageStyle
          : state.config.imageStyle,
      requestedImageStyle:
        challenge && challenge.metadata && challenge.metadata.requestedImageStyle
          ? challenge.metadata.requestedImageStyle
          : state.config.imageStyle,
      imageStylePool:
        challenge && challenge.metadata && challenge.metadata.imageStylePool
          ? challenge.metadata.imageStylePool
          : state.config.allowedStyles,
      size: state.config.size,
      status: state.status,
      challengeId: challenge && challenge.id ? challenge.id : "",
      responseToken: state.responseToken,
      expiresAt: challenge && challenge.expiresAt ? challenge.expiresAt : null,
      extra: extra || null,
    };
  }

  function emit(state, name, extra) {
    state.root.dispatchEvent(
      new CustomEvent(name, {
        bubbles: true,
        detail: eventDetail(state, extra),
      }),
    );
  }

  function setResponseToken(state, token) {
    state.responseToken = token || "";
    var input = state.root.querySelector("[data-captcha-response]");
    if (input) {
      input.name = state.config.responseField;
      input.value = state.responseToken;
    }
  }

  function isNotRobot(state) {
    return Boolean(
      (state.challenge && state.challenge.type === "not-robot") ||
      (!state.challenge && state.config.type === "not-robot"),
    );
  }

  function updateNotRobotState(state) {
    var panel = state.root.querySelector("[data-captcha-not-robot]");
    var button = state.root.querySelector("[data-captcha-not-robot-button]");
    var empty = state.root.querySelector("[data-captcha-not-robot-empty]");
    var spinner = state.root.querySelector("[data-captcha-not-robot-spinner]");
    var check = state.root.querySelector("[data-captcha-not-robot-check]");
    var control = state.root.querySelector("[data-captcha-not-robot-control]");
    var label = state.root.querySelector("[data-captcha-not-robot-label]");
    var active = isNotRobot(state);
    var pending = state.notRobotPending || state.status === "verifying";
    var verified = state.status === "verified";

    show(panel, active && state.status !== "loading" && state.status !== "idle");
    if (!active) return;

    if (button) {
      button.disabled = state.config.disabled || pending || verified || state.status === "expired";
      button.setAttribute("aria-pressed", verified ? "true" : "false");
    }
    show(empty, !pending && !verified);
    show(spinner, pending && !verified);
    show(check, verified);

    if (control) control.dataset.verified = verified ? "true" : "false";
    if (label) {
      label.textContent = verified ? "Verified" : pending ? "Checking…" : "I'm not a robot";
    }
  }

  function setStatus(state, status, message) {
    state.status = status;
    state.root.dataset.captchaStatus = status;
    setBusy(state.root, status === "loading" || status === "verifying");

    var loading = state.root.querySelector("[data-captcha-loading]");
    var challenge = state.root.querySelector("[data-captcha-challenge]");
    var success = state.root.querySelector("[data-captcha-success]");
    var error = state.root.querySelector("[data-captcha-error]");
    var badge = state.root.querySelector("[data-captcha-verified-badge]");
    var verifyButton = state.root.querySelector("[data-captcha-verify]");
    var verifyIcon = state.root.querySelector("[data-captcha-verify-icon]");
    var verifySpinner = state.root.querySelector("[data-captcha-verify-spinner]");
    var verifyLabel = state.root.querySelector("[data-captcha-verify-label]");
    var answer = state.root.querySelector("[data-captcha-answer]");

    show(loading, status === "loading");
    show(
      challenge,
      status !== "loading" &&
        status !== "idle" &&
        state.config.provider !== "turnstile" &&
        state.config.provider !== "recaptcha" &&
        state.config.provider !== "hcaptcha",
    );
    show(badge, status === "verified");

    if (state.config.showStatus) {
      show(success, status === "verified");
      show(error, Boolean(message) && status !== "verified");
    } else {
      show(success, false);
      show(error, false);
    }

    if (message && status === "verified")
      text(state.root, "[data-captcha-success-message]", message);
    if (message && status !== "verified") text(state.root, "[data-captcha-error-message]", message);

    if (answer) {
      answer.disabled =
        state.config.disabled ||
        status === "verified" ||
        status === "expired" ||
        status === "loading" ||
        status === "verifying";
      answer.setAttribute("aria-invalid", status === "incorrect" ? "true" : "false");
    }

    if (verifyButton) {
      verifyButton.disabled =
        state.config.disabled ||
        status === "loading" ||
        status === "verifying" ||
        status === "verified" ||
        status === "expired";
    }

    show(verifyIcon, status !== "verifying");
    show(verifySpinner, status === "verifying");
    if (verifyLabel) verifyLabel.textContent = status === "verifying" ? "Verifying…" : "Verify";

    updateNotRobotState(state);
    updateControls(state);
  }

  function clearTimer(state) {
    if (state.timer) {
      window.clearInterval(state.timer);
      state.timer = null;
    }
  }

  function updateCountdown(state) {
    var countdown = state.root.querySelector("[data-captcha-countdown]");
    if (!countdown || !state.challenge || !state.challenge.expiresAt) {
      show(countdown, false);
      return;
    }

    var remaining = Math.max(0, Math.ceil((Number(state.challenge.expiresAt) - Date.now()) / 1000));
    countdown.textContent = remaining + "s";
    show(countdown, remaining > 0 && state.status !== "verified");

    if (remaining <= 0 && state.status !== "verified" && state.status !== "expired") {
      clearTimer(state);
      setResponseToken(state, "");
      setStatus(state, "expired", state.config.expiredMessage);
      emit(state, "expired");
    }
  }

  function startTimer(state) {
    clearTimer(state);
    if (!state.challenge || !state.challenge.expiresAt) return;
    updateCountdown(state);
    state.timer = window.setInterval(function () {
      updateCountdown(state);
    }, 1000);
  }

  function resetUi(state) {
    if (state.audioPlayer) {
      state.audioPlayer.pause();
      state.audioPlayer.removeAttribute("src");
      state.audioPlayer.load();
      state.audioPlayer = null;
    }
    if (state.notRobotTimer) {
      window.clearTimeout(state.notRobotTimer);
      state.notRobotTimer = null;
    }

    state.notRobotPending = false;
    state.challengeLoadedAt = 0;
    state.answer = "";
    state.selections = [];
    state.challenge = null;
    setResponseToken(state, "");

    var answer = state.root.querySelector("[data-captcha-answer]");
    var items = state.root.querySelector("[data-captcha-items]");
    var imageWrap = state.root.querySelector("[data-captcha-image-wrap]");
    var providerMount = state.root.querySelector("[data-captcha-provider-mount]");
    var honeypot = state.root.querySelector("[data-captcha-honeypot]");
    var notRobot = state.root.querySelector("[data-captcha-not-robot]");

    if (answer) answer.value = "";
    if (items) items.replaceChildren();
    show(items, false);
    show(imageWrap, false);
    show(notRobot, false);
    if (honeypot) {
      honeypot.value = "";
      honeypot.name = "";
    }
    if (
      providerMount &&
      state.config.provider !== "turnstile" &&
      state.config.provider !== "recaptcha" &&
      state.config.provider !== "hcaptcha"
    ) {
      providerMount.replaceChildren();
      show(providerMount, false);
    }
  }

  function updateControls(state) {
    var challenge = state.challenge;
    var audio = state.root.querySelector("[data-captcha-audio]");
    var audioAlternative = state.root.querySelector("[data-captcha-audio-alternative]");
    var refresh = state.root.querySelector("[data-captcha-refresh]");
    var verify = state.root.querySelector("[data-captcha-verify]");
    var footer = state.root.querySelector("[data-captcha-footer]");
    var isExternal =
      state.config.provider === "turnstile" ||
      state.config.provider === "recaptcha" ||
      state.config.provider === "hcaptcha";
    var notRobot = isNotRobot(state);
    var locked =
      state.config.disabled ||
      state.status === "loading" ||
      state.status === "verifying" ||
      state.notRobotPending;

    show(footer, !notRobot);
    show(
      audio,
      !notRobot &&
        state.config.showAudio &&
        state.config.showListen &&
        Boolean(challenge && challenge.audioUrl) &&
        state.status !== "verified",
    );
    show(
      audioAlternative,
      !notRobot &&
        state.config.showAudio &&
        Boolean(challenge && challenge.type === "image") &&
        state.status !== "verified",
    );
    show(refresh, !notRobot && state.config.showRefresh && state.status !== "verified");
    show(verify, !notRobot && state.config.showVerify && !isExternal);

    if (audio) audio.disabled = locked;
    if (audioAlternative) audioAlternative.disabled = locked;
    if (refresh) refresh.disabled = locked;
    if (verify)
      verify.disabled = locked || state.status === "verified" || state.status === "expired";
  }

  function renderItems(state, challenge) {
    var container = state.root.querySelector("[data-captcha-items]");
    var template = state.root.querySelector("[data-captcha-item-template]");
    if (
      !container ||
      !template ||
      !Array.isArray(challenge.items) ||
      challenge.items.length === 0
    ) {
      show(container, false);
      return;
    }

    container.replaceChildren();
    container.setAttribute("aria-label", challenge.prompt || "Select matching images");

    challenge.items.forEach(function (item, index) {
      var fragment = template.content.cloneNode(true);
      var button = fragment.querySelector("button");
      var image = fragment.querySelector("[data-captcha-item-image]");
      if (!button || !image) return;

      button.dataset.captchaItemId = item.id;
      button.setAttribute("aria-label", item.alt || "Challenge tile " + (index + 1));
      image.src = item.image;
      image.alt = "";
      button.addEventListener("click", function () {
        toggleItem(state, item.id, button);
      });
      container.appendChild(fragment);
    });

    show(container, true);
  }

  function toggleItem(state, itemId, button) {
    if (state.config.disabled || (state.status !== "ready" && state.status !== "incorrect")) return;

    // An incorrect answer is still the same usable challenge until the server
    // expires it or exhausts its attempts. Let the user amend the selection
    // immediately and clear the stale error/locked control state.
    if (state.status === "incorrect") setStatus(state, "ready", "");

    var index = state.selections.indexOf(itemId);
    var selected = index >= 0;

    if (selected) {
      state.selections.splice(index, 1);
    } else {
      var maximum = Number(
        state.challenge && state.challenge.maxSelections ? state.challenge.maxSelections : 0,
      );
      if (maximum > 0 && state.selections.length >= maximum) {
        var removed = state.selections.shift();
        var previousButton = state.root.querySelector(
          '[data-captcha-item-id="' + CSS.escape(removed) + '"]',
        );
        if (previousButton) {
          previousButton.setAttribute("aria-pressed", "false");
          show(previousButton.querySelector("[data-captcha-item-check]"), false);
        }
      }
      state.selections.push(itemId);
    }

    var nowSelected = state.selections.indexOf(itemId) >= 0;
    button.setAttribute("aria-pressed", nowSelected ? "true" : "false");
    show(button.querySelector("[data-captcha-item-check]"), nowSelected);
    emit(state, "input", { selections: state.selections.slice() });

    var minimum = Number(
      state.challenge && state.challenge.minSelections ? state.challenge.minSelections : 0,
    );
    if (state.config.autoVerify && minimum > 0 && state.selections.length >= minimum) verify(state);
  }

  function renderChallenge(state, challenge) {
    state.challenge = challenge;
    state.challengeLoadedAt = Date.now();
    state.notRobotPending = false;
    state.answer = "";
    state.selections = [];

    var notRobot = challenge.type === "not-robot";
    state.root.dataset.captchaType = challenge.type || state.config.type;
    state.root.dataset.captchaResolvedImageStyle =
      challenge.metadata && challenge.metadata.imageStyle
        ? String(challenge.metadata.imageStyle)
        : state.config.imageStyle;
    text(
      state.root,
      "[data-captcha-prompt]",
      challenge.prompt || "Complete the security challenge.",
    );

    var promptRow = state.root.querySelector("[data-captcha-prompt-row]");
    var notRobotPanel = state.root.querySelector("[data-captcha-not-robot]");
    show(promptRow, !notRobot);
    show(notRobotPanel, notRobot);

    var imageWrap = state.root.querySelector("[data-captcha-image-wrap]");
    var image = state.root.querySelector("[data-captcha-image]");
    if (!notRobot && challenge.image && image) {
      image.src = challenge.image;
      show(imageWrap, true);
    } else {
      if (image) image.removeAttribute("src");
      show(imageWrap, false);
    }

    if (notRobot) {
      var items = state.root.querySelector("[data-captcha-items]");
      if (items) items.replaceChildren();
      show(items, false);
    } else {
      renderItems(state, challenge);
    }

    var answerWrap = state.root.querySelector("[data-captcha-answer-wrap]");
    var answer = state.root.querySelector("[data-captcha-answer]");
    var needsAnswer = !notRobot && !challenge.items && challenge.inputMode !== "none";
    show(answerWrap, needsAnswer);
    if (answer) {
      answer.value = "";
      answer.inputMode = challenge.inputMode === "numeric" ? "numeric" : "text";
      answer.setAttribute("aria-invalid", "false");
    }

    var honeypot = state.root.querySelector("[data-captcha-honeypot]");
    if (honeypot) honeypot.name = challenge.honeypotField || "";

    setStatus(state, "ready", "");
    startTimer(state);
    emit(state, "challenge", challenge);
    emit(state, "ready");

    if (
      state.config.autoVerify &&
      !notRobot &&
      challenge.inputMode === "none" &&
      !challenge.items
    ) {
      window.setTimeout(function () {
        verify(state);
      }, 0);
    }
  }

  async function createChallenge(state, requestedPresentation) {
    if (state.config.disabled) return;

    if (
      state.config.provider === "turnstile" ||
      state.config.provider === "recaptcha" ||
      state.config.provider === "hcaptcha"
    ) {
      await mountExternal(state);
      return;
    }

    clearTimer(state);
    resetUi(state);
    setStatus(state, "loading", "");

    try {
      var response = await fetch(state.config.endpoint, {
        method: "POST",
        credentials: "same-origin",
        headers: {
          "content-type": "application/json",
          accept: "application/json",
        },
        body: JSON.stringify({
          siteKey: state.config.siteKey,
          action: state.config.action,
          type: state.config.type,
          presentation: requestedPresentation || state.config.presentation,
          difficulty: state.config.difficulty,
          disturbance: state.config.disturbance,
          imageStyle: state.config.imageStyle,
          allowedStyles: state.config.allowedStyles,
          excludedStyles: state.config.excludedStyles,
          randomizeStyle: state.config.randomizeStyle,
          locale: state.config.locale,
          responseField: state.config.responseField,
        }),
      });

      var result = await response.json().catch(function () {
        return null;
      });

      if (!response.ok || !result || !result.id) {
        throw new Error(result && result.message ? result.message : "Challenge request failed");
      }

      renderChallenge(state, result);
    } catch (error) {
      setStatus(state, "network-error", state.config.networkMessage);
      emit(state, "error", { error: error instanceof Error ? error.message : String(error) });
    }
  }

  async function verify(state) {
    state.notRobotPending = false;
    if (
      state.config.disabled ||
      state.status === "loading" ||
      state.status === "verifying" ||
      state.status === "expired" ||
      state.status === "verified"
    ) {
      updateNotRobotState(state);
      updateControls(state);
      return;
    }

    if (!state.challenge) {
      await createChallenge(state);
      return;
    }

    var answerInput = state.root.querySelector("[data-captcha-answer]");
    var honeypotInput = state.root.querySelector("[data-captcha-honeypot]");
    state.answer = answerInput ? answerInput.value : "";

    setStatus(state, "verifying", "");
    emit(state, "verify");

    try {
      var response = await fetch(state.challenge.verifyUrl || state.config.verifyEndpoint, {
        method: "POST",
        credentials: "same-origin",
        headers: {
          "content-type": "application/json",
          accept: "application/json",
        },
        body: JSON.stringify({
          challengeId: state.challenge.id,
          action: state.config.action,
          answer: state.answer,
          selections: state.selections,
          honeypot: honeypotInput ? honeypotInput.value : "",
          timingToken: state.challenge.timingToken || "",
        }),
      });

      var result = await response.json().catch(function () {
        return null;
      });

      if (!response.ok || !result || !result.success) {
        setResponseToken(state, "");
        var expired = Boolean(result && result.code === "expired");
        setStatus(
          state,
          expired ? "expired" : "incorrect",
          expired
            ? state.config.expiredMessage
            : result && result.message
              ? result.message
              : state.config.incorrectMessage,
        );
        emit(state, "failure", result || null);
        return;
      }

      clearTimer(state);
      setResponseToken(state, result.responseToken || "");
      setStatus(state, "verified", "Verification completed.");
      emit(state, "success", result);
    } catch (error) {
      setStatus(state, "network-error", state.config.networkMessage);
      emit(state, "error", { error: error instanceof Error ? error.message : String(error) });
    }
  }

  function verifyNotRobot(state) {
    if (state.config.disabled || state.notRobotPending || state.status !== "ready") return;
    if (!state.challenge || state.challenge.type !== "not-robot") {
      createChallenge(state);
      return;
    }

    var configuredMinimum = Number(
      state.challenge.metadata && state.challenge.metadata.minCompletionMs !== undefined
        ? state.challenge.metadata.minCompletionMs
        : 800,
    );
    var minimum = Number.isFinite(configuredMinimum) ? Math.max(0, configuredMinimum) : 800;
    var elapsed = Math.max(0, Date.now() - state.challengeLoadedAt);
    var remaining = Math.max(0, minimum - elapsed);

    state.notRobotPending = true;
    updateNotRobotState(state);
    updateControls(state);
    emit(state, "input", { checked: true });

    var complete = function () {
      state.notRobotTimer = null;
      state.notRobotPending = false;
      verify(state);
    };

    if (remaining > 0) state.notRobotTimer = window.setTimeout(complete, remaining);
    else complete();
  }

  function providerDefinition(provider) {
    if (provider === "turnstile") {
      return {
        url: "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
        globalName: "turnstile",
      };
    }
    if (provider === "recaptcha") {
      return {
        url: "https://www.google.com/recaptcha/api.js?render=explicit",
        globalName: "grecaptcha",
      };
    }
    if (provider === "hcaptcha") {
      return {
        url: "https://js.hcaptcha.com/1/api.js?render=explicit",
        globalName: "hcaptcha",
      };
    }
    return null;
  }

  function loadProviderScript(definition) {
    if (window[definition.globalName]) return Promise.resolve(window[definition.globalName]);
    if (scriptPromises.has(definition.url)) return scriptPromises.get(definition.url);

    var promise = new Promise(function (resolve, reject) {
      var script = document.querySelector('script[src="' + definition.url + '"]');
      if (!script) {
        script = document.createElement("script");
        script.src = definition.url;
        script.async = true;
        script.defer = true;
        document.head.appendChild(script);
      }

      script.addEventListener(
        "load",
        function () {
          if (window[definition.globalName]) resolve(window[definition.globalName]);
          else reject(new Error("CAPTCHA provider did not initialize"));
        },
        { once: true },
      );
      script.addEventListener(
        "error",
        function () {
          reject(new Error("CAPTCHA provider script failed"));
        },
        { once: true },
      );
    });

    scriptPromises.set(definition.url, promise);
    return promise;
  }

  async function mountExternal(state) {
    var definition = providerDefinition(state.config.provider);
    if (!definition) return;

    if (!state.config.siteKey) {
      setStatus(state, "provider-error", "A public site key is required.");
      return;
    }

    var mount = state.root.querySelector("[data-captcha-provider-mount]");
    if (!mount) return;

    show(mount, true);
    setStatus(state, "loading", "");

    try {
      var api = await loadProviderScript(definition);

      if (state.externalWidgetId !== null && typeof api.reset === "function") {
        setResponseToken(state, "");
        api.reset(state.externalWidgetId);
        setStatus(state, "ready", "");
        emit(state, "ready");
        return;
      }

      mount.replaceChildren();
      state.externalApi = api;
      state.externalWidgetId = api.render(mount, {
        sitekey: state.config.siteKey,
        theme: "auto",
        size:
          state.config.size === "compact"
            ? "compact"
            : state.config.provider === "turnstile"
              ? "flexible"
              : "normal",
        action: state.config.action,
        callback: function (token) {
          setResponseToken(state, token);
          setStatus(state, "verified", "Verification completed.");
          emit(state, "success");
        },
        "expired-callback": function () {
          setResponseToken(state, "");
          setStatus(state, "expired", state.config.expiredMessage);
          emit(state, "expired");
        },
        "error-callback": function (providerError) {
          setResponseToken(state, "");
          setStatus(state, "provider-error", state.config.networkMessage);
          emit(state, "error", { error: providerError || "provider-error" });
        },
      });

      setStatus(state, "ready", "");
      emit(state, "ready");
    } catch (error) {
      setStatus(state, "provider-error", state.config.networkMessage);
      emit(state, "error", { error: error instanceof Error ? error.message : String(error) });
    }
  }

  function playAudio(state) {
    if (!state.challenge || !state.challenge.audioUrl || state.config.disabled) return;

    if (state.audioPlayer) {
      state.audioPlayer.pause();
      state.audioPlayer.currentTime = 0;
    }

    var audio = new Audio();
    state.audioPlayer = audio;
    audio.preload = "auto";
    audio.volume = 1;
    audio.src = new URL(state.challenge.audioUrl, window.location.href).href;

    audio.addEventListener(
      "ended",
      function () {
        if (state.audioPlayer === audio) state.audioPlayer = null;
        emit(state, "audioEnd");
        emit(state, "audio-end");
      },
      { once: true },
    );

    audio.addEventListener(
      "error",
      function () {
        if (state.audioPlayer === audio) state.audioPlayer = null;
        var mediaError = audio.error;
        setStatus(
          state,
          "network-error",
          "The audio challenge could not be played. Load a new challenge and try again.",
        );
        emit(state, "error", {
          code: "audio-playback-failed",
          audioUrl: audio.src,
          mediaErrorCode: mediaError ? mediaError.code : null,
        });
      },
      { once: true },
    );

    audio.load();
    audio
      .play()
      .then(function () {
        emit(state, "audioStart");
        emit(state, "audio-start");
      })
      .catch(function (error) {
        if (state.audioPlayer === audio) state.audioPlayer = null;
        setStatus(
          state,
          "network-error",
          "The audio challenge could not be played. Check the audio endpoint and try again.",
        );
        emit(state, "error", {
          code: "audio-playback-rejected",
          audioUrl: audio.src,
          error: error instanceof Error ? error.message : String(error),
        });
      });
  }

  function passesSubmitGuard(state) {
    if (!state.config.required || state.responseToken) return true;
    setStatus(state, "incorrect", state.config.requiredMessage);
    emit(state, "failure", { code: "missing-input" });
    var target =
      state.root.querySelector("[data-captcha-answer]") ||
      state.root.querySelector("[data-captcha-not-robot-button]") ||
      state.root.querySelector("[data-captcha-verify]");
    if (target && typeof target.focus === "function") target.focus();
    return false;
  }

  function onFormSubmit(state, event) {
    // Schema-backed forms run this guard after their ordinary fields validate.
    if (state.form && state.form.__wireValidateBound) return;
    if (passesSubmitGuard(state)) return;
    event.preventDefault();
    event.stopImmediatePropagation();
  }

  function initialize(root) {
    if (!(root instanceof HTMLElement) || states.has(root)) return;

    if (!root.id) {
      instanceCounter += 1;
      root.id = "wrn-captcha-" + instanceCounter;
    }

    var state = {
      root: root,
      config: config(root),
      challenge: null,
      answer: "",
      selections: [],
      responseToken: "",
      status: "idle",
      timer: null,
      notRobotTimer: null,
      notRobotPending: false,
      challengeLoadedAt: 0,
      externalApi: null,
      externalWidgetId: null,
      audioPlayer: null,
      form: root.closest("form"),
      formSubmitListener: null,
      formSuccessListener: null,
      formErrorListener: null,
    };
    states.set(root, state);
    root.dataset.captchaSize = state.config.size;

    var response = root.querySelector("[data-captcha-response]");
    if (response) response.name = state.config.responseField;

    var answer = root.querySelector("[data-captcha-answer]");
    var verifyButton = root.querySelector("[data-captcha-verify]");
    var refreshButton = root.querySelector("[data-captcha-refresh]");
    var audioButton = root.querySelector("[data-captcha-audio]");
    var audioAlternative = root.querySelector("[data-captcha-audio-alternative]");
    var notRobotButton = root.querySelector("[data-captcha-not-robot-button]");

    if (answer) {
      answer.addEventListener("input", function () {
        state.answer = answer.value;
        emit(state, "input", { answerLength: state.answer.length });
      });
      answer.addEventListener("keydown", function (event) {
        if (event.key === "Enter") {
          event.preventDefault();
          verify(state);
        }
      });
    }

    if (verifyButton)
      verifyButton.addEventListener("click", function () {
        verify(state);
      });
    if (notRobotButton)
      notRobotButton.addEventListener("click", function () {
        verifyNotRobot(state);
      });
    if (refreshButton)
      refreshButton.addEventListener("click", function () {
        emit(state, "refresh");
        createChallenge(state, state.config.presentation);
      });
    if (audioButton)
      audioButton.addEventListener("click", function () {
        playAudio(state);
      });
    if (audioAlternative)
      audioAlternative.addEventListener("click", function () {
        emit(state, "refresh", { presentation: "audio" });
        createChallenge(state, "audio");
      });

    root.addEventListener("captcha-reset", function () {
      emit(state, "refresh");
      createChallenge(state, state.config.presentation);
    });

    if (state.form) {
      state.submitGuard = function () {
        return passesSubmitGuard(state);
      };
      state.form.__wireSubmitGuards = state.form.__wireSubmitGuards || [];
      state.form.__wireSubmitGuards.push(state.submitGuard);
      state.formSubmitListener = function (event) {
        onFormSubmit(state, event);
      };
      state.formSuccessListener = function () {
        createChallenge(state, state.config.presentation);
      };
      state.formErrorListener = function (event) {
        // Verified response tokens are single-use. The receiving API may
        // consume one before another form field fails, so retry with a fresh
        // challenge instead of reusing an already-consumed token.
        var detail = event && event.detail ? event.detail : {};
        var captchaFailed =
          detail.code === "missing-input" ||
          detail.code === "invalid-input" ||
          detail.code === "expired" ||
          detail.code === "already-used" ||
          detail.code === "action-mismatch" ||
          detail.code === "hostname-mismatch" ||
          detail.code === "session-mismatch";
        if (state.config.resetOnError || captchaFailed)
          createChallenge(state, state.config.presentation);
      };
      state.form.addEventListener("submit", state.formSubmitListener, true);
      state.form.addEventListener("wire:success", state.formSuccessListener);
      state.form.addEventListener("wire:error", state.formErrorListener);
    }

    updateNotRobotState(state);
    updateControls(state);
    if (state.config.disabled) setStatus(state, "disabled", "");
    else if (state.config.autoLoad) createChallenge(state, state.config.presentation);
  }

  function scan(scope) {
    var host = scope || document;
    if (host instanceof Element && host.matches("[data-wrn-captcha]")) initialize(host);
    host.querySelectorAll("[data-wrn-captcha]").forEach(initialize);
  }

  function cleanupRoot(root) {
    var state = states.get(root);
    if (!state) return;
    if (state.timer) clearInterval(state.timer);
    if (state.notRobotTimer) clearTimeout(state.notRobotTimer);
    if (state.audioPlayer) {
      try {
        state.audioPlayer.pause();
        state.audioPlayer.src = "";
      } catch {
        // Best-effort cleanup for browser audio implementations.
      }
    }
    if (state.form && state.formSubmitListener) {
      state.form.removeEventListener("submit", state.formSubmitListener, true);
    }
    if (state.form && state.form.__wireSubmitGuards && state.submitGuard) {
      state.form.__wireSubmitGuards = state.form.__wireSubmitGuards.filter(function (guard) {
        return guard !== state.submitGuard;
      });
    }
    if (state.form && state.formSuccessListener) {
      state.form.removeEventListener("wire:success", state.formSuccessListener);
    }
    if (state.form && state.formErrorListener) {
      state.form.removeEventListener("wire:error", state.formErrorListener);
    }
    try {
      if (state.externalApi && state.externalWidgetId != null) {
        if (typeof state.externalApi.remove === "function")
          state.externalApi.remove(state.externalWidgetId);
        else if (typeof state.externalApi.reset === "function")
          state.externalApi.reset(state.externalWidgetId);
      }
    } catch {
      // Third-party providers may reject cleanup after navigation.
    }
    states.delete(root);
  }

  function unmount(scope) {
    var host = scope || document;
    var roots = [];
    if (host instanceof Element && host.matches("[data-wrn-captcha]")) roots.push(host);
    if (host.querySelectorAll)
      host.querySelectorAll("[data-wrn-captcha]").forEach(function (root) {
        roots.push(root);
      });
    roots.forEach(cleanupRoot);
  }

  var runtime = {
    scan: scan,
    mount: scan,
    unmount: unmount,
    reset: function (element) {
      var root = typeof element === "string" ? document.querySelector(element) : element;
      var state = root ? states.get(root) : null;
      if (state) createChallenge(state, state.config.presentation);
    },
    verify: function (element) {
      var root = typeof element === "string" ? document.querySelector(element) : element;
      var state = root ? states.get(root) : null;
      return state ? verify(state) : Promise.resolve();
    },
  };

  window[RUNTIME_KEY] = runtime;
  window.__wrnexusRuntimes = window.__wrnexusRuntimes || {};
  window.__wrnexusRuntimes.captcha = { mount: scan, unmount: unmount };

  if (document.readyState === "loading") {
    document.addEventListener(
      "DOMContentLoaded",
      function () {
        scan(document);
      },
      { once: true },
    );
  } else {
    scan(document);
  }

  new MutationObserver(function (records) {
    records.forEach(function (record) {
      record.addedNodes.forEach(function (node) {
        if (node.nodeType === 1) scan(node);
      });
    });
  }).observe(document.documentElement, { childList: true, subtree: true });
})();
