// ── Core types · @weeconnectpay/payment-fields ──────────────────
//
// All public types live here. The React and web-component layers re-export
// the ones consumers need; the rest are SDK-internal.
//
// The marquee type in this file is `CloverElementStyles` — see the "Clover
// SDK constraints" section of the SDK plan (~/.claude/plans/...) for why this
// matters. It replaces the legacy `type CloverElementStyles = object` that
// silently absorbed every selector and property typo for years.

// ── Clover element types (mirrors the SDK's enum) ───────────────

export const CloverElementType = {
  CARD: 'CARD',
  NUMBER: 'CARD_NUMBER',
  DATE: 'CARD_DATE',
  CVV: 'CARD_CVV',
  POSTAL_CODE: 'CARD_POSTAL_CODE',
  STREET_ADDRESS: 'CARD_STREET_ADDRESS',
  NAME: 'CARD_NAME',
  EMAIL_ADDRESS: 'CARD_EMAIL_ADDRESS',
  PHONE_NUMBER: 'CARD_PHONE_NUMBER',
  PAYMENT_REQUEST_BUTTON: 'PAYMENT_REQUEST_BUTTON',
} as const;

export type CloverElementType = (typeof CloverElementType)[keyof typeof CloverElementType];

/**
 * SDK-internal identifier for each rendered field. One-to-one with the Clover
 * element types except `CARD` (composite — we don't use it).
 */
export type FieldKey =
  | 'cardNumber'
  | 'cardDate'
  | 'cardCvv'
  | 'cardPostalCode'
  | 'cardName'
  | 'cardEmail'
  | 'paymentRequestButton';

/** Maps the SDK's FieldKey to Clover's element type. */
export const FIELD_KEY_TO_CLOVER_TYPE: Record<FieldKey, CloverElementType> = {
  cardNumber: CloverElementType.NUMBER,
  cardDate: CloverElementType.DATE,
  cardCvv: CloverElementType.CVV,
  cardPostalCode: CloverElementType.POSTAL_CODE,
  cardName: CloverElementType.NAME,
  cardEmail: CloverElementType.EMAIL_ADDRESS,
  paymentRequestButton: CloverElementType.PAYMENT_REQUEST_BUTTON,
};

// ── Clover styling JSON (the marquee type fix) ───────────────────

/**
 * CSS properties Clover supports inside its iframe `<input>`. Curated from
 * production usage in the WP plugin + Clover SDK behavior. Properties not
 * listed here are silently ignored by Clover; we don't pretend they work.
 */
export interface CloverCssProperties {
  // Typography
  color?: string;
  fontFamily?: string;
  fontSize?: string;
  fontWeight?: string | number;
  lineHeight?: string | number;
  letterSpacing?: string;
  textAlign?: 'left' | 'right' | 'center' | 'inherit' | 'initial';
  textIndent?: string;
  // Box model
  padding?: string;
  margin?: string;
  height?: string;
  width?: string;
  // Border (shorthand only — Clover ignores per-edge overrides)
  border?: string;
  borderRadius?: string;
  // Background (limited support)
  background?: string;
  backgroundColor?: string;
}

/**
 * Element-scoped selectors Clover honors. Each is the lower-kebab form of the
 * Clover element type (`CARD_EMAIL_ADDRESS` → `card-email-address`). Using a
 * shortened form (e.g. `card-email`) is silently ignored — Clover does not
 * warn, the style just never applies. Discovery captured 2026-05-18.
 */
type CloverElementSelector =
  | 'card-number'
  | 'card-date'
  | 'card-cvv'
  | 'card-postal-code'
  | 'card-name'
  | 'card-email-address'
  | 'card-phone-number'
  | 'card-street-address';

/**
 * Placeholder pseudo-classes Clover honors. The vendor-prefixed forms are
 * required by older Clover versions; modern `::placeholder` is also accepted.
 * Production code in the WP plugin sets all four prefixed variants.
 */
type PlaceholderPseudo =
  | '::placeholder'
  | '::-webkit-input-placeholder'
  | '::-moz-placeholder'
  | ':-ms-input-placeholder'
  | ':-moz-placeholder';

/**
 * Every selector key Clover's `elements.create(type, styles)` accepts. The
 * SDK exports this union so consumers get autocomplete and typo safety on
 * a previously-untyped JSON payload.
 *
 * - `'input'` — applies to the input in every element
 * - `'<pseudo>'` — global placeholder rule
 * - `'card-<type> input'` — per-element input rule
 * - `'card-<type> input<pseudo>'` — per-element placeholder rule
 */
export type CloverStyleSelector =
  | 'input'
  | PlaceholderPseudo
  | CloverElementSelector
  | `${CloverElementSelector} input`
  | `${CloverElementSelector} input${PlaceholderPseudo}`;

/**
 * Strongly-typed styles object for `clover.elements().create()`. Replaces
 * `type CloverElementStyles = object`.
 *
 * Most consumers should NOT construct this directly — pass a higher-level
 * `theme` on `PaymentFieldsConfig` and let `compileTheme()` emit the JSON.
 */
