/**
 * Messaging Service (FCM)
 *
 * Servicio para Firebase Cloud Messaging (Push Notifications).
 * Permite solicitar permisos, obtener tokens, escuchar mensajes y manejar
 * navegación (deep linking) cuando el usuario toca una notificación.
 */
import { Injector, NgZone } from '@angular/core';
import { Observable } from 'rxjs';
import { EnablePushResult, NotificationAction, NotificationClickEvent, NotificationPayload, NotificationPermission, ValtechFirebaseConfig } from './types';
import * as i0 from "@angular/core";
/**
 * Callback opcional de registro de device para `MessagingService.enable()`.
 *
 * El registro del device vive en `AuthService` (necesita el JWT + el endpoint
 * del backend). `MessagingService` NO puede depender de `AuthService` sin
 * crear un ciclo (AuthService → MessagingService). Por eso el caller le pasa
 * el paso de registro como callback. Recibe el token FCM y devuelve si el
 * device quedó registrado.
 */
export type RegisterDeviceFn = (token: string) => Promise<boolean>;
/**
 * Opciones de `MessagingService.enable()`.
 */
export interface EnablePushOptions {
    /**
     * Paso opcional de registro de device en el backend. Si se omite, `enable()`
     * resuelve con `status: 'enabled'` apenas obtiene el token (sin registrar).
     */
    registerDevice?: RegisterDeviceFn;
}
/**
 * Estado interno del servicio de messaging
 */
interface MessagingState {
    token: string | null;
    permission: NotificationPermission;
    isSupported: boolean;
}
/**
 * Servicio para Firebase Cloud Messaging (FCM).
 *
 * Permite recibir notificaciones push en la aplicación web.
 * Requiere VAPID key configurada en ValtechFirebaseConfig.
 *
 * @example
 * ```typescript
 * @Component({...})
 * export class NotificationComponent {
 *   private messaging = inject(MessagingService);
 *
 *   token = signal<string | null>(null);
 *
 *   async enableNotifications() {
 *     // Solicitar permiso y obtener token
 *     const token = await this.messaging.requestPermission();
 *
 *     if (token) {
 *       this.token.set(token);
 *       // Enviar token a tu backend para almacenarlo
 *       await this.backend.registerDeviceToken(token);
 *     }
 *   }
 *
 *   // Escuchar mensajes en foreground
 *   messages$ = this.messaging.onMessage();
 * }
 * ```
 */
