import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { NeetruError } from './errors.js';

/** Tipo do tenant alvo da subscription (PF=uid, PJ=orgId). */
type CheckoutTenantType = 'pf' | 'pj';
/** Status do `checkout_intents/{intentId}` no Core. */
type CheckoutIntentStatus = 'pending' | 'kyc_in_progress' | 'stripe_redirected' | 'completed' | 'expired' | 'cancelled';
interface CheckoutStartInput {
    /** Slug do produto no catálogo (ex: `neetru-pulse`). */
    productId: string;
    /** Slug do plano no catálogo do produto (ex: `pro_monthly`). */
    planId: string;
    /** Pra onde o produto SaaS quer voltar pós-checkout. https obrigatório (ou http://localhost em dev). */
    callbackUrl: string;
    /** Override pra checkout em nome de uma org. Default: PF do uid logado. */
    tenantType?: CheckoutTenantType;
    /** Quando `tenantType='pj'`, id da org. Ignorado quando PF. */
    tenantId?: string;
    /**
     * Em browsers, default `true` — SDK faz `window.location.href = redirectUrl`.
     * Passe `false` se quiser controlar o redirect manualmente.
     */
    autoRedirect?: boolean;
    /**
     * **C2 (auditoria 2026-06-10):** access_token OIDC do **usuário final** que
     * está assinando. OBRIGATÓRIO no modo HTTP real — o endpoint de checkout do
     * Core (`/api/v1/checkout/intents`) autentica a identidade do cliente (Bearer
     * access_token OIDC ou cookie de sessão), NÃO a chave de API do SDK (`nrt_*`).
     * Enviar a chave `nrt_*` resultava em 401 sempre. O SDK é BFF-pattern: o app
     * consumidor gerencia o ciclo OIDC e passa o access_token aqui.
     */
    accessToken?: string;
}
/** Auth por chamada do checkout — access_token OIDC do usuário final. */
interface CheckoutAuthOpts {
    /** access_token OIDC do usuário final (vide CheckoutStartInput.accessToken). */
    accessToken?: string;
}
interface CheckoutStartResult {
    intentId: string;
    redirectUrl: string;
    status: CheckoutIntentStatus;
    expiresAt: string;
    /** True se KYC ainda precisa ser coletado no portal. UI pode mostrar hint. */
    requiresKyc: boolean;
}
interface CheckoutIntentInfo {
    intentId: string;
    uid: string;
    targetTenantId: string;
    targetTenantType: CheckoutTenantType;
    productId: string;
    planId: string;
    callbackUrl: string;
    status: CheckoutIntentStatus;
    /** Stripe Checkout Session id quando avançou pra `stripe_redirected`. */
    stripeSessionId?: string | null;
    expiresAt: string;
    /** True se já passou `expiresAt` mas Core ainda não marcou expired. */
    isStale?: boolean;
}
interface CheckoutNamespace {
    /**
     * Inicia checkout. Em browser, redireciona automaticamente pro portal.
     * Em Node/SSR, retorna o resultado sem efeitos colaterais — caller decide
     * o que fazer com `redirectUrl`.
     *
     * Dev mode (`NEETRU_ENV=dev`): retorna URL fake `https://localhost:9003/portal/checkout/mock-XXXX`.
     */
    start(input: CheckoutStartInput): Promise<CheckoutStartResult>;
    /** Lê estado atual do intent (Core). Requer `opts.accessToken` no HTTP real. */
    get(intentId: string, opts?: CheckoutAuthOpts): Promise<CheckoutIntentInfo>;
    /** Cancela um intent que ainda não virou `stripe_redirected`. Requer `opts.accessToken` no HTTP real. */
    cancel(intentId: string, opts?: CheckoutAuthOpts): Promise<{
        ok: true;
        alreadyCancelled: boolean;
    }>;
}
/**
 * Mock dev — retorna URL fake `https://localhost:9003/portal/checkout/mock-XXXX`
 * sem tocar no Core. Útil pra dev externo SaaS testar UI/redirect sem precisar
 * de Bearer token Neetru.
 *
 * Dev mode persiste intents em mapa in-memory pro `get`/`cancel` retornarem
 * resultado consistente.
 */
declare class MockCheckout implements CheckoutNamespace {
    private readonly intents;
    start(input: CheckoutStartInput): Promise<CheckoutStartResult>;
    get(intentId: string, _opts?: CheckoutAuthOpts): Promise<CheckoutIntentInfo>;
    cancel(intentId: string, _opts?: CheckoutAuthOpts): Promise<{
        ok: true;
        alreadyCancelled: boolean;
    }>;
}
declare function createCheckoutNamespace(config: ResolvedConfig): CheckoutNamespace;

type ProductNotificationSeverity = 'info' | 'success' | 'warning' | 'error';
interface ProductNotification {
    id: string;
    /** UID do usuário final (do produto, não staff Neetru). */
    userId: string;
    /** Identificador semântico do evento (ex: `order.received`). */
    kind: string;
    severity: ProductNotificationSeverity;
    title: string;
    body?: string;
    /** Link opcional pra ação. */
    link?: string;
    /** Metadata livre (serializável). */
    metadata?: Record<string, string | number | boolean | null>;
    createdAt: string;
    /** ISO quando user marcou lida. */
    readAt?: string;
    /** ISO quando user descartou. */
    dismissedAt?: string;
}
interface SendNotificationInput {
    /**
     * **3.0 (breaking):** productId é obrigatório (Core valida via Zod). Default
     * resolve do `config.productId`; lança `validation_failed` se ausente.
     */
    productId?: string;
    userId: string;
    kind: string;
    title: string;
    severity?: ProductNotificationSeverity;
    body?: string;
    link?: string;
    metadata?: Record<string, string | number | boolean | null>;
    /**
     * Dedup — se já existir notificação com mesmo fingerprint dentro de 24h,
     * NÃO cria duplicata. Útil pra evitar spam quando webhook reentrega.
     */
    fingerprint?: string;
}
interface ListNotificationsOptions {
    /**
     * **3.0 (breaking):** productId é obrigatório no Core. Default resolve do
     * `config.productId`.
     */
    productId?: string;
    /** Inclui notificações dismissed (default false). */
    includeDismissed?: boolean;
    /** Filtro por unread. */
    onlyUnread?: boolean;
    /** Máx itens (default 50, máx 200). */
    limit?: number;
}
/** 3.0: scope option pra markRead/dismiss (productId obrigatório no Core). */
interface NotificationScopeOptions {
    productId?: string;
}
interface NotificationsNamespace {
    /** Produto envia uma notificação pra um usuário do produto. */
    send(input: SendNotificationInput): Promise<ProductNotification>;
    /** Lista notificações de um usuário. */
    list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
    /** Marca como lida. */
    markRead(id: string, options?: NotificationScopeOptions): Promise<{
        ok: true;
    }>;
    /** Descarta. */
    dismiss(id: string, options?: NotificationScopeOptions): Promise<{
        ok: true;
    }>;
}
declare function createNotificationsNamespace(config: ResolvedConfig): NotificationsNamespace;
declare class MockNotifications implements NotificationsNamespace {
    private notifications;
    private nextId;
    send(input: SendNotificationInput): Promise<ProductNotification>;
    list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
    markRead(id: string, _options?: NotificationScopeOptions): Promise<{
        ok: true;
    }>;
    dismiss(id: string, _options?: NotificationScopeOptions): Promise<{
        ok: true;
    }>;
}

/** Eventos Neetru que produtos podem assinar. Lista expansível por minor bump. */
type WebhookEvent = 'subscription.activated' | 'subscription.cancelled' | 'subscription.payment_failed' | 'subscription.trial_ending' | 'usage.quota_exceeded' | 'account.suspended' | 'account.reactivated' | 'support.ticket_replied';
interface WebhookEndpoint {
    id: string;
    url: string;
    events: WebhookEvent[];
    /** Quando true, `X-Neetru-Signature` é enviado em cada dispatch. */
    hasSecret: boolean;
    status: 'active' | 'degraded' | 'disabled';
    /** ISO timestamp do último dispatch bem-sucedido. */
    lastDeliveryAt?: string;
    /** Quantos dispatches falharam consecutivos (resetado em sucesso). */
    consecutiveFailures?: number;
    createdAt: string;
}
interface RegisterWebhookInput {
    /**
     * **3.0 (breaking):** `productId` é obrigatório no body. Se ausente,
     * SDK tenta resolver do `config.productId` (default no `createNeetruClient`);
     * lança `validation_failed` se ambos faltarem.
     */
    productId?: string;
    url: string;
    events: WebhookEvent[];
    /**
     * Secret pra HMAC SHA-256 (mín 16 chars). Recomendado.
     * Quando ausente, dispatches vão sem assinatura — produto deve ter
     * IP allowlist ou outra defesa.
     */
    secret?: string;
}
interface WebhookTestResult {
    ok: boolean;
    statusCode?: number;
    durationMs?: number;
    error?: string;
}
/**
 * 3.0: opções de scoping — todos os verbos exigem `productId` (default do
 * `config.productId` se ausente).
 */
interface WebhookScopeOptions {
    productId?: string;
}
interface WebhooksNamespace {
    /** Registra um novo endpoint. */
    register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
    /** Lista endpoints registrados pelo produto. */
    list(options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
    /** Remove um endpoint. */
    unregister(id: string, options?: WebhookScopeOptions): Promise<{
        ok: true;
    }>;
    /** Dispara evento de teste pra validar conectividade. */
    test(id: string, options?: WebhookScopeOptions): Promise<WebhookTestResult>;
}
/**
 * Verifica assinatura HMAC SHA-256 de payload de webhook recebido pelo
 * produto consumer. **3.0**: Universal (browser/Node/Edge) via WebCrypto.subtle.
 *
 * Constant-time compare via byte-level XOR accumulator (não usa
 * `timingSafeEqual` do Node porque WebCrypto não tem equivalente direto).
 *
 * **3.1.3 (auditoria 2026-06-10)**: o Core assina HMAC sobre
 * `` `${timestamp}.${rawBody}` `` (vide `computeWebhookSignature` em
 * `functions/src/lib/webhook-delivery-core.ts`) — o `timestamp` é o valor do
 * header `X-Neetru-Timestamp`. Passe-o no 4º parâmetro; sem ele o HMAC é
 * computado só sobre o body e NUNCA confere com entregas reais do Core.
 * Prefira `verifyWebhookRequest`, que lê os headers e já faz tudo.
 *
 * @param payload Corpo cru da request (string ou Uint8Array). NÃO use o objeto
 *                parseado — JSON.stringify pode diferir do que foi assinado.
 * @param signature Header `X-Neetru-Signature` recebido (formato `sha256=<hex>`).
 * @param secret Secret registrado em `webhooks.register({ secret })`.
 * @param timestamp Valor do header `X-Neetru-Timestamp` (ms epoch). Quando
 *                presente, o HMAC cobre `` `${timestamp}.${payload}` ``
 *                (esquema canônico do Core — replay protection embutida).
 * @returns Promise<boolean> — true se assinatura confere, false caso contrário.
 *
 * @example
 * ```ts
 * // Next.js Route Handler (Edge ou Node runtime — funciona em ambos)
 * export async function POST(req: Request) {
 *   const raw = await req.text();
 *   const sig = req.headers.get('X-Neetru-Signature');
 *   const ts = req.headers.get('X-Neetru-Timestamp');
 *   const ok = await verifyWebhookSignature(raw, sig, process.env.NEETRU_WEBHOOK_SECRET!, ts ?? undefined);
 *   if (!ok) return new Response('unauthorized', { status: 401 });
 *   const event = JSON.parse(raw);
 *   // ... handle event
 *   return Response.json({ ok: true });
 * }
 * ```
 *
 * @throws NeetruError('runtime_unsupported') se runtime não expõe WebCrypto.
 *   Não retorna `false` silencioso — caller precisa distinguir "assinatura
 *   inválida" (return false) de "ambiente não suporta" (throw).
 */
declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string, timestamp?: string | number): Promise<boolean>;
/**
 * Opções de `verifyWebhookRequest`.
 */
