/**
 * BroadcastChannel State Synchronization
 * FE-STATE-002: Synchronize Zustand state across browser tabs using BroadcastChannel
 *
 * This middleware allows Zustand stores to automatically sync their state
 * across all open tabs/windows of the same origin.
 *
 * NOTE: This sync mechanism coexists with React Query cache synchronization
 * (see reactQuerySync.ts). Both use BroadcastChannel but with different channel
 * names and message formats, so they do not conflict:
 * - Zustand sync: `veza-store-${storeName}` channels with BroadcastMessage format
 * - React Query sync: `veza-react-query-sync` channel with ReactQuerySyncMessage format
 */

import { StateCreator } from 'zustand';
import { logger } from './logger';

/**
 * Options for broadcast synchronization
 */
export interface BroadcastSyncOptions {
  /** Channel name for BroadcastChannel (default: store name) */
  channelName?: string;
  /** Whether to enable synchronization (default: true) */
  enabled?: boolean;
  /** Function to filter which state changes should be synced */
  shouldSync?: <T>(state: T, prevState: T | null) => boolean;
  /** 
   * Action 4.2.1.1: Callback to invalidate React Query cache when state is synced
   * Called when state updates are broadcast or received from other tabs
   */
  onStateSync?: <T>(state: T, prevState: T | null) => void;
}

/**
 * BroadcastChannel message format
 * CRITIQUE FIX #14: Ajout d'un ID unique pour chaque message pour éviter les doublons
 */
interface BroadcastMessage<T = unknown> {
  type: 'state-update' | 'state-request' | 'state-response';
  storeName: string;
  state?: T;
  timestamp: number;
  messageId?: string; // CRITIQUE FIX #14: ID unique pour éviter les doublons
}

/**
 * Create a BroadcastChannel instance for a store
 */
function createBroadcastChannel(storeName: string): BroadcastChannel | null {
  if (typeof window === 'undefined' || !window.BroadcastChannel) {
    logger.warn(
      '[BroadcastSync] BroadcastChannel not supported in this environment',
    );
    return null;
  }

  try {
    return new BroadcastChannel(`veza-store-${storeName}`);
  } catch (error) {
    logger.warn(
      `[BroadcastSync] Failed to create BroadcastChannel for ${storeName}`,
      {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        storeName,
      },
    );
    return null;
  }
}

/**
 * Zustand middleware for BroadcastChannel synchronization
 *
 * @example
 * ```typescript
 * export const useMyStore = create<MyState>()(
 *   broadcastSync(
 *     (set, get) => ({
 *       // store implementation
 *     }),
 *     { channelName: 'my-store' }
 *   )
 * );
 * ```
 */
