export interface SojoMe {
    /** Stable id for this player in your game. Third-party games get a pairwise id (different in every game). */
    sub: string;
    handle: string;
    name: string;
    /** Profile picture URL (pbs.twimg.com). */
    pfp: string;
    perks: string[];
    /** true in test mode (the test wallet). Display only, not a security signal. */
    test?: boolean;
}
export interface SojoSupporter {
    status: "yes" | "no";
    /** When the paid month ends (ms since epoch), or null. */
    ends: number | null;
}
export type SojoCorner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
export interface SojoUiPlacement {
    corner?: SojoCorner;
    /** Pixels from both edges of that corner, 0 to 64. Default 12. */
    inset?: number;
    /** true hides the pill. */
    hidden?: boolean;
    /**
     * The signed-out pill (the language and Sign in). Default: shown when the game has more than one language.
     * true shows it with one language; false never shows it.
     */
    guest?: boolean;
}
export interface SojoInitOptions {
    /** Your registered game id (also the token `aud`). */
    game: string;
    /** Send the `load` event automatically. Default true. */
    track?: boolean;
    /** First-party only: trade an existing sign-in for a sojo session (Tweetcraft). */
    legacy?: {
        provider: string;
        token: string | null | (() => string | null | Promise<string | null>);
    };
    /**
     * sojo's in-game pill (avatar and @handle; a tap opens gems, Top up and Account settings). sojo draws it in its own
     * frame, pinned to your viewport. Default { corner: "top-right", inset: 12 }. false hides it.
     */
    ui?: boolean | SojoUiPlacement;
    /** Narrows this page to some of the game's registered languages (BCP 47 tags). Never adds one. */
    locales?: string[];
}
export interface SojoGems {
    balance: number;
    /** Below 0 after a chargeback: spending pauses. */
    paused: boolean;
}
export interface SojoSpendOptions {
    amount: number;
    item: string;
    /** 1-64 of A-Za-z0-9._:- ; makes the spend idempotent. */
    ref: string;
    /** First-party games only, at most 20 gems, no confirm. */
    quick?: boolean;
    note?: string;
}
export interface SojoSpend {
    /** Ed25519 JWT (typ sojo-receipt, aud = your game, sub = the token's sub). */
    receipt: string;
    balance: number;
    amount: number;
    item: string;
    ref: string;
    /** true when this ref was already spent (no gems taken twice). */
    repeat: boolean;
}
export type SojoPack = "gems_500" | "gems_1100" | "gems_2400" | "gems_6500";
/** Thrown by subscribe, saveCard and spend (and sojo.x for a bad handle or post id). */
export interface SojoError extends Error {
    code: string;
}
export interface SojoClient {
    /** Your game id. */
    readonly game: string;
    me(): Promise<SojoMe | null>;
    /** Opens sojo's sign-in sheet (drawn by sojo inside its own frame). Call from a click. */
    signIn(opts?: {
        text?: string;
    }): Promise<SojoMe | null>;
    /** A 15-minute Ed25519 game token (aud = your game), or null when signed out. */
    token(): Promise<string | null>;
    /** Royal status (one Sojo-wide plan: the same in every game; the code id stays "supporter"), or null when signed out. Trust the token's perks on your server. */
    supporter(): Promise<SojoSupporter | null>;
    /**
     * Opens Sojo's Subscribe sheet for Royal ($10 a month by card: 500 gems each month and your game's perks; sign-in first if needed).
     * Call from a click. Resolves after subscribing (every game's token perks then carry "supporter"), or null if closed.
     * Rejects with an Error carrying .code (signed_out, busy…).
     */
    subscribe(): Promise<{
        status: "active";
        renews: number;
        balance: number;
    } | null>;
    /**
     * Opens sojo's Save a card sheet (Stripe's card form, a SetupIntent; nothing is charged; sign-in first if needed).
     * Call from a click. Resolves with the saved card's brand and last 4, or null if closed.
     * Rejects with an Error carrying .code (signed_out, busy…).
     */
    saveCard(): Promise<{
        card: {
            brand: string;
            last4: string;
        } | null;
    } | null>;
    /** The gem balance, or null when signed out. paused: below 0 after a chargeback (spending pauses). */
    gems(): Promise<SojoGems | null>;
    /** Opens sojo's Top up sheet (sign-in first if needed). Resolves when it closes. */
    topUp(opts?: {
        pack?: SojoPack;
    }): Promise<{
        balance: number;
        bought: number;
    } | null>;
    /**
     * Spends gems on an item. sojo puts its Confirm card just above the calling button (or opens Not enough gems, which buys the gap first).
     * Resolves with a signed receipt (verify it with verifySojoReceipt before granting the item), or null if closed.
     * ref (1-64 of A-Za-z0-9._:-) makes it idempotent. quick: first-party games, at most 20 gems, no confirm.
     * Rejects with an Error carrying .code (signed_out, busy, bad_amount, bad_ref, ref_used…).
     */
    spend(opts: SojoSpendOptions): Promise<SojoSpend | null>;
    /** Hides (false) or shows (true) sojo's pill, for example during play, or moves it ({ corner, inset }). Returns the client. */
    ui(opts: boolean | SojoUiPlacement): SojoClient;
    /** Called now and on every change. Returns an unsubscribe function. */
    onChange(fn: (me: SojoMe | null) => void): () => void;
    /** The player's language, one of the game's locales (BCP 47), or null until sojo's frame answers. */
    readonly locale: string | null;
    /** The player's country (ISO 3166-1 alpha-2) from their connection, or null. A hint, not proof. */
    readonly region: string | null;
    /** true while sojo's frame runs in test mode (a dev build: the test wallet, Stripe test cards). Display only, not a security signal. */
    readonly test: boolean;
    /** Called with the locale once it's known and on every change. Returns an unsubscribe function. */
    onLocale(fn: (locale: string) => void): () => void;
    /** Opens Sojo's language picker (only with more than one language). Call from a click. Resolves with the locale. */
    languages(): Promise<string | null>;
    /**
     * Called with the new gem balance whenever it changes while the game is open: a purchase, a spend, a subscribe or
     * renewal charge, a Top up from sojo's pill. Not called for the first balance (read that with gems()). Display data
     * only; verify spends with their receipts. Returns an unsubscribe function.
     */
    onGems(fn: (g: SojoGems) => void): () => void;
    /** Best-effort client event: a core event or "<game>.<name>". */
    track(ev: string, props?: Record<string, unknown>, extra?: {
        world?: string;
        src?: string;
    }): void;
    /** Opens X's composer through sojo.x (the X app where it can) and records share_open and x_intent. Call from a click. */
    share(opts?: {
        text?: string;
        url?: string;
    }): Promise<SojoXResult>;
    /** X intents (docs/integrate.md, X posts). Call from a click. Each records x_intent {kind, via}. */
    readonly x: SojoX;
    /** Last known handle and picture, for drawing UI before the network answers. Not proof. */
    readonly cached: SojoCached | null;
    /** Sojo Ads (docs/ads.md). */
    readonly ads: SojoAds;
}
export interface SojoCached {
    handle: string;
    name: string;
    pfp: string;
}
/** One slot's fill this period. Never the link: only the display domain. fill is null for an empty slot. */
export interface SojoFill {
    fill: string | null;
    slot: string | null;
    /** A large ad (2×2): the group key and the plots it covers. */
    group: string | null;
    slots?: string[];
    format: string;
    w: number;
    h: number;
    /** The rendition PNG (immutable). */
    url?: string;
    /** Palette indices, row by row (pixel_plot). */
    pixels?: number[];
    palette?: string[];
    brand?: string;
    headline?: string;
    /** "Sponsored by <brand>" */
    label?: string;
    domain?: string;
    /** false for a test fill: no link, no report. */
    clickable: boolean;
    /** Sojo's test ad from the simulator's preview (?sojo_ad_preview=): never counted or billed. */
    test?: boolean;
}
export interface SojoFills {
    period_end: number;
    fills: SojoFill[];
    test?: boolean;
}
export interface SojoAds {
    /** The current fills (public, the same for every viewer). */
    fills(): Promise<SojoFills | null>;
    /** Called with the new fills at each period (the hour mark). Returns an unsubscribe function. */
    onChange(fn: (f: SojoFills) => void): () => void;
    /** Fire and forget: the fill was at least `ratio` on screen for `ms` in a row (pixel_plot: 0.4 for 500 ms). */
    seen(fill: SojoFill | string, opts: {
        ratio: number;
        ms: number;
    }): void;
    /** A one-use sojo.gg/go URL for the label link (10 minutes), or null. Get a new one after each tap. */
    link(fill: SojoFill | string): Promise<{
        url: string;
        expires_at: number;
    } | null>;
    /** Opens Sojo's report sheet in its frame. Call from a click. */
    report(fill: SojoFill | string): Promise<{
        sent: boolean;
    }>;
}
/** What to check a pasted post against (docs/api.md, Posts). quotes: "launch" checks the game's launch post
 * (its own settings), set at signing time — never sent to Sojo by the client. A literal id checks that quote. */
