import { AnalyticsSink, AnalyticsEvent } from '../analytics/index.js';

/**
 * @refraction-ui/analytics-sink-ga4 — option contracts.
 *
 * GA4 is "just a sink" in the epic's model (no privileged engine). This
 * adapter implements the `AnalyticsSink` SPI from `@refraction-ui/analytics`
 * in two interchangeable modes:
 *
 *   - `client-sdk` — lazy-loads gtag.js in the browser (no hard vendor dep;
 *     the script is injected on first delivery only). Bridges Consent Mode.
 *   - `http`       — GA4 Measurement Protocol (`/mp/collect`). No browser
 *     library is ever loaded — server-relay friendly.
 *
 * Default = `http` (protocol adapter, no vendor lib in the browser), matching
 * the epic's "default = protocol adapters" stance.
 */
/** Minimal gtag.js function signature (we never import the real types). */
type GtagFn = (...args: unknown[]) => void;
/** GA4 Consent Mode signal values. */
type ConsentState = 'granted' | 'denied';
/**
 * GA4 Consent Mode bridge — maps our consent categories to the gtag
 * `consent` command. Only used in `client-sdk` mode.
 */
interface GA4ConsentBridge {
    /**
     * Default consent state pushed via `gtag('consent', 'default', …)` before
     * the GA4 tag loads. Keys are GA4 consent types
     * (`analytics_storage`, `ad_storage`, `ad_user_data`,
     * `ad_personalization`, `functionality_storage`, …).
     */
    default?: Record<string, ConsentState>;
    /**
     * Maps a refraction consent category → the GA4 consent types it controls.
     * When the router reports the sink may deliver (category granted) the
     * adapter pushes `gtag('consent', 'update', { <types>: 'granted' })`.
     * Example: `{ analytics: ['analytics_storage'] }`.
     */
    map?: Record<string, string[]>;
}
interface GA4CommonOptions {
    /** GA4 Measurement ID, e.g. `G-XXXXXXXXXX`. */
    measurementId: string;
    /**
     * Consent categories this sink requires. The router will not deliver until
     * all are granted. Default: `['analytics']`.
     */
    consentCategories?: string[];
    /** Stable sink name. Default `'ga4'`. */
    name?: string;
}
/** `client-sdk` mode — runs gtag.js in the browser. */
interface GA4ClientSdkOptions extends GA4CommonOptions {
    mode: 'client-sdk';
    /**
     * Inject the GA4 Consent Mode bridge. The default state (if any) is pushed
     * before the tag loads; category grants drive `consent: update`.
     */
    consentMode?: GA4ConsentBridge;
    /**
     * Inject an existing gtag function (tests / apps that manage the tag
     * themselves). When provided, the script loader is NOT used and **no**
     * vendor script is injected.
     */
    gtag?: GtagFn;
    /**
     * Inject the script loader (tests / SSR-safe apps). Receives the gtag.js
     * src URL; must resolve once the script has executed. Defaults to a DOM
     * `<script>` injector (browser only).
     */
    scriptLoader?: (src: string) => Promise<void>;
    /** Override the gtag.js base URL (testing). */
    gtagSrcBase?: string;
}
/** `http` mode — GA4 Measurement Protocol. No browser library. */
interface GA4HttpOptions extends GA4CommonOptions {
    mode?: 'http';
    /** GA4 Measurement Protocol API secret (server-side credential). */
    apiSecret: string;
    /** Override the `/mp/collect` base URL (testing / EU endpoint). */
    endpoint?: string;
    /** Use the Measurement Protocol `/debug/mp/collect` validation endpoint. */
    debug?: boolean;
    /** Injected fetch (defaults to global fetch). Never loads a vendor lib. */
    fetchImpl?: typeof fetch;
}
/** Discriminated union of the two modes. */
type GA4SinkOptions = GA4ClientSdkOptions | GA4HttpOptions;

/**
 * `createGA4Sink` — the single entry point. Dispatches on `mode`:
 *   - `mode: 'client-sdk'` → lazy gtag.js adapter
 *   - `mode: 'http'` (default) → Measurement Protocol adapter, no vendor lib
 *
 * Default is `http` to honour the epic's "default = protocol adapters (no
 * vendor libs in the browser)" stance. GA4 is just a sink — register it via
 * `createAnalytics({ sinks: [createGA4Sink(...)] })` or `analytics.addSink`.
 *
 * Both factories are independent modules. The `http` factory has ZERO
 * references to gtag.js / DOM-script code, and the `client-sdk` factory only
 * touches the vendor script lazily (first delivery). Selecting one mode never
 * executes the other's load path; consumers that only ever construct an
 * `http` sink never run any vendor code.
 */

declare function createGA4Sink(options: GA4SinkOptions): AnalyticsSink;