interface VerifyWebhookRequestOptions {
    /**
     * Janela de tolerância em milliseconds para o skew de timestamp.
     * Default: 300_000 (5 minutos), conforme documentado na spec de webhooks.
     * Valores menores aumentam a proteção contra replay; valores maiores acomodam
     * clocks com drift. Não use valores acima de 600_000 (10 min).
     */
    toleranceMs?: number;
}
/**
 * Resultado de `verifyWebhookRequest`.
 */
type VerifyWebhookRequestResult = {
    ok: true;
} | {
    ok: false;
    reason: 'invalid_signature' | 'timestamp_missing' | 'timestamp_expired' | 'runtime_unsupported';
};
/**
 * Helper server-only que combina:
 *   1. Verificação de assinatura HMAC-SHA256 (`X-Neetru-Signature`)
 *   2. **Replay-protection** via skew de timestamp (`X-Neetru-Timestamp`)
 *
 * Rejeita o request se o timestamp estiver fora da janela `toleranceMs`
 * (default ±5 min). Isso fecha a janela de replay onde um atacante captura
 * um request válido e o re-envia mais tarde.
 *
 * **Use este helper em vez de `verifyWebhookSignature` diretamente** — ele
 * é a forma correta de validar um webhook Neetru em produção.
 *
 * @param payload Corpo cru da request (string ou Uint8Array).
 * @param headers Headers do request (pode ser um `Headers` Web API ou
 *   `Record<string, string | undefined>`). Lidos: `X-Neetru-Signature` e
 *   `X-Neetru-Timestamp`.
 * @param secret Secret registrado em `webhooks.register({ secret })`.
 * @param options Opções adicionais (ex: `toleranceMs`).
 * @returns `{ ok: true }` ou `{ ok: false, reason }` — nunca lança, a menos
 *   que o runtime não suporte WebCrypto (então propaga `NeetruError('runtime_unsupported')`).
 *
 * @example
 * ```ts
 * // Next.js Route Handler
 * export async function POST(req: Request) {
 *   const raw = await req.text();
 *   const result = await verifyWebhookRequest(raw, req.headers, process.env.NEETRU_WEBHOOK_SECRET!);
 *   if (!result.ok) {
 *     console.warn('Webhook rejeitado:', result.reason);
 *     return new Response('unauthorized', { status: 401 });
 *   }
 *   const event = JSON.parse(raw);
 *   // ... handle event
 *   return Response.json({ ok: true });
 * }
 * ```
 */
