// Type definitions for ShopFast Elements (https://<your-store>/_sf/elements.js).
// Docs: https://shopfa.st/docs/elements
// Use: save next to your code and add `/// <reference path="shopfast-elements.d.ts" />`.
// Version 1.0. Money is in minor units of the store's currency (2400 = $24.00).

declare namespace ShopFastElements {
  type ElementType =
    | 'checkout' | 'summary' | 'email' | 'payment' | 'coupon' | 'consent' | 'billingAddress' | 'customFields'
    | 'product' | 'buyButton' | 'cartButton' | 'payButton' | 'tip' | 'offers';

  type EventName = 'ready' | 'change' | 'error' | 'resize' | 'focus' | 'blur' | 'navigate' | 'trace' | 'loaderror' | 'complete' | 'cart' | 'waiver';

  interface ClientOptions {
    /** Your store's address. Defaults to where elements.js was loaded from. */
    store?: string;
    /** Your CSP nonce, put on every frame and style tag the script adds. */
    nonce?: string;
    /** Log every event to the console. */
    debug?: boolean;
  }

  interface Item {
    /** A product handle or a prd_ id. */
    product: string;
    /** An option's var_ id. The first available option when left out. */
    variant?: string | null;
    /** 1 to 100. */
    quantity?: number;
    /** Pay what you want: the price as the buyer would type it ("12.50"). */
    amount?: string | null;
  }

  type TokenName =
    | 'accent' | 'onAccent' | 'text' | 'textMuted' | 'border' | 'surface' | 'surfaceSunken' | 'input' | 'danger' | 'success' | 'warning'
    | 'radius' | 'gap' | 'fontSize' | 'font' | 'fontHeading' | 'fontMono';

  type FontSpec = string | { family: string; weights?: number[]; italic?: boolean };

  /** Selector: one .sf-el-* class, optionally one [data-state] and one pseudo-class. Values: strings or numbers (px). */
  type Rules = Record<string, Record<string, string | number>>;

  interface Brand {
    mode?: 'light' | 'dark' | 'auto';
    /** "bare": structure only, no borders, backgrounds, radius or padding. */
    preset?: 'default' | 'bare';
    tokens?: Partial<Record<TokenName, string | number>>;
    /** Google Fonts only, up to four families. */
    fonts?: FontSpec[];
    rules?: Rules;
  }

  interface GroupOptions {
    items?: Item[];
    brand?: Brand;
    /** Language and number format, like "de" or "fr-CA". */
    locale?: string;
    /** The store always charges in its own currency; a different one is reported as a trace. */
    currency?: string;
    /** Replace built-in words by key. */
    strings?: Record<string, string>;
    /** Where a gateway that takes over the whole tab sends the buyer back to (same site as the page). */
    returnUrl?: string;
  }

  interface ElementOptions {
    /* checkout */
    showSummary?: boolean;
    title?: string;
    /* summary */
    editable?: boolean;
    showOffers?: boolean;
    showCodes?: boolean;
    showProgress?: boolean;
    /* email */
    label?: string;
    placeholder?: string;
    /* payment: list | grid; product: card | row | compact */
    layout?: 'list' | 'grid' | 'card' | 'row' | 'compact';
    /* coupon */
    collapsed?: boolean;
    /* consent */
    showSeller?: boolean;
    /* product, buyButton */
    product?: string;
    variant?: string | null;
    quantity?: number;
    showDescription?: boolean;
    showQuantity?: boolean;
    showTiers?: boolean;
    action?: 'add' | 'buy';
    showPrice?: boolean;
    /* buttons */
    text?: string;
    size?: 'sm' | 'md' | 'lg';
    block?: boolean;
    showTotal?: boolean;
  }

  type ErrorCode =
    | 'load_failed' | 'origin_not_allowed' | 'store_unavailable' | 'network_error' | 'session_expired'
    | 'out_of_stock' | 'product_not_found' | 'cart_empty' | 'cart_full' | 'quantity_limit' | 'coupon_invalid' | 'gift_card_invalid'
    | 'email_invalid' | 'email_rejected' | 'field_required' | 'field_invalid' | 'consent_required' | 'waiver_required'
    | 'email_verification_required' | 'verification_code_invalid' | 'element_missing'
    | 'sanctions_blocked' | 'checkout_blocked' | 'rate_limited' | 'checkout_expired' | 'order_locked'
    | 'method_unavailable' | 'payments_not_set_up' | 'payment_declined' | 'payment_failed' | 'payment_window_blocked' | 'payment_cancelled'
    | 'unknown';

