import * as react from 'react';
import { StyleProp, ViewStyle } from 'react-native';

/**
 * How the story button looks. Buttons: a white pill, a dark pill, or an outline. Swipe ups: a
 * chevron over the label, or over a white pill; swiping up on the story then opens the link.
 */
declare const CTA_STYLES: readonly ["pill", "dark", "outline", "swipe", "swipe-pill"];
type CtaStyle = (typeof CTA_STYLES)[number];

/**
 * Story circle appearance, version 1. Dependency-free so every SDK (web now,
 * iOS/Android/React Native in Phase 4) can share the same defaults and merge
 * rules. Import from "@stories/contracts/appearance".
 *
 * Resolution order: built-in defaults < app "story bar style" < group overrides.
 */
declare const APPEARANCE_VERSION: 1;
declare const ringStyles: readonly ["gradient", "solid", "none"];
declare const ringAnimations: readonly ["none", "rotate", "pulse", "shimmer"];
declare const animationSpeeds: readonly ["slow", "normal", "fast"];
declare const badgeTypes: readonly ["none", "preset", "text", "emoji", "countdown"];
declare const badgePositions: readonly ["bottom", "top-right"];
declare const titleWeights: readonly [400, 600, 700];
interface RingAppearance {
    style: (typeof ringStyles)[number];
    /** 1 colour for solid, 2–3 for gradient (hex #rrggbb). */
    colors: string[];
    /** Gradient angle in degrees. */
    angle: number;
    /** Ring thickness, 2–6 px. */
    width: number;
    /** Space between ring and image, 0–6 px. */
    gap: number;
    /** "background" = the page background, "transparent", or a hex colour. */
    gapColor: string;
    /** Ring colour once every story in the group has been watched. */
    seenColor: string;
    animation: (typeof ringAnimations)[number];
    speed: (typeof animationSpeeds)[number];
    /** Stop animating once the group has been seen. */
    animateOnlyUnseen: boolean;
}
interface BadgeAppearance {
    type: (typeof badgeTypes)[number];
    /** Preset label, custom text (≤ 6 chars) or a single emoji. Ignored for none/countdown. */
    text: string;
    background: string;
    color: string;
    position: (typeof badgePositions)[number];
}
interface TitleAppearance {
    hidden: boolean;
    /** "inherit" (the page's text colour) or a hex colour. */
    color: string;
    weight: (typeof titleWeights)[number];
    maxLines: 1 | 2;
}
interface Appearance {
    v: typeof APPEARANCE_VERSION;
    ring: RingAppearance;
    badge: BadgeAppearance;
    title: TitleAppearance;
}
/** Circle cover renditions as served to SDKs. */
interface CoverDto {
    /** Square still image sized for the circle (also the poster for animated covers). */
    image: string;
    /** Looping muted MP4 for animated (GIF) covers. */
    video?: string;
    animated: boolean;
}
/**
 * Shape and size of every item in the story bar. App-wide only (Settings);
 * groups can't override it.
 */
interface BarLayout {
    /** Item width in px (ring included). */
    width: number;
    /** Item height in px (ring included). Taller than wide = portrait cards. */
    height: number;
    /** Corner rounding as % of the shorter side: 50 = circle/pill, 0 = square corners. */
    corner: number;
    /** Space between items in px. */
    gap: number;
}

/**
 * Dependency-free types for the public SDK API. SDKs import these from
 * "@stories/contracts/sdk-types" so their bundles and .d.ts never touch Zod.
 */

declare const eventTypes: readonly ["impression", "group_open", "story_view", "cta_click", "group_complete", "share", "tap_forward", "tap_back", "group_skip", "exit"];
type EventType = (typeof eventTypes)[number];
/**
 * Activity outside the story bar, for active users and traffic: a page load with the SDK
 * (web) or an app session starting (apps). They belong to no story group.
 */