export declare class MessagingService {
    private injector;
    private config;
    private platformId;
    private ngZone;
    private messageSubject;
    private notificationClickSubject;
    private stateSubject;
    private unsubscribeOnMessage?;
    /** Flag para persistir mensajes FCM en localStorage (debugging) */
    private readonly debugPersistence;
    /** Key para localStorage de mensajes FCM (debugging) */
    private readonly DEBUG_STORAGE_KEY;
    /**
     * Key de localStorage donde se persiste el token FCM.
     *
     * El token vive en un `BehaviorSubject` en memoria y se pierde en cada
     * recarga / suspensión de la PWA (iOS reinicia PWAs con frecuencia). Persistir
     * el token permite hidratar el estado en cold start y que la UI no parpadee.
     * Es un *optimistic hint* — la verdad la confirma el siguiente `getToken()`.
     */
    private readonly TOKEN_STORAGE_KEY;
    /**
     * Timeout (ms) para `navigator.serviceWorker.ready` dentro de `getToken()`.
     *
     * En un cold load el SW puede no estar activado aún y `serviceWorker.ready`
     * no resolver nunca. Pasado este tiempo, `getToken()` rechaza limpio en lugar
     * de colgarse indefinidamente.
     */
    private readonly SW_READY_TIMEOUT_MS;
    /**
     * Timeout (ms) del watchdog de `enable()` antes de auto-recargar la página.
     *
     * El flujo de activación a veces se cuelga ANTES de `getToken()` — caso
     * típico: `Notification.requestPermission()` no muestra el popup del SO en un
     * cold load. El timeout de `getToken()` (SW_READY_TIMEOUT_MS) no cubre eso;
     * por eso `enable()` envuelve el flujo completo en este watchdog.
     *
     * Un flujo exitoso real llega a token+device en ~4s; 15s es holgado y no
     * atrapa un éxito lento.
     */
    private readonly ENABLE_WATCHDOG_MS;
    /**
     * Key de sessionStorage — garantiza un solo auto-reload por sesión. Si el
     * flujo se cuelga una 2ª vez tras el reload, NO se recarga de nuevo (anti-loop).
     */
    private readonly AUTORELOAD_FLAG;
    constructor(injector: Injector, config: ValtechFirebaseConfig, platformId: Object, ngZone: NgZone);
    /**
     * Obtiene la instancia de Messaging via Firebase SDK directo (NO AngularFire DI).
     *
     * **Por qué no usamos `injector.get(Messaging)`:**
     * AngularFire `provideMessaging` registra un APP_INITIALIZER que internamente
     * resuelve `await isSupported()`. Si `injector.get(Messaging)` se llama antes
     * que ese APP_INITIALIZER termine (ej. otros APP_INITIALIZER del cliente
     * construyen AuthService → MessagingService → toca Messaging temprano),
     * AngularFire lanza el error "APP_INITIALIZER ... has not resolved". Angular
     * DI **cachea errores de factory** — una vez que falló, todos los
     * `injector.get` siguientes retornan el mismo error cacheado. Retry loops
     * no funcionan.
     *
     * **Workaround:** bypass AngularFire entirely y usar Firebase SDK directo
     * (`firebase/messaging.getMessaging(getApp())`). AngularFire es un wrapper
     * sobre el mismo SDK — la instancia que obtenemos es funcionalmente idéntica.
     * `getApp()` retorna la default Firebase app inicializada por
     * `provideFirebaseApp(() => initializeApp(config.firebase))`. Si por alguna
     * razón no está inicializada aún, hacemos initializeApp idempotente.
     */
    private fcmSupportPromise?;
    private getMessagingInstance;
    /**
     * Inicializa el servicio de messaging
     */
    private initializeMessaging;
    /**
     * Configura listener para mensajes del Service Worker.
     * Recibe eventos cuando el usuario hace click en una notificación background.
     */
    private setupServiceWorkerListener;
    /**
     * Verifica si FCM está soportado en el navegador actual
     */
    private checkSupport;
    /**
     * Solicita permiso de notificaciones y obtiene el token FCM.
     *
     * @returns Token FCM si se otorgó permiso, null si se denegó
     *
     * @example
     * ```typescript
     * const token = await messaging.requestPermission();
     * if (token) {
     *   console.log('Token FCM:', token);
     *   // Enviar a backend
     * } else {
     *   console.log('Permiso denegado o no soportado');
     * }
     * ```
     */
    requestPermission(): Promise<string | null>;
    /**
     * Flujo completo de activación de push, robusto, cross-app.
     *
     * Orquesta: pedir permiso → SW ready → obtener token FCM → (opcional)
     * registrar el device en backend. Es el punto de orquestación único que cada
     * app del factory consume — la lógica de robustez vive aquí, no en cada página.
     *
     * **Watchdog de auto-reload.** El flujo a veces se cuelga ANTES de `getToken()`
     * (ej. `Notification.requestPermission()` que no muestra el popup en un cold
     * load) — el timeout interno de `getToken()` no cubre ese caso. Por eso
     * `enable()` envuelve el flujo entero en un watchdog: si no alcanza un estado
     * terminal en `ENABLE_WATCHDOG_MS` (15s):
     *  - 1ª vez en la sesión → marca un flag en `sessionStorage` y hace
     *    `window.location.reload()` (un fresh load suele tener el SW activo).
     *    Resuelve con `{ status: 'timeout', reloaded: true }`.
     *  - ya se auto-recargó antes → NO recarga (anti-loop): limpia el flag y
     *    resuelve con `{ status: 'timeout', reloaded: false }` para que la app
     *    muestre un error.
     *
     * NO hace throw — siempre resuelve con un `EnablePushResult` descriptivo que
     * la página consumidora inspecciona para decidir qué toast mostrar.
     *
     * @param options.registerDevice Callback opcional que registra el device en
     *   el backend (vive en `AuthService` — se pasa como callback para evitar el
     *   ciclo de DI AuthService ↔ MessagingService).
     *
     * @example
     * ```typescript
     * const result = await messaging.enable({
     *   registerDevice: (token) => auth.registerDevice(token).then(r => r.registered),
     * });
     * if (result.status === 'enabled') {
     *   // result.token disponible — push activo
     * } else if (result.status === 'timeout' && !result.reloaded) {
     *   // mostrar error: el flujo se colgó y ya se consumió el auto-reload
     * }
     * ```
     */
    enable(options?: EnablePushOptions): Promise<EnablePushResult>;
    /**
     * Flujo real de activación, sin el watchdog (lo envuelve `enable()`).
     * Resuelve siempre con un `EnablePushResult` — no hace throw.
     */
    private runEnableFlow;
    /** True si ya se auto-recargó una vez en esta sesión. */
    private hasAutoReloaded;
    /** Marca que se consumió el auto-reload de esta sesión. */
    private markAutoReloaded;
    /** Limpia el flag anti-loop — tras un éxito o un fallo definitivo. */
    private clearAutoReloadFlag;
    /**
     * Obtiene el token FCM actual (sin solicitar permiso).
     *
     * @returns Token FCM si está disponible, null si no
     *
     * @example
     * ```typescript
     * const token = await messaging.getToken();
     * ```
     */
    getToken(): Promise<string | null>;
    /**
     * Resuelve el `ServiceWorkerRegistration` del SW de FCM.
     *
     * El SW `/firebase-messaging-sw.js` ya se registra una vez en el bootstrap de
     * la app (`config.ts`). Reutilizamos ese registro en lugar de re-registrarlo
     * en cada `getToken()` — re-registrar repetidamente dispara revalidaciones del
     * SW en iOS PWA. Solo registramos como fallback si todavía no existe.
     */
    private resolveServiceWorkerRegistration;
    /**
     * Espera a que el Service Worker quede activo (`navigator.serviceWorker.ready`)
     * pero con un timeout duro.
     *
     * `navigator.serviceWorker.ready` resuelve cuando hay un SW *activo*. En un
     * cold load (primera visita, SW recién registrado, iOS PWA recién abierta) el
     * SW puede quedar en estado `installing`/`waiting` y `ready` no resolver nunca
     * → `getToken()` se cuelga. El `Promise.race` contra un `setTimeout` garantiza
     * que esta espera termina: o gana `ready` (caso normal) o gana el timeout y
     * lanzamos un error claro para que `getToken()` rechace en vez de colgarse.
     *
     * El timer se limpia en ambas ramas para no dejar timers colgando.
     */
    private waitForServiceWorkerReady;
    /**
     * Persiste el token FCM en localStorage (o lo limpia si es null/empty).
     */
    private persistToken;
    /**
     * Hidrata el estado del token desde localStorage en el arranque del servicio.
     */
    private hydrateTokenFromStorage;
    /**
     * Elimina el token FCM actual (unsubscribe de notificaciones).
     *
     * @example
     * ```typescript
     * await messaging.deleteToken();
     * console.log('Token eliminado, no recibirá más notificaciones');
     * ```
     */
    deleteToken(): Promise<void>;
    /**
     * Observable de mensajes recibidos en foreground.
     *
     * IMPORTANTE: Los mensajes en background son manejados por el Service Worker.
     *
     * @returns Observable que emite cuando llega un mensaje en foreground
     *
     * @example
     * ```typescript
     * messaging.onMessage().subscribe(payload => {
     *   console.log('Mensaje recibido:', payload);
     *   // Mostrar notificación custom o actualizar UI
     * });
     * ```
     */
    onMessage(): Observable<NotificationPayload>;
    /**
     * Configura el listener de mensajes en foreground
     */
    private setupMessageListener;
    /**
     * Persiste un mensaje FCM en localStorage para debugging.
     * Solo se usa cuando debugMessagePersistence está habilitado.
     * Mantiene los últimos 50 mensajes.
     */
    private persistMessageForDebug;
    /**
     * Obtiene el estado actual del permiso de notificaciones.
     *
     * @returns 'granted' | 'denied' | 'default'
     *
     * @example
     * ```typescript
     * const permission = messaging.getPermissionState();
     * if (permission === 'granted') {
     *   // Ya tiene permiso
     * } else if (permission === 'default') {
     *   // Puede solicitar permiso
     * } else {
     *   // Denegado, debe habilitar manualmente
     * }
     * ```
     */
    getPermissionState(): NotificationPermission;
    /**
     * Verifica si FCM está soportado en el navegador actual.
     * Usa lógica inteligente para evitar falsos negativos por timing del Injector.
     *
     * @returns true si FCM está soportado
     *
     * @example
     * ```typescript
     * if (await messaging.isSupported()) {
     *   // Puede usar notificaciones push
     * } else {
     *   // Navegador no soporta o no tiene Service Worker
     * }
     * ```
     */
    isSupported(): Promise<boolean>;
    /**
     * iOS sin estar instalado como PWA standalone — push web no disponible.
     * Aplica a Safari y a Edge/Chrome/Firefox en iOS (todos WebKit por mandato Apple).
     */
    private isIOSWithoutStandalone;
    /**
     * Obtiene el token actual sin hacer request.
     *
     * @returns Token almacenado o null
     */
    get currentToken(): string | null;
    /**
     * Observable del estado completo del servicio de messaging.
     */
    get state$(): Observable<MessagingState>;
    /**
     * Verifica si el usuario ya otorgó permiso de notificaciones.
     */
    get hasPermission(): boolean;
    /**
     * Hidrata el estado del token desde un valor externo (ej: localStorage).
     * Útil cuando el token ya existe pero el MessagingService no lo tiene en memoria.
     *
     * @param token Token FCM a setear
     *
     * @example
     * ```typescript
     * const storedToken = localStorage.getItem('fcm_token');
     * if (storedToken) {
     *   messaging.setTokenFromStorage(storedToken);
     * }
     * ```
     */
    setTokenFromStorage(token: string): void;
    /**
     * Observable de clicks en notificaciones.
     *
     * Emite cuando el usuario hace click en una notificación (foreground o background).
     * Usa este observable para navegar a la página correspondiente.
     *
     * @returns Observable que emite NotificationClickEvent
     *
     * @example
     * ```typescript
     * @Component({...})
     * export class AppComponent {
     *   private messaging = inject(MessagingService);
     *   private router = inject(Router);
     *
     *   constructor() {
     *     this.messaging.onNotificationClick().subscribe(event => {
     *       if (event.action.route) {
     *         this.router.navigate([event.action.route], {
     *           queryParams: event.action.queryParams
     *         });
     *       }
     *     });
     *   }
     * }
     * ```
     */
    onNotificationClick(): Observable<NotificationClickEvent>;
    /**
     * Extrae la acción de navegación de los datos de una notificación.
     *
     * Busca campos específicos en el payload de datos:
     * - `route`: Ruta interna de la app (ej: '/orders/123')
     * - `url`: URL externa (ej: 'https://example.com')
     * - `action_type`: Tipo de acción personalizada
     * - Campos con prefijo `action_`: Datos adicionales
     *
     * @param data - Datos del payload de la notificación
     * @returns Acción de navegación extraída
     *
     * @example
     * ```typescript
     * // Payload desde el backend:
     * // { route: '/orders/123', action_type: 'view_order', action_orderId: '123' }
     *
     * const action = messaging.extractActionFromData(notification.data);
     * // { route: '/orders/123', actionType: 'view_order', actionData: { orderId: '123' } }
     * ```
     */
    extractActionFromData(data?: Record<string, string>): NotificationAction;
    /**
     * Emite manualmente un evento de click en notificación.
     *
     * Útil para manejar clicks en notificaciones foreground donde
     * la app decide mostrar un banner custom.
     *
     * @param notification - Payload de la notificación
     *
     * @example
     * ```typescript
     * messaging.onMessage().subscribe(notification => {
     *   // Mostrar banner custom
     *   this.showBanner(notification, () => {
     *     // Usuario hizo click en el banner
     *     messaging.handleNotificationClick(notification);
     *   });
     * });
     * ```
     */
    handleNotificationClick(notification: NotificationPayload): void;
    /**
     * Verifica si una notificación tiene acción de navegación.
     *
     * @param data - Datos del payload
     * @returns true si tiene route o url
     */
    hasNavigationAction(data?: Record<string, string>): boolean;
    /**
     * Parsea un query string en un objeto.
     */
    private parseQueryString;
    static ɵfac: i0.ɵɵFactoryDeclaration<MessagingService, never>;
    static ɵprov: i0.ɵɵInjectableDeclaration<MessagingService>;
}
export {};