export function broadcastSync<T extends object>(
  config: StateCreator<T>,
  options: BroadcastSyncOptions = {},
): StateCreator<T> {
  return (set, get, api) => {
    const storeName = options.channelName || 'default-store';
    const enabled = options.enabled !== false;
    const shouldSync = options.shouldSync || (() => true);
    const onStateSync = options.onStateSync;

    let channel: BroadcastChannel | null = null;
    let isReceivingUpdate = false;
    let lastState: T | null = null;
    // CRITIQUE FIX #14: Système de timestamps pour déterminer quelle mise à jour est la plus récente
    let lastUpdateTimestamp = 0;
    // CRITIQUE FIX #14: Set pour tracker les messages déjà traités (éviter les doublons)
    const processedMessages = new Set<string>();
    // CRITIQUE FIX #14: Queue pour les mises à jour en attente
    const pendingUpdates: Array<{
      state: T;
      timestamp: number;
      messageId: string;
    }> = [];

    // Initialize BroadcastChannel
    if (enabled) {
      channel = createBroadcastChannel(storeName);

      if (channel) {
        // CRITIQUE FIX #14: Fonction pour traiter les mises à jour en queue
        const processPendingUpdates = () => {
          if (pendingUpdates.length === 0 || isReceivingUpdate) {
            return;
          }

          // Trier par timestamp (plus récent en premier)
          pendingUpdates.sort((a, b) => b.timestamp - a.timestamp);

          // Traiter la mise à jour la plus récente
          const update = pendingUpdates.shift();
          if (update && update.timestamp > lastUpdateTimestamp) {
            isReceivingUpdate = true;
            set(update.state);
            lastState = update.state;
            lastUpdateTimestamp = update.timestamp;

            // Nettoyer les anciens messages traités (garder seulement les 100 derniers)
            if (processedMessages.size > 100) {
              const toDelete = Array.from(processedMessages).slice(0, 50);
              toDelete.forEach((id) => processedMessages.delete(id));
            }

            // Reset flag après traitement
            setTimeout(() => {
              isReceivingUpdate = false;
              // Traiter la prochaine mise à jour en queue
              processPendingUpdates();
            }, 50); // Réduire le délai pour une meilleure réactivité
          }
        };

        // Listen for state updates from other tabs
        channel.onmessage = (event: MessageEvent<BroadcastMessage>) => {
          const message = event.data;

          // Action 2.3.1.3: Type guard to ensure this is a valid BroadcastMessage
          // This prevents processing messages from other sync mechanisms (e.g., React Query sync)
          if (
            !message ||
            typeof message !== 'object' ||
            !message.type ||
            !message.storeName ||
            typeof message.timestamp !== 'number'
          ) {
            // Not a valid BroadcastMessage - ignore (likely from a different sync mechanism)
            return;
          }

          // Verify message type is one of the expected Zustand sync types
          if (
            message.type !== 'state-update' &&
            message.type !== 'state-request' &&
            message.type !== 'state-response'
          ) {
            // Not a Zustand sync message - ignore
            return;
          }

          // Verify storeName matches (extra safety check)
          if (message.storeName !== storeName) {
            // Message is for a different store - ignore
            return;
          }

          // CRITIQUE FIX #14: Générer un ID unique pour ce message s'il n'en a pas
          const messageId =
            message.messageId ||
            `${message.type}-${message.timestamp}-${Math.random()}`;

          // Ignorer les messages déjà traités (éviter les doublons)
          if (processedMessages.has(messageId)) {
            return;
          }

          if (message.type === 'state-update' && message.state) {
            // Ignore updates from the same tab si on est déjà en train de recevoir
            if (isReceivingUpdate) {
              // CRITIQUE FIX #14: Ajouter à la queue au lieu de rejeter
              pendingUpdates.push({
                state: message.state as T,
                timestamp: message.timestamp,
                messageId,
              });
              // Trier la queue et traiter si possible
              processPendingUpdates();
              return;
            }

            // CRITIQUE FIX #14: Vérifier si cette mise à jour est plus récente que la dernière
            if (message.timestamp <= lastUpdateTimestamp) {
              // Mise à jour obsolète, l'ignorer
              processedMessages.add(messageId);
              return;
            }

            // Check if we should sync this update
            if (shouldSync(message.state, lastState)) {
              processedMessages.add(messageId);
              isReceivingUpdate = true;
              const prevState = lastState; // Save previous state before updating
              set(message.state as T);
              const newState = message.state as T;
              lastState = newState;
              lastUpdateTimestamp = message.timestamp;

              // Action 4.2.1.1: Invalidate React Query cache when state is synced from another tab
              if (onStateSync) {
                try {
                  onStateSync(newState, prevState);
                } catch (error) {
                  logger.warn(
                    '[BroadcastSync] Error in onStateSync callback',
                    {
                      error: error instanceof Error ? error.message : String(error),
                      stack: error instanceof Error ? error.stack : undefined,
                      storeName,
                    },
                  );
                }
              }

              // Reset flag after a short delay
              setTimeout(() => {
                isReceivingUpdate = false;
                // Traiter les mises à jour en queue après le délai
                processPendingUpdates();
              }, 50); // Réduire le délai pour une meilleure réactivité
            } else {
              processedMessages.add(messageId);
            }
          } else if (message.type === 'state-request') {
            // Another tab is requesting the current state
            const currentState = get();
            // Only serialize data, not functions (functions can't be cloned)
            const serializableState = JSON.parse(JSON.stringify(currentState));
            if (channel) {
              channel.postMessage({
                type: 'state-response',
                storeName,
                state: serializableState,
                timestamp: Date.now(),
              } as BroadcastMessage);
            }
          } else if (message.type === 'state-response' && message.state) {
            // CRITIQUE FIX #14: Received state from another tab (initial sync)
            // Vérifier le timestamp pour éviter d'écraser un état plus récent
            if (!lastState || message.timestamp > lastUpdateTimestamp) {
              processedMessages.add(messageId);
              isReceivingUpdate = true;
              const prevState = lastState; // Save previous state before updating
              set(message.state as T);
              const newState = message.state as T;
              lastState = newState;
              lastUpdateTimestamp = message.timestamp;

              // Action 4.2.1.1: Invalidate React Query cache when state is synced from another tab
              if (onStateSync) {
                try {
                  onStateSync(newState, prevState);
                } catch (error) {
                  logger.warn(
                    '[BroadcastSync] Error in onStateSync callback',
                    {
                      error: error instanceof Error ? error.message : String(error),
                      stack: error instanceof Error ? error.stack : undefined,
                      storeName,
                    },
                  );
                }
              }

              setTimeout(() => {
                isReceivingUpdate = false;
                processPendingUpdates();
              }, 50);
            } else {
              processedMessages.add(messageId);
            }
          }
        };

        // Request initial state from other tabs on startup
        channel.postMessage({
          type: 'state-request',
          storeName,
          timestamp: Date.now(),
        } as BroadcastMessage);
      }
    }

    // Create the store with wrapped set function
    const store = config(
      (...args) => {
        if (!isReceivingUpdate) {
          set(...args);

          // Broadcast state update to other tabs
          if (channel && enabled) {
            const newState = get();

            if (shouldSync(newState, lastState)) {
              // CRITIQUE FIX #14: Utiliser un timestamp précis et un ID unique pour chaque message
              const timestamp = Date.now();
              const messageId = `update-${timestamp}-${Math.random()}`;

              // Only serialize data, not functions (functions can't be cloned)
              const serializableState = JSON.parse(JSON.stringify(newState));
              channel.postMessage({
                type: 'state-update',
                storeName,
                state: serializableState,
                timestamp,
                messageId, // CRITIQUE FIX #14: Ajouter l'ID unique
              } as BroadcastMessage);

              // Action 4.2.1.1: Invalidate React Query cache when state is updated locally
              if (onStateSync) {
                try {
                  onStateSync(newState, lastState);
                } catch (error) {
                  logger.warn(
                    '[BroadcastSync] Error in onStateSync callback',
                    {
                      error: error instanceof Error ? error.message : String(error),
                      stack: error instanceof Error ? error.stack : undefined,
                      storeName,
                    },
                  );
                }
              }

              lastState = newState;
              lastUpdateTimestamp = timestamp;
            }
          }
        } else {
          // If we're receiving an update, just set without broadcasting
          set(...args);
        }
      },
      get,
      api,
    );

    return store;
  };
}

/**
 * Helper to create a synchronized store with both persistence and broadcast sync
 *
 * @example
 * ```typescript
 * export const useMyStore = create<MyState>()(
 *   persist(
 *     broadcastSync(
 *       (set, get) => ({
 *         // store implementation
 *       }),
 *       { channelName: 'my-store' }
 *     ),
 *     { name: 'my-store-storage' }
 *   )
 * );
 * ```
 */
export function createSynchronizedStore<T extends object>(
  config: StateCreator<T>,
  options: {
    persist?: { name: string; partialize?: (state: T) => Partial<T> };
    broadcast?: BroadcastSyncOptions;
  } = {},
) {
  let store = config;

  // Apply broadcast sync if enabled
  if (options.broadcast?.enabled !== false) {
    store = broadcastSync(store, options.broadcast) as StateCreator<T>;
  }

  // Apply persistence if configured
  if (options.persist) {
    const { persist: persistMiddleware } = require('zustand/middleware');
    store = persistMiddleware(store, {
      name: options.persist.name,
      partialize: options.persist.partialize,
    }) as StateCreator<T>;
  }

  return store;
}