  interface ElementsError {
    type: 'load' | 'session' | 'validation' | 'order' | 'payment';
    code: ErrorCode;
    /** For the buyer; may change. Handle `code`. */
    message: string;
    element: ElementType | null;
    field: string | null;
    recoverable: boolean;
  }

  interface CompletedOrder {
    checkoutId: string;
    orderId: string | null;
    number: string | null;
    /** The order page: status for anyone, keys and downloads once the buyer opens it from their email or the element. */
    url: string;
    totalMinor: number;
    currency: string;
    test: boolean;
  }

  interface CartLine {
    id: string;
    productId: string;
    handle: string;
    variantId: string | null;
    title: string;
    option: string | null;
    quantity: number;
    unitMinor: number;
    lineMinor: number;
  }

  interface EventMap {
    ready: { element: ElementType | null; store: { name: string; currency: string; testMode: boolean } | null };
    change: {
      element: ElementType | null;
      kind: 'email' | 'quantity' | 'method' | 'country' | 'consent' | 'marketing' | 'waiver' | 'field' | 'code' | 'tip' | 'variant' | 'offer' | 'outcome';
      value: string | number | boolean | null;
      complete: boolean;
      empty: boolean;
    };
    error: ElementsError;
    resize: { element: ElementType; height: number; width: number };
    focus: { element: ElementType };
    blur: { element: ElementType };
    navigate: { url: string; mode: 'popup' | 'redirect'; method: string };
    trace: { level: 'info' | 'warn' | 'error'; source: 'brand' | 'controller' | 'element' | 'sdk'; code: string; message: string; element: ElementType | null };
    loaderror: { element: ElementType | null; reason: 'timeout' | 'error' | 'origin_not_allowed' };
    complete: { order: CompletedOrder };
    /** taxIncluded: the store's prices include tax, so taxMinor is inside totalMinor, not added to it. */
    cart: { count: number; currency: string; lines: CartLine[]; subtotalMinor: number; discountMinor: number; taxMinor: number; totalMinor: number; taxIncluded: boolean; codes: string[] };
    waiver: { shown: boolean; required: boolean; country: string | null };
  }

  interface Element {
    readonly type: ElementType;
    mount(target: string | HTMLElement): Element;
    update(options: ElementOptions): Element;
    focus(): Element;
    unmount(): Element;
    destroy(): void;
    on<E extends 'ready' | 'change' | 'focus' | 'blur' | 'resize' | 'loaderror' | 'trace'>(event: E, handler: (data: EventMap[E], name: E) => void): () => void;
  }

  interface Elements {
    create(type: ElementType, options?: ElementOptions): Element;
    getElement(type: ElementType): Element | null;
    on<E extends EventName>(event: E, handler: (data: EventMap[E], name: E) => void): () => void;
    on(event: '*', handler: (data: EventMap[EventName], name: EventName) => void): () => void;
    once<E extends EventName>(event: E, handler: (data: EventMap[E], name: E) => void): () => void;
    off(event: EventName | '*', handler: (...args: never[]) => void): void;
    update(options: { items?: Item[]; brand?: Brand; locale?: string; strings?: Record<string, string>; returnUrl?: string | null }): void;
    addItem(item: Item | Item[]): void;
    edit(): void;
    open(): void;
    close(): void;
    destroy(): void;
  }

  type ConfirmResult = { order: CompletedOrder; error: null } | { order: null; error: ElementsError };

  interface Client {
    readonly store: string;
    readonly version: string;
    elements(options?: GroupOptions): Elements;
    /** Pay from your own button. Call it inside the click handler. */
    confirmPayment(options: { elements: Elements; returnUrl?: string }): Promise<ConfirmResult>;
  }
}

declare function ShopFast(options?: ShopFastElements.ClientOptions): ShopFastElements.Client;
declare function ShopFast(store: string, options?: Omit<ShopFastElements.ClientOptions, 'store'>): ShopFastElements.Client;

interface Window {
  ShopFast: typeof ShopFast;
}
