// ── usePaymentFields hook ───────────────────────────────────────
//
// React 18+ binding for `PaymentFieldsMachine`. Uses `useSyncExternalStore`
// so React stays in sync with the machine's snapshot without unnecessary
// re-renders or tearing in concurrent mode.
//
// Lifecycle: one machine per hook invocation. The machine is created lazily
// on first render and destroyed on unmount. Re-configuring requires unmount
// (e.g., key change on the parent) — the SDK does not support config swaps
// because Clover iframes can't be re-themed after mount.

import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
import {
  PaymentFieldsMachine,
  type MountTargets,
  type PaymentFieldsConfig,
  type PaymentFieldsError,
  type PaymentFieldsSnapshot,
  type PaymentFieldsState,
  type ThreeDsInterim,
  type ThreeDsResult,
  type TokenizationResult,
  type ValidationSnapshot,
} from '../core/index';

export interface UsePaymentFieldsOptions extends PaymentFieldsConfig {}

/**
 * Input to `usePaymentFields`. Either a config (hook constructs the machine
 * and destroys it on unmount) or an existing machine (hook subscribes only,
 * leaves lifecycle to the caller — used by the web-component layer).
 */
export type UsePaymentFieldsInput = UsePaymentFieldsOptions | PaymentFieldsMachine;

export interface UsePaymentFieldsReturn {
  // ── Flattened snapshot fields (most common reads) ──
  readonly state: PaymentFieldsState;
  readonly validation: ValidationSnapshot;
  readonly result?: TokenizationResult;
  readonly error?: PaymentFieldsError;
  readonly threeDs?: ThreeDsInterim;
  /** Full snapshot, for hosts that want everything at once. */
  readonly snapshot: PaymentFieldsSnapshot;

  // ── Actions (stable references) ──
  readonly mount: (targets: MountTargets) => Promise<void>;
  readonly tokenize: () => Promise<TokenizationResult>;
  readonly runThreeDs: (
    interim: ThreeDsInterim,
    options?: { readonly extraBody?: Record<string, unknown> },
  ) => Promise<ThreeDsResult>;
  readonly resetToIdle: () => void;

  /** Escape hatch for advanced cases (the underlying machine instance). */
  readonly machine: PaymentFieldsMachine;
}

export function usePaymentFields(input: UsePaymentFieldsInput): UsePaymentFieldsReturn {
  // Lazy-init pattern: capture the machine on first render, persist across renders.
  // In React Strict Mode the function body runs twice; the ref persists, so we
  // only construct one machine. `ownsRef` tracks whether we should destroy it on
  // unmount (true when we constructed it; false when the caller passed one in).
  const machineRef = useRef<PaymentFieldsMachine | null>(null);
  const ownsRef = useRef<boolean>(false);
  if (machineRef.current === null) {
    if (input instanceof PaymentFieldsMachine) {
      machineRef.current = input;
      ownsRef.current = false;
    } else {
      machineRef.current = new PaymentFieldsMachine(input);
      ownsRef.current = true;
    }
  }
  const machine = machineRef.current;

  // Subscribe + getSnapshot must have stable identity for useSyncExternalStore.
  const subscribe = useCallback(
    (onChange: () => void) => machine.subscribe(() => onChange()),
    [machine],
  );
  const getSnapshot = useCallback(() => machine.getSnapshot(), [machine]);
  const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);

  // Destroy on unmount only if we own the machine. Externally-supplied machines
  // are the caller's responsibility (web-component layer destroys in disconnectedCallback).
  useEffect(() => {
    return () => {
      if (ownsRef.current) {
        machine.destroy();
      }
      machineRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const mount = useCallback((targets: MountTargets) => machine.mount(targets), [machine]);
  const tokenize = useCallback(() => machine.tokenize(), [machine]);
  const runThreeDs = useCallback(
    (interim: ThreeDsInterim, options?: { readonly extraBody?: Record<string, unknown> }) =>
      machine.runThreeDs(interim, options),
    [machine],
  );
  const resetToIdle = useCallback(() => machine.resetToIdle(), [machine]);

  return useMemo<UsePaymentFieldsReturn>(
    () => ({
      state: snapshot.state,
      validation: snapshot.validation,
      result: snapshot.result,
      error: snapshot.error,
      threeDs: snapshot.threeDs,
      snapshot,
      mount,
      tokenize,
      runThreeDs,
      resetToIdle,
      machine,
    }),
    [snapshot, mount, tokenize, runThreeDs, resetToIdle, machine],
  );
}
