(function initVisaFlowXDetector(global) {
  "use strict";

  const { DomUtils, Parser } = global.VisaFlowX;

  const IVAC_ORIGIN = "https://appointment.ivacbd.com";

  const SELECTORS = Object.freeze({
    contact: [
      "input[name='mobile_no']",
      "input[name='contact_no']",
      "input[name='phone']",
      "input[name='username']",
      "input[type='tel']",
      "input[placeholder*='mobile' i]",
      "input[placeholder*='contact' i]",
      "input[placeholder*='phone' i]"
    ],
    password: [
      "input[type='password']",
      "input[name='password']",
      "input[placeholder*='password' i]"
    ],
    signInButton: [
      "button[type='submit']",
      "input[type='submit']",
      "button"
    ],
    verification: [
      "iframe[src*='challenges.cloudflare.com']",
      "iframe[src*='turnstile']",
      ".cf-turnstile",
      "[data-cf-turnstile]",
      "[data-sitekey]"
    ],
    otpInput: [
      "input[autocomplete='one-time-code']",
      "input[inputmode='numeric']",
      "input[name*='otp' i]",
      "input[id*='otp' i]",
      "input[placeholder*='otp' i]",
      "input[name*='verification' i]",
      "input[id*='verification' i]",
      "input[placeholder*='verification' i]",
      "input[aria-label*='verification code' i]",
      "input[aria-label*='otp' i]"
    ],
    success: [
      ".ctp-success",
      ".mark.mark-green",
      ".checkmark",
      ".is-success"
    ]
  });

  const OTP_TEXT = Object.freeze([
    "otp",
    "one time password",
    "one-time password",
    "verification code",
    "security code",
    "authentication code",
    "enter code"
  ]);

  const VERIFICATION_TOKEN_SELECTORS = Object.freeze([
    "input[name='cf-turnstile-response']",
    "textarea[name='cf-turnstile-response']",
    "input[name='cf_turnstile_response']",
    "input[name='cf_challenge_response']",
    "textarea[name='cf_challenge_response']",
    "[name='cf-turnstile-response']",
    "[name='cf_challenge_response']"
  ]);
  const TURNSTILE_TARGET_SELECTORS = Object.freeze([
    "iframe[src*='challenges.cloudflare.com']",
    "iframe[src*='turnstile']",
    "iframe[title*='Widget containing']",
    "iframe[title*='Cloudflare security challenge']",
    ".cf-turnstile",
    "[data-cf-turnstile]",
    "[data-sitekey]"
  ]);
  const FORBIDDEN_INTERACTION_SELECTOR = [
    "input[type='password']",
    "button[aria-label*='password' i]",
    "button[title*='password' i]",
    "button[class*='password' i]",
    "button[class*='eye' i]",
    "button[class*='toggle' i]",
    ".eye-icon",
    ".password-toggle",
    ".icon",
    "svg",
    "img"
  ].join(",");
  const SIGN_IN_TEXT_RE = /\b(sign\s*in(?:\s*now)?|login|continue|proceed)\b/i;
  const FORBIDDEN_CONTROL_RE = /(^|\s|-|_)(eye|toggle|password-toggle|show-password|hide-password|icon)(\s|-|_|$)/i;
  const TOKEN_STABILIZATION_MS = 0;
  let tokenCandidateValue = "";
  let tokenCandidateSeenAt = 0;

  function isIvacPage() {
    return location.origin === IVAC_ORIGIN;
  }

  function findContactInput(root = document) {
    return DomUtils.first(SELECTORS.contact, root);
  }

  function findPasswordInput(root = document) {
    return DomUtils.first(SELECTORS.password, root);
  }

  function classText(element) {
    const value = element?.className;
    return String(value?.baseVal || value || "");
  }

  function controlText(element) {
    return [
      element?.value,
      element?.innerText,
      element?.textContent,
      element?.getAttribute?.("aria-label"),
      element?.getAttribute?.("title")
    ].filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
  }

  function hasUsableBox(element, minWidth = 20, minHeight = 20) {
    if (!element?.getBoundingClientRect || element.isConnected === false) return false;
    if (element.closest?.("template,[hidden]")) return false;
    const rect = element.getBoundingClientRect();
    if (!rect || rect.width < minWidth || rect.height < minHeight) return false;
    const viewportWidth = global.innerWidth || document.documentElement?.clientWidth || 0;
    const viewportHeight = global.innerHeight || document.documentElement?.clientHeight || 0;
    if (viewportWidth && (rect.right <= 0 || rect.left >= viewportWidth)) return false;
    if (viewportHeight && (rect.bottom <= 0 || rect.top >= viewportHeight)) return false;
    const style = global.getComputedStyle ? global.getComputedStyle(element) : null;
    if (style && (
      style.visibility === "hidden"
      || style.display === "none"
      || style.pointerEvents === "none"
      || Number(style.opacity || 1) === 0
    )) {
      return false;
    }
    return true;
  }

  function isForbiddenInteractionTarget(element) {
    if (!element) return true;
    
    // Explicitly ALLOW Cloudflare iframes to fix the interaction regression
    if (element.tagName?.toLowerCase() === "iframe" && frameLooksLikeVerification(element)) {
      return false;
    }

    const tagName = element.tagName?.toLowerCase();
    if (["svg", "img"].includes(tagName)) return true;
    if (element.matches?.("input[type='password']")) return true;
    if (element.closest?.(FORBIDDEN_INTERACTION_SELECTOR)) return true;

    const identity = [
      classText(element),
      element.id,
      element.getAttribute?.("aria-label"),
      element.getAttribute?.("title"),
      element.getAttribute?.("data-testid")
    ].filter(Boolean).join(" ");
    return FORBIDDEN_CONTROL_RE.test(identity);
  }

  function isSafeSignInButton(element) {
    if (!element || isForbiddenInteractionTarget(element)) return false;
    const tagName = element.tagName?.toLowerCase();
    if (!["button", "input"].includes(tagName) && element.getAttribute?.("role") !== "button") return false;
    if (element.disabled || element.getAttribute?.("aria-disabled") === "true") return false;
    if (element.getAttribute?.("aria-busy") === "true") return false;
    if (looksBusy(element)) return false;
    // Step 5 Validation: offsetWidth > 80, offsetHeight > 30
    if (!hasUsableBox(element, 80, 30)) return false;

    const type = String(element.getAttribute?.("type") || "").toLowerCase();
    if (tagName === "input" && !["submit", "button"].includes(type)) return false;
    if (tagName === "input") return SIGN_IN_TEXT_RE.test(controlText(element)) || type === "submit";
    if (type === "submit") return true;
    return SIGN_IN_TEXT_RE.test(controlText(element));
  }

  function looksBusy(element) {
    if (!element) return false;
    const identity = [
      classText(element),
      element.getAttribute?.("aria-label"),
      element.getAttribute?.("title"),
      element.getAttribute?.("data-state")
    ].filter(Boolean).join(" ");
    if (/\b(loading|busy|submitting|spinner)\b/i.test(identity)) return true;
    return Boolean(element.querySelector?.([
      ".spinner",
      ".loader",
      ".loading",
      "[class*='spinner' i]",
      "[class*='loader' i]",
      "[aria-label*='loading' i]",
      "[aria-busy='true']"
    ].join(",")));
  }

  function findSignInButton(root = document) {
    const submit = DomUtils.safeQueryAll("button[type='submit'], input[type='submit']", root)
      .find(isSafeSignInButton);
    if (submit) return submit;

    return DomUtils.safeQueryAll("button, input[type='button'], input[type='submit'], [role='button']", root)
      .find((element) => isSafeSignInButton(element) && SIGN_IN_TEXT_RE.test(controlText(element))) || null;
  }

  function frameLooksLikeSuccess(frame) {
    return false; // Cross-origin frames are handled via CAPTCHA_RESULT messages
  }

  function frameLooksLikeVerification(frame) {
    if (!frame) return false;
    const text = [
      frame.src,
      frame.name,
      frame.id,
      frame.title,
      frame.getAttribute?.("aria-label")
    ].filter(Boolean).join(" ").toLowerCase();
    return /challenges\.cloudflare\.com|turnstile|cf-turnstile/.test(text);
  }

  function styleAllowsDisplay(element) {
    let current = element;
    while (current && current.nodeType === Node.ELEMENT_NODE) {
      if (current.closest?.("template,[hidden]")) return false;
      if (current.getAttribute?.("aria-hidden") === "true") return false;
      const style = global.getComputedStyle ? global.getComputedStyle(current) : null;
      if (style && (
        style.visibility === "hidden"
        || style.display === "none"
        || Number(style.opacity || 1) === 0
      )) {
        return false;
      }
      current = current.parentElement;
    }
    return true;
  }

  function isVisibleVerificationElement(element) {
    if (!hasUsableBox(element, 20, 20)) return false;
    if (!styleAllowsDisplay(element)) return false;
    return true;
  }

  function collectDeep(selector, root = document, depth = 0, result = []) {
    if (!selector || !root || depth > 4) return result;
    DomUtils.safeQueryAll(selector, root).forEach((element) => result.push(element));
    DomUtils.safeQueryAll("*", root).forEach((element) => {
      if (element.shadowRoot) collectDeep(selector, element.shadowRoot, depth + 1, result);
      if (element.tagName?.toLowerCase() !== "iframe") return;
      try {
        if (element.contentDocument) collectDeep(selector, element.contentDocument, depth + 1, result);
      } catch (_) {
        // Cross-origin frames are validated from iframe metadata.
      }
    });
    return result;
  }

  function containsTurnstileResponse(element) {
    if (!element?.querySelector) return false;
    return VERIFICATION_TOKEN_SELECTORS.some((selector) => DomUtils.safeQuery(selector, element));
  }

  function containsPasswordControl(element) {
    return Boolean(element?.querySelector?.("input[type='password']"));
  }

  function matchesAllowedTurnstileSelector(element) {
    return TURNSTILE_TARGET_SELECTORS.some((selector) => {
      try {
        return element.matches?.(selector);
      } catch (_) {
        return false;
      }
    });
  }

  function isSafeTurnstileTarget(element) {
    if (!element || isForbiddenInteractionTarget(element)) return false;
    if (!isVisibleVerificationElement(element)) return false;
    if (containsPasswordControl(element)) return false;

    const tagName = element.tagName?.toLowerCase();
    if (tagName === "iframe") return frameLooksLikeVerification(element);
    if (matchesAllowedTurnstileSelector(element)) return true;

    const visibleFrame = DomUtils.safeQueryAll("iframe", element)
      .find((frame) => frameLooksLikeVerification(frame) && isVisibleVerificationElement(frame));
    if (visibleFrame) return true;

    return containsTurnstileResponse(element);
  }

  function tokenContainer(root = document) {
    for (const selector of VERIFICATION_TOKEN_SELECTORS) {
      const token = collectDeep(selector, root).find((element) => element?.name || String(element?.value || "").trim().length > 20);
      const container = token?.closest?.(".cf-turnstile,[data-cf-turnstile],[data-sitekey],.cf-turnstile-wrapper");
      if (isSafeTurnstileTarget(container)) return container;
      const parent = token?.parentElement;
      if (parent && parent.tagName?.toLowerCase() !== "form" && isSafeTurnstileTarget(parent)) return parent;
    }
    return null;
  }

  function resolveTurnstileTarget(root = document) {
    for (const selector of TURNSTILE_TARGET_SELECTORS) {
      const target = collectDeep(selector, root).find(isSafeTurnstileTarget);
      if (target) {
        return {
          element: target,
          selector,
          frame: target.tagName?.toLowerCase() === "iframe"
            ? target
            : DomUtils.safeQuery("iframe[src*='challenges.cloudflare.com'], iframe[src*='turnstile']", target)
        };
      }
    }

    const fromToken = tokenContainer(root);
    if (fromToken) {
      return {
        element: fromToken,
        selector: "turnstile-response-container",
        frame: DomUtils.safeQuery("iframe[src*='challenges.cloudflare.com'], iframe[src*='turnstile']", fromToken)
      };
    }

    return null;
  }

  function isActiveVerificationCandidate(element) {
    return isSafeTurnstileTarget(element);
  }

  function queryDeep(selectors = [], root = document, depth = 0) {
    if (!root || depth > 4) return null;
    for (const selector of selectors) {
      const direct = DomUtils.safeQueryAll(selector, root).find(isActiveVerificationCandidate);
      if (direct) return direct;
    }

    const elements = DomUtils.safeQueryAll("*", root);
    for (const element of elements) {
      if (element.shadowRoot) {
        const found = queryDeep(selectors, element.shadowRoot, depth + 1);
        if (found) return found;
      }

      if (element.tagName?.toLowerCase() === "iframe") {
        if (isActiveVerificationCandidate(element)) return element;
        try {
          const frameDocument = element.contentDocument;
          if (frameDocument) {
            const found = queryDeep(selectors, frameDocument, depth + 1);
            if (found) return found;
          }
        } catch (_) {
          // Cross-origin frames cannot be inspected; visible challenge frames are detected by their iframe metadata.
        }
      }
    }
    return null;
  }

  function hasCompletedVerification(root = document) {
    return getVerificationTokenState(root).stable;
  }

  function visibleStateText(root = document) {
    const target = root?.body || root?.documentElement || root;
    if (!target) return "";
    return [
      target.innerText,
      target.textContent,
      target.getAttribute?.("aria-label"),
      target.getAttribute?.("title")
    ].filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
  }

  function hasSuccessText(root = document) {
    return /\bsuccess(?:!|\b)/i.test(visibleStateText(root));
  }

  function hasVerifiedCheckbox(root = document) {
    return Boolean(
      DomUtils.safeQuery("[role='checkbox'][aria-checked='true']", root)
      || DomUtils.safeQuery("input[type='checkbox']:checked", root)
      || DomUtils.safeQuery(".ctp-checkbox-label.is-checked,.ctp-checkbox-label[aria-checked='true']", root)
    );
  }

  function findVerificationToken(root = document) {
    for (const selector of VERIFICATION_TOKEN_SELECTORS) {
      const input = DomUtils.safeQuery(selector, root);
      const value = String(input?.value || "").trim();
      if (value.length > 20) return value;
    }

    const elements = DomUtils.safeQueryAll("*", root);
    for (const element of elements) {
      if (element.shadowRoot) {
        const shadowValue = findVerificationToken(element.shadowRoot);
        if (shadowValue) return shadowValue;
      }
      if (element.tagName?.toLowerCase() !== "iframe") continue;
      try {
        if (element.contentDocument) {
          const frameValue = findVerificationToken(element.contentDocument);
          if (frameValue) return frameValue;
        }
      } catch (_) {
        // Ignore cross-origin frames.
      }
    }
    return "";
  }

  function getVerificationTokenState(root = document) {
    const value = findVerificationToken(root);
    const now = Date.now();
    if (!value) {
      tokenCandidateValue = "";
      tokenCandidateSeenAt = 0;
      return { stable: false, pending: false, value: "" };
    }

    if (value !== tokenCandidateValue) {
      tokenCandidateValue = value;
      tokenCandidateSeenAt = now;
      return { stable: true, pending: false, value };
    }

    return {
      stable: now - tokenCandidateSeenAt >= TOKEN_STABILIZATION_MS,
      pending: now - tokenCandidateSeenAt < TOKEN_STABILIZATION_MS,
      value
    };
  }

  function detectVerification(root = document) {
    const tokenState = getVerificationTokenState(root);
    const target = resolveTurnstileTarget(root);
    // Visual success check: If widget shows success mark, treat as completed
    const isVisuallySuccessful = target?.element && (
      hasSuccessText(root) ||
      hasVerifiedCheckbox(root) ||
      target.element.querySelector?.(".ctp-success, .is-success, .checkmark, .mark.mark-green") ||
      (target.frame && frameLooksLikeSuccess(target.frame))
    );

    if (tokenState.stable || isVisuallySuccessful) {
      return { present: false, completed: true, selector: target?.selector || VERIFICATION_TOKEN_SELECTORS[0], element: null };
    }

    if (target?.element) {
      return {
        present: true,
        completed: false,
        selector: target.selector,
        element: target.element,
        frame: target.frame || null
      };
    }
    if (tokenState.pending) {
      return { present: false, completed: false, pending: true, selector: VERIFICATION_TOKEN_SELECTORS[0], element: null };
    }
    return { present: false, completed: false, selector: "", element: null };
  }

  function looksLikeOtpInput(element) {
    if (!element || element.disabled || element.readOnly) return false;
    const tag = element.tagName?.toLowerCase();
    if (!["input", "textarea"].includes(tag)) return false;
    if (!DomUtils.isVisible(element)) return false;
    const attributes = [
      element.getAttribute("autocomplete"),
      element.getAttribute("inputmode"),
      element.getAttribute("name"),
      element.getAttribute("id"),
      element.getAttribute("placeholder"),
      element.getAttribute("aria-label")
    ].filter(Boolean).join(" ").toLowerCase();
    return ["one-time-code", "otp", "verification", "security code", "auth code"].some((word) => attributes.includes(word));
  }

  function findOtpInput(root = document) {
    for (const selector of SELECTORS.otpInput) {
      const element = DomUtils.safeQuery(selector, root);
      if (looksLikeOtpInput(element)) return element;
    }
    return DomUtils.safeQueryAll("input, textarea", root).find(looksLikeOtpInput) || null;
  }

  function detectOtp(root = document) {
    let digitBoxes = [];
    if (typeof document !== "undefined") {
      const allInputs = Array.from(root.querySelectorAll("input[type='tel'], input[type='text'], input[inputmode='numeric']"));
      digitBoxes = allInputs.filter((el) => {
        const max = Number(el.getAttribute("maxlength") || el.getAttribute("size") || 0);
        return max === 1 && el.offsetParent !== null;
      });
    }

    if (digitBoxes.length >= 4 && digitBoxes.length <= 8) {
      return { present: true, input: digitBoxes[0], inputs: digitBoxes, reason: "otp_multibox" };
    }

    if (typeof location !== "undefined" && location.pathname.toLowerCase().includes("verify-login-phone-otp")) {
      return { present: true, input: findOtpInput(root) || root.querySelector("input"), reason: "otp_route" };
    }

    const input = findOtpInput(root);
    if (input) return { present: true, input, reason: "otp_input" };

    const text = DomUtils.visibleText(root).toLowerCase();
    if (OTP_TEXT.some((phrase) => text.includes(phrase))) {
      return { present: true, input: root.querySelector("input[type='text'], input[type='tel'], input[inputmode='numeric']"), reason: "otp_text" };
    }
    return { present: false, input: null, reason: "" };
  }

  function detectPage(root = document) {
    const text = DomUtils.visibleText(root);
    const error = Parser.detectPageError(text, location.href);
    const otp = detectOtp(root);
    const verification = detectVerification(root);
    const contact = findContactInput(root);
    const password = findPasswordInput(root);
    const signInButton = findSignInButton(root);
    const path = location.pathname.toLowerCase();

    let type = "unknown";
    if (!isIvacPage()) type = "unsupported";
    else if (otp.present) type = "otp";
    else if (error) type = "error";
    else if (path.includes("signin") || (contact && password)) type = "signin";
    else if (/dashboard|appointment|profile|payment|application/.test(path) && !password) type = "completed";

    return {
      type,
      text,
      error,
      otp,
      verification,
      contact,
      password,
      signInButton,
      url: location.href
    };
  }

  const Detector = Object.freeze({
    IVAC_ORIGIN,
    OTP_TEXT,
    SELECTORS,
    detectOtp,
    detectPage,
    detectVerification,
    hasCompletedVerification,
    findContactInput,
    findOtpInput,
    findPasswordInput,
    findSignInButton,
    isIvacPage,
    isSafeSignInButton,
    looksLikeOtpInput,
    resolveTurnstileTarget
  });

  global.VisaFlowX = Object.assign(global.VisaFlowX || {}, { Detector });
})(typeof globalThis !== "undefined" ? globalThis : this);