declare const activityTypes: readonly ["page_view", "app_open"];
type ActivityType = (typeof activityTypes)[number];
type StoryType = 'image' | 'video';
interface FeedStoryDto {
    id: string;
    /** Name shown at the top of the story (the story's own name, or the group title). */
    title?: string;
    type: StoryType;
    url: string;
    durationMs: number;
    width: number | null;
    height: number | null;
    alt: string;
    /** `style` is absent from older servers ("pill"). */
    cta: {
        label: string;
        url: string;
        style?: CtaStyle;
    } | null;
    /** Interactive stickers drawn over the image; absent from older servers. */
    interactions?: FeedInteractionDto[];
    /**
     * How it fits the screen: "fill" covers it (edges may be cropped on some phones), "fit" shows
     * all of it, with any gaps in `fill` colours. Absent from older servers ("fit", black gaps).
     */
    fit?: StoryFit;
    /** Gap colours for "fit": top to bottom. Null = black. */
    fill?: StoryFill | null;
    /**
     * Videos designed in the studio: a transparent 1080 × 1920 image of their text, shapes and
     * stickers, drawn over the video, fitted to the screen as a 1080 × 1920 story image would be
     * (`fit`). Absent: nothing to draw.
     */
    overlay?: string;
}
type StoryFit = 'fill' | 'fit';
interface StoryFill {
    /** "#rrggbb". */
    top: string;
    bottom: string;
}
/**
 * An interactive sticker, placed in the story image's 1080 × 1920 design space: `x`, `y` are
 * its centre and `width` its width there. Each SDK maps them onto the image as displayed.
 */
type FeedInteractionDto = {
    id: string;
    x: number;
    y: number;
    width: number;
    accent: string;
} & ({
    kind: 'poll';
    question: string;
    options: string[];
} | {
    kind: 'quiz';
    question: string;
    options: string[];
    correct: number;
} | {
    kind: 'slider';
    question: string;
    emoji: string;
} | {
    kind: 'link';
    label: string;
    url: string;
} | {
    kind: 'countdown';
    title: string;
    endsAt: string;
});
/** What people answered so far: votes per option (poll, quiz) or the average (slider, 0–100). */
interface AnswerResultsDto {
    total: number;
    counts?: number[];
    average?: number;
}
interface FeedGroupDto {
    id: string;
    title: string;
    /** Still cover image (also the poster of animated covers). */
    coverUrl: string;
    /** Circle cover renditions; absent from older servers. */
    cover?: CoverDto;
    /** Resolved circle appearance; absent from older servers (SDK theme is used instead). */
    appearance?: Appearance;
    /** Schedule end, for countdown badges. */
    endsAt?: string | null;
    updatedAt: string;
    stories: FeedStoryDto[];
}
interface FeedDto {
    app: {
        id: string;
        deepLinkSchemes: string[];
        /** Viewers may share stories (absent from older servers = off). */
        sharing?: boolean;
        /**
         * Mobile apps: the link shared stories open, with {group} and {story} placeholders
         * (websites share the page URL instead).
         */
        shareLink?: string;
        /** Shape and size set in the dashboard; absent = the SDK theme's size is used. */
        layout?: BarLayout;
    };
    /** The story bar this feed is for (absent from older servers). */
    bar?: {
        id: string;
        key: string;
    };
    groups: FeedGroupDto[];
}

/**
 * The story viewer as a pure state machine. Renderers (web, React Native)
 * dispatch actions from taps, swipes, keys and timers, and draw whatever state
 * comes back. Keeping it pure makes navigation easy to test.
 */
interface StoryLike {
    id: string;
    durationMs: number;
    /** Decides the viewability rule (images: 1 s on screen; videos: 2 s of playback). */
    type?: 'image' | 'video';
}
interface GroupLike {
    id: string;
    stories: readonly StoryLike[];
}
type CloseReason = 'user' | 'completed';

/** A story event: about a group (and story) in the bar or viewer. */
interface StoryEvent {
    type: EventType;
    groupId: string;
    storyId?: string;
    /** Key of the story bar it happened in; omitted = the main bar. */
    bar?: string;
}
/** Activity outside the bar: a page view (web) or app session start (apps). */
interface ActivityEvent {
    type: ActivityType;
}
type TrackedEvent = StoryEvent | ActivityEvent;

type StoriesErrorCode = 'invalid_token' | 'origin_not_allowed' | 'unknown_bar' | 'network' | 'server' | 'invalid_response';
declare class StoriesError extends Error {
    readonly code: StoriesErrorCode;
    readonly status?: number | undefined;
    constructor(code: StoriesErrorCode, message: string, status?: number | undefined);
}

/** localStorage, AsyncStorage, or anything with the same shape. */
interface KeyValueStorage {
    getItem(key: string): string | null | Promise<string | null>;
    setItem(key: string, value: string): void | Promise<void>;
}
/**
 * Remembers which stories this device has seen, so circles can show a
 * "viewed" ring. A group counts as seen when all of its current stories are,
 * so adding a story makes the ring active again.
 */