export type CloverElementStyles = Partial<Record<CloverStyleSelector, CloverCssProperties>>;

/**
 * High-level theme config. The SDK compiles this into a `CloverElementStyles`
 * JSON object internally — most apps never need to touch the raw selectors.
 */
export interface CloverFieldTheme {
  readonly fontFamily?: string;
  readonly fontSize?: string;
  readonly textColor?: string;
  readonly placeholderColor?: string;
  /**
   * Height of the input inside each Clover iframe. Must match (or slightly
   * exceed) the host wrapper's height so the entire wrapper is clickable.
   * Defaults to `'46px'` to match `.wcp-pf__field` in the SDK stylesheet.
   */
  readonly inputHeight?: string;
  /** Extra style overrides merged on top of the theme-emitted defaults. */
  readonly overrides?: CloverElementStyles;
}

// ── Window-attached Clover globals (typed, not declared) ─────────
//
// We deliberately do NOT augment `Window` from a package. Globals leak into
// every consumer's project. Instead the loader returns typed handles and the
// rest of the SDK takes those as parameters.

export interface CloverGlobal {
  new (pakms: string): CloverInstance;
}

export interface CloverInstance {
  apiKey?: string;
  merchantId?: string;
  locale?: string;
  options?: CloverOptions;
  elements: () => CloverElements;
  createToken: () => Promise<CloverIframeTokenizationResponse>;
}

export interface CloverOptions {
  locale?: string;
  merchantId?: string;
  showSecurePayments?: boolean;
  showPrivacyPolicy?: boolean;
}

export interface CloverElements {
  create: (
    elementType: CloverElementType,
    styles: CloverElementStyles | CloverPaymentRequestData,
  ) => CloverElement;
}

export interface CloverElement {
  mount: (selector: string) => void;
  addEventListener: (type: CloverElementEvent, listener: (event: Event) => void) => void;
}

export type CloverElementEvent =
  | 'change'
  | 'blur'
  | 'paymentMethodStart'
  | 'paymentMethod'
  | 'paymentMethodEnd';

export interface CloverIframeTokenizationResponse extends Event {
  token?: string;
  errors?: Partial<Record<CloverElementType, string>>;
  card?: {
    exp_month: string;
    exp_year: string;
    first6: string;
    last4: string;
    brand: string;
    address_zip?: string;
  };
}

/** Shape passed to `elements.create(PAYMENT_REQUEST_BUTTON, paymentReqData)`. */
export interface CloverPaymentRequestData {
  readonly paymentReqData: {
    readonly total: {
      readonly label: string;
      readonly currency: string;
      readonly amount: number;
    };
    readonly options?: {
      readonly button?: {
        readonly buttonType?: 'short' | 'long';
      };
    };
  };
}

// ── 3DS globals (typed, not declared) ────────────────────────────

export interface Clover3DSGlobal {
  new (config: { merchantUuid: string }): ThreeDSUtil;
}

export interface ThreeDSUtil {
  getBrowserInfo: () => ThreeDsBrowserInfo;
  perform3DSFingerPrinting: (args: {
    threeDSServerTransID: string;
    threeDSMethodUrl: string;
    methodNotificationUrl: string;
  }) => void;
  perform3DSChallenge: (args: {
    messageVersion: string;
    acsTransID: string;
    acsUrl: string;
    threeDSServerTransID: string;
  }) => void;
}

export type ThreeDsBrowserInfo = Record<string, unknown>;

// ── SDK state machine ────────────────────────────────────────────

/**
 * Lifecycle states the SDK transitions through. See the lifecycle preview at
 * `design-previews/06-states.html` for what the customer sees in each state.
 */
export type PaymentFieldsState =
  | 'idle'
  | 'gpay_opening'
  | 'gpay_ready'
  | 'gpay_cancelled'
  | 'tokenizing'
  | 'submitting'
  | 'threeds_method'
  | 'threeds_challenge'
  | 'finalizing'
  | 'done'
  | 'error';

/**
 * Per-field validation state. Two error fields — `error` is the raw signal
 * from Clover, `displayedError` is what the UI should actually render. They
 * diverge for the deferred-error UX rule: while the user is mid-typing, raw
 * `error` may report "incomplete card", but we don't surface it until they
 * blur once. After first blur, the two stay in sync. See README → Events &
 * Validation, and the comment block at the top of `validation-machine.ts`.
 */
export interface FieldValidationState {
  /** Clover-reported: user has interacted with the field. */
  readonly touched: boolean;
  /** Clover-reported: raw error message, fires on every `change` event. */
  readonly error?: string;
  /** SDK-derived: user has blurred this field at least once. */
  readonly hasBlurred: boolean;
  /** SDK-derived: at least one `change` event has been observed for this field. */
  readonly hadChangeEver: boolean;
  /** SDK-derived: the error to render. Undefined until first blur (deferred-error rule). */
  readonly displayedError?: string;
}

export interface ValidationSnapshot {
  /** Per-field touched + error state. Only fields actually mounted appear. */
  readonly fields: { readonly [K in FieldKey]?: FieldValidationState };
  /** True when all required fields are valid (no errors, all touched). */
  readonly canSubmit: boolean;
}