declare function verifyWebhookRequest(payload: string | Uint8Array, headers: Headers | Record<string, string | undefined>, secret: string, options?: VerifyWebhookRequestOptions): Promise<VerifyWebhookRequestResult>;
declare function createWebhooksNamespace(config: ResolvedConfig): WebhooksNamespace;
declare class MockWebhooks implements WebhooksNamespace {
    private endpoints;
    private nextId;
    register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
    list(_options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
    unregister(id: string, _options?: WebhookScopeOptions): Promise<{
        ok: true;
    }>;
    test(id: string, _options?: WebhookScopeOptions): Promise<WebhookTestResult>;
}

/**
 * Tipos internos da camada offline do @neetru/sdk.
 *
 * Estes tipos SÃO internos ao módulo `db/offline/` — não fazem parte da
 * superfície pública do SDK. A superfície pública é o `DbCollectionRef` e
 * o `DbSnapshot` de `src/types.ts`.
 *
 * Derivados de:
 *   - I3-sdk-offline.md §3 (store schemas)
 *   - I3-sdk-offline.md §4 (fila de escritas)
 *   - I3-sdk-offline.md §5 (query descriptor)
 *   - I3-sdk-offline.md §7 (conflito LWW)
 *   - 02-sdk.md §3.2 (DbSnapshot, DbWhereFilter, DbQuery)
 */
/** Identificador de um documento (string opaca). */
type DocId = string;
/** Identificador de uma mutação (UUID v4 client-generated). */
type MutationId = string;
/** Operações possíveis na fila de escritas. */
type MutationKind = 'add' | 'set' | 'update' | 'remove';
/**
 * Uma mutação enfileirada (escrita durável pendente).
 * Equivalente a `QueuedMutation` do I3 §3.3 (`mutations` store).
 */
interface Mutation {
    /** Número de sequência autoincrement — define a ordem total da fila. */
    seq: number;
    /** UUID v4 client-generated — chave de idempotência. */
    mutationId: MutationId;
    /** Nome da coleção. */
    collection: string;
    /** ID do documento alvo (client-gen para `add`, existente para os demais). */
    docId: DocId;
    /** Operação. */
    op: MutationKind;
    /** Dados da operação. `null` para `remove`. */
    payload: Record<string, unknown> | null;
    /**
     * `serverVersion` que o cliente viu ao escrever.
     * `null` para `add` (doc não existia) ou quando o cliente nunca sincronizou.
     * Usado pelo servidor para detectar conflito (I3 §7.4).
     */
    baseVersion: string | null;
    /** Epoch ms do relógio do CLIENTE — só para diagnóstico e ordenação local. */
    enqueuedAt: number;
    /** Número de tentativas de replay. */
    attempts: number;
    /** Último erro de replay (string). `null` se nenhuma tentativa ainda. */
    lastError: string | null;
    /** Estado de envio desta mutação. */
    status: 'queued' | 'inflight' | 'failed';
    /** Agrupa mutações de um `batch()` atômico. `null` se mutação individual. */
    batchId: string | null;
}
/**
 * Razão pela qual uma escrita foi descartada no LWW (I3 §3.3 `conflict_log`).
 */
type ConflictReason = 'lww_server_newer' | 'rejected_permission' | 'rejected_validation';
/**
 * Registro de uma escrita perdedora — gravado no `conflict_log` (I3 §3.3).
 * Entregue ao produto via `onWriteResult` (status: `'superseded'`).
 */
interface ConflictRecord {
    id?: number;
    collection: string;
    docId: DocId;
    mutationId: MutationId;
    losingData: Record<string, unknown>;
    winningData: Record<string, unknown>;
    reason: ConflictReason;
    detectedAt: number;
    delivered: boolean;
}
/**
 * Estado de sincronização da camada offline (I3 §10 / 02-sdk §3.4).
 * Exposto em `client.db.syncState`.
 *
 * - `idle`    — online e em repouso (sem sync ativo).
 * - `syncing` — sync em progresso (push + pull + realtime).
 * - `offline` — conexão perdida ou inexistente.
 * - `error`   — erro persistente após retries (reservado para uso futuro).
 */
type SyncStatus = 'idle' | 'offline' | 'syncing' | 'error';
interface SyncState {
    status: SyncStatus;
    pendingWrites: number;
    lastSyncedAt: number | null;
    isLeaderTab: boolean;
}
/** Função para cancelar uma subscrição (onSnapshot, onDoc, onSyncStateChanged). */
type Unsubscribe = () => void;

/**
 * SyncEngine — orquestrador da camada offline do @neetru/sdk.
 *
 * Implementa as 3 fases de sincronização definidas em I3 §6:
 *
 *   FASE 1 — DRENAR A FILA (push)
 *     Envia mutações pendentes ao Core via SyncTransport.
 *     Cada mutação é marcada inflight, enviada e depois:
 *       - CONFIRMADA    → removida da fila (markApplied)
 *       - SUPERADA (LWW)→ ConflictRecord gravado, cache atualizado
 *       - REJEITADA     → resolveRejected + ConflictRecord + removida
 *       - FALHA TRANSIENTE → markRetry + PARA a drenagem (ordem preservada)
 *
 *   FASE 2 — RECONCILIAR O CACHE (pull)
 *     Busca mudanças do servidor desde o watermark ou resume token.
 *     Cada documento passa pelo ConflictResolver (LWW) e é gravado no LocalStore.
 *     Changes são emitidos pelo ChangeBus.
 *     Se `resyncRequired` → aciona full resync em vez de pull incremental.
 *
 *   FASE 3 — REABRIR LISTENERS (realtime)
 *     Atualiza SyncState para refletir que o cache está reconciliado.
 *     O ChangeBus já foi alimentado nas fases anteriores — os listeners
 *     ativos na camada superior (onSnapshot/onDoc) recebem snapshots frescos.
 *
 * Garantias:
 *   - SOMENTE a aba LÍDER executa o sync (TabCoordinator.isLeader()).
 *   - Re-entrante-safe: um sync em progresso bloqueia novos disparos.
 *   - Fail-closed: falha transiente retém a mutação; falha de pull não
 *     corrompe o cache nem o watermark.
 *   - Idempotente: o SyncTransport usa mutationId como chave de idempotência.
 *
 * O SyncTransport é INJETADO — a ligação real com o Core acontece na camada
 * de adaptação, não aqui. Isso torna o SyncEngine 100% testável com fake.
 */

/**
 * Documento retornado pelo servidor em push/pull/resync.
 */
interface ServerDoc {
    collection: string;
    id: string;
    data: Record<string, unknown>;
    serverVersion: string;
    serverTimestamp: number;
    /** `true` se o doc foi deletado no servidor. */
    deleted: boolean;
}
/**
 * Resultado individual de uma mutação enviada ao Core (FASE 1).
 */
type MutationResult = {
    mutationId: string;
    outcome: 'confirmed';
    serverVersion: string;
    serverTimestamp: number;
} | {
    mutationId: string;
    outcome: 'superseded';
    serverVersion: string;
    serverTimestamp: number;
    serverData: Record<string, unknown>;
} | {
    mutationId: string;
    outcome: 'rejected';
    reason: 'rejected_permission' | 'rejected_validation';
    serverVersion: string | null;
    serverData: Record<string, unknown> | null;
};
/** Retorno de pushMutations. */
interface PushMutationsResult {
    results: MutationResult[];
}
/** Retorno de pullChanges. */
interface PullChangesResult {
    docs: ServerDoc[];
    newWatermark: number | null;
    /** `true` quando o resume token / watermark ficou inválido (gap grande). */
    resyncRequired: boolean;
}
/** Retorno de fullResync. */
interface FullResyncResult {
    docs: ServerDoc[];
    newWatermark: number | null;
}
/**
 * Contrato do transporte de sync — injetado no SyncEngine.
 *
 * A implementação real usa os endpoints `/api/sdk/v1/db/*` do Core.
 * Em testes, usa-se um FakeSyncTransport.
 *
 * Per I3 §6:
 *   - `pushMutations`  → FASE 1: envia mutações em ordem de seq.
 *   - `pullChanges`    → FASE 2: busca delta desde o watermark/resumeToken.
 *   - `fullResync`     → FASE 2 (fallback): lista completa das coleções ativas.
 */
interface SyncTransport {
    /**
     * Envia um lote de mutações ao Core.
     * O Core aplica, verifica conflito LWW e retorna um resultado por mutação.
     * A ordem do array deve ser preservada (seq crescente).
     */
    pushMutations(mutations: Mutation[]): Promise<PushMutationsResult>;
    /**
     * Busca documentos modificados no servidor desde `sinceWatermark`.
     * `sinceWatermark === null` indica primeiro sync (sem base conhecida).
     * `resumeToken` é o token de change stream (nosql-vm); pode ser null.
     *
     * Retorna `resyncRequired: true` se o gap é grande demais para pull incremental.
     */
    pullChanges(sinceWatermark: number | null, resumeToken: string | null): Promise<PullChangesResult>;
    /**
     * Faz um full resync das coleções informadas.
     * Retorna TODOS os documentos atuais (paginado internamente pelo transporte).
     * Docs ausentes na resposta foram deletados durante o gap.
     */
    fullResync(collections: string[]): Promise<FullResyncResult>;
    /**
     * (Opcional) Registra uma subscription realtime para uma coleção específica.
     *
     * Implementado pelo WebSocket transport (nosql-vm): chama
     * `NeetruRealtimeClient.subscribe()` e alimenta os frames delta/resync
     * no `ChangeBus` da camada offline.
     *
     * Transportes REST e Firestore não implementam este método (undefined).
     * O chamador (`DbCollectionRefImpl.onSnapshot`) verifica antes de invocar.
     *
     * @param collection - Nome da coleção a subscrever.
     * @param onChange   - Callback que recebe changes quando um delta/resync chega.
     *                     `null` payload = resync full necessário.
     * @returns Função de unsubscribe.
     */
    subscribeCollection?: (collection: string, onChange: (changes: Array<{
        type: 'added' | 'modified' | 'removed';
        docId: string;
        data: Record<string, unknown> | null;
    }>, needsResync: boolean) => void) => () => void;
}

/**
 * field-value.ts — Sentinels de escrita server-side para o Mundo Documentos.
 *
 * Resolve duas lacunas confirmadas (bug_05aedb8b) da superfície `client.db`:
 *
 *  1. **Timestamp resolvido no servidor** — antes deste módulo, o produto que
 *     quisesse `createdAt`/`updatedAt` confiáveis tinha que usar `Date.now()`
 *     no CLIENTE, que é spoofável e sofre clock-skew. Agora:
 *
 *         await orders.add({ total: 99, createdAt: serverTimestamp() });
 *
 *     O marcador atravessa o transporte REST como um objeto JSON namespaceado
 *     e o Core o substitui por `FieldValue.serverTimestamp()` no momento da
 *     escrita — relógio do SERVIDOR, não do cliente.
 *
 *  2. **Increment atômico** — antes, `update(id, { n: x+1 })` era read-modify-write
 *     no cliente, sujeito a lost-update sob concorrência. Agora:
 *
 *         await counters.update('global', { hits: increment(1) });
 *
 *     O Core aplica `FieldValue.increment(1)` atomicamente no documento.
 *
 * ## Contrato de fio (wire format)
 *
 * Os sentinels são objetos JSON simples — sobrevivem `JSON.stringify` no
 * transporte REST e ao `IndexedDB structured clone` da camada offline:
 *
 *   serverTimestamp()  → `{ "__neetru__": "serverTimestamp" }`
 *   increment(n)       → `{ "__neetru__": "increment", "operand": n }`
 *
 * A chave-âncora `__neetru__` é deliberadamente improvável de colidir com
 * dados de produto. O Core valida e resolve esses marcadores; qualquer objeto
 * com `__neetru__` desconhecido é rejeitado server-side (fail-closed).
 *
 * ## Aplicação otimista (offline-first)
 *
 * A camada offline aplica uma APROXIMAÇÃO local imediata (para a UI não piscar):
 *   - `serverTimestamp()` → `Date.now()` local (substituído pelo valor real no sync).
 *   - `increment(n)`      → soma `n` ao valor numérico atual do campo (0 se ausente).
 * O valor canônico vem sempre do servidor no pull/confirm subsequente.
 *
 * @module
 */
/** Chave-âncora dos marcadores de sentinel no wire format. */
declare const NEETRU_SENTINEL_KEY: "__neetru__";
/** Marcador de timestamp resolvido pelo servidor. */
interface ServerTimestampSentinel {
    readonly __neetru__: 'serverTimestamp';
}
/** Marcador de incremento atômico aplicado pelo servidor. */
interface IncrementSentinel {
    readonly __neetru__: 'increment';
    /** Delta a somar (pode ser negativo para decremento). */
    readonly operand: number;
}
/**
 * União de todos os sentinels de campo. Use `serverTimestamp()` / `increment()`
 * para construir — nunca monte o objeto à mão.
 */
type FieldSentinel = ServerTimestampSentinel | IncrementSentinel;
/**
 * Sentinel que instrui o servidor a gravar o timestamp do MOMENTO da escrita
 * usando o relógio do servidor (não do cliente).
 *
 * @example
 * ```ts
 * import { serverTimestamp } from '@neetru/sdk/db';
 * await orders.add({ total: 99, createdAt: serverTimestamp() });
 * ```
 */
declare function serverTimestamp(): ServerTimestampSentinel;
/**
 * Sentinel que instrui o servidor a incrementar atomicamente o campo numérico
 * por `n` (negativo decrementa). Se o campo não existir/não for numérico, o
 * servidor o trata como `0` antes de somar (semântica `FieldValue.increment`).
 *
 * @param n — delta inteiro ou fracionário a somar.
 * @example
 * ```ts
 * import { increment } from '@neetru/sdk/db';
 * await counters.update('global', { hits: increment(1) });
 * await wallet.update(uid, { balance: increment(-50) });
 * ```
 */
declare function increment(n: number): IncrementSentinel;
/** `true` se `v` é qualquer sentinel de campo Neetru. */
declare function isFieldSentinel(v: unknown): v is FieldSentinel;
/** `true` se `v` é o sentinel `serverTimestamp()`. */
declare function isServerTimestampSentinel(v: unknown): v is ServerTimestampSentinel;
/** `true` se `v` é o sentinel `increment(n)` com `operand` numérico válido. */
declare function isIncrementSentinel(v: unknown): v is IncrementSentinel;

/**
 * NeetruDbError — classe de erro especializada para o módulo `db`.
 *
 * Conforma com 02-sdk.md §3.5: `NeetruDbError extends NeetruError`,
 * campo `code` fechado em `NeetruDbErrorCode`, campo `retryable` booleano,
 * campo `dbId` opaco para correlação.
 *
 * Herda de `NeetruError` para que um `catch (e) { if (e instanceof NeetruError) }`
 * genérico continue pegando erros de DB. Quem quer granularidade usa
 * `instanceof NeetruDbError` e o `code` fechado.
 */

/**
 * Códigos de erro específicos do módulo `db`.
 *
 * - `db_unavailable`        — lifecycle: provisionando / falhou / arquivado.
 *                             NÃO significa "sem rede" — offline é transparente.
 * - `db_not_found`          — documento/coleção não encontrado no servidor.
 * - `db_conflict`           — conflito de escrita detectado.
 * - `db_quota_exceeded`     — quota de escrita/leitura excedida no banco.
 * - `db_permission_denied`  — sem permissão para a operação.
 * - `db_invalid_query`      — query inválida (campo, operador, tipo).
 * - `db_timeout`            — operação excedeu o timeout.
 * - `offline_quota_exceeded`— IndexedDB cheio; a escrita não pode ser enfileirada.
 * - `offline_unavailable`   — IndexedDB indisponível; SDK degrado para cache RAM.
 */
type NeetruDbErrorCode = 'db_unavailable' | 'db_not_found' | 'db_conflict' | 'db_quota_exceeded' | 'db_permission_denied' | 'db_invalid_query' | 'db_timeout' | 'offline_quota_exceeded' | 'offline_unavailable';
/**
 * Erro tipado do módulo `db` — estende `NeetruError` para compatibilidade.
 *
 * @example
 * ```ts
 * try {
 *   await client.db.collection('orders').add({ ... });
 * } catch (e) {
 *   if (e instanceof NeetruDbError && e.code === 'offline_quota_exceeded') {
 *     // IndexedDB cheio — notificar usuário
 *   }
 * }
 * ```
 */
declare class NeetruDbError extends NeetruError {
    /** Código de erro fechado — específico de DB. */
    readonly code: NeetruDbErrorCode;
    /**
     * `true` para erros transientes que o produto pode tentar novamente.
     * São retryable: `db_unavailable`, `db_conflict`, `db_timeout`.
     */
    readonly retryable: boolean;
    /**
     * ID opaco do banco lógico — só para correlação com logs do Core.
     * Nunca deve ser exibido ao usuário final.
     */
    readonly dbId?: string;
    constructor(code: NeetruDbErrorCode, message: string, dbId?: string);
}

/**
 * DbCollectionRef — superfície de Documentos offline-first do @neetru/sdk (M2).
 *
 * Implementa a API pública conforme especificada em:
 *   - 02-sdk.md §3.2 (contrato TypeScript DbCollectionRef)
 *   - I2-mundo-documentos.md §3.2 (superfície dupla CRUD + realtime)
 *   - I3-sdk-offline.md §10 (camada offline, lifecycle, SyncState)
 *
 * ### Semântica de Promise de escrita (02-sdk.md §3.2 nota negrito)
 * `add/set/update/remove` resolvem quando a escrita está **durável localmente**
 * (cache + fila), NÃO quando o servidor confirma. Isso é offline-first por design:
 * sem rede, `add()` resolve imediatamente. Para saber quando sincronizou, o produto
 * observa `onWriteResult` (future / SyncEngine). A Promise só rejeita por:
 * - validação síncrona (nome de coleção inválido, id vazio)
 * - `offline_quota_exceeded` (IndexedDB cheio)
 *
 * ### Injeção do transporte realtime
 * O `RealtimeTransport` é injetado via `createOfflineDocumentsNamespace({ transport })`.
 * A implementação atual usa o `SyncEngine` para push/pull (sync offline ↔ servidor).
 * Transportes futuros (Firestore Web SDK / WebSocket gateway) são injetados aqui —
 * sem alterar a superfície pública.
 */

/**
 * Permite que cada campo de `T` aceite, além do seu tipo declarado, um sentinel
 * de escrita server-side (`serverTimestamp()` / `increment()`).
 *
 * Sem isso, um produto que tipa `createdAt: number` veria erro de tipo ao passar
 * `serverTimestamp()`. Esta projeção mantém o autocomplete dos campos do produto
 * e ainda autoriza os sentinels onde fizer sentido.
 *
 * @example
 * ```ts
 * interface Order { total: number; createdAt: number; hits: number }
 * orders.add({ total: 10, createdAt: serverTimestamp(), hits: increment(1) });
 * ```
 */
type WithFieldSentinels<T> = {
    [K in keyof T]: T[K] | FieldSentinel;
};
interface DbWhereFilter$1 {
    field: string;
    op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'not-in';
    value: unknown;
}
interface DbQuery$1 {
    where?: DbWhereFilter$1[];
    orderBy?: {
        field: string;
        direction?: 'asc' | 'desc';
    };
    /** Máximo de documentos. Default 20, max 500. */
    limit?: number;
    /** Cursor opaco — devolvido em `DbListResult.nextCursor`. */
    cursor?: string;
}
interface DbDoc<T = Record<string, unknown>> {
    id: string;
    data: T;
}
interface DbListResult<T = Record<string, unknown>> {
    docs: DbDoc<T>[];
    nextCursor: string | null;
    fromCache: boolean;
    stale: boolean;
    hasPendingWrites: boolean;
    changes: Array<{
        type: 'added' | 'modified' | 'removed';
        doc: DbDoc<T>;
    }>;
}
interface DbGetResult<T = Record<string, unknown>> {
    docs: Array<DbDoc<T>>;
    fromCache: boolean;
    stale: boolean;
    hasPendingWrites: boolean;
    changes: Array<{
        type: 'added' | 'modified' | 'removed';
        doc: DbDoc<T>;
    }>;
}
type DbChangeType = 'added' | 'modified' | 'removed';
/**
 * Operação atômica para uso em `DbCollectionRef.batch()`.
 *
 * Discriminated union pelo campo `kind` — cada variante deixa claro
 * quais campos são obrigatórios:
 *
 * - `add`    — insere com id gerado pelo cliente; `data` obrigatório.
 * - `set`    — cria ou sobrescreve pelo `id`; `data` obrigatório.
 * - `update` — mescla campos em doc existente pelo `id`; `data` obrigatório.
 * - `remove` — tombstone pelo `id`; sem `data`.
 *
 * @example
 * ```ts
 * const ops: DbBatchOp[] = [
 *   { kind: 'add',    data: { name: 'produto A', price: 10 } },
 *   { kind: 'set',    id: 'sku-1', data: { name: 'produto B', price: 20 } },
 *   { kind: 'update', id: 'sku-2', data: { price: 25 } },
 *   { kind: 'remove', id: 'sku-3' },
 * ];
 * await col.batch(ops);
 * ```
 */
type DbBatchOp<T = Record<string, unknown>> = {
    kind: 'add';
    data: WithFieldSentinels<Omit<T, 'id'>>;
} | {
    kind: 'set';
    id: string;
    data: WithFieldSentinels<Omit<T, 'id'>>;
} | {
    kind: 'update';
    id: string;
    data: Partial<WithFieldSentinels<Omit<T, 'id'>>>;
} | {
    kind: 'remove';
    id: string;
};
/**
 * Contrato do transporte de realtime — injetado no namespace.
 *
 * A implementação real usa os endpoints `/api/sdk/v1/db/*` do Core.
 * Em testes, usa-se um fake in-memory.
 *
 * Este é o **seam de injeção** que conecta a camada offline ao servidor real.
 * Transportes futuros (Firestore Web SDK direto / WebSocket gateway) são
 * implementados aqui, sem modificar a superfície pública `DbCollectionRef`.
 */
type RealtimeTransport = SyncTransport;
/**
 * Referência a um documento específico.
 * Parte da superfície `DbCollectionRef.doc(id)`.
 *
 * Expõe as propriedades de identidade do documento de forma análoga ao
 * `DocumentReference` do Firestore SDK, evitando a necessidade de adaptadores
 * externos que reattachavam `collection` + `id` manualmente.
 *
 * @example
 * ```ts
 * const ref = orders.doc('order-123');
 * console.log(ref.id);         // "order-123"
 * console.log(ref.collection); // "orders"
 * console.log(ref.path);       // "orders/order-123"
 * ```
 */
interface DbDocRef<T = Record<string, unknown>> {
    /** ID do documento dentro da coleção. */
    readonly id: string;
    /** Caminho completo no formato "collection/docId". */
    readonly path: string;
    /** Nome da coleção parent. */
    readonly collection: string;
    get(): Promise<DbGetResult<T> | null>;
    set(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
        ok: true;
    }>;
    update(data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
        ok: true;
    }>;
    remove(): Promise<{
        ok: true;
    }>;
    onSnapshot(cb: (snap: DbGetResult<T> | null) => void): Unsubscribe;
}
/**
 * Referência a uma coleção — superfície dupla CRUD + realtime.
 *
 * Conforme 02-sdk.md §3.2 / I2 §3.2.
 */
interface DbCollectionRef$1<T = Record<string, unknown>> {
    /** Retorna snapshot de um documento. `null` se não existir (nem no cache). */
    get(id: string): Promise<DbGetResult<T> | null>;
    /** Lista documentos com query opcional. */
    list(q?: DbQuery$1): Promise<DbListResult<T>>;
    /** Adiciona doc com id gerado pelo cliente (UUID v4). Durável localmente. */
    add(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
        ok: true;
        id: string;
    }>;
    /** Cria ou sobrescreve um doc pelo id. Durável localmente. */
    set(id: string, data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
        ok: true;
    }>;
    /** Atualiza campos de um doc (merge). Durável localmente. */
    update(id: string, data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
        ok: true;
    }>;
    /** Remove um doc (tombstone local). Durável localmente. */
    remove(id: string): Promise<{
        ok: true;
    }>;
    /** Aplica múltiplas operações atomicamente na fila. */
    batch(ops: DbBatchOp<T>[]): Promise<{
        ok: true;
    }>;
    /** Escuta um documento específico. */
    onDoc(id: string, cb: (doc: T | null) => void): Unsubscribe;
    /** Escuta a coleção inteira (ou subconjunto via query). */
    onSnapshot(q: DbQuery$1 | undefined, cb: (snap: DbListResult<T>) => void): Unsubscribe;
    /** Retorna uma referência ao documento `id`. */
    doc(id: string): DbDocRef<T>;
}
/**
 * Namespace `db.documents` — ponto de entrada para coleções de documentos.
 *
 * Analogia a `NeetruDb` de 02-sdk.md §3.1 mas escoped para o Mundo Documentos.
 */
interface NeetruDbDocuments {
    collection<T = Record<string, unknown>>(name: string): DbCollectionRef$1<T>;
    /** Estado atual de sincronização (offline/online/syncing). */
    readonly syncState: SyncState;
    onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;
    /** Força flush da fila de mutações pendentes. */
    flush(): Promise<void>;
    /** Limpa o cache local (IndexedDB) — operação destrutiva. */
    clearCache(): Promise<void>;
    /**
     * Retorna os registros do log de conflitos LWW gravados pelo SyncEngine.
     * Um conflito ocorre quando a mutação local é descartada porque o servidor
     * já avançou a versão do documento (Last-Write-Wins).
     */
    getConflicts(): Promise<ConflictRecord[]>;
}
interface CreateOfflineDocumentsOptions {
    /**
     * Nome do banco IndexedDB — deve ser único por produto × dbId × env.
     * Convenção: `neetru-db__{productSlug}__{dbId}__{env}`.
     */
    dbName: string;
    /**
     * Transporte de sync — implementa `SyncTransport` do SyncEngine.
     * Este é o **seam de injeção** do transporte real.
     *
     * Implementações possíveis:
     *   - `RestSyncTransport`      — REST → Core (padrão em staging/prod)
     *   - `FirestoreRealtimeTransport` — Firestore Web SDK direto (engine=firestore)
     *   - `WebSocketTransport`     — WebSocket → gateway (engine=nosql-vm)
     *   - `FakeRealtimeTransport`  — in-memory (testes)
     */
    transport: RealtimeTransport;
    /**
     * Estado inicial de conectividade.
     * Default: `navigator.onLine` se disponível, senão `true`.
     */
    startOnline?: boolean;
    /**
     * `true` = sem contenção multi-aba (sempre líder).
     * Padrão em testes; em produção usa Web Locks.
     */
    singleTab?: boolean;
    /**
     * Intervalo do sync periódico em ms. `0` desativa.
     * Default: 5 * 60 * 1000 (5min).
     */
    periodicSyncIntervalMs?: number;
}

declare const DELTA_OPS: readonly ["delta", "resync", "stale", "error", "pong", "drain"];
type DeltaOp = (typeof DELTA_OPS)[number];
interface DbQuery {
    filter?: Record<string, unknown>;
    limit?: number;
    orderBy?: string;
    orderDir?: 'asc' | 'desc';
}
interface DbChange {
    type: string;
    documentId: string;
    data?: Record<string, unknown>;
}
interface DeltaFrame {
    op: DeltaOp;
    subscriptionId: string;
    changes?: DbChange[];
    reason?: string;
}
type InboundFrame = DeltaFrame;
type RealtimeQuery = DbQuery;
/**
 * Estado observavel da conexao WebSocket.
 *
 * - 'connecting'    : tentando (re)conectar — socket em aberto ou backoff ativo.
 * - 'connected'     : WebSocket aberto e pronto para enviar/receber.
 * - 'disconnected'  : sem conexao ativa, sem tentativa pendente.
 * - 'draining'      : gateway enviou drain; aguardando reconexao com backoff.
 */
type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'draining';
/** Listener de mudanca de estado da conexao. */
type ConnectionStateListener = (state: ConnectionState) => void;
/**
 * Callback entregue a cada `subscribe()`.
 * Recebe o frame inbound (delta/resync/stale/error) ja roteado para esta
 * subscription especifica.
 */
type SubscriptionCallback = (frame: InboundFrame) => void;
/**
 * Subconjunto do WebSocket nativo necessario para o NeetruRealtimeClient.
 * Injetavel em testes via FakeWebSocket.
 *
 * Cobre o que usamos: envio de mensagem, leitura de estado e event handlers.
 * Nao inclui streams, arraybuffer ou blob — o protocolo so usa JSON text.
 */
interface WebSocketLike {
    readonly readyState: number;
    send(data: string): void;
    close(code?: number, reason?: string): void;
    onopen: ((event: Event) => void) | null;
    onmessage: ((event: MessageEvent) => void) | null;
    onclose: ((event: CloseEvent) => void) | null;
    onerror: ((event: Event) => void) | null;
}
/** Factory que cria instancias de WebSocketLike. Injetavel nos testes. */
type WebSocketFactory = (url: string) => WebSocketLike;
/**
 * Ticket obtido de `POST /api/sdk/v1/db/realtime/ticket` pelo Core.
 *
 * O `token` e embedding em `query.filter._ticket` de cada frame `subscribe`
 * enviado ao gateway, que o valida via
 * `POST /api/sdk/v1/db/realtime/validate`.
 *
 * TTL tipico: 60s. Single-use-ish (o gateway valida na primeira mensagem).
 */
interface RealtimeTicket {
    /** ID do ticket (opaco, para correlacao no lado do servidor). */
    ticketId: string;
    /** Token curto que o gateway extrai e valida. */
    token: string;
    /** ISO timestamp de expiracao. */
    expiresAt: string;
    /** TTL em milissegundos (atalho — igual a `expiresAt - now`). */
    ttlMs: number;
}
/**
 * Provedor de ticket injetavel.
 *
 * - Producao: implementacao padrao que chama
 *   `POST /api/sdk/v1/db/realtime/ticket` via camada HTTP do SDK.
 * - Testes: stub que retorna um ticket fixo (ou rejeita para simular falha).
 *
 * Deve retornar um ticket fresco a cada chamada — tickets tem TTL 60s e
 * sao single-use-ish (um por conexao WS).
 */
type TicketProvider = () => Promise<RealtimeTicket>;
/** Configuracao do NeetruRealtimeClient. */
interface RealtimeClientOptions {
    /**
     * URL do gateway `neetru-realtime`.
     * Ex.: `wss://realtime.neetru.com/ws`
     */
    gatewayUrl: string;
    /**
     * Provedor de ticket de autenticacao realtime injetavel.
     *
     * - Chamado antes de CADA abertura de WebSocket (primeira conexao e
     *   reconexoes). Se falhar, a conexao e abortada e o backoff se aplica.
     * - Producao: implementacao padrao usa `POST /api/sdk/v1/db/realtime/ticket`
     *   via HTTP do SDK (requer `apiKey` configurado).
     * - Testes: stub determinístico (nao precisa de Core rodando).
     *
     * OBRIGATORIO: o cliente nao abre WebSocket sem ticket valido.
     */
    ticketProvider: TicketProvider;
    /**
     * ID opaco do banco logico (BL-8 fix).
     *
     * Embutido em `query.filter._dbId` de cada frame `subscribe` enviado ao
     * gateway. O gateway (`@neetru/realtime-changestream._extractDbId`) exige
     * este campo para rotear a subscription ao banco correto — a ausencia causa
     * rejeicao silenciosa.
     *
     * Opcional para compatibilidade reversa; ausente → nenhum `_dbId` no frame.
     */
    dbId?: string;
    /**
     * Factory de WebSocket injetavel.
     * Default: `(url) => new WebSocket(url)`.
     * Injetar em testes para evitar rede real.
     */
    webSocketFactory?: WebSocketFactory;
    /**
     * Injecao de `setTimeout`/`clearTimeout` para testes de backoff sem relogio
     * real (fake timers). Default: `globalThis.setTimeout/clearTimeout`.
     */
    setTimeoutFn?: typeof setTimeout;
    clearTimeoutFn?: typeof clearTimeout;
    /**
     * Delay base do backoff exponencial em ms. Default: 1000.
     * O delay efetivo no attempt N e: `min(baseMs * 2^N, maxMs) * jitter`.
     */
    backoffBaseMs?: number;
    /**
     * Delay maximo do backoff em ms. Default: 30_000.
     */
    backoffMaxMs?: number;
    /**
     * Intervalo de heartbeat ping/pong em ms. Default: 25_000.
     * Usar 0 para desabilitar (util em testes).
     */
    heartbeatIntervalMs?: number;
}

/**
 * Tipos do Mundo SQL — `client.db.sql()`.
 *
 * Alinhado com 02-sdk.md §3.3 e 00-RELATORIO-CENTRAL.md §6.1 (peça 7+8 —
 * `NeetruLeaseIssuer` + `SqlLeaseManager`).
 *
 * REGRA DURA: `DbSqlLease` nunca é logado, nunca é persistido, nunca é exposto
 * ao produto. Vive só na RAM do processo.
 */

/**
 * Lease SQL de curta duração emitido pelo Core via `POST /api/sdk/v1/db/lease`.
 *
 * Alinhado com `DbSqlLease` do Core (`src/lib/db/lease/types.ts`).
 *
 * - `port` sempre 5433 — o lease aponta pro PgBouncer, nunca à porta crua.
 * - `credentialVersion` — bump indica rotação; o `SqlLeaseManager` reabre
 *   o pool ao detectar versão diferente.
 * - `expiresAt` — ISO string; o SDK renova aos ~80% do TTL (12 min em TTL=15 min).
 */
interface DbSqlLease {
    leaseId: string;
    host: string;
    port: 5433;
    dbName: string;
    user: string;
    /** Plaintext — trafega só sobre HTTPS, NUNCA logado/persistido. */
    password: string;
    /** CA do servidor para TLS — null até mTLS do Core (TODO P0-3). */
    sslca: string | null;
    /** Cert de cliente — null até mTLS do Core. */
    clientCert: string | null;
    /** Chave de cliente — null até mTLS do Core. */
    clientKey: string | null;
    /**
     * Versão da credencial no Secret Manager. Bump = rotação de senha.
     * O manager fecha o pool antigo e abre um novo quando esta versão muda.
     */
    credentialVersion: number;
    /** Expiração ISO — SDK renova ~2 min antes. */
    expiresAt: string;
}
/**
 * Handle SQL retornado por `client.db.sql(schema)`.
 *
 * Superfície pública exata de 02-sdk.md §3.3.
 *
 * @template TSchema — schema Drizzle do produto (`pgTable(...)`)
 */
interface NeetruSqlClient<TSchema extends Record<string, unknown>> {
    /** Handle Drizzle tipado pelo schema. */
    readonly orm: NodePgDatabase<TSchema>;
    /**
     * Transação ACID real com rollback automático em exceção.
     *
     * @param fn — callback que recebe o handle Drizzle transacional.
     * @param opts — `isolationLevel` opcional.
     */
    transaction<T>(fn: (tx: NodePgDatabase<TSchema>) => Promise<T>, opts?: {
        isolationLevel?: 'read committed' | 'repeatable read' | 'serializable';
    }): Promise<T>;
    /** Drena o pool — chamar no shutdown do produto. */
    close(): Promise<void>;
}
/**
 * Opções do segundo argumento de `client.db.sql(schema, options?)`.
 *
 * Contrato C-H (00-RELATORIO-CENTRAL.md §3.8):
 *   `client.db.sql(schema, { database? })` — quando `database` é fornecido,
 *   identifica o banco SQL alvo entre N bancos do produto. Quando ausente,
 *   resolve para o banco padrão (comportamento v1.x preservado).
 */
interface SqlOptions {
    /**
     * ID opaco do banco SQL — emitido pelo Core em `POST /api/sdk/v1/db`.
     *
     * Necessário quando o produto registrou mais de um banco SQL (ex.: `caixa`
     * + `analytics`). O valor flui para o lease request e o Core emite um
     * `DbSqlLease` escopado a esse banco.
     *
     * Quando omitido, o Core resolve o banco padrão do produto (geralmente o
     * único registrado, ou o banco homônimo do produto).
     */
    database?: string;
    /**
     * SQL DDL pra rodar antes de retornar o handle — **apenas honrado por `MockDb`**
     * (env `dev` ou tests usando o mock interno).
     *
     * Use pra criar tabelas no setup de testes vitest. Em produção (lease real),
     * o setup de schema vem das migrations + provisionamento. Esta opção é
     * silenciosamente ignorada por leases reais.
     *
     * @example
     * await client.db.sql(schema, {
     *   initSql: [
     *     'CREATE TABLE customers (id text primary key, name text)',
     *     'CREATE TABLE sales (id text primary key, customer_id text)',
     *   ],
     * });
     *
     * bug_a7f2ae25fda54129b52b6dd55aaa6050 (HIGH) — MockDb agora implementa
     * sql() in-memory via `pg-mem`. Esta opção destrava o setup.
     */
    initSql?: string | string[];
}

/**
 * NeetruRealtimeClient — cliente WebSocket multiplexado do gateway (M3).
 *
 * Componente #18 do relatório central do Conselho Neetru Core DB.
 * Par cliente do serviço `neetru-realtime` (componente #19,
 * `@neetru/realtime-transport`).
 *
 * Responsabilidades:
 * - Manter EXATAMENTE UMA conexão WebSocket ao gateway.
 * - Multiplexar N subscriptions independentes sobre ela.
 * - Reconectar com backoff exponencial + jitter em caso de queda.
 * - Em reconexão, reenviar todos os frames `subscribe` ativos.
 * - Responder / enviar heartbeat ping/pong por protocolo.
 * - Rotear frames inbound para o callback correto por `subscriptionId`.
 * - Emitir mudanças de `ConnectionState` para observadores externos.
 *
 * Protocolo de frames (contrato inlined em `./types.ts` — MASTER-CRIT-038):
 *   ENVIA  → `subscribe` | `unsubscribe` | `ping`
 *   RECEBE ← `delta` | `resync` | `stale` | `error` | `pong` | `drain`
 *
 * Deps: ZERO externas. Apenas WebSocket nativo (injetável).
 *
 * Nota: a integração de `NeetruRealtimeClient` na superfície pública
 * `client.db` é um follow-up deliberado — ver seção de pendências no
 * relatório de entrega.
 */

/**
 * Cliente WebSocket multiplexado do gateway `neetru-realtime`.
 *
 * @example
 * ```ts
 * const rt = new NeetruRealtimeClient({
 *   gatewayUrl: 'wss://realtime.neetru.com/ws',
 * });
 *
 * const subId = rt.subscribe('orders', { filter: { status: 'open' } }, (frame) => {
 *   if (frame.op === 'delta') applyChanges(frame.changes);
 *   if (frame.op === 'resync') fullReload();
 * });
 *
 * rt.onConnectionState((state) => console.log('connection:', state));
 *
 * // Para parar:
 * rt.unsubscribe(subId);
 * rt.close();
 * ```
 */
declare class NeetruRealtimeClient {
    private readonly _gatewayUrl;
    private readonly _wsFactory;
    private readonly _setTimeout;
    private readonly _clearTimeout;
    private readonly _backoffBaseMs;
    private readonly _backoffMaxMs;
    private readonly _heartbeatIntervalMs;
    private readonly _ticketProvider;
    /** ID do banco lógico (BL-8) — embutido em query.filter._dbId de cada subscribe. */
    private readonly _dbId;
    /** Ticket atual — obtido antes de cada abertura de WS e embutido nos frames. */
    private _currentTicket;
    /** Subscriptions ativas: id → entry */
    private _subscriptions;
    /** Listeners do estado da conexão. */
    private _stateListeners;
    /** Estado atual da conexão. */
    private _connectionState;
    /** Socket atual (pode ser null entre tentativas). */
    private _ws;
    /** Número da tentativa de reconexão corrente (reseta após conexão bem-sucedida). */
    private _reconnectAttempt;
    /** Handle do timer de backoff pendente. */
    private _reconnectTimer;
    /** Handle do timer de heartbeat. */
    private _heartbeatTimer;
    /** Indica que o cliente foi encerrado via `close()`. Não reconecta mais. */
    private _closed;
    /**
     * Frames de subscribe que precisam ser enviados mas a socket ainda não
     * está aberta (estado CONNECTING). Drenados no onopen.
     */
    private _pendingFrames;
    constructor(options: RealtimeClientOptions);
    /**
     * Registra uma subscription em `collection` com `query` opcional.
     *
     * Envia um frame `subscribe` ao gateway (ou enfileira se o socket ainda
     * não está aberto). O `callback` é invocado com cada frame `delta`,
     * `resync`, `stale` ou `error` roteado para esta subscription.
     *
     * @returns O `subscriptionId` opaco — usar para cancelar via `unsubscribe()`.
     */
    subscribe(collection: string, query: RealtimeQuery, callback: SubscriptionCallback): string;
    /**
     * Cancela uma subscription.
     *
     * Envia um frame `unsubscribe` ao gateway e remove o listener local.
     * Frames subsequentes com este `subscriptionId` são silenciosamente descartados.
     */
    unsubscribe(subscriptionId: string): void;
    /**
     * Registra um listener de mudanças no estado da conexão.
     *
     * O listener é invocado imediatamente com o estado atual e depois a cada
     * transição. Retorna uma função `unsubscribe`.
     */
    onConnectionState(listener: ConnectionStateListener): () => void;
    /**
     * Encerra o cliente definitivamente.
     *
     * Fecha o socket ativo, cancela timers pendentes e marca o cliente como
     * encerrado (sem mais reconexões).
     */
    close(): void;
    /**
     * Busca um ticket de autenticação e, em seguida, cria um novo WebSocket e
     * instala os handlers de evento.
     *
     * A busca de ticket é obrigatória antes de abrir qualquer WS (primeira
     * conexão e reconexões). Se a busca falhar, a conexão é abortada com
     * estado `'connecting'` e o backoff reconectará em seguida.
     *
     * Fail-closed: o WebSocket NUNCA é aberto sem um ticket válido.
     */
    private _connect;
    /** Implementação assíncrona de _connect — busca ticket e abre WS. */
    private _connectWithTicket;
    private _handleOpen;
    private _handleMessage;
    private _handleClose;
    private _handleError;
    private _handleDrain;
    private _routeToSubscription;
    /**
     * Reenvía frames `subscribe` para todas as subscriptions ativas.
     * Chamado no `onopen` de reconexões para restaurar o estado remoto.
     *
     * Durante a PRIMEIRA conexão: _pendingFrames já foi drenado com os
     * subscribes iniciais acima. ResubscribeAll envia novamente — como a
     * lista de subscriptions foi populada pelos `subscribe()` calls anteriores
     * ao open, e os frames drenados também os incluem, teríamos duplicatas.
     *
     * Solução: resubscribeAll só é invocado após drenar _pendingFrames, e
     * como o gateway é idempotente para o mesmo subscriptionId (segundo o
     * design do componente #19), a duplicata é inócua. Em reconexões, onde
     * _pendingFrames está vazio, os frames são os únicos enviados.
     *
     * Para o caso do primeiro open com subscribes anteriores ao open, os
     * frames já foram drenados de _pendingFrames — não chamamos resubscribeAll
     * se era a primeira conexão (_reconnectAttempt era 0 antes de resetar).
     * Rastreamos isso com _hasConnectedOnce.
     */
    private _hasConnectedOnce;
    private _resubscribeAll;
    /**
     * Constrói o descriptor `query` para um frame `subscribe`, embutindo o
     * `token` do ticket corrente em `query.filter._ticket` e o `_dbId` do
     * banco lógico em `query.filter._dbId` (BL-8 fix).
     *
     * O gateway extrai `filter._ticket` do primeiro frame `subscribe` e valida
     * contra `POST /api/sdk/v1/db/realtime/validate`. `filter._dbId` é exigido
     * pelo `@neetru/realtime-changestream._extractDbId` para rotear a
     * subscription ao banco correto — sem ele a subscription é rejeitada.
     *
     * Retorna `undefined` se a query seria vazia E não há ticket (estado
     * transitório antes do primeiro ticket — não deve ocorrer normalmente pois
     * `_connect` só abre o WS após obter o ticket).
     */
    private _buildSubscribeQuery;
    /** Agenda uma tentativa de reconexão com backoff exponencial + jitter. */
    private _scheduleReconnect;
    /**
     * Calcula o delay de backoff para o attempt N.
     *
     * Fórmula: `min(baseMs * 2^N, maxMs) * jitter`
     * onde `jitter ∈ [JITTER_MIN, JITTER_MAX]` (±50%).
     */
    private _computeBackoffDelay;
    private _cancelReconnectTimer;
    /** Agenda o próximo ping. Não-operacional se heartbeatIntervalMs === 0. */
    private _scheduleHeartbeat;
    private _cancelHeartbeatTimer;
    private _sendPing;
    /**
     * Envia o frame imediatamente se o socket está aberto; caso contrário,
     * enfileira em `_pendingFrames` para envio no próximo `onopen`.
     */
    private _sendOrBuffer;
    /** Serializa e envia o frame imediatamente via WebSocket. */
    private _sendNow;
    private _setState;
    /** Remove todos os handlers de um WebSocket (evita disparo após close()). */
    private _detachHandlers;
}

/**
 * Transporte SDK↔Core do banco lógico — determina o protocolo de realtime.
 *
 * - `'firestore'` — Firestore Web SDK como transporte (memoryLocalCache).
 * - `'nosql-vm'`  — WebSocket via gateway `neetru-realtime`.
 * - `'rest'`      — REST puro (default MVP; sem realtime nativo).
 *
 * Nota: "transport" descreve o protocolo SDK↔Core (REST / Firestore / WebSocket).
 * Não confundir com "engine" de armazenamento do admin database (firestore-instance,
 * cloud-sql-postgres, vm-postgres-single…), que é uma configuração do Core —
 * um storage engine pode ser acessado por vários SDK transports.
 *
 * O produto NUNCA vê a diferença de transporte — o comportamento offline
 * é idêntico nos dois transportes de Documentos (02-sdk.md §3.7).
 */
type NeetruDbTransport = 'firestore' | 'nosql-vm' | 'rest';
/**
 * @deprecated Use `NeetruDbTransport`. Este alias será removido em v3.0.
 *
 * O nome "engine" colide com o conceito de storage engine do admin database
 * (firestore-instance, cloud-sql-postgres, vm-postgres-single…).
 * No contexto do SDK, o campo configura o TRANSPORTE (REST/Firestore/WebSocket),
 * não o engine de armazenamento. Use `transport` em vez de `engine`.
 */
type NeetruDbEngine = NeetruDbTransport;
/**
 * Opções de configuração do banco lógico.
 *
 * Normalmente geradas pelo scaffold `neetru db init` em `db/client.ts`.
 * Em testes, injetar diretamente.
 */
interface NeetruDbOptions {
    /**
     * ID opaco do banco lógico — emitido pelo Core em `POST /api/sdk/v1/db`.
     * Necessário para o lease SQL e para scoping das coleções.
     */
    dbId?: string;
    /**
     * Transporte SDK↔Core — determina o protocolo de realtime.
     * Default: `'rest'` (MVP — funciona para todos os storage engines SQL e Documentos).
     *
     * Valores:
     * - `'rest'`      — REST puro (sem realtime nativo; compatível com todos os storage engines).
     * - `'firestore'` — Firestore Web SDK como transporte (memoryLocalCache).
     * - `'nosql-vm'`  — WebSocket via gateway `neetru-realtime`.
     *
     * Não confundir com o storage engine do admin database (firestore-instance,
     * cloud-sql-postgres, vm-postgres-single…). Um storage engine pode ser acessado
     * por vários transports SDK (ex.: cloud-sql-postgres funciona com transport='rest').
     */
    transport?: NeetruDbTransport;
    /**
     * @deprecated Use `transport` em vez de `engine`. Será removido em v3.0.
     *
     * O nome "engine" colide com o conceito de storage engine do admin database
     * (firestore-instance, cloud-sql-postgres, vm-postgres-single…).
     * Se `transport` e `engine` forem passados simultaneamente, `transport` prevalece.
     */
    engine?: NeetruDbEngine;
    /**
     * Opções do transporte Firestore (somente quando `transport === 'firestore'`).
     * O `firestore` handle (inicializado com `memoryLocalCache`) é injetado aqui.
     *
     * Injeção explícita — o SDK não importa `firebase/firestore` diretamente
     * para não poluir o bundle de quem não usa este transporte.
     */
    firestoreTransport?: RealtimeTransport;
    /**
     * Opções do transporte WebSocket (somente quando `transport === 'nosql-vm'`).
     * URL do gateway `neetru-realtime`.
     */
    realtimeGatewayUrl?: string;
    /**
     * Coleções declaradas pelo produto — necessário para pullChanges/fullResync
     * com os transportes Firestore e nosql-vm.
     *
     * Default: `[]` (o SyncEngine sincroniza coleções conforme são acessadas).
     */
    collections?: string[];
    /**
     * Nome do banco IndexedDB. Convenção: `neetru-db__{productSlug}__{dbId}__{env}`.
     * Gerado automaticamente se ausente.
     */
    dbName?: string;
    /**
     * `true` = sem contenção multi-aba (sempre líder).
     * Padrão em SSR/testes; em produção usa Web Locks.
     */
    singleTab?: boolean;
    /**
     * Transporte de sync customizado (injetado em testes).
     * Se presente, sobrepõe a detecção por `transport`.
     */
    _transport?: RealtimeTransport;
    /**
     * Factory de WebSocket injetável (somente quando `transport === 'nosql-vm'`).
     * Default: `(url) => new WebSocket(url)`.
     * Injetar em testes para evitar rede real.
     */
    _wsFactory?: WebSocketFactory;
}
/**
 * Superfície pública do namespace `client.db` — alinhada com 02-sdk.md §3.1.
 *
 * ```ts
 * const client = createNeetruClient({ ... });
 *
 * // Mundo Documentos (offline-first):
 * const orders = client.db.collection<Order>('orders');
 * await orders.add({ ... });
 * orders.onSnapshot(query, snap => renderOrders(snap.docs));
 *
 * // Mundo SQL (lease + pg.Pool + Drizzle):
 * const sql = await client.db.sql(schema);
 * const rows = await sql.orm.select().from(schema.orders);
 *
 * // Camada offline:
 * const state = client.db.syncState;
 * client.db.onSyncStateChanged(s => console.log(s.pendingWrites));
 * await client.db.flush(); // força sync
 * ```
 */
interface NeetruDb {
    /**
     * Retorna uma `DbCollectionRef` para a coleção `name`.
     *
     * Offline-first: reads vêm do cache local; writes são aplicados
     * otimisticamente e enfileirados para sync. Engine-aware: o transporte
     * realtime é selecionado automaticamente pelo `engine` configurado.
     */
    collection<T = Record<string, unknown>>(name: string): DbCollectionRef$1<T>;
    /**
     * Abre o handle SQL: busca o lease no Core, abre o `pg.Pool`, cria o
     * handle Drizzle tipado pelo `schema`. Operação async, 1x no startup.
     *
     * Em `NEETRU_ENV=dev`, retorna um `MockSqlClient` que não abre socket.
     *
     * @param schema   — Schema Drizzle do produto (`pgTable(...)` em `db/schema.ts`).
     * @param options  — Opções opcionais (contrato C-H).
     *   `options.database` desambigua quando o produto tem N>1 bancos SQL.
     *   Quando omitido, o Core resolve o banco padrão/homônimo do produto.
     *
     * @example
     * ```ts
     * import * as schema from './db/schema';
     * // banco padrão (único ou homônimo):
     * export const db = await client.db.sql(schema);
     * // banco específico entre N bancos SQL:
     * export const analytics = await client.db.sql(schema, { database: 'analytics' });
     * ```
     */
    sql<TSchema extends Record<string, unknown>>(schema: TSchema, options?: SqlOptions): Promise<NeetruSqlClient<TSchema>>;
    /** Estado atual de sincronização da camada offline. */
    readonly syncState: SyncState;
    /**
     * Subscreve a mudanças de `SyncState`.
     * Retorna função de unsubscribe.
     */
    onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;
    /**
     * Força flush da fila de mutações pendentes.
     * Útil antes de um `beforeunload` ou shutdown do produto.
     */
    flush(): Promise<void>;
    /**
     * Limpa o cache local (IndexedDB) — operação destrutiva.
     * Use com cuidado: dados não sincronizados serão perdidos.
     */
    clearCache(): Promise<void>;
    /**
     * Retorna os registros de conflito LWW pendentes de entrega.
     * Permite ao produto inspecionar escritas perdidas.
     */
    getConflicts(): Promise<ConflictRecord[]>;
}
/**
 * Transporte REST puro — usa os endpoints `/api/sdk/v1/datastore/*` do Core.
 *
 * BL-2 fix: `pushMutations` agora chama o Core de verdade via `httpRequest`.
 * Sem `config`, lança `NeetruDbError('db_unavailable')` — NUNCA retorna
 * `confirmed` para uma escrita que não foi enviada.
 *
 * - `pushMutations`  → POST/PATCH/PUT/DELETE `/api/sdk/v1/datastore/{coll}/{id}`
 * - `pullChanges`    → GET `/api/sdk/v1/datastore/{coll}` (best-effort por coleção)
 * - `fullResync`     → GET por coleção (lista completa)
 *
 * Exportado para testes.
 */
declare function createRestSyncTransport(config?: ResolvedConfig): RealtimeTransport;
/**
 * Retorna o `NeetruRealtimeClient` para uso em `subscribeRealtime` fora do
 * `SyncTransport` (extensão nosql-vm — não faz parte do contrato base).
 *
 * H-10 fix: agora aceita um `dbId` opcional que é passado para o cliente
 * (BL-8 — o cliente embute `_dbId` nos frames de subscribe).
 *
 * @param gatewayUrl      URL do gateway WebSocket.
 * @param ticketProvider  Provedor de ticket obrigatório.
 * @param dbId            ID do banco lógico (BL-8).
 * @param wsFactory       Factory de WebSocket injetável (testes).
 */
declare function getWebSocketRealtimeClient(gatewayUrl: string, ticketProvider: TicketProvider, dbId?: string, wsFactory?: WebSocketFactory): NeetruRealtimeClient;
/**
 * Cria o namespace `client.db` completo.
 *
 * **Factory única** — `createNeetruDb` é a única factory pública.
 * O split sync/async é um detalhe de implementação interno: o `NeetruDb`
 * retornado inicializa a camada offline (IndexedDB) de forma lazy na
 * primeira operação real, portanto:
 *
 * - Pode ser chamado de contextos síncronos (como `createNeetruClient`).
 * - Pode ser chamado com `await` quando se deseja garantir que o store
 *   esteja aquecido antes da primeira op (equivalente a chamar `flush()`).
 * - `client.db.flush()` garante que o IndexedDB está aberto.
 *
 * @param config   — config resolvida do cliente (apiKey, baseUrl, env, etc.)
 * @param dbOpts   — opções do banco lógico (engine, dbId, collections, etc.)
 *
 * @example
 * ```ts
 * // Uso síncrono (dentro de createNeetruClient):
 * const db = createNeetruDb(resolved, config.db);
 *
 * // Uso com await (quando se quer garantir warmup):
 * const db = await createNeetruDb(resolved, config.db);
 * await db.flush(); // opcional — já está aquecido
 *
 * // O produto usa:
 * const orders = db.collection<Order>('orders');
 * const sqlDb  = await db.sql(schema);
 * ```
 */
declare function createNeetruDb(config: ResolvedConfig, dbOpts?: NeetruDbOptions): NeetruDb;

/**
 * Tipos públicos do SDK Neetru.
 *
 * Vendor-neutral: nada aqui referencia `firebase/*`, `stripe`, ou qualquer
 * provider interno. Caller só vê superfície neutra (Product, Entitlement, ...).
 */
/** Default base URL pra API pública Neetru. */
declare const DEFAULT_BASE_URL = "https://api.neetru.com";
/**
 * Configuração passada pra `createNeetruClient`.
 */
interface NeetruClientConfig {
    /**
     * Bearer token Neetru (formato `nrt_<keyId>_<secret>`). Opcional —
     * se ausente, o SDK tenta `process.env.NEETRU_API_KEY` em Node.
     *
     * Endpoints públicos (catalog) funcionam sem apiKey. Endpoints autenticados
     * (entitlements, telemetry) lançam `missing_api_key` se ausente.
     */
    apiKey?: string;
    /**
     * 3.1.3 — `client_id` do client OIDC confidencial do produto, registrado em
     * `oidc_clients/{id}` no Core (par id + segredo, modelo Firebase/Facebook).
     *
     * Usado como `client_id` no fluxo `auth.signIn()` (Authorization Code + PKCE).
     * É **distinto** do `keyId` embutido na `apiKey` (`nrt_<keyId>_<secret>`), que
     * identifica a API key de máquina — não um client OIDC. Se ausente, `signIn`
     * cai no `keyId` por compat, mas o `/authorize` do Core só reconhece um
     * `oidc_clients` válido, então registre e passe `oidcClientId` em produção.
     */
    oidcClientId?: string;
    /**
     * URL base da API Neetru. Default: `https://api.neetru.com`.
     * Útil em testes apontando pra staging ou mock server.
     */
    baseUrl?: string;
    /**
     * Implementação de fetch customizada. Default: `globalThis.fetch`.
     * Necessário em runtimes que não exponham fetch global por default.
     */
    fetch?: typeof globalThis.fetch;
    /**
     * Modo de runtime. `dev` ativa mocks (auth retorna user fixture, usage só
     * loga, support retorna lista fake). `workspace` e `prod` chamam HTTP real.
     *
     * Default: lê `process.env.NEETRU_ENV` (Node) ou `globalThis.NEETRU_ENV`
     * (browser); fallback `prod`.
     */
    env?: 'dev' | 'workspace' | 'prod';
    /**
     * Override de mocks — útil em tests do consumer pra injetar fixtures
     * determinísticos (sem depender de NEETRU_ENV=dev).
     */
    mocks?: {
        auth?: AuthNamespace;
        usage?: UsageNamespace;
        support?: SupportNamespace;
        entitlements?: NeetruClient['entitlements'];
        /** v2.0: aceita NeetruDb (inclui MockDb). DbNamespace legado não é mais aceito. */
        db?: NeetruDb;
        webhooks?: WebhooksNamespace;
        notifications?: NotificationsNamespace;
    };
    /**
     * v0.3 — productId default usado por `usage.report()` / `usage.check()` /
     * `db.collection()` quando não passado explicitamente nas options.
     */
    productId?: string;
    /**
     * v0.3 — tenantId default usado por `usage.report()` / `usage.check()` /
     * `db.collection()` quando não passado explicitamente nas options.
     */
    tenantId?: string;
    /**
     * v2.0 — Configuração do banco lógico: transport, dbId, coleções.
     * Gerado pelo scaffold `neetru db init`. Opcional — padrão é transport='rest'.
     *
     * Use `transport` (v2.3+) em vez do campo `engine` depreciado.
     * Valores: `'rest'` | `'firestore'` | `'nosql-vm'`.
     */
    db?: NeetruDbOptions;
    /**
     * 3.0 — hosts adicionais permitidos para `baseUrl`. Por default, SDK aceita
     * `*.neetru.com` HTTPS + localhost. Para apontar pra staging/proxy custom em
     * `https://meu-proxy.empresa.com`, adicione `['empresa.com']`.
     *
     * Bypass total: `NEETRU_ALLOW_INSECURE_BASEURL=1` no env.
     */
    allowedHosts?: string[];
}
/**
 * Status do produto no catálogo público.
 */
type ProductStatus = 'live' | 'soon' | 'beta';
/**
 * Produto SaaS publicado pelo Neetru Core.
 *
 * Schema neutro — alinhado com `public_products/{slug}` no backend mas
 * nunca expõe campos internos (Firestore Timestamps, draft state, etc).
 */
interface Product {
    /** Identificador estável do produto, ex: `neetru-pulse`. */
    slug: string;
    /** Nome de exibição, ex: `Neetru Pulse`. */
    name: string;
    /** Subtítulo curto, ex: `Gestão de operações`. */
    tagline?: string;
    /** Descrição longa em prosa. */
    description?: string;
    /** Status público do produto. */
    status?: ProductStatus;
    /** Hint de ícone (catálogo de keys interno do Core). */
    iconKey?: string;
    /** Link override do CTA principal (default: página do produto). */
    ctaHref?: string;
    /** Label override do CTA. */
    ctaLabel?: string;
    /** Lista opcional de planos cobrados. Pode ser preenchida v0.2+. */
    plans?: ProductPlan[];
}
/** Plano cobrado de um produto (placeholder v0.1 — schema final v0.2). */
interface ProductPlan {
    id: string;
    name: string;
    /** Preço mensal em centavos (BRL). Pode ser undefined em planos custom. */
    amountCents?: number;
    features?: string[];
}
/**
 * Resposta do `client.catalog.list()`.
 */
interface CatalogListResponse {
    products: Product[];
    fetchedAt: string;
}
/**
 * Resposta do `client.entitlements.check(productSlug, feature)`.
 *
 * `allowed` é o sinal forte; `reason` ajuda a debugar (não exibir pro user
 * final).
 */
interface EntitlementCheck {
    allowed: boolean;
    productSlug: string;
    feature: string;
    /** Reason code estável: `granted` | `not_subscribed` | `feature_not_in_plan` | `expired`. */
    reason?: string;
}
/**
 * Payload pra `client.telemetry.event()`.
 */
interface TelemetryEventInput {
    /** Nome do evento, ex: `dashboard_opened`, `report_exported`. */
    name: string;
    /** Atributos adicionais (chave → valor primitivo). */
    properties?: Record<string, string | number | boolean | null>;
    /** Timestamp ISO opcional; default = server time. */
    timestamp?: string;
}
/**
 * Confirmação de aceitação do evento de telemetria.
 */
interface TelemetryEventAck {
    ok: true;
    /** ID gerado pelo backend pra evento (`usage_events/{id}`). */
    eventId: string;
}
type TelemetryLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
interface TelemetryLogInput {
    level: TelemetryLogLevel;
    message: string;
    metadata?: Record<string, unknown>;
    /** Override correlationId (default: lê dos headers/env automaticamente). */
    correlationId?: string;
    /** Slug do produto. Se ausente, o SDK usa `config.productId` como default
     *  (o backend NÃO infere — exige productSlug no body, fail-closed). */
    productSlug?: string;
}
interface TelemetryLogAck {
    ok: true;
    /** ID do log gravado (`logs/{productId}/{yyyymmdd}/{logId}`) — opcional em mocks. */
    logId?: string;
    /** Indicador de modo (mock vs http). */
    mode: 'mock' | 'http';
}
/**
 * Handle imutável retornado por `createNeetruClient`. Carrega namespaces
 * `auth`, `catalog`, `entitlements`, `telemetry`, `usage`, `support`.
 *
 * Cada namespace é exposto como objeto chamável — espelha API Firebase
 * (`auth`, `firestore`) ou Stripe (`paymentIntents`, `customers`).
 */
interface NeetruClient {
    /** Configuração resolvida (apiKey, baseUrl, fetch). Read-only. */
    readonly config: ResolvedConfig;
    /** Namespace auth (v0.2 — OIDC + mocks dev). */
    readonly auth: AuthNamespace;
    /** Namespace catálogo público de produtos. */
    readonly catalog: {
        /** Lista produtos publicados. */
        list(opts?: CatalogListOptions): Promise<CatalogListResponse>;
        /** Busca produto único por slug. */
        get(slug: string): Promise<Product>;
    };
    /** Namespace entitlements (v1.2: cache LRU 60s TTL). */
    readonly entitlements: {
        /**
         * Verifica se o portador da apiKey pode usar `feature` do produto `slug`.
         * Cacheado 60s. `{ cacheBust: true }` força ida ao servidor.
         */
        check(productSlug: string, feature: string, opts?: {
            cacheBust?: boolean;
        }): Promise<boolean>;
        /** Variante que retorna o objeto completo com `reason`. */
        checkDetailed(productSlug: string, feature: string, opts?: {
            cacheBust?: boolean;
        }): Promise<EntitlementCheck>;
    };
    /** Namespace telemetria (v1.2: track fire-and-forget + flush). */
    readonly telemetry: {
        /** Persiste um evento single-shot. await retorna { ok, eventId }. */
        event(input: TelemetryEventInput): Promise<TelemetryEventAck>;
        /** Fire-and-forget: enqueue + flush 500ms debounce. Sem retorno. */
        track(input: TelemetryEventInput): void;
        /** Força flush da queue do track() (chamar antes de unload). */
        flush(): Promise<void>;
        /**
         * Registra um log estruturado per-product (Sprint 6).
         *
         * - Em `NEETRU_ENV=dev`: apenas console.{level} (sem network).
         * - Em `workspace`/`prod`: POST `/api/sdk/v1/telemetry/log` com Bearer + correlationId.
         */
        log(input: TelemetryLogInput): Promise<TelemetryLogAck>;
    };
    /** Namespace usage (v0.2 — track + quota; v0.3 — report + check). */
    readonly usage: UsageNamespace;
    /** Namespace support (v0.2 — tickets). */
    readonly support: SupportNamespace;
    /**
     * Namespace db (v2.0 — Mundo Documentos offline-first + Mundo SQL).
     *
     * Breaking change v2.0: `client.db` agora é `NeetruDb` em vez de `DbNamespace`.
     *   - `db.collection(name)` → `DbCollectionRef` (offline-first, com `onSnapshot`,
     *     `batch`, `doc(id)`, `onDoc`; `list()` retorna `DbListResult` com cursor).
     *   - `db.sql(schema)` → `Promise<NeetruSqlClient>` (Mundo SQL).
     *   - `db.syncState` / `db.flush()` / `db.clearCache()` (camada offline).
     *
     * O antigo `DbNamespace` (list retornava `T[]`) foi removido em v2.0.
     */
    readonly db: NeetruDb;
    /** Namespace checkout (v1.1 — start/get/cancel intent + auto-redirect). */
    readonly checkout: CheckoutNamespace;
    /** Namespace webhooks (v1.2 — register/list/unregister/test). */
    readonly webhooks: WebhooksNamespace;
    /** Namespace notifications (v1.2 — send/list/markRead/dismiss). */
    readonly notifications: NotificationsNamespace;
}
/**
 * User retornado pelo OIDC ID token. Schema neutro — não vaza Firebase
 * decoded token shape. Subset estável do RFC 7519 + custom claims Neetru.
 */
interface NeetruUser {
    /** Subject — uid estável. */
    uid: string;
    email: string;
    emailVerified?: boolean;
    displayName?: string;
    photoURL?: string;
    /** Custom claim Neetru — true quando staff. */
    isStaff?: boolean;
    /** Custom claim Neetru — true quando customer enrolled. */
    isCustomer?: boolean;
    /** Tenant assigned (multi-tenant deployments). */
    tenantId?: string;
    /** Outros claims OIDC (aud, iss, iat, exp). */
    [extra: string]: unknown;
}
interface SignInOptions {
    /** OIDC redirect_uri override. Default = window.location origin. */
    redirectUri?: string;
    /** OIDC scope. Default `openid profile email`. */
    scope?: string;
    /** Onde mandar após login completo. Default = window.location.href. */
    postLoginRedirect?: string;
}
type AuthStateListener = (user: NeetruUser | null) => void;
/**
 * 3.1.3 — Resultado de `auth.handleRedirectCallback()`: o material que o **backend
 * do produto** (confidential client) usa pra trocar o code por tokens no
 * endpoint `/api/v1/oauth/token` (POST x-www-form-urlencoded, `grant_type=
 * authorization_code`, autenticação `Basic base64(clientId:clientSecret)`).
 *
 * O SDK nunca faz essa troca no browser (exigiria o `client_secret`). Ele só
 * gera/valida o PKCE + state e entrega estes campos pro lado servidor.
 */
interface RedirectCallbackResult {
    /** `authorization_code` emitido pelo IdP — single-use, TTL curto. */
    code: string;
    /** PKCE `code_verifier` (RFC 7636) — enviar junto do code no `/token`. */
    codeVerifier: string;
    /**
     * `redirect_uri` exato usado no `/authorize`. O `/token` exige reenvio
     * idêntico (exact match) — passe este valor sem alterar.
     */
    redirectUri: string;
    /**
     * `nonce` enviado no `/authorize`. O backend deve validar que o claim
     * `nonce` do `id_token` retornado bate com este valor (anti-replay OIDC).
     */
    nonce: string;
    /** `state` validado (CSRF). Exposto pra logging/correlação. */
    state: string;
    /**
     * Destino pós-login que o app passou em `signIn({ postLoginRedirect })`.
     * `undefined` se não informado.
     */
    postLoginRedirect?: string;
}
interface AuthNamespace {
    /**
     * Inicia fluxo de login OIDC (Authorization Code + PKCE). Em dev
     * (`NEETRU_ENV=dev`) retorna mock user direto sem redirect. Em browser
     * gera `state` (CSRF), `nonce` e o par PKCE `code_verifier`/`code_challenge`
     * (S256), persiste a transação em `sessionStorage` e redireciona pro
     * `/authorize`. Complete o fluxo no retorno com `handleRedirectCallback()`.
     */
    signIn(options?: SignInOptions): Promise<NeetruUser | void>;
    /**
     * 3.1.3 — Processa o retorno do IdP após `signIn`. Lê `code` + `state` da URL
     * (default `location.href`; passe `currentUrl` pra override), valida o
     * `state` contra a transação salva em `signIn` (CSRF), consome a transação
     * (single-use), limpa os params da URL e devolve o material pro backend
     * trocar por tokens. Retorna `null` se a URL não é um callback (sem `code`).
     *
     * @throws {NeetruError} `oidc_state_mismatch` se o `state` não bate ou não há
     *   transação pendente; ou um erro com `.code` = o `error` do IdP (ex:
     *   `access_denied`) quando a URL traz `?error=`.
     */
    handleRedirectCallback(currentUrl?: string): Promise<RedirectCallbackResult | null>;
    /**
     * 3.1.3 — Retorna o `id_token` cru (string JWT) que o app armazenou em
     * `localStorage['neetru_id_token']`, ou `null` se ausente/expirado. Útil pra
     * mandar como `Authorization: Bearer` ao BFF do produto. Para os claims
     * decodificados use `getUser()`; para validar assinatura use `verifyToken()`.
     */
    getIdToken(): string | null;
    /** Limpa session local + revoga refresh token no servidor. */
    signOut(): Promise<void>;
    /** Retorna o user atual (do id_token cached) ou `null` se não logado. */
    getUser(): NeetruUser | null;
    /**
     * Subscreve a mudanças de estado de auth. Listener é invocado imediatamente
     * com user atual. Retorna função de unsubscribe.
     */
    onAuthStateChanged(listener: AuthStateListener): () => void;
    /**
     * Valida um id_token JWT emitido por `auth.neetru.com` server-side.
     *
     * Usa JWKS local cacheado (1h TTL, configurável via `jwksCacheTtlMs`).
     * Ideal para Next.js route handlers, middleware e qualquer ambiente Node/Edge
     * server-side onde verificação rápida sem round-trip é necessária.
     *
     * @param token - JWT id_token obtido do cookie ou header Authorization.
     * @param options - Opções opcionais de verificação.
     * @returns `NeetruUser` com claims validados.
     *
     * @throws {NeetruError} com `.code`:
     *   - `token_expired` — token expirado (`exp` no passado)
     *   - `token_invalid_signature` — assinatura não verificada pela JWKS
     *   - `token_invalid_audience` — `aud` não bate com o `clientId` configurado
     *   - `token_invalid_issuer` — `iss` não é `https://auth.neetru.com`
     *
     * @example
     * ```ts
     * // Next.js route handler
     * import { createNeetruClient } from '@neetru/sdk';
     * const client = createNeetruClient({ apiKey: 'nrt_...' });
     *
     * export async function POST(req: Request) {
     *   const token = req.cookies.get('neetru-session')?.value;
     *   try {
     *     const user = await client.auth.verifyToken(token);
     *     // user.uid, user.email, user.isStaff etc disponíveis
     *   } catch (err) {
     *     return new Response('Unauthorized', { status: 401 });
     *   }
     * }
     * ```
     */
    verifyToken(token: string, options?: VerifyTokenOptions): Promise<NeetruUser>;
}
/** Opções para `auth.verifyToken()`. */
interface VerifyTokenOptions {
    /**
     * TTL do cache de JWKS em milissegundos. Default: 3600000 (1h).
     * Valores menores aumentam latência (mais fetches); maiores aumentam
     * o tempo de janela após rotação de chaves.
     */
    jwksCacheTtlMs?: number;
}
/**
 * 3.0 BREAKING: `UsageEventInput` mantido só como tipo para tests/legacy
 * imports — não é mais retorno de método. `usage.track`/`usage.getQuota`
 * **REMOVIDOS** em 3.0. Use `usage.report(resource, qty, options)`.
 */
interface UsageEventInput {
    event: string;
    properties?: Record<string, string | number | boolean | null>;
    quantity?: number;
}
/**
 * 3.0 BREAKING: `UsageQuota` mantido só para compat de imports. Não é mais
 * retorno de método ativo. `usage.report()` devolve `{value, limit, remaining}`
 * inline; `usage.check()` devolve detalhes do entitlement.
 */
interface UsageQuota {
    metric: string;
    used: number;
    limit: number;
    resetsAt?: string;
    plan?: string;
}
/**
 * 3.0 — superfície limpa: `report` (POST metered) + `check` (GET entitlement).
 *
 * Métodos removidos vs 2.x:
 *   - `track(event, props)` — vaporware contra Core. Use `report()`.
 *   - `getQuota(metric)` — endpoint nunca existiu. `report()` devolve quota.
 */
interface UsageNamespace {
    /**
     * Reporta consumo metered. Incrementa `usage_counters` no Core atomicamente.
     * Idempotency-Key gerado por chamada (defesa anti-dup em retry transient).
     *
     * `productId`/`tenantId` lidos do contexto do client se ausentes nas options.
     */
    report(resource: string, qty?: number, options?: {
        productId?: string;
        tenantId?: string;
    }): Promise<{
        ok: true;
        counterId?: string;
        value?: number;
        limit?: number;
        remaining?: number;
        status?: string;
    }>;
    /**
     * Verifica entitlement de um resource/feature. GET cacheável (HTTP 60s).
     * Resposta inclui `allowed`, `reason`, `remaining`, `limit`, `planId`,
     * `planFeatures` e `behavior` ('readonly' quando limit_exceeded).
     */
    check(resource: string, options?: {
        productId?: string;
        tenantId?: string;
    }): Promise<{
        allowed: boolean;
        reason?: string;
        remaining?: number;
        limit?: number;
        planId?: string | null;
        planFeatures?: string[];
        behavior?: 'readonly' | null;
    }>;
}
/**
 * Namespace `db` (v0.3) — wrapper minimalista para CRUD em coleções tenant-scoped
 * via Core REST. Em dev retorna fixtures in-memory por collection.
 *
 * O Core injeta automaticamente o tenantId no path do Firestore:
 * `tenant_{tid}_{name}/{docId}`.
 */
/** Filtro `where` simples — alinhado com endpoint REST `field:op:value`. */
interface DbWhereFilter {
    field: string;
    op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'in';
    value: string | number | boolean | null | Array<string | number | boolean>;
}
interface DbListOptions {
    limit?: number;
    /** Lista de filtros — máximo 5 (alinhado ao backend). */
    where?: DbWhereFilter[];
}
interface DbCollectionRef {
    /** Lista documentos com filtros opcionais. */
    list<T = Record<string, unknown>>(opts?: DbListOptions): Promise<T[]>;
    /** Lê um documento por id. Retorna `null` se não existe. */
    get<T = Record<string, unknown>>(id: string): Promise<T | null>;
    /** Cria/upsert um documento com id explícito. */
    set(id: string, data: Record<string, unknown>): Promise<{
        ok: true;
    }>;
    /**
     * v0.3.1 — Adiciona doc com id auto-gerado pelo backend. Retorna `{id}` do
     * doc criado. Diferente de `set(id, data)` que requer caller fornecer id.
     */
    add(data: Record<string, unknown>): Promise<{
        ok: true;
        id: string;
    }>;
    /** Atualiza doc com merge — só campos passados em `data`. */
    update(id: string, data: Record<string, unknown>): Promise<{
        ok: true;
    }>;
    /** Deleta um documento. */
    remove(id: string): Promise<{
        ok: true;
    }>;
}
interface DbNamespace {
    collection(name: string): DbCollectionRef;
}
type SupportSeverity = 'low' | 'normal' | 'high' | 'urgent';
type SupportStatus = 'open' | 'pending' | 'resolved' | 'closed';
interface SupportTicket {
    id: string;
    subject: string;
    message: string;
    severity: SupportSeverity;
    status: SupportStatus;
    createdAt: string;
    updatedAt?: string;
    /** Slug do produto referenciado. */
    productSlug?: string;
}
interface CreateTicketInput {
    subject: string;
    message: string;
    severity?: SupportSeverity;
    productSlug?: string;
    /**
     * **L-05 fix (Opus review 2026-05-27):** metadata opcional (Core já aceita
     * via `metadata: z.record(...)` no Zod). Útil pra anexar contexto como
     * `{ orderId, userAgent, route, sessionId }` sem inflar subject/message.
     *
     * Chaves limitadas a 64 chars; valores primitive (string/number/boolean/null).
     */
    metadata?: Record<string, string | number | boolean | null>;
}
interface SupportNamespace {
    /**
     * Cria um novo ticket. **3.0**: POST `/api/sdk/v1/support/tickets`
     * (canonical, requer `productId` no body — default `config.productId`).
     * Mock em dev.
     */
    createTicket(input: CreateTicketInput): Promise<SupportTicket>;
    /**
     * Lista meus tickets abertos. **3.0**: GET `/api/sdk/v1/support/tickets?productId=...`
     * (canonical). `options.productSlug` override do `config.productId` default.
     */
    listMyTickets(options?: {
        productSlug?: string;
    }): Promise<SupportTicket[]>;
}
/**
 * Modo do SDK. `dev` ativa mocks automáticos (úteis pra testes e
 * desenvolvimento sem precisar de backend rodando).
 */
type NeetruEnv = 'dev' | 'workspace' | 'prod';
/**
 * Configuração resolvida (defaults aplicados, fetch garantido).
 */
interface ResolvedConfig {
    readonly apiKey?: string;
    /** 3.1.3 — client_id OIDC confidencial do produto (vide NeetruClientConfig). */
    readonly oidcClientId?: string;
    readonly baseUrl: string;
    readonly fetch: typeof globalThis.fetch;
    /** Resolved env — `dev` | `workspace` | `prod`. Default `prod`. */
    readonly env: NeetruEnv;
    /** Default productId (v0.3). */
    readonly productId?: string;
    /** Default tenantId (v0.3). */
    readonly tenantId?: string;
}
/** Opções pra `catalog.list()`. */
interface CatalogListOptions {
    /** Inclui rascunhos (apenas com Bearer staff). Default false. */
    includeDrafts?: boolean;
}

export { type ProductStatus as $, type AuthNamespace as A, type NeetruClient as B, type CatalogListOptions as C, DEFAULT_BASE_URL as D, type EntitlementCheck as E, type FieldSentinel as F, type NeetruClientConfig as G, type NeetruDb as H, type IncrementSentinel as I, type NeetruDbDocuments as J, type NeetruDbEngine as K, type ListNotificationsOptions as L, MockCheckout as M, NEETRU_SENTINEL_KEY as N, NeetruDbError as O, type NeetruDbErrorCode as P, type NeetruDbOptions as Q, type NeetruDbTransport as R, type NeetruEnv as S, type NeetruSqlClient as T, type NeetruUser as U, type NotificationScopeOptions as V, type NotificationsNamespace as W, type Product as X, type ProductNotification as Y, type ProductNotificationSeverity as Z, type ProductPlan as _, type AuthStateListener as a, type RealtimeTransport as a0, type RedirectCallbackResult as a1, type RegisterWebhookInput as a2, type ResolvedConfig as a3, type SendNotificationInput as a4, type ServerTimestampSentinel as a5, type SignInOptions as a6, type SqlOptions as a7, type SupportNamespace as a8, type SupportSeverity as a9, increment as aA, isFieldSentinel as aB, isIncrementSentinel as aC, isServerTimestampSentinel as aD, serverTimestamp as aE, verifyWebhookRequest as aF, verifyWebhookSignature as aG, type SupportStatus as aa, type SupportTicket as ab, type SyncState as ac, type TelemetryEventAck as ad, type TelemetryEventInput as ae, type TelemetryLogAck as af, type TelemetryLogInput as ag, type Unsubscribe as ah, type UsageEventInput as ai, type UsageNamespace as aj, type UsageQuota as ak, type VerifyTokenOptions as al, type VerifyWebhookRequestOptions as am, type VerifyWebhookRequestResult as an, type WebhookEndpoint as ao, type WebhookEvent as ap, type WebhookScopeOptions as aq, type WebhookTestResult as ar, type WebhooksNamespace as as, type WithFieldSentinels as at, createCheckoutNamespace as au, createNeetruDb as av, createNotificationsNamespace as aw, createRestSyncTransport as ax, createWebhooksNamespace as ay, getWebSocketRealtimeClient as az, type CatalogListResponse as b, type CheckoutAuthOpts as c, type CheckoutIntentInfo as d, type CheckoutIntentStatus as e, type CheckoutNamespace as f, type CheckoutStartInput as g, type CheckoutStartResult as h, type CheckoutTenantType as i, type ConflictRecord as j, type CreateOfflineDocumentsOptions as k, type CreateTicketInput as l, type DbBatchOp as m, type DbChangeType as n, type DbCollectionRef$1 as o, type DbDoc as p, type DbDocRef as q, type DbGetResult as r, type DbListResult as s, type DbNamespace as t, type DbQuery$1 as u, type DbSqlLease as v, type DbWhereFilter as w, type DbWhereFilter$1 as x, MockNotifications as y, MockWebhooks as z };