declare class SeenStore {
    private readonly storage;
    private ids;
    private readonly key;
    constructor(storage: KeyValueStorage | null, appId: string);
    load(): Promise<void>;
    has(storyId: string): boolean;
    markSeen(storyId: string): void;
    isGroupSeen(group: GroupLike): boolean;
    /** First unseen story in a group, or 0 if it's fully seen (replays from the start). */
    firstUnseenIndex(group: GroupLike): number;
    private persist;
}

/** A person's answer to a sticker, and the results they were shown. */
interface StoredAnswer {
    value: number;
    results?: AnswerResultsDto;
}
/**
 * Answers kept on the device, so a story shows what the person answered when they come back.
 * Keyed by story and sticker; the oldest are dropped past ANSWERS_MAX_IDS.
 */
declare class AnswerStore {
    private readonly storage;
    private map;
    private readonly key;
    constructor(storage: KeyValueStorage | null, appId: string);
    load(): Promise<void>;
    get(storyId: string, interactionId: string): StoredAnswer | undefined;
    set(storyId: string, interactionId: string, answer: StoredAnswer): void;
}

/** A new app session starts after this long in the background (the Firebase rule). */
declare const SESSION_TIMEOUT_MS: number;

/** Visual options for the story bar, shared by all SDKs. */
interface StoriesTheme {
    /** Circle diameter in px (including the ring). */
    circleSize: number;
    /** Ring thickness in px. */
    ringWidth: number;
    /** Gap between ring and image in px. */
    ringGap: number;
    /** Colours of the unseen ring gradient (one colour = solid ring). */
    ringColors: string[];
    /** Ring colour for groups whose stories have all been seen. */
    seenRingColor: string;
    /** Group title. */
    titleColor: string;
    titleFont: string;
    titleSize: number;
    /** Hide titles under the circles. */
    hideTitles: boolean;
    /** Space around the bar (px). */
    barPadding: number;
    /** Space between circles (px). */
    gap: number;
}

declare const SDK_VERSION = "0.1.1";
/** The hosted Stories API. Pass `apiUrl` to use your own server. */
declare const DEFAULT_API_URL = "https://stories-dashboard.fly.dev";
interface StoriesClientOptions {
    /** Public token from the dashboard (Apps → your mobile app). */
    token: string;
    /** Stories API base URL. */
    apiUrl?: string;
    /** Send impression/view/click analytics. Default true. */
    analytics?: boolean;
    /**
     * Where "viewed" state and the last feed are kept. Defaults to AsyncStorage when
     * installed (otherwise memory, for this session only); null disables it.
     */
    storage?: KeyValueStorage | null;
    /**
     * Count unique viewers (reach) with an anonymous ID kept in `storage`. Default true.
     * Pass false until the person consents if your privacy rules require it, then call
     * `setReach(true)` on the client. Needs persistent storage (AsyncStorage).
     */
    reach?: boolean;
}
/** Loads the feed and holds per-app state (seen stories, analytics) shared by every bar. */
declare class StoriesClient {
    readonly options: StoriesClientOptions;
    readonly apiUrl: string;
    /** One feed per story bar key ('' = the main bar) and tags, shared by the bars showing it. */
    private feedPromises;
    private seenStores;
    private answerStores;
    private queue;
    private appState;
    constructor(options: StoriesClientOptions);
    private leftAt;
    private viewerId;
    private reachOn;
    /**
     * Turns unique-viewer counting on or off, e.g. when someone gives or withdraws
     * consent. Turning it off also deletes the stored anonymous ID.
     */
    setReach(enabled: boolean): void;
    private get storage();
    private feedKey;
    /**
     * Fetches a story bar's feed once and shares it between the bars showing it; pass force
     * to refetch. `bar` is the key set in the dashboard; omitted = the main bar. `tags` are the
     * person's tags (normalised), for groups with a tag filter.
     */
    loadFeed(force?: boolean, bar?: string, tags?: readonly string[]): Promise<FeedDto>;
    /** A story bar's feed from the previous launch, if any. */
    cachedFeed(bar?: string, tags?: readonly string[]): Promise<FeedDto | null>;
    seenStore(appId: string): Promise<SeenStore>;
    /** Answers to polls, quizzes and sliders kept on this device. */
    answerStore(appId: string): Promise<AnswerStore>;
    /** Sends an answer to a poll, quiz or slider; resolves to the results, or null if it failed. */
    sendAnswer(body: {
        storyId: string;
        interactionId: string;
        value: number;
    }): Promise<AnswerResultsDto | null>;
    track(event: TrackedEvent): void;
    destroy(): void;
}
/**
 * Starts the SDK when the app launches, so app sessions and active users are counted
 * even before (or without) a story bar on screen. Bars with the same options share it.
 *
 *   startStories({ token: 'pk_live_…', apiUrl: 'https://stories.example.com' });
 */