/**
 * GA4 `http` adapter — Measurement Protocol (`/mp/collect`).
 *
 * Server-relay friendly: this path NEVER loads gtag.js or any browser
 * library. It only POSTs JSON to the Measurement Protocol endpoint:
 *
 *   POST {endpoint}/mp/collect?measurement_id={id}&api_secret={secret}
 *   Content-Type: application/json
 *   { client_id, user_id?, user_properties?, events: [{ name, params }] }
 *
 * GA4 Measurement Protocol constraints honoured here:
 *   - up to 25 events per request → batches are chunked.
 *   - `identify` envelopes carry no event; they still POST so user_id /
 *     user_properties propagate (events array may be empty for a user-props
 *     only ping, which GA4 accepts).
 *   - 2xx (incl. 204) = accepted. The MP endpoint always returns 2xx for
 *     well-formed requests; the `debug` endpoint returns validation messages.
 */

/**
 * Create the GA4 Measurement-Protocol (`http`) sink.
 *
 * No vendor library is loaded on this path under any circumstance.
 */
declare function createGA4HttpSink(options: GA4HttpOptions): AnalyticsSink;

/**
 * GA4 `client-sdk` adapter — lazy gtag.js.
 *
 * The vendor library is NEVER a hard dependency and is NEVER bundled. On the
 * first delivery the adapter:
 *   1. installs the gtag stub + dataLayer,
 *   2. pushes the Consent Mode default (if a bridge is configured),
 *   3. lazily injects `https://www.googletagmanager.com/gtag/js?id=<id>`
 *      via a `<script>` tag (or an injected loader for tests/SSR),
 *   4. runs `gtag('js', Date)` + `gtag('config', <id>, { send_page_view:false })`.
 *
 * Subsequent deliveries map each canonical envelope through the shared mapper
 * and call `gtag('set', 'user_properties', …)` / `gtag('set', { user_id })` /
 * `gtag('event', name, params)`.
 *
 * Consent Mode bridge: `init` pushes `consent: default`; `deliver` is only
 * reached when the router's consent gate already allows this sink, at which
 * point `consent: update` is pushed for the mapped GA4 consent types.
 */

/**
 * Create the GA4 gtag.js (`client-sdk`) sink. The vendor script is dynamically
 * loaded on first delivery only — there is no static import of any GA4/gtag
 * library, so `http`-mode consumers never pull this code path.
 */
declare function createGA4ClientSdkSink(options: GA4ClientSdkOptions): AnalyticsSink;

/**
 * Canonical envelope → GA4 mapping.
 *
 * One mapper, two consumers: the `client-sdk` adapter feeds the result into
 * `gtag('event', name, params)` / `gtag('set', ...)`, and the `http` adapter
 * serialises it into a Measurement Protocol `/mp/collect` payload. Keeping the
 * mapping in one place guarantees the two modes stay behaviourally identical.
 *
 * Identity / param mapping (issue #216, epic #213):
 *   anonymousId  → client_id           (GA4 device/browser id)
 *   userId       → user_id             (GA4 User-ID, cross-device)
 *   properties   → event params        (track/page/screen/group payload)
 *   identify     → user_properties     (traits become GA4 user properties)
 *   sessionId    → session_id param    (so GA4 sessionisation can align)
 *
 * GA4 event-name normalisation: GA4 recommends snake_case event names and
 * forbids spaces. `page` → `page_view`, `screen` → `screen_view` (GA4
 * Enhanced-Measurement parity); everything else is lower_snake_cased.
 */

/** A GA4 event ready for gtag.js or the Measurement Protocol. */
interface GA4Event {
    /** GA4 event name (snake_case, no spaces). */
    name: string;
    /** Event params (GA4 caps these; we pass them through verbatim). */
    params: Record<string, unknown>;
}
/** The fully-mapped result for a single canonical envelope. */
interface GA4Mapped {
    /** GA4 client_id (from anonymousId). Always present. */
    clientId: string;
    /** GA4 user_id (from userId). Present only after identify. */
    userId?: string;
    /**
     * GA4 user_properties (from identify/group traits). Each value is wrapped
     * in `{ value }` as the Measurement Protocol requires; the gtag adapter
     * unwraps when it calls `gtag('set', 'user_properties', ...)`.
     */
    userProperties?: Record<string, {
        value: unknown;
    }>;
    /**
     * The GA4 event to send. `identify` calls carry no event (they only set
     * user_id / user_properties), so this is optional.
     */
    event?: GA4Event;
}
/** Lower_snake_case an arbitrary event/trait name for GA4. */
declare function toGa4Name(name: string): string;
/** Map a Segment call type + name to its GA4 event name. */
declare function ga4EventName(ev: AnalyticsEvent): string | undefined;
/**
 * Map one canonical envelope to its GA4 representation. Pure — no transport,
 * no vendor lib; both the gtag and Measurement-Protocol adapters consume this.
 */
declare function mapEvent(ev: AnalyticsEvent): GA4Mapped;

export { type ConsentState, type GA4ClientSdkOptions, type GA4ConsentBridge, type GA4Event, type GA4HttpOptions, type GA4Mapped, type GA4SinkOptions, type GtagFn, createGA4ClientSdkSink, createGA4HttpSink, createGA4Sink, ga4EventName, mapEvent, toGa4Name };