export interface PaymentFieldsSnapshot {
  readonly state: PaymentFieldsState;
  readonly validation: ValidationSnapshot;
  /** Set when state is 'error' or after a recoverable failure. */
  readonly error?: PaymentFieldsError;
  /** Set after successful tokenization (either path). */
  readonly result?: TokenizationResult;
  /** Set during the 3DS flow. */
  readonly threeDs?: ThreeDsInterim;
}

export interface PaymentFieldsError {
  readonly code: PaymentFieldsErrorCode;
  readonly message: string;
  readonly cause?: unknown;
}

export type PaymentFieldsErrorCode =
  | 'clover_load_failed'
  | 'clover_3ds_load_failed'
  | 'not_mounted'
  | 'rate_limited'
  | 'tokenization_failed'
  | 'gpay_unavailable'
  | 'threeds_prerequisites_missing'
  | 'threeds_timeout'
  | 'threeds_finalize_failed'
  | 'unknown';

// ── Tokenization result ──────────────────────────────────────────

export interface TokenizationResult {
  /** 'card' = manual entry tokenized via `clover.createToken()`. 'google_pay' = captured from `PAYMENT_REQUEST_BUTTON` paymentMethod event. */
  readonly source: 'card' | 'google_pay';
  readonly token: string;
  readonly card: {
    readonly brand: string;
    readonly last4: string;
    readonly expMonth?: string;
    readonly expYear?: string;
    readonly first6?: string;
    readonly addressZip?: string;
  };
  /** 3DS browser fingerprint, collected when 3DS is enabled. */
  readonly browserInfo?: ThreeDsBrowserInfo;
}

// ── 3DS flow types ───────────────────────────────────────────────

export interface ThreeDsInterim {
  readonly state: 'method_required' | 'challenge_required';
  readonly chargeId: string;
  readonly fields: ThreeDsInterimFields;
}

export interface ThreeDsInterimFields {
  readonly acsUrl?: string;
  readonly acsTransactionId?: string;
  readonly methodUrl?: string;
  readonly methodNotificationUrl?: string;
  readonly threeDsServerTransactionId?: string;
  /** Clover varies the field name between `threeds_protocol_version` and `message_version`. The SDK normalizes to this. */
  readonly protocolVersion?: string;
}

export type ThreeDsResult =
  | { readonly kind: 'success'; readonly redirect?: string; readonly data?: unknown }
  | { readonly kind: 'escalation'; readonly next: ThreeDsInterim }
  | { readonly kind: 'failure'; readonly message: string };

// ── Config (what the host passes to PaymentFieldsMachine) ────────

export interface PaymentFieldsConfig {
  // Clover SDK init
  readonly pakmsKey: string;
  readonly merchantId: string;
  readonly locale: string;

  // Cart context (for Payment Request Button)
  readonly cartTotal: number;
  readonly currency: string;
  readonly country?: string;

  // SDK script URLs (env-overridable)
  readonly cloverSdkUrl: string;
  readonly clover3DSSdkUrl?: string;

  // Features
  readonly features?: PaymentFieldsFeatures;

  // Endpoints (3DS finalize is host-supplied)
  readonly endpoints?: PaymentFieldsEndpoints;

  // Styling
  readonly theme?: CloverFieldTheme;

  // Optional callbacks (in addition to subscribe())
  readonly onTokenized?: (result: TokenizationResult) => void;
  readonly onThreeDsRequired?: (interim: ThreeDsInterim) => void;
  readonly onError?: (error: PaymentFieldsError) => void;
}

export interface PaymentFieldsFeatures {
  readonly googlePay?: boolean;
  readonly threeDSecure?: boolean;
  /**
   * Cardholder name + email field visibility.
   * - `'auto'` (default) — visible when 3DSecure is enabled
   * - `'always'` — visible always
   * - `'hidden'` — never rendered
   */
  readonly cardholderFields?: 'auto' | 'always' | 'hidden';
  readonly savedCredentials?: SavedCredential | readonly SavedCredential[];
}

export interface PaymentFieldsEndpoints {
  /** Host's POST URL for 3DS finalization (e.g., WC's `/wp-json/.../3ds/finalize`). */
  readonly threeDsFinalize?: string;
  /** Returns headers (nonce, auth, etc.) for the finalize POST. Called per-request. */
  readonly headers?: HeadersProvider;
}

export type HeadersProvider = () =>
  | Record<string, string>
  | Promise<Record<string, string>>;

export interface SavedCredential {
  readonly token: string;
  readonly last4: string;
  readonly brand: string;
}

// ── Mounting ─────────────────────────────────────────────────────

export interface MountTargets {
  readonly cardNumber: string;
  readonly cardDate: string;
  readonly cardCvv: string;
  readonly cardPostalCode: string;
  readonly cardName?: string;
  readonly cardEmail?: string;
  readonly paymentRequestButton?: string;
}

// ── Observer pattern ─────────────────────────────────────────────

export type PaymentFieldsListener = (snapshot: PaymentFieldsSnapshot) => void;
export type Unsubscribe = () => void;
