/**
 * otpInputEngine.js
 * OTP Testing & Automation Framework — Input Engine
 *
 * FOR DEV / STAGING / QA / ACCESSIBILITY TESTING ONLY.
 *
 * fillOtpCode(code, inputs):
 *   - Types simulation (char by char)
 *   - Native InputEvent + React synthetic event dispatch
 *   - Multi-box and single-field support
 *   - Retry and race-condition safety
 *   - Verification after input
 */
(function initVisaFlowXOtpInputEngine(global) {
  "use strict";

  function log(msg) {
    console.log("[VisaFlowX][OtpInputEngine]", msg);
  }

  // ── React-compatible value setter ────────────────────────────
  function setNativeValue(el, value) {
    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
    if (nativeInputValueSetter) {
      nativeInputValueSetter.call(el, value);
    } else {
      el.value = value;
    }
  }

  // ── Dispatch all required events for framework compatibility ─
  function dispatchInputEvents(el, value) {
    const events = [
      new Event("input",  { bubbles: true, cancelable: true }),
      new Event("change", { bubbles: true, cancelable: true }),
      new KeyboardEvent("keydown",  { bubbles: true, key: value, code: "Digit" + value }),
      new KeyboardEvent("keypress", { bubbles: true, key: value }),
      new KeyboardEvent("keyup",    { bubbles: true, key: value }),
    ];
    events.forEach((e) => el.dispatchEvent(e));

    // React 16+ synthetic events
    if (el._reactFiber || el.__reactFiber) {
      try {
        const inputEvent = new InputEvent("input", {
          bubbles: true, cancelable: true, data: value, inputType: "insertText"
        });
        el.dispatchEvent(inputEvent);
      } catch (_) {}
    }
  }

  // ── Delay helper ─────────────────────────────────────────────
  function delay(ms) {
    return new Promise((r) => setTimeout(r, ms));
  }

  /**
   * Fill a single-field OTP input with the given code.
   * @param {string} code   The OTP string to type.
   * @param {HTMLElement} el  The target input element.
   */
  async function fillSingleField(code, el) {
    if (!el || !code) throw new Error("fillSingleField: missing input or code");
    el.focus();
    el.select?.();

    // Type character by character with human-like timing
    for (const ch of String(code)) {
      setNativeValue(el, el.value + ch);
      dispatchInputEvents(el, ch);
      await delay(30 + Math.random() * 40);
    }

    // Final verification
    const filled = el.value === String(code);
    if (!filled) {
      // Fallback: set entire value at once
      setNativeValue(el, code);
      dispatchInputEvents(el, code);
    }
    log(`Single-field filled: "${el.value}" (expected: "${code}")`);
    return el.value === String(code);
  }

  /**
   * Fill a multi-box OTP (one digit per input).
   * @param {string}          code    The OTP string.
   * @param {HTMLElement[]}   inputs  Array of digit input elements.
   */
  async function fillMultiBox(code, inputs) {
    if (!inputs?.length || !code) throw new Error("fillMultiBox: missing inputs or code");
    const digits = String(code).slice(0, inputs.length);

    for (let i = 0; i < digits.length; i++) {
      const el  = inputs[i];
      const ch  = digits[i];
      if (!el) continue;
      el.focus();
      setNativeValue(el, ch);
      dispatchInputEvents(el, ch);
      await delay(40 + Math.random() * 40);
      // Move focus to next input
      inputs[i + 1]?.focus?.();
    }

    // Verify
    const filled = inputs.slice(0, digits.length).map((el) => el.value).join("") === digits;
    log(`Multi-box filled: "${inputs.map((e) => e.value).join("")}" (expected: "${digits}")`);
    return filled;
  }

  /**
   * fillOtpCode — main entry point.
   * Automatically detects single-field vs multi-box.
   * @param {string}                code   OTP string.
   * @param {HTMLElement|HTMLElement[]} input  Single element or array.
   * @param {object}                opts   { retries: 2 }
   */
  async function fillOtpCode(code, input, opts = {}) {
    const retries = Number(opts.retries ?? 2);
    let targetInput = input;

    // Auto-detect if not provided
    if (!targetInput) {
      const detected = global.VisaFlowX?.OtpDetector?.detectOtpFields?.();
      if (detected && detected.present) {
        targetInput = detected.inputs;
      }
    }

    // Context-aware fallback scan of DOM
    if (!targetInput && typeof document !== "undefined") {
      const allInputs = Array.from(document.querySelectorAll("input[type='tel'], input[type='text'], input[inputmode='numeric']"));
      const 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) {
        targetInput = digitBoxes;
      } else {
        targetInput = document.querySelector("input[autocomplete='one-time-code']") || document.querySelector("input[name*='otp' i]");
      }
    }

    if (!targetInput) {
      return { ok: false, error: "No OTP input fields detected on page" };
    }

    let lastError;

    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        if (Array.isArray(targetInput)) {
          const ok = await fillMultiBox(code, targetInput);
          if (ok) return { ok: true, mode: "multi_box" };
        } else {
          const ok = await fillSingleField(code, targetInput);
          if (ok) return { ok: true, mode: "single_field" };
        }
        lastError = new Error("Value mismatch after fill");
      } catch (err) {
        lastError = err;
        log(`Fill attempt ${attempt + 1} failed: ${err.message}`);
      }
      if (attempt < retries) await delay(200);
    }

    return { ok: false, error: lastError?.message || "Fill failed after retries" };
  }

  const OtpInputEngine = Object.freeze({ fillOtpCode, fillSingleField, fillMultiBox });

  global.VisaFlowX = Object.assign(global.VisaFlowX || {}, {
    OtpInputEngine,
    fillOtpCode: fillOtpCode
  });

  if (typeof module !== "undefined" && module.exports) {
    module.exports = OtpInputEngine;
  }
})(typeof globalThis !== "undefined" ? globalThis : this);
