(function attachBackgroundGoPayApprove(root, factory) {
  root.MultiPageBackgroundGoPayApprove = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGoPayApproveModule() {
  const GOPAY_SOURCE = 'gopay-flow';
  const GOPAY_OTP_SOURCE = 'gopay-otp-flow';
  const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
  const GOPAY_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/gopay-flow.js'];
  const GOPAY_WAIT_TIMEOUT_MS = 120000;
  const GOPAY_POLL_INTERVAL_MS = 1000;
  const GOPAY_LINKING_RETRY_WAIT_MS = 15000;
  const GOPAY_LINKING_STABLE_WAIT_MS = 60000;
  const GOPAY_OTP_FRAME_URL_PATTERN = /\/linking\/otp\b|gopayapi\.com\/linking\/otp/i;
  const GOPAY_PIN_FRAME_URL_PATTERN = /pin-web-client\.gopayapi\.com\/auth\/pin|\/auth\/pin\/verify|linking-validate-pin|merchants-gws-app\.gopayapi\.com\/payment\/validate-pin|\/payment\/validate-pin/i;
  const GOPAY_PAYMENT_FRAME_URL_PATTERN = /merchants-gws-app\.gopayapi\.com\/(?:payment\/details|app\/challenge)|\/gopay-tokenization\/pay/i;

  function createGoPayApproveExecutor(deps = {}) {
    const {
      addLog,
      chrome,
      completeStepFromBackground,
      ensureContentScriptReadyOnTabUntilStopped,
      getTabId,
      isTabAlive,
      registerTab,
      sendTabMessageUntilStopped,
      setState,
      sleepWithStop,
      waitForTabCompleteUntilStopped,
      clickWithDebugger = null,
      requestGoPayOtpInput = null,
      throwIfStopped = null,
    } = deps;

    function normalizeText(value = '') {
      return String(value || '').replace(/\s+/g, ' ').trim();
    }

    function formatGoPayTerminalError(pageState = {}) {
      const terminalError = pageState?.terminalError || {};
      const terminalMessage = normalizeText(terminalError.message || 'GoPay 页面显示支付失败或会话已失效。');
      const rawText = normalizeText(terminalError.rawText || pageState?.textPreview || '');
      const rawSuffix = rawText ? ` 页面提示：${rawText.slice(0, 180)}` : '';
      return `${terminalMessage}${rawSuffix}`;
    }

    function createGoPayStableStateTracker() {
      let signature = '';
      let firstSeenAt = 0;
      return {
        update(pageState = {}, currentUrl = '') {
          const nextSignature = [
            pageState.url || currentUrl || '',
            pageState.hasPhoneInput ? 'phone' : '',
            pageState.hasOtpInput ? 'otp' : '',
            pageState.hasPinInput ? 'pin' : '',
            pageState.hasPayNowButton ? 'pay' : '',
            pageState.hasContinueButton ? 'continue' : '',
            normalizeText(pageState.textPreview || '').slice(0, 700),
          ].join('::');
          const now = Date.now();
          if (nextSignature !== signature) {
            signature = nextSignature;
            firstSeenAt = now;
          }
          return {
            signature,
            stableMs: firstSeenAt ? now - firstSeenAt : 0,
          };
        },
        reset() {
          signature = '';
          firstSeenAt = 0;
        },
      };
    }

    async function restartGoPayCheckoutFromStep6(tabId, reason = '') {
      const message = normalizeText(reason || 'GoPay 支付页已失效或点击后没有进入下一步。');
      await addLog(`步骤 8：${message} 正在关闭当前 GoPay/Checkout 页面，并回到步骤 6 重新创建 Plus Checkout。`, 'warn');
      if (Number.isInteger(tabId) && chrome?.tabs?.remove) {
        await chrome.tabs.remove(tabId).catch(() => {});
      }
      await setState({
        plusCheckoutTabId: null,
        plusCheckoutUrl: null,
        plusReturnUrl: '',
        plusPaypalApprovedAt: null,
        plusGoPayApprovedAt: null,
      });
      throw new Error(`GOPAY_RESTART_FROM_STEP6::步骤 8：${message} 已关闭当前支付页，请从步骤 6 重新创建 Plus Checkout。`);
    }

    async function handleGoPayTerminalError(pageState = {}, tabId = null) {
      if (pageState?.hasTerminalError || pageState?.terminalError) {
        await restartGoPayCheckoutFromStep6(tabId, formatGoPayTerminalError(pageState));
      }
    }

    function isGoPayUrl(url = '') {
      return /gopay|gojek|midtrans|xendit|stripe|checkout/i.test(String(url || ''))
        && !/chatgpt\.com\/checkout/i.test(String(url || ''));
    }

    function isReturnUrl(url = '') {
      return /https:\/\/(?:chatgpt\.com|chat\.openai\.com|openai\.com)\//i.test(String(url || ''))
        && !/gopay|gojek|midtrans|xendit|stripe/i.test(String(url || ''));
    }

    async function findOpenTabId(predicate) {
      if (!chrome?.tabs?.query) {
        return 0;
      }
      const tabs = await chrome.tabs.query({}).catch(() => []);
      const candidates = (Array.isArray(tabs) ? tabs : [])
        .filter((tab) => Number.isInteger(tab?.id) && predicate(tab.url || ''));
      if (!candidates.length) {
        return 0;
      }
      const match = candidates.find((tab) => tab.active && tab.currentWindow)
        || candidates.find((tab) => tab.active)
        || candidates[0];
      if (match?.id && chrome?.tabs?.update) {
        await chrome.tabs.update(match.id, { active: true }).catch(() => {});
      }
      return match?.id || 0;
    }

    async function resolveGoPayTabId(state = {}) {
      const registeredGoPayTabId = await getTabId(GOPAY_SOURCE);
      if (registeredGoPayTabId && await isTabAlive(GOPAY_SOURCE)) {
        return registeredGoPayTabId;
      }
      const discoveredGoPayTabId = await findOpenTabId(isGoPayUrl);
      if (discoveredGoPayTabId) {
        await addLog('步骤 8：已从当前浏览器标签中发现 GoPay 页面，正在接管继续执行。', 'info');
        if (typeof registerTab === 'function') {
          await registerTab(GOPAY_SOURCE, discoveredGoPayTabId);
        }
        return discoveredGoPayTabId;
      }
      const checkoutTabId = await getTabId(PLUS_CHECKOUT_SOURCE);
      if (checkoutTabId) {
        if (typeof registerTab === 'function') {
          await registerTab(GOPAY_SOURCE, checkoutTabId);
        }
        return checkoutTabId;
      }
      const storedTabId = Number(state.plusCheckoutTabId) || 0;
      if (storedTabId) {
        if (typeof registerTab === 'function') {
          await registerTab(GOPAY_SOURCE, storedTabId);
        }
        return storedTabId;
      }
      throw new Error('步骤 8：未找到 GoPay 标签页，请先完成步骤 7。');
    }

    async function ensureGoPayReady(tabId, logMessage = '') {
      await waitForTabCompleteUntilStopped(tabId);
      await sleepWithStop(800);
      await ensureContentScriptReadyOnTabUntilStopped(GOPAY_SOURCE, tabId, {
        inject: GOPAY_INJECT_FILES,
        injectSource: GOPAY_SOURCE,
        logMessage: logMessage || '步骤 8：GoPay 页面仍在加载，等待脚本就绪...',
      });
    }

    async function getTabFrames(tabId) {
      if (!chrome?.webNavigation?.getAllFrames) {
        return [{ frameId: 0, url: '' }];
      }
      const frames = await chrome.webNavigation.getAllFrames({ tabId }).catch(() => null);
      if (!Array.isArray(frames) || !frames.length) {
        return [{ frameId: 0, url: '' }];
      }
      return frames
        .filter((frame) => Number.isInteger(frame?.frameId))
        .sort((left, right) => Number(left.frameId) - Number(right.frameId));
    }

    function isGoPayOtpFrameUrl(url = '') {
      return GOPAY_OTP_FRAME_URL_PATTERN.test(String(url || ''));
    }

    function isGoPayPinFrameUrl(url = '') {
      return GOPAY_PIN_FRAME_URL_PATTERN.test(String(url || ''));
    }

    function isGoPayPaymentFrameUrl(url = '') {
      return GOPAY_PAYMENT_FRAME_URL_PATTERN.test(String(url || ''));
    }

    async function inspectGoPayFramesByDom(tabId) {
      if (!chrome?.scripting?.executeScript) {
        return [];
      }
      try {
        const results = await chrome.scripting.executeScript({
          target: { tabId, allFrames: true },
          func: () => {
            const normalize = (value = '') => String(value || '').replace(/\s+/g, ' ').trim();
            const visible = (el) => {
              if (!el) return false;
              const rect = el.getBoundingClientRect?.();
              const style = window.getComputedStyle?.(el);
              return Boolean(rect && rect.width > 0 && rect.height > 0)
                && (!style || (style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0));
            };
            const controls = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="button"], input[type="submit"]'))
              .filter((el) => visible(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true');
            const inputs = Array.from(document.querySelectorAll('input, textarea')).filter(visible);
            const text = normalize(document.body?.innerText || document.body?.textContent || '');
            const controlTexts = controls.map((el) => normalize([
              el.textContent,
              el.value,
              el.getAttribute?.('data-testid'),
              el.getAttribute?.('aria-label'),
              el.getAttribute?.('title'),
              el.id,
            ].filter(Boolean).join(' ')));
            const inputHints = inputs.map((el) => normalize([
              el.getAttribute?.('data-testid'),
              el.getAttribute?.('aria-label'),
              el.getAttribute?.('placeholder'),
              el.getAttribute?.('name'),
              el.id,
              el.className,
              el.type,
              el.inputMode,
            ].filter(Boolean).join(' ')));
            const hasTerminalError = /waktunya\s+habis|ulang(?:i)?\s+prosesnya\s+dari\s+awal|time(?:'s|\s+is)?\s+(?:out|expired)|session\s+expired|expired|technical\s+error|terjadi\s+kesalahan|payment\s+failed|pembayaran\s+gagal|transaksi\s+gagal|declined|failed/i.test(text);
            const isPinPage = /pin|6\s*digit|masukkin\s+pin|masukkan\s+pin|ketik\s+6\s+digit|enter\s+pin|支付密码/i.test(text)
              || /pin-web-client\.gopayapi\.com|\/auth\/pin|\/payment\/validate-pin|linking-validate-pin/i.test(location.href || '');
            const hasPinInput = isPinPage
              || inputHints.some((hint) => /pin-input|(?:^|[\s_-])pin(?:$|[\s_-])|password|numeric|支付密码/i.test(hint));
            const hasOtpInput = !hasPinInput && (/otp|one[-\s]*time|kode|verification|whatsapp|验证码|短信/i.test(text)
              || inputHints.some((hint) => /otp|code|kode|verification|whatsapp/i.test(hint)));
            const hasPayNowButton = controlTexts.some((item) => /^\s*pay\s+now\s*$/i.test(item)
              || /^\s*bayar(?:\s+sekarang)?(?:\s*rp[\s\S]*)?\s*$/i.test(item)
              || /(?:^|\s)pay-button(?:\s|$)/i.test(item));
            const hasContinueButton = controlTexts.some((item) => /continue|next|submit|verify|confirm|authorize|allow|lanjut|lanjutkan|berikut|kirim|konfirmasi|hubungkan|sambungkan|tautkan|setuju|izinkan|link|继续|下一步|提交|验证|确认|授权|绑定|关联/i.test(item));
            return {
              url: location.href,
              hasTerminalError,
              hasPinInput,
              hasOtpInput,
              hasPayNowButton,
              hasContinueButton,
              textPreview: text.slice(0, 240),
            };
          },
        });
        return (Array.isArray(results) ? results : [])
          .filter((item) => Number.isInteger(item?.frameId) && item.result)
          .map((item) => ({
            frameId: item.frameId,
            ...(item.result || {}),
          }));
      } catch (_) {
        return [];
      }
    }

    function getGoPayDomFrameKind(frame = {}) {
      if (frame.hasPinInput) return 'pin';
      if (frame.hasOtpInput) return 'otp';
      if (frame.hasPayNowButton) return 'payment';
      if (frame.hasContinueButton) return 'payment';
      if (frame.hasTerminalError) return 'payment';
      return '';
    }

    function getGoPayDomFramePriority(frame = {}) {
      if (frame.hasTerminalError) return 120;
      if (frame.hasPinInput) return 110;
      if (frame.hasOtpInput) return 100;
      if (frame.hasPayNowButton) return 90;
      if (frame.hasContinueButton) return 80;
      return 0;
    }

    function isGoPayDebuggerTargetUrl(url = '') {
      const value = String(url || '');
      return isGoPayOtpFrameUrl(value)
        || isGoPayPinFrameUrl(value)
        || isGoPayPaymentFrameUrl(value)
        || /gopayapi\.com|app\.midtrans\.com\/snap/i.test(value);
    }

    function getGoPayDebuggerProbePriority(probe = {}) {
      const domPriority = getGoPayDomFramePriority(probe);
      const url = String(probe.url || '');
      const typeBonus = probe.type === 'iframe' ? 30 : 0;
      const urlBonus = isGoPayPinFrameUrl(url)
        ? 12
        : (isGoPayPaymentFrameUrl(url) ? 10 : (isGoPayOtpFrameUrl(url) ? 8 : 0));
      return domPriority + typeBonus + urlBonus;
    }

    async function getGoPayDebuggerTargets(tabId) {
      if (!chrome?.debugger?.getTargets) {
        return [];
      }
      const targets = await chrome.debugger.getTargets().catch(() => []);
      return (Array.isArray(targets) ? targets : [])
        .filter((target) => target?.id && isGoPayDebuggerTargetUrl(target.url || target.title || ''))
        .filter((target) => {
          if (!Number.isInteger(tabId) || !Number.isInteger(target.tabId)) {
            return true;
          }
          return target.tabId === tabId;
        });
    }

    function buildGoPayDebuggerStateExpression() {
      return `(() => {
        const normalize = (value = '') => String(value || '').replace(/\\s+/g, ' ').trim();
        const visible = (el) => {
          if (!el) return false;
          let node = el;
          while (node && node.nodeType === 1) {
            if (node.hidden || node.getAttribute?.('aria-hidden') === 'true' || node.getAttribute?.('inert') !== null) {
              return false;
            }
            const nodeStyle = window.getComputedStyle?.(node);
            if (nodeStyle && (nodeStyle.display === 'none' || nodeStyle.visibility === 'hidden' || Number(nodeStyle.opacity) === 0)) {
              return false;
            }
            node = node.parentElement;
          }
          const rect = el.getBoundingClientRect?.();
          return Boolean(rect && rect.width > 0 && rect.height > 0);
        };
        const enabled = (el) => Boolean(el)
          && !el.disabled
          && el.getAttribute?.('aria-disabled') !== 'true';
        const getText = (el) => normalize([
          el?.textContent,
          el?.innerText,
          el?.value,
          el?.getAttribute?.('data-testid'),
          el?.getAttribute?.('aria-label'),
          el?.getAttribute?.('title'),
          el?.getAttribute?.('placeholder'),
          el?.getAttribute?.('name'),
          el?.id,
          typeof el?.className === 'string' ? el.className : el?.getAttribute?.('class'),
          el?.type,
          el?.inputMode,
        ].filter(Boolean).join(' '));
        const bodyText = normalize(document.body?.innerText || document.body?.textContent || '');
        const controls = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="button"], input[type="submit"]'))
          .filter((el) => visible(el) && enabled(el));
        const inputs = Array.from(document.querySelectorAll('input, textarea'))
          .filter((el) => visible(el) && enabled(el));
        const controlTexts = controls.map(getText);
        const inputHints = inputs.map(getText);
        const isPinPage = /pin|password|passcode|security|sandi|6\\s*digit|masukkin\\s+pin|masukkan\\s+pin|ketik\\s+6\\s+digit|enter\\s+pin|支付密码/i.test(bodyText)
          || /pin-web-client\\.gopayapi\\.com|\\/auth\\/pin|\\/payment\\/validate-pin|linking-validate-pin/i.test(location.href || '');
        const hasTerminalError = /waktunya\\s+habis|ulang(?:i)?\\s+prosesnya\\s+dari\\s+awal|time(?:'s|\\s+is)?\\s+(?:out|expired)|session\\s+expired|expired|kedaluwarsa|technical\\s+error|terjadi\\s+kesalahan|error\\s+teknis|kendala\\s+teknis|gak\\s+bisa\\s+diproses|coba\\s+lagi\\s+nanti|payment\\s+failed|pembayaran\\s+gagal|transaksi\\s+gagal|ditolak|declined|failed/i.test(bodyText);
        const hasPinInput = isPinPage || inputHints.some((hint) => /pin-input|(?:^|[\\s_-])pin(?:$|[\\s_-])|password|numeric|支付密码/i.test(hint));
        const hasOtpInput = !hasPinInput && (/otp|one[-\\s]*time|kode|verification|whatsapp|验证码|短信/i.test(bodyText)
          || inputHints.some((hint) => /otp|code|kode|verification|whatsapp/i.test(hint)));
        const hasPhoneInput = !hasOtpInput && !hasPinInput && inputs.some((input) => {
          const hint = getText(input);
          const type = String(input.type || input.getAttribute?.('type') || '').toLowerCase();
          return type === 'tel'
            || /gopay|go\\s*pay|phone|mobile|whatsapp|wa|nomor|ponsel|telepon|hp|手机|手机号|电话号码|电话/i.test(hint);
        });
        const hasPayNowButton = controlTexts.some((item) => /^\\s*pay\\s+now\\s*$/i.test(item)
          || /^\\s*bayar(?:\\s+sekarang)?(?:\\s*rp[\\s\\S]*)?\\s*$/i.test(item)
          || /(?:^|\\s)pay-button(?:\\s|$)/i.test(item));
        const hasContinueButton = !hasPayNowButton && !hasPhoneInput && controlTexts.some((item) => /continue|next|submit|verify|confirm|authorize|allow|lanjut|lanjutkan|berikut|kirim|konfirmasi|hubungkan|sambungkan|tautkan|setuju|izinkan|link|继续|下一步|提交|验证|确认|授权|绑定|关联/i.test(item));
        const completed = /success|successful|completed|selesai|berhasil|approved|authorized|支付成功|绑定成功|已授权/i.test(bodyText)
          && !hasPhoneInput
          && !hasOtpInput
          && !hasPinInput;
        return {
          url: location.href,
          readyState: document.readyState,
          hasTerminalError,
          hasPhoneInput,
          hasPinInput,
          hasOtpInput,
          hasPayNowButton,
          hasContinueButton,
          completed,
          textPreview: bodyText.slice(0, 500),
          inputHints: inputHints.slice(0, 12),
        };
      })()`;
    }

    function buildGoPayDebuggerClickPayExpression() {
      return `(async () => {
        const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
        const normalize = (value = '') => String(value || '').replace(/\\s+/g, ' ').trim();
        const visible = (el) => {
          if (!el) return false;
          let node = el;
          while (node && node.nodeType === 1) {
            if (node.hidden || node.getAttribute?.('aria-hidden') === 'true' || node.getAttribute?.('inert') !== null) {
              return false;
            }
            const nodeStyle = window.getComputedStyle?.(node);
            if (nodeStyle && (nodeStyle.display === 'none' || nodeStyle.visibility === 'hidden' || Number(nodeStyle.opacity) === 0)) {
              return false;
            }
            node = node.parentElement;
          }
          const rect = el.getBoundingClientRect?.();
          return Boolean(rect && rect.width > 0 && rect.height > 0);
        };
        const getText = (el) => normalize([
          el?.textContent,
          el?.innerText,
          el?.value,
          el?.getAttribute?.('data-testid'),
          el?.getAttribute?.('aria-label'),
          el?.getAttribute?.('title'),
          el?.id,
        ].filter(Boolean).join(' '));
        const candidates = Array.from(document.querySelectorAll('button, [role="button"], input[type="button"], input[type="submit"]'))
          .filter((el) => visible(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true');
        const target = candidates.find((el) => {
          const text = getText(el);
          return /(?:^|\\s)pay-button(?:\\s|$)/i.test(text)
            || /^\\s*pay\\s+now\\s*$/i.test(text)
            || /^\\s*bayar(?:\\s+sekarang)?(?:\\s*rp[\\s\\S]*)?\\s*$/i.test(text);
        });
        if (!target) {
          return { clicked: false, reason: 'target_not_found', url: location.href, textPreview: normalize(document.body?.innerText || '').slice(0, 240) };
        }
        target.scrollIntoView?.({ block: 'center', inline: 'center' });
        await sleep(120);
        try { target.focus?.({ preventScroll: true }); } catch (_) { try { target.focus?.(); } catch (__) {} }
        const rect = target.getBoundingClientRect?.();
        const clientX = rect ? Math.round(rect.left + rect.width / 2) : 0;
        const clientY = rect ? Math.round(rect.top + rect.height / 2) : 0;
        const eventInit = { bubbles: true, cancelable: true, composed: true, view: window, detail: 1, clientX, clientY, button: 0, buttons: 1 };
        if (typeof PointerEvent === 'function') {
          target.dispatchEvent(new PointerEvent('pointerover', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true }));
          target.dispatchEvent(new PointerEvent('pointermove', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true }));
          target.dispatchEvent(new PointerEvent('pointerdown', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true }));
        }
        target.dispatchEvent(new MouseEvent('mouseover', eventInit));
        target.dispatchEvent(new MouseEvent('mousemove', eventInit));
        target.dispatchEvent(new MouseEvent('mousedown', eventInit));
        if (typeof PointerEvent === 'function') {
          target.dispatchEvent(new PointerEvent('pointerup', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true, buttons: 0 }));
        }
        target.dispatchEvent(new MouseEvent('mouseup', { ...eventInit, buttons: 0 }));
        target.dispatchEvent(new MouseEvent('click', { ...eventInit, buttons: 0 }));
        target.click?.();
        await sleep(500);
        return {
          clicked: true,
          url: location.href,
          target: \`\${String(target.tagName || '').toUpperCase()} \${getText(target)}\`.trim(),
          rect: rect ? { left: rect.left, top: rect.top, width: rect.width, height: rect.height, centerX: rect.left + rect.width / 2, centerY: rect.top + rect.height / 2 } : null,
        };
      })()`;
    }

    function buildGoPayDebuggerClickContinueExpression() {
      return `(async () => {
        const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
        const normalize = (value = '') => String(value || '').replace(/\\s+/g, ' ').trim();
        const visible = (el) => {
          if (!el) return false;
          const rect = el.getBoundingClientRect?.();
          const style = window.getComputedStyle?.(el);
          return Boolean(rect && rect.width > 0 && rect.height > 0)
            && (!style || (style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0));
        };
        const getText = (el) => normalize([
          el?.textContent,
          el?.innerText,
          el?.value,
          el?.getAttribute?.('data-testid'),
          el?.getAttribute?.('aria-label'),
          el?.getAttribute?.('title'),
          el?.id,
        ].filter(Boolean).join(' '));
        const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="button"], input[type="submit"]'))
          .filter((el) => visible(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true');
        const target = candidates.find((el) => {
          const text = getText(el);
          if (/^\\s*pay\\s+now\\s*$/i.test(text) || /^\\s*bayar(?:\\s+sekarang)?(?:\\s*rp[\\s\\S]*)?\\s*$/i.test(text)) {
            return false;
          }
          return /continue|next|submit|verify|confirm|authorize|allow|lanjut|lanjutkan|berikut|kirim|konfirmasi|hubungkan|sambungkan|tautkan|setuju|izinkan|link|继续|下一步|提交|验证|确认|授权|绑定|关联/i.test(text);
        });
        if (!target) {
          return { clicked: false, reason: 'target_not_found', url: location.href, textPreview: normalize(document.body?.innerText || '').slice(0, 240) };
        }
        target.scrollIntoView?.({ block: 'center', inline: 'center' });
        await sleep(120);
        try { target.focus?.({ preventScroll: true }); } catch (_) { try { target.focus?.(); } catch (__) {} }
        const rect = target.getBoundingClientRect?.();
        const clientX = rect ? Math.round(rect.left + rect.width / 2) : 0;
        const clientY = rect ? Math.round(rect.top + rect.height / 2) : 0;
        const eventInit = { bubbles: true, cancelable: true, composed: true, view: window, detail: 1, clientX, clientY, button: 0, buttons: 1 };
        if (typeof PointerEvent === 'function') {
          target.dispatchEvent(new PointerEvent('pointerdown', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true }));
        }
        target.dispatchEvent(new MouseEvent('mousedown', eventInit));
        if (typeof PointerEvent === 'function') {
          target.dispatchEvent(new PointerEvent('pointerup', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true, buttons: 0 }));
        }
        target.dispatchEvent(new MouseEvent('mouseup', { ...eventInit, buttons: 0 }));
        target.dispatchEvent(new MouseEvent('click', { ...eventInit, buttons: 0 }));
        target.click?.();
        await sleep(500);
        return {
          clicked: true,
          url: location.href,
          target: \`\${String(target.tagName || '').toUpperCase()} \${getText(target)}\`.trim(),
        };
      })()`;
    }

    function buildGoPayDebuggerFocusPinExpression() {
      return `(() => {
        const visible = (el) => {
          if (!el) return false;
          const rect = el.getBoundingClientRect?.();
          const style = window.getComputedStyle?.(el);
          return Boolean(rect && rect.width > 0 && rect.height > 0)
            && (!style || (style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0));
        };
        const inputs = Array.from(document.querySelectorAll('input, textarea')).filter((el) => visible(el) && !el.disabled);
        const target = inputs.find((el) => /pin/i.test([
          el.getAttribute?.('data-testid'),
          el.getAttribute?.('aria-label'),
          el.getAttribute?.('placeholder'),
          el.getAttribute?.('name'),
          el.id,
          el.className,
          el.type,
          el.inputMode,
        ].filter(Boolean).join(' '))) || inputs.find((el) => String(el.type || '').toLowerCase() === 'password') || inputs[0];
        if (!target) {
          return { focused: false, reason: 'pin_input_not_found', textPreview: String(document.body?.innerText || '').replace(/\\s+/g, ' ').trim().slice(0, 240) };
        }
        target.scrollIntoView?.({ block: 'center', inline: 'center' });
        try { target.focus?.({ preventScroll: true }); } catch (_) { try { target.focus?.(); } catch (__) {} }
        target.click?.();
        const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
        if (setter) {
          setter.call(target, '');
        } else {
          target.value = '';
        }
        target.dispatchEvent(new Event('input', { bubbles: true }));
        target.dispatchEvent(new Event('change', { bubbles: true }));
        const rect = target.getBoundingClientRect?.();
        return {
          focused: true,
          target: \`\${String(target.tagName || '').toUpperCase()} \${target.getAttribute?.('data-testid') || target.type || ''}\`.trim(),
          rect: rect ? { left: rect.left, top: rect.top, width: rect.width, height: rect.height, centerX: rect.left + rect.width / 2, centerY: rect.top + rect.height / 2 } : null,
        };
      })()`;
    }

    function buildGoPayDebuggerFocusOtpExpression() {
      return `(() => {
        const normalize = (value = '') => String(value || '').replace(/\\s+/g, ' ').trim();
        const visible = (el) => {
          if (!el) return false;
          const rect = el.getBoundingClientRect?.();
          const style = window.getComputedStyle?.(el);
          return Boolean(rect && rect.width > 0 && rect.height > 0)
            && (!style || (style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0));
        };
        const inputs = Array.from(document.querySelectorAll('input, textarea')).filter((el) => visible(el) && !el.disabled);
        const target = inputs.find((el) => /otp|one[-\\s]*time|kode|verification|whatsapp|code/i.test([
          el.getAttribute?.('data-testid'),
          el.getAttribute?.('aria-label'),
          el.getAttribute?.('placeholder'),
          el.getAttribute?.('name'),
          el.id,
          el.className,
          el.type,
          el.inputMode,
        ].filter(Boolean).join(' '))) || inputs[0];
        if (!target) {
          return { focused: false, reason: 'otp_input_not_found', textPreview: normalize(document.body?.innerText || '').slice(0, 240) };
        }
        target.scrollIntoView?.({ block: 'center', inline: 'center' });
        try { target.focus?.({ preventScroll: true }); } catch (_) { try { target.focus?.(); } catch (__) {} }
        target.click?.();
        const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
        if (setter) {
          setter.call(target, '');
        } else {
          target.value = '';
        }
        target.dispatchEvent(new Event('input', { bubbles: true }));
        target.dispatchEvent(new Event('change', { bubbles: true }));
        const rect = target.getBoundingClientRect?.();
        return {
          focused: true,
          target: \`\${String(target.tagName || '').toUpperCase()} \${target.getAttribute?.('data-testid') || target.type || ''}\`.trim(),
          rect: rect ? { left: rect.left, top: rect.top, width: rect.width, height: rect.height, centerX: rect.left + rect.width / 2, centerY: rect.top + rect.height / 2 } : null,
        };
      })()`;
    }

    async function withDebuggerTarget(targetId, callback) {
      if (!chrome?.debugger?.attach || !chrome?.debugger?.sendCommand || !targetId) {
        throw new Error('debugger_target_unavailable');
      }
      const target = { targetId };
      let attached = false;
      try {
        await chrome.debugger.attach(target, '1.3');
        attached = true;
      } catch (err) {
        throw new Error(`GoPay iframe 调试器附加失败：${err?.message || String(err || 'unknown_error')}`);
      }
      try {
        return await callback(target);
      } finally {
        if (attached) {
          await chrome.debugger.detach(target).catch(() => {});
        }
      }
    }

    async function evaluateGoPayDebuggerTarget(targetId, expression) {
      return withDebuggerTarget(targetId, async (debuggee) => {
        const result = await chrome.debugger.sendCommand(debuggee, 'Runtime.evaluate', {
          expression,
          returnByValue: true,
          awaitPromise: true,
        });
        if (result?.exceptionDetails) {
          throw new Error(result.exceptionDetails?.text || 'GoPay iframe 脚本执行失败');
        }
        return result?.result?.value || {};
      });
    }

    async function typeDigitsWithDebugger(debuggee, digits, delayMs = 180) {
      for (const digit of String(digits || '').split('')) {
        throwIfStopped?.();
        await chrome.debugger.sendCommand(debuggee, 'Input.insertText', { text: digit });
        await sleepWithStop(delayMs);
      }
    }

    async function submitGoPayOtpWithDebuggerTarget(targetId, code = '') {
      const normalizedCode = normalizeGoPayOtp(code);
      if (!normalizedCode) {
        throw new Error('GoPay WhatsApp 验证码为空。');
      }
      return withDebuggerTarget(targetId, async (debuggee) => {
        const focused = await chrome.debugger.sendCommand(debuggee, 'Runtime.evaluate', {
          expression: buildGoPayDebuggerFocusOtpExpression(),
          returnByValue: true,
          awaitPromise: true,
        });
        const focusValue = focused?.result?.value || {};
        if (!focusValue.focused) {
          throw new Error(focusValue.reason || 'GoPay 验证码输入框未找到');
        }
        await sleepWithStop(200);
        await typeDigitsWithDebugger(debuggee, normalizedCode, 160);
        await sleepWithStop(500);
        const continueResult = await chrome.debugger.sendCommand(debuggee, 'Runtime.evaluate', {
          expression: buildGoPayDebuggerClickContinueExpression(),
          returnByValue: true,
          awaitPromise: true,
        }).catch(() => null);
        const continueValue = continueResult?.result?.value || {};
        return {
          otpSubmitted: true,
          clicked: Boolean(continueValue.clicked),
          clickTarget: continueValue.target || '',
          phase: 'otp_submitted_debugger_target',
          target: focusValue.target || '',
        };
      });
    }

    async function submitGoPayPinWithDebuggerTarget(targetId, pin = '') {
      const normalizedPin = normalizeGoPayOtp(pin);
      if (!normalizedPin) {
        throw new Error('GoPay PIN 为空，请先在侧边栏配置。');
      }
      return withDebuggerTarget(targetId, async (debuggee) => {
        const focused = await chrome.debugger.sendCommand(debuggee, 'Runtime.evaluate', {
          expression: buildGoPayDebuggerFocusPinExpression(),
          returnByValue: true,
          awaitPromise: true,
        });
        const focusValue = focused?.result?.value || {};
        if (!focusValue.focused) {
          throw new Error(focusValue.reason || 'GoPay PIN 输入框未找到');
        }
        await sleepWithStop(250);
        await typeDigitsWithDebugger(debuggee, normalizedPin, 220);
        await sleepWithStop(500);
        return {
          pinSubmitted: true,
          clicked: false,
          clickTarget: '',
          phase: 'pin_submitted_debugger_target',
          target: focusValue.target || '',
        };
      });
    }

    async function inspectGoPayDebuggerTargets(tabId) {
      const targets = await getGoPayDebuggerTargets(tabId);
      const probes = [];
      for (const target of targets) {
        try {
          const state = await evaluateGoPayDebuggerTarget(target.id, buildGoPayDebuggerStateExpression());
          const probe = {
            targetId: target.id,
            type: target.type || '',
            title: target.title || '',
            tabId: target.tabId,
            url: state.url || target.url || '',
            ...state,
          };
          if (getGoPayDebuggerProbePriority(probe) > 0) {
            probes.push(probe);
          }
        } catch (_) {
          // Ignore targets that are navigating or cannot be attached at the moment.
        }
      }
      return probes.sort((left, right) => getGoPayDebuggerProbePriority(right) - getGoPayDebuggerProbePriority(left));
    }

    async function sendGoPayDebuggerTargetCommand(targetId, type, payload = {}) {
      if (type === 'GOPAY_GET_STATE') {
        return evaluateGoPayDebuggerTarget(targetId, buildGoPayDebuggerStateExpression());
      }
      if (type === 'GOPAY_CLICK_PAY_NOW') {
        const result = await evaluateGoPayDebuggerTarget(targetId, buildGoPayDebuggerClickPayExpression());
        if (!result?.clicked) {
          return { clicked: false, clickTarget: '', reason: result?.reason || 'target_not_found' };
        }
        return {
          clicked: true,
          clickTarget: result.target || 'GoPay iframe Bayar',
          phase: 'pay_clicked_debugger_target',
        };
      }
      if (type === 'GOPAY_CLICK_CONTINUE') {
        const result = await evaluateGoPayDebuggerTarget(targetId, buildGoPayDebuggerClickContinueExpression());
        if (!result?.clicked) {
          return { clicked: false, clickTarget: '', reason: result?.reason || 'target_not_found' };
        }
        return {
          clicked: true,
          clickTarget: result.target || 'GoPay iframe continue',
          phase: 'continue_clicked_debugger_target',
        };
      }
      if (type === 'GOPAY_SUBMIT_OTP') {
        return submitGoPayOtpWithDebuggerTarget(targetId, payload.code || payload.otp || '');
      }
      if (type === 'GOPAY_SUBMIT_PIN') {
        return submitGoPayPinWithDebuggerTarget(targetId, payload.pin || payload.gopayPin || '');
      }
      throw new Error(`GoPay iframe 调试器暂不支持命令：${type}`);
    }

    async function findGoPayActionFrame(tabId) {
      const frames = await getTabFrames(tabId);
      const domFrames = (await inspectGoPayFramesByDom(tabId))
        .filter((item) => Number.isInteger(item.frameId) && item.frameId !== 0 && getGoPayDomFramePriority(item) > 0)
        .sort((left, right) => getGoPayDomFramePriority(right) - getGoPayDomFramePriority(left));
      if (domFrames.length) {
        const picked = domFrames[0];
        return {
          frameId: picked.frameId,
          kind: getGoPayDomFrameKind(picked),
          url: picked.url || frames.find((item) => item.frameId === picked.frameId)?.url || '',
        };
      }

      const debuggerFrames = (await inspectGoPayDebuggerTargets(tabId))
        .filter((target) => target.type === 'iframe');
      if (debuggerFrames.length) {
        const picked = debuggerFrames[0];
        return {
          frameId: null,
          targetId: picked.targetId,
          kind: getGoPayDomFrameKind(picked),
          url: picked.url || '',
          via: 'debugger-target',
        };
      }

      const pinFrame = frames.find((item) => isGoPayPinFrameUrl(item.url));
      if (pinFrame && Number.isInteger(pinFrame.frameId)) {
        return { frameId: pinFrame.frameId, kind: 'pin', url: pinFrame.url || '' };
      }
      const otpFrame = frames.find((item) => isGoPayOtpFrameUrl(item.url));
      if (otpFrame && Number.isInteger(otpFrame.frameId)) {
        return { frameId: otpFrame.frameId, kind: 'otp', url: otpFrame.url || '' };
      }

      const paymentFrames = frames
        .filter((item) => Number.isInteger(item.frameId) && item.frameId !== 0 && isGoPayPaymentFrameUrl(item.url));
      for (const frame of paymentFrames) {
        const ready = await ensureGoPayOtpFrameReady(tabId, frame.frameId);
        if (!ready) continue;
        try {
          const frameState = await sendGoPayFrameCommand(tabId, frame.frameId, 'GOPAY_GET_STATE', {});
          if (frameState?.hasPayNowButton || frameState?.hasPinInput || frameState?.hasOtpInput || frameState?.hasContinueButton || frameState?.hasTerminalError) {
            return { frameId: frame.frameId, kind: getGoPayDomFrameKind(frameState) || 'payment', url: frame.url || '' };
          }
        } catch (_) {
          // Keep scanning; frame may still be navigating.
        }
      }

      return { frameId: null, kind: '', url: '' };
    }

    async function sendGoPayFrameCommand(tabId, frameId, type, payload = {}) {
      const result = await chrome.tabs.sendMessage(tabId, {
        type,
        source: 'background',
        payload,
      }, { frameId });
      if (result?.error) {
        throw new Error(result.error);
      }
      return result || {};
    }

    async function pingGoPayFrame(tabId, frameId) {
      try {
        const pong = await chrome.tabs.sendMessage(tabId, {
          type: 'PING',
          source: 'background',
          payload: {},
        }, { frameId });
        return Boolean(pong?.ok && (!pong.source || pong.source === GOPAY_OTP_SOURCE || pong.source === GOPAY_SOURCE));
      } catch (_) {
        return false;
      }
    }

    async function ensureGoPayOtpFrameReady(tabId, frameId) {
      if (!Number.isInteger(frameId)) {
        return false;
      }
      if (await pingGoPayFrame(tabId, frameId)) {
        return true;
      }
      if (!chrome?.scripting?.executeScript) {
        return false;
      }
      try {
        await chrome.scripting.executeScript({
          target: { tabId, frameIds: [frameId] },
          func: (injectedSource) => {
            window.__MULTIPAGE_SOURCE = injectedSource;
          },
          args: [GOPAY_OTP_SOURCE],
        });
        await chrome.scripting.executeScript({
          target: { tabId, frameIds: [frameId] },
          files: GOPAY_INJECT_FILES,
        });
      } catch (_) {
        // The frame may navigate during injection; the caller will retry on the next loop.
      }
      await sleepWithStop(300);
      return await pingGoPayFrame(tabId, frameId);
    }

    async function getGoPayState(tabId) {
      const result = await sendTabMessageUntilStopped(tabId, GOPAY_SOURCE, {
        type: 'GOPAY_GET_STATE',
        source: 'background',
        payload: {},
      });
      if (result?.error) {
        throw new Error(result.error);
      }
      return result || {};
    }

    async function sendGoPayCommand(tabId, type, payload = {}) {
      const result = await sendTabMessageUntilStopped(tabId, GOPAY_SOURCE, {
        type,
        source: 'background',
        payload,
      });
      if (result?.error) {
        throw new Error(result.error);
      }
      return result || {};
    }

    async function clickGoPayTargetWithDebugger(tabId, targetMessageType = 'GOPAY_GET_CONTINUE_TARGET', frameId = null) {
      if (typeof clickWithDebugger !== 'function') {
        return { clicked: false, reason: 'debugger_click_unavailable' };
      }
      const target = Number.isInteger(frameId)
        ? await sendGoPayFrameCommand(tabId, frameId, targetMessageType, {})
        : await sendGoPayCommand(tabId, targetMessageType, {});
      const rect = target?.rect || null;
      if (!target?.found || !rect || !Number.isFinite(rect.centerX) || !Number.isFinite(rect.centerY)) {
        return { clicked: false, reason: 'target_not_found', clickTarget: target?.target || '' };
      }
      if (Number.isInteger(frameId)) {
        return { clicked: false, reason: 'debugger_click_skipped_for_frame_target', clickTarget: target.target || '' };
      }
      await clickWithDebugger(tabId, rect);
      return { clicked: true, clickTarget: target.target || '' };
    }


    async function clickGoPayContinueWithDebugger(tabId, frameId = null) {
      return clickGoPayTargetWithDebugger(tabId, 'GOPAY_GET_CONTINUE_TARGET', frameId);
    }

    async function clickGoPayPayNowWithDebugger(tabId, frameId = null) {
      return clickGoPayTargetWithDebugger(tabId, 'GOPAY_GET_PAY_NOW_TARGET', frameId);
    }

    async function clickGoPayPayButtonInAnyFrame(tabId) {
      if (!chrome?.scripting?.executeScript) {
        return { clicked: false, reason: 'scripting_unavailable' };
      }
      try {
        const results = await chrome.scripting.executeScript({
          target: { tabId, allFrames: true },
          func: () => {
            const normalize = (value = '') => String(value || '').replace(/\s+/g, ' ').trim();
            const isVisible = (el) => {
              if (!el) return false;
              let node = el;
              while (node && node.nodeType === 1) {
                if (node.hidden || node.getAttribute?.('aria-hidden') === 'true' || node.getAttribute?.('inert') !== null) {
                  return false;
                }
                const nodeStyle = window.getComputedStyle?.(node);
                if (nodeStyle && (nodeStyle.display === 'none' || nodeStyle.visibility === 'hidden' || Number(nodeStyle.opacity) === 0)) {
                  return false;
                }
                node = node.parentElement;
              }
              const rect = el.getBoundingClientRect?.();
              return Boolean(rect && rect.width > 0 && rect.height > 0);
            };
            const getText = (el) => normalize([
              el?.textContent,
              el?.value,
              el?.getAttribute?.('data-testid'),
              el?.getAttribute?.('aria-label'),
              el?.getAttribute?.('title'),
              el?.id,
            ].filter(Boolean).join(' '));
            const candidates = Array.from(document.querySelectorAll('button, [role="button"], input[type="button"], input[type="submit"]'))
              .filter((el) => isVisible(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true');
            const target = candidates.find((el) => {
              const text = getText(el);
              return /(?:^|\s)pay-button(?:\s|$)/i.test(text)
                || /^\s*bayar(?:\s+sekarang)?(?:\s*rp[\s\S]*)?\s*$/i.test(text);
            });
            if (!target) {
              return { clicked: false, url: location.href };
            }
            target.scrollIntoView?.({ block: 'center', inline: 'center' });
            try { target.focus?.(); } catch (_) {}
            const rect = target.getBoundingClientRect?.();
            const clientX = rect ? Math.round(rect.left + rect.width / 2) : 0;
            const clientY = rect ? Math.round(rect.top + rect.height / 2) : 0;
            const eventInit = { bubbles: true, cancelable: true, composed: true, view: window, clientX, clientY, button: 0, buttons: 1 };
            if (typeof PointerEvent === 'function') {
              target.dispatchEvent(new PointerEvent('pointerdown', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true }));
            }
            target.dispatchEvent(new MouseEvent('mousedown', eventInit));
            if (typeof PointerEvent === 'function') {
              target.dispatchEvent(new PointerEvent('pointerup', { ...eventInit, pointerId: 1, pointerType: 'mouse', isPrimary: true, buttons: 0 }));
            }
            target.dispatchEvent(new MouseEvent('mouseup', { ...eventInit, buttons: 0 }));
            target.dispatchEvent(new MouseEvent('click', { ...eventInit, buttons: 0 }));
            target.click?.();
            return {
              clicked: true,
              url: location.href,
              target: `${String(target.tagName || '').toUpperCase()} ${getText(target)}`.trim(),
            };
          },
        });
        const clicked = (Array.isArray(results) ? results : []).find((item) => item?.result?.clicked);
        if (clicked?.result?.clicked) {
          return {
            clicked: true,
            frameId: clicked.frameId,
            url: clicked.result.url || '',
            clickTarget: clicked.result.target || '',
          };
        }
      } catch (error) {
        return { clicked: false, reason: error?.message || String(error || 'unknown_error') };
      }
      return { clicked: false, reason: 'target_not_found' };
    }

    async function waitForGoPayState(tabId, predicate, options = {}) {
      const timeoutMs = Math.max(0, Math.floor(Number(options.timeoutMs) || GOPAY_WAIT_TIMEOUT_MS));
      const startedAt = Date.now();
      while (Date.now() - startedAt < timeoutMs) {
        const tab = await chrome.tabs.get(tabId).catch(() => null);
        if (!tab) {
          throw new Error('步骤 8：GoPay 标签页已关闭，无法继续。');
        }
        const url = String(tab.url || '');
        if (isReturnUrl(url)) {
          return { returned: true, url };
        }
        await ensureGoPayReady(tabId, '步骤 8：GoPay 页面正在切换，等待脚本重新就绪...');
        const actionFrame = await findGoPayActionFrame(tabId);
        const actionFrameId = actionFrame.frameId;
        const actionTargetId = actionFrame.targetId;
        if (Number.isInteger(actionFrameId)) {
          await ensureGoPayOtpFrameReady(tabId, actionFrameId);
        }
        const pageState = actionTargetId
          ? await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_GET_STATE', {})
          : (Number.isInteger(actionFrameId)
            ? await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_GET_STATE', {})
            : await getGoPayState(tabId));
        if (predicate(pageState, tab)) {
          return { pageState, tab };
        }
        await sleepWithStop(GOPAY_POLL_INTERVAL_MS);
      }
      return { timeout: true };
    }

    async function clickGoPayContinueBestEffort(tabId) {
      const actionFrame = await findGoPayActionFrame(tabId);
      const actionFrameId = actionFrame.frameId;
      const actionTargetId = actionFrame.targetId;
      if (Number.isInteger(actionFrameId)) {
        await ensureGoPayOtpFrameReady(tabId, actionFrameId);
      }

      try {
        if (actionTargetId) {
          const result = await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_CLICK_CONTINUE', {});
          if (result?.clicked) {
            return result;
          }
        } else if (Number.isInteger(actionFrameId)) {
          const result = await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_CLICK_CONTINUE', {});
          if (result?.clicked) {
            return result;
          }
        } else {
          const result = await sendGoPayCommand(tabId, 'GOPAY_CLICK_CONTINUE', {});
          if (result?.clicked) {
            return result;
          }
        }
      } catch (_) {
        // Fall through to a real debugger click below.
      }

      return clickGoPayContinueWithDebugger(tabId, actionFrameId);
    }

    function normalizeGoPayCountryCode(value = '') {
      const normalized = String(value || '').trim().replace(/[^\d+]/g, '');
      const digits = normalized.replace(/\D/g, '');
      return digits ? `+${digits}` : '+86';
    }

    function normalizeGoPayOtp(value = '') {
      return String(value || '').trim().replace(/[^\d]/g, '');
    }

    function resolveGoPayCredentials(state = {}) {
      return {
        countryCode: normalizeGoPayCountryCode(state.gopayCountryCode || '+86'),
        phone: normalizeText(state.gopayPhone || ''),
        otp: normalizeGoPayOtp(state.gopayOtp || ''),
        pin: String(state.gopayPin || ''),
      };
    }

    async function requestManualGoPayOtp(existingCode = '') {
      if (typeof requestGoPayOtpInput !== 'function') {
        throw new Error('步骤 8：未配置 GoPay 验证码，也无法打开侧边栏输入弹窗。');
      }
      await addLog(existingCode
        ? '步骤 8：检测到上次保存的 GoPay 验证码，将弹窗请你确认或改填新验证码。'
        : '步骤 8：请在侧边栏弹窗中输入 GoPay 验证码，提交后会继续填写 PIN。', 'info');
      const response = await requestGoPayOtpInput({ code: existingCode });
      if (response?.error) {
        throw new Error(response.error);
      }
      const code = normalizeGoPayOtp(response?.code || '');
      if (!code || response?.cancelled) {
        throw new Error('步骤 8：GoPay 验证码输入已取消。');
      }
      return code;
    }

    async function executeGoPayApprove(state = {}) {
      const credentials = resolveGoPayCredentials(state);
      if (!credentials.phone) {
        throw new Error('步骤 8：未配置 GoPay 手机号，请先在侧边栏填写。');
      }
      if (!credentials.pin) {
        throw new Error('步骤 8：未配置 GoPay PIN，请先在侧边栏填写。');
      }

      const tabId = await resolveGoPayTabId(state);
      await ensureGoPayReady(tabId);
      await setState({ plusCheckoutTabId: tabId });

      function resetTransientStepFlags() {
        otpSubmitted = false;
        pinSubmitted = false;
        continueClickAttempts = 0;
        lastContinueClickSignature = '';
        payNowClickAttempts = 0;
        lastPayNowClickSignature = '';
        stableStateTracker?.reset?.();
      }

      let phoneSubmitted = false;
      let otpSubmitted = false;
      let pinSubmitted = false;
      let loggedWaiting = false;
      let continueClickAttempts = 0;
      let lastContinueClickSignature = '';
      let payNowClickAttempts = 0;
      let lastPayNowClickSignature = '';
      const stableStateTracker = createGoPayStableStateTracker();

      while (true) {
        throwIfStopped?.();
        const tab = await chrome.tabs.get(tabId).catch(() => null);
        const currentUrl = String(tab?.url || '');
        if (currentUrl && isReturnUrl(currentUrl)) {
          await addLog('步骤 8：GoPay 已跳转回 ChatGPT / OpenAI 页面，准备进入回跳确认。', 'ok');
          break;
        }

        await ensureGoPayReady(tabId, '步骤 8：GoPay 页面正在切换，等待脚本重新就绪...');
        const actionFrame = await findGoPayActionFrame(tabId);
        const actionFrameId = actionFrame.frameId;
        const actionTargetId = actionFrame.targetId;
        if (Number.isInteger(actionFrameId)) {
          await ensureGoPayOtpFrameReady(tabId, actionFrameId);
        }
        const pageState = actionTargetId
          ? await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_GET_STATE', {})
          : (Number.isInteger(actionFrameId)
            ? await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_GET_STATE', {})
            : await getGoPayState(tabId));
        const stableState = stableStateTracker.update(pageState, currentUrl);
        await handleGoPayTerminalError(pageState, tabId);

        if (pageState.completed) {
          await addLog('步骤 8：GoPay 页面已显示完成状态，准备进入回跳确认。', 'ok');
          break;
        }

        if (pageState.hasPayNowButton) {
          const payNowSignature = `${actionFrame.kind || 'top'}::${pageState.url || currentUrl || ''}::${pageState.textPreview || ''}`.slice(0, 700);
          if (payNowSignature === lastPayNowClickSignature) {
            payNowClickAttempts += 1;
          } else {
            lastPayNowClickSignature = payNowSignature;
            payNowClickAttempts = 1;
          }
          if (!Number.isInteger(actionFrameId) && payNowClickAttempts > 2) {
            const framePayResult = await clickGoPayPayButtonInAnyFrame(tabId);
            if (framePayResult?.clicked) {
              await addLog(`步骤 8：顶层仍显示 Pay now，但已在 GoPay iframe 中点击 Bayar 按钮：${framePayResult.clickTarget || 'Bayar'}。`, 'info');
              await sleepWithStop(2500);
              resetTransientStepFlags();
              loggedWaiting = false;
              continue;
            }
            await addLog('步骤 8：顶层 Pay now 已重复出现，暂未识别到 iframe 内 Bayar/PIN；继续等待页面切换，不再自动回退步骤 6。', 'warn');
            await sleepWithStop(3000);
            loggedWaiting = false;
            continue;
          }
          const paymentLabel = actionFrame.kind === 'payment' ? '最终 Bayar 确认' : 'Pay now';
          await addLog(`步骤 8：检测到 GoPay ${paymentLabel} 按钮，正在点击完成支付...`, 'info');
          const payResult = actionTargetId
            ? await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_CLICK_PAY_NOW', {})
            : (Number.isInteger(actionFrameId)
              ? await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_CLICK_PAY_NOW', {})
              : await sendGoPayCommand(tabId, 'GOPAY_CLICK_PAY_NOW', {}));
          if (payResult?.clickTarget) {
            await addLog(`步骤 8：已点击 GoPay 支付按钮：${payResult.clickTarget}`, 'info');
          }
          await sleepWithStop(2500);
          const decision = await waitForGoPayState(tabId, (nextState) => (
            nextState.hasTerminalError
            || nextState.completed
            || !nextState.hasPayNowButton
          ), { timeoutMs: 15000 });
          await handleGoPayTerminalError(decision.pageState, tabId);
          if (decision.returned) {
            await addLog('步骤 8：GoPay 支付后已跳转回 ChatGPT / OpenAI 页面，准备进入回跳确认。', 'ok');
            break;
          }
          if (decision.pageState) {
            resetTransientStepFlags();
          }
          if (decision.timeout) {
            if (!Number.isInteger(actionFrameId)) {
              const framePayResult = await clickGoPayPayButtonInAnyFrame(tabId);
              if (framePayResult?.clicked) {
                await addLog(`步骤 8：已在 GoPay iframe 中点击 Bayar 按钮：${framePayResult.clickTarget || 'Bayar'}。`, 'info');
                await sleepWithStop(2500);
                resetTransientStepFlags();
                loggedWaiting = false;
                continue;
              }
            }
            const debuggerResult = await clickGoPayPayNowWithDebugger(tabId, actionFrameId);
            if (debuggerResult?.clickTarget) {
              await addLog(`步骤 8：已使用 debugger 点击 GoPay 支付按钮：${debuggerResult.clickTarget}`, 'info');
            }
            await sleepWithStop(2500);
            if (!Number.isInteger(actionFrameId)) {
              const lateFramePayResult = await clickGoPayPayButtonInAnyFrame(tabId);
              if (lateFramePayResult?.clicked) {
                await addLog(`步骤 8：顶层 Pay now 点击后，已在 GoPay iframe 中补点 Bayar 按钮：${lateFramePayResult.clickTarget || 'Bayar'}。`, 'info');
                await sleepWithStop(2500);
                resetTransientStepFlags();
                loggedWaiting = false;
                continue;
              }
              await addLog('步骤 8：顶层 Pay now 兜底点击后仍未识别到 iframe 内 Bayar/PIN，继续等待，不自动回退步骤 6。', 'warn');
            }
          }
          resetTransientStepFlags();
          loggedWaiting = false;
          continue;
        }

        if (pageState.hasPhoneInput && !phoneSubmitted) {
          await addLog(`步骤 8：正在切换 GoPay 区号 ${credentials.countryCode} 并填写手机号...`, 'info');
          await sendGoPayCommand(tabId, 'GOPAY_SUBMIT_PHONE', {
            countryCode: credentials.countryCode,
            phone: credentials.phone,
          });
          phoneSubmitted = true;
          continueClickAttempts = 0;
          lastContinueClickSignature = '';
          loggedWaiting = false;
          await sleepWithStop(1500);
          continue;
        }

        if (pageState.hasPinInput && !pinSubmitted) {
          await addLog('步骤 8：正在填写 GoPay PIN...', 'info');
          if (actionTargetId) {
            await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_SUBMIT_PIN', { pin: credentials.pin });
          } else if (Number.isInteger(actionFrameId)) {
            await ensureGoPayOtpFrameReady(tabId, actionFrameId);
            await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_SUBMIT_PIN', { pin: credentials.pin });
          } else {
            await sendGoPayCommand(tabId, 'GOPAY_SUBMIT_PIN', { pin: credentials.pin });
          }
          pinSubmitted = true;
          continueClickAttempts = 0;
          lastContinueClickSignature = '';
          loggedWaiting = false;
          const afterPinDecision = await waitForGoPayState(tabId, (nextState) => (
            nextState.hasTerminalError
            || nextState.hasPayNowButton
            || nextState.completed
          ), { timeoutMs: 20000 });
          await handleGoPayTerminalError(afterPinDecision.pageState, tabId);
          if (afterPinDecision.returned) {
            await addLog('步骤 8：GoPay PIN 后已跳转回 ChatGPT / OpenAI 页面，准备进入回跳确认。', 'ok');
            break;
          }
          if (afterPinDecision.pageState?.hasPayNowButton) {
            await addLog('步骤 8：GoPay PIN 已通过，等待点击 Pay now 完成支付。', 'info');
            resetTransientStepFlags();
          }
          await sleepWithStop(800);
          continue;
        }

        if (pageState.hasOtpInput && !pageState.hasPinInput && !otpSubmitted) {
          const code = await requestManualGoPayOtp(credentials.otp);
          credentials.otp = code;
          await addLog('步骤 8：正在填写 GoPay 验证码...', 'info');
          if (actionTargetId) {
            await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_SUBMIT_OTP', { code });
          } else if (Number.isInteger(actionFrameId)) {
            await ensureGoPayOtpFrameReady(tabId, actionFrameId);
            await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_SUBMIT_OTP', { code });
          } else {
            await ensureGoPayReady(tabId, '步骤 8：已获取 GoPay 验证码，等待 GoPay 页面就绪...');
            await sendGoPayCommand(tabId, 'GOPAY_SUBMIT_OTP', { code });
          }
          otpSubmitted = true;
          continueClickAttempts = 0;
          lastContinueClickSignature = '';
          loggedWaiting = false;
          await sleepWithStop(1500);
          continue;
        }

        if (pageState.hasContinueButton) {
          const continueSignature = `${pageState.url || currentUrl || ''}::${pageState.textPreview || ''}`.slice(0, 700);
          if (continueSignature === lastContinueClickSignature) {
            continueClickAttempts += 1;
          } else {
            lastContinueClickSignature = continueSignature;
            continueClickAttempts = 1;
          }
          if (continueClickAttempts > 2) {
            const stableBeforeRetrySeconds = Math.round(stableState.stableMs / 1000);
            await addLog(`步骤 8：GoPay 确认按钮点击后页面仍未变化，先等待 linking 页面加载/跳转（已稳定 ${stableBeforeRetrySeconds}s）。`, 'warn');
            const decision = await waitForGoPayState(tabId, (nextState) => (
              nextState.hasTerminalError
              || nextState.hasOtpInput
              || nextState.hasPinInput
              || nextState.hasPayNowButton
              || nextState.completed
              || !nextState.hasContinueButton
            ), { timeoutMs: GOPAY_LINKING_RETRY_WAIT_MS });
            await handleGoPayTerminalError(decision.pageState, tabId);
            if (decision.returned) {
              await addLog('步骤 8：GoPay 已跳转回 ChatGPT / OpenAI 页面，准备进入回跳确认。', 'ok');
              break;
            }
            if (!decision.timeout) {
              continueClickAttempts = 0;
              lastContinueClickSignature = '';
              stableStateTracker.reset();
              loggedWaiting = false;
              continue;
            }
            const refreshedState = decision.pageState || pageState;
            const refreshedStableState = stableStateTracker.update(refreshedState, currentUrl);
            const stableSeconds = Math.round(refreshedStableState.stableMs / 1000);
            if (stableSeconds < Math.round(GOPAY_LINKING_STABLE_WAIT_MS / 1000)) {
              await addLog(`步骤 8：GoPay linking 页面还在同一状态（${stableSeconds}s），改用兜底点击 Hubungkan/确认按钮后继续等待。`, 'info');
              const retryResult = await clickGoPayContinueBestEffort(tabId);
              if (retryResult?.clickTarget) {
                await addLog(`步骤 8：已兜底点击 GoPay 控件：${retryResult.clickTarget}`, 'info');
              }
              continueClickAttempts = 2;
              await sleepWithStop(2500);
              loggedWaiting = false;
              continue;
            }
            await addLog('步骤 8：GoPay linking 页面长时间没有变化，已暂停自动重复点击。请手动点击页面上的 Hubungkan/确认按钮，插件会继续等待后续页面。', 'warn');
            const manualDecision = await waitForGoPayState(tabId, (nextState) => (
              nextState.hasTerminalError
              || nextState.hasOtpInput
              || nextState.hasPinInput
              || nextState.hasPayNowButton
              || nextState.completed
              || !nextState.hasContinueButton
            ), { timeoutMs: GOPAY_WAIT_TIMEOUT_MS });
            await handleGoPayTerminalError(manualDecision.pageState, tabId);
            if (manualDecision.returned) {
              await addLog('步骤 8：GoPay 已跳转回 ChatGPT / OpenAI 页面，准备进入回跳确认。', 'ok');
              break;
            }
            if (!manualDecision.timeout) {
              continueClickAttempts = 0;
              lastContinueClickSignature = '';
              stableStateTracker.reset();
              loggedWaiting = false;
              continue;
            }
            throw new Error('步骤 8：GoPay linking 页面长时间无变化，请手动点击 Hubungkan/确认按钮后重新执行或继续当前步骤。');
          }
          await addLog(`步骤 8：检测到 GoPay 继续/确认按钮，正在点击${continueClickAttempts > 1 ? `（第 ${continueClickAttempts} 次）` : ''}...`, 'info');
          const clickResult = continueClickAttempts === 1
            ? (actionTargetId
              ? await sendGoPayDebuggerTargetCommand(actionTargetId, 'GOPAY_CLICK_CONTINUE', {})
              : (Number.isInteger(actionFrameId)
                ? await sendGoPayFrameCommand(tabId, actionFrameId, 'GOPAY_CLICK_CONTINUE', {})
                : await sendGoPayCommand(tabId, 'GOPAY_CLICK_CONTINUE', {})))
            : await clickGoPayContinueWithDebugger(tabId, actionFrameId);
          if (clickResult?.clickTarget) {
            await addLog(`步骤 8：已点击 GoPay 控件：${clickResult.clickTarget}${continueClickAttempts > 1 ? '（debugger 真实鼠标事件）' : ''}`, 'info');
          }
          await sleepWithStop(2000);
          loggedWaiting = false;
          continue;
        }

        if (!loggedWaiting) {
          loggedWaiting = true;
          await addLog('步骤 8：等待 GoPay 手机号、验证码、PIN 或完成页面出现...', 'info');
        }

        const decision = await waitForGoPayState(tabId, (nextState) => (
          nextState.hasTerminalError
          || nextState.hasPhoneInput
          || nextState.hasOtpInput
          || nextState.hasPinInput
          || nextState.hasContinueButton
          || nextState.hasPayNowButton
          || nextState.completed
        ), { timeoutMs: 10000 });
        await handleGoPayTerminalError(decision.pageState, tabId);
        if (decision.returned) {
          await addLog('步骤 8：GoPay 已跳转回 ChatGPT / OpenAI 页面，准备进入回跳确认。', 'ok');
          break;
        }
        if (decision.timeout) {
          await sleepWithStop(GOPAY_POLL_INTERVAL_MS);
        }
      }

      await setState({ plusGoPayApprovedAt: Date.now() });
      await completeStepFromBackground(8, {
        plusGoPayApprovedAt: Date.now(),
      });
    }

    return {
      executeGoPayApprove,
    };
  }

  return {
    createGoPayApproveExecutor,
  };
});