export interface SojoPostExpect {
    links?: string[];
    quotes?: string | "launch";
    after?: number;
    before?: number;
}
/** A signed post receipt from sojo.x.paste() (typ sojo-post). Verify it server-side with verifySojoPost before
 * counting the post; store post_id so it counts once. */
export interface SojoPostReceipt {
    receipt: string;
    post: {
        id: string;
        created_at: number;
        links: string[];
        quoted_id: string | null;
        text: string;
    };
}
export type SojoXKind = "post" | "follow" | "like" | "repost" | "reply";
/** url is added to the end of the text; you may also put links in text yourself. */
export interface SojoXPost {
    text?: string;
    url?: string;
    reply_to?: string;
    hashtags?: string | string[];
    via?: string;
}
/** Every field an intent can take: post's, plus id (like, repost, reply) and handle (follow). */
export type SojoXOptions = SojoXPost & {
    id?: string;
    handle?: string;
};
/** via: "web" (X's page opened), "app" (the X app took it), "blocked" (no gesture or a blocked popup: show url as a link). */
export interface SojoXResult {
    kind: SojoXKind;
    via: "web" | "app" | "blocked";
    url: string;
}
/** web: X's https intent; app: the X app's scheme (iOS); android: Chrome's intent:// URL with web as its fallback. */
export interface SojoXLinks {
    kind: SojoXKind;
    web: string;
    app: string;
    android: string;
}
export interface SojoX {
    /** X's composer. reply_to makes it a reply. */
    post(opts?: SojoXPost): Promise<SojoXResult>;
    /** Follow a handle (with or without @). */
    follow(handle: string): Promise<SojoXResult>;
    /** Like a post (its id or x.com link). The X app opens the post instead. */
    like(id: string): Promise<SojoXResult>;
    /** Repost a post (its id or x.com link). The X app opens the post instead. */
    repost(id: string): Promise<SojoXResult>;
    /** Reply to a post. */
    reply(id: string, text?: string): Promise<SojoXResult>;
    /** The links, for your own <a> (no event recorded). Throws bad_intent for a bad handle or id. */
    links(kind: SojoXKind, opts?: SojoXOptions): SojoXLinks;
    /**
     * Opens Sojo's paste sheet in its frame: the player pastes an X post's link, Sojo checks it (the same way your
     * server's postsCheck call does) with the signed-in player as the required author, and signs the result into a
     * receipt your server verifies with verifySojoPost. Resolves null if the player closes the sheet.
     * Rejects with an Error carrying .code (signed_out, busy...).
     */
    paste(opts?: {
        expect?: SojoPostExpect;
    }): Promise<SojoPostReceipt | null>;
}
/** X intents without a game client (no events). */
export interface SojoXStatic {
    open(kind: SojoXKind, opts?: SojoXOptions): Promise<SojoXResult>;
    links(kind: SojoXKind, opts?: SojoXOptions): SojoXLinks;
    kinds: SojoXKind[];
}
/** window.Sojo, set by the <script> build (https://id.sojo.gg/v1/sdk.js). */
export interface SojoGlobal {
    version: number;
    /** The package version (packages/sdk/package.json), e.g. "0.1.7". sojo.gg/lab/x reads this live, never hardcoded. */
    sdkVersion: string;
    /** Sojo's id origin (from the script's src). */
    base: string;
    x: SojoXStatic;
    init(opts: SojoInitOptions): SojoClient;
}
