// ── Clover SDK loaders ──────────────────────────────────────────
//
// Loads the Clover and Clover3DS scripts idempotently. Three cases handled:
//
// 1. The global is already set on `window` — common in WP, where the host
//    enqueues the Clover SDK as a script dependency that loads BEFORE our
//    adapter runs. Resolve immediately.
// 2. A `<script>` tag for the URL is already in the DOM but hasn't fired its
//    `load` event yet — poll the global until it appears (we can't attach a
//    `load` listener after the event has already fired, which is the
//    failure mode the v0.1 loader hit in WP).
// 3. Neither — inject our own `<script>` tag, then poll for the global.
//
// Polling is intentional: it works regardless of whether the script tag was
// injected by us or by the host, and regardless of whether `load` already
// fired. The poll interval is short enough that there's no visible delay.

import type { Clover3DSGlobal, CloverGlobal } from './types';

const POLL_INTERVAL_MS = 50;
const DEFAULT_TIMEOUT_MS = 10_000;

const inFlight = new Map<string, Promise<void>>();

interface WaitOptions {
  readonly url: string;
  readonly globalName: string;
  readonly timeoutMs?: number;
}

function waitForGlobal({ url, globalName, timeoutMs = DEFAULT_TIMEOUT_MS }: WaitOptions): Promise<void> {
  const w = window as unknown as Record<string, unknown>;

  // Fast path: global already present.
  if (w[globalName] !== undefined) {
    return Promise.resolve();
  }

  const cached = inFlight.get(url);
  if (cached) {
    return cached;
  }

  const promise = new Promise<void>((resolve, reject) => {
    // If the host hasn't already added a script tag, add one.
    const existingTag = document.querySelector<HTMLScriptElement>(`script[src="${url}"]`);
    if (!existingTag) {
      const script = document.createElement('script');
      script.src = url;
      script.async = true;
      script.onerror = () => {
        inFlight.delete(url);
        reject(new Error(`Failed to load script: ${url}`));
      };
      document.head.appendChild(script);
    }

    // Poll for the global. Works whether the script was just injected by us
    // (load event will fire in the future) or by the host before our code
    // started (load event already fired, can't listen for it now).
    const start = Date.now();
    const poll = (): void => {
      if (w[globalName] !== undefined) {
        resolve();
        return;
      }
      if (Date.now() - start >= timeoutMs) {
        inFlight.delete(url);
        reject(new Error(`Timed out waiting for window.${globalName} from ${url}`));
        return;
      }
      setTimeout(poll, POLL_INTERVAL_MS);
    };
    poll();
  });

  inFlight.set(url, promise);
  return promise;
}

export async function loadClover(url: string): Promise<CloverGlobal> {
  await waitForGlobal({ url, globalName: 'Clover' });
  const Clover = (window as unknown as { Clover?: CloverGlobal }).Clover;
  if (!Clover) {
    throw new Error('Clover SDK loaded but window.Clover is missing.');
  }
  return Clover;
}

export async function loadClover3DS(url: string): Promise<Clover3DSGlobal> {
  await waitForGlobal({ url, globalName: 'Clover3DS' });
  const Clover3DS = (window as unknown as { Clover3DS?: Clover3DSGlobal }).Clover3DS;
  if (!Clover3DS) {
    throw new Error('Clover3DS SDK loaded but window.Clover3DS is missing.');
  }
  return Clover3DS;
}