declare function startStories(options: StoriesClientOptions): StoriesClient;
/** One client per token/API URL/analytics setting, shared by every bar in the app. */
declare function getClient(options: StoriesClientOptions): StoriesClient;

interface StoryOpenEvent {
    groupId: string;
    storyId: string;
    groupIndex: number;
    storyIndex: number;
    groupTitle: string;
}
interface CtaClickEvent {
    url: string;
    label: string;
    groupId: string;
    storyId: string;
    /** Closes the viewer (useful when you handle the link with your own navigation). */
    close(): void;
}
interface ShareEvent {
    /** The story's link: your share link from the dashboard, with {group} and {story} filled in. */
    url: string;
    /** Story name (or the group title). */
    title: string;
    groupId: string;
    storyId: string;
}
interface ViewerCallbacks {
    /**
     * Called when someone taps Share (shown when sharing is on in the dashboard). Return false
     * to share yourself; otherwise the SDK opens the phone's share sheet with the link.
     */
    onShare?(event: ShareEvent): boolean | undefined;
    onStoryOpen?(event: StoryOpenEvent): void;
    /** Return false to stop the SDK from opening the link. */
    onCtaClick?(event: CtaClickEvent): boolean | undefined;
    onClose?(reason: CloseReason): void;
}
/**
 * How the viewer opens and closes. `slide`: up from the bottom; shrinks away towards the bottom
 * of the screen. `zoom`: springs out of the tapped circle and goes back into the circle of the
 * group being watched (slides when that circle isn't on screen). With reduced motion, a fade.
 */
type StoriesTransition = 'slide' | 'zoom';

interface StoriesBarProps extends StoriesClientOptions, ViewerCallbacks {
    /**
     * Which story bar to show: its key from the dashboard (Settings → Apps & websites),
     * e.g. "home-feed". Omitted = the main bar.
     */
    bar?: string;
    /**
     * Tags for the person using the app (e.g. ['london', 'premium']), from your own user data.
     * Story groups with a tag filter (Edit group → Audience) only show to people whose tags pass it.
     * Use labels for groups of people, never personal data such as emails or IDs. Changing
     * them (e.g. after sign-in) reloads the feed.
     */
    tags?: readonly string[];
    theme?: Partial<StoriesTheme>;
    /**
     * Colour of the screen behind the bar, used for the gap between ring and
     * image. Defaults to white, or black in dark mode.
     */
    backgroundColor?: string;
    /**
     * How the viewer opens and closes: `'slide'` (default: up from the bottom, shrinking away
     * towards the bottom) or `'zoom'` (out of the tapped circle and back into it).
     */
    transition?: StoriesTransition;
    /** Called with the published groups once loaded (may be empty). */
    onLoad?(groups: FeedGroupDto[]): void;
    /** Called if the feed can't be loaded. The bar then renders nothing. */
    onFail?(error: StoriesError): void;
    style?: StyleProp<ViewStyle>;
}
interface StoriesBarHandle {
    /** Published groups currently shown. */
    readonly groups: readonly FeedGroupDto[];
    /** Opens the viewer at a group (by id or index). Returns false if not found. */
    open(group: string | number): boolean;
    close(): void;
    /** Refetches the feed and re-renders. */
    refresh(): Promise<void>;
}
/**
 * Instagram-style story bar. Tapping a circle opens the full-screen viewer.
 *
 *   <StoriesBar token="pk_live_…" apiUrl="https://stories.example.com" />
 *   <StoriesBar token="pk_live_…" bar="product-page" />   (a second story bar)
 */
declare const StoriesBar: react.ForwardRefExoticComponent<StoriesBarProps & react.RefAttributes<StoriesBarHandle>>;

export { type CloseReason, type CtaClickEvent, DEFAULT_API_URL, type FeedGroupDto, type KeyValueStorage, SDK_VERSION, SESSION_TIMEOUT_MS, type ShareEvent, StoriesBar, type StoriesBarHandle, type StoriesBarProps, StoriesClient, type StoriesClientOptions, StoriesError, type StoriesTheme, type StoriesTransition, type StoryOpenEvent, type ViewerCallbacks, getClient, startStories };
