/**
 * State Cleanup
 * FE-STATE-012: Clean up unused state to prevent memory leaks
 * 
 * Provides utilities and middleware for cleaning up unused state data
 * in Zustand stores to prevent memory leaks.
 */

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

/**
 * Cleanup strategy
 */
export type CleanupStrategy =
  | 'size_limit' // Limit by size (number of items)
  | 'age_limit' // Limit by age (time-based)
  | 'both' // Both size and age limits
  | 'custom'; // Custom cleanup function

/**
 * Cleanup configuration
 */
export interface CleanupConfig {
  /** Strategy to use */
  strategy: CleanupStrategy;
  /** Maximum number of items to keep (for size_limit) */
  maxSize?: number;
  /** Maximum age in milliseconds (for age_limit) */
  maxAge?: number;
  /** Custom cleanup function */
  cleanupFn?: (state: unknown) => unknown;
  /** Fields to clean up (if not specified, cleans entire state) */
  fields?: string[];
  /** Enable automatic periodic cleanup */
  autoCleanup?: boolean;
  /** Interval for automatic cleanup in milliseconds (default: 5 minutes) */
  cleanupInterval?: number;
}

/**
 * Cleanup options for middleware
 */
export interface CleanupOptions {
  /** Store name for logging */
  storeName: string;
  /** Cleanup configurations for different state fields */
  configs: Record<string, CleanupConfig>;
  /** Enable cleanup (default: true) */
  enabled?: boolean;
}

/**
 * Clean up state based on size limit
 */
function cleanupBySize<T>(
  items: T[],
  maxSize: number,
  getTimestamp?: (item: T) => number,
): T[] {
  if (items.length <= maxSize) {
    return items;
  }

  // If we have timestamps, keep the most recent items
  if (getTimestamp) {
    const sorted = [...items].sort((a, b) => getTimestamp(b) - getTimestamp(a));
    return sorted.slice(0, maxSize);
  }

  // Otherwise, keep the first items
  return items.slice(0, maxSize);
}

/**
 * Clean up state based on age limit
 */
function cleanupByAge<T>(
  items: T[],
  maxAge: number,
  getTimestamp: (item: T) => number,
): T[] {
  const now = Date.now();
  const cutoff = now - maxAge;

  return items.filter((item) => {
    const timestamp = getTimestamp(item);
    return timestamp >= cutoff;
  });
}

/**
 * Clean up normalized state by size
 */
function cleanupNormalizedBySize<T extends { id: string }>(
  normalized: { byId: Record<string, T>; allIds: string[] },
  maxSize: number,
  getTimestamp?: (item: T) => number,
): { byId: Record<string, T>; allIds: string[] } {
  if (normalized.allIds.length <= maxSize) {
    return normalized;
  }

  let itemsToKeep: T[];

  if (getTimestamp) {
    // Sort by timestamp and keep most recent
    const items = normalized.allIds.map((id) => normalized.byId[id]);
    const sorted = items.sort((a, b) => getTimestamp(b) - getTimestamp(a));
    itemsToKeep = sorted.slice(0, maxSize);
  } else {
    // Keep first items
    itemsToKeep = normalized.allIds
      .slice(0, maxSize)
      .map((id) => normalized.byId[id]);
  }

  const newById: Record<string, T> = {};
  const newAllIds: string[] = [];

  for (const item of itemsToKeep) {
    newById[item.id] = item;
    newAllIds.push(item.id);
  }

  return { byId: newById, allIds: newAllIds };
}

/**
 * Clean up normalized state by age
 */
function cleanupNormalizedByAge<T extends { id: string }>(
  normalized: { byId: Record<string, T>; allIds: string[] },
  maxAge: number,
  getTimestamp: (item: T) => number,
): { byId: Record<string, T>; allIds: string[] } {
  const now = Date.now();
  const cutoff = now - maxAge;

  const newById: Record<string, T> = {};
  const newAllIds: string[] = [];

  for (const id of normalized.allIds) {
    const item = normalized.byId[id];
    const timestamp = getTimestamp(item);
    if (timestamp >= cutoff) {
      newById[id] = item;
      newAllIds.push(id);
    }
  }

  return { byId: newById, allIds: newAllIds };
}

/**
 * Apply cleanup to a state field
 */
function applyCleanup<T = unknown>(
  value: T,
  config: CleanupConfig,
): T {
  if (!config.strategy || config.strategy === 'custom') {
    if (config.cleanupFn) {
      return config.cleanupFn(value) as T;
    }
    return value;
  }

  // Handle arrays
  if (Array.isArray(value)) {
    const getTimestamp = (item: unknown): number => {
      if (typeof item === 'object' && item !== null) {
        const obj = item as Record<string, unknown>;
        if ('created_at' in obj && typeof obj.created_at === 'string') {
          return new Date(obj.created_at).getTime();
        }
        if ('timestamp' in obj && typeof obj.timestamp === 'number') {
          return obj.timestamp;
        }
        if ('updated_at' in obj && typeof obj.updated_at === 'string') {
          return new Date(obj.updated_at).getTime();
        }
      }
      return Date.now();
    };

    let cleaned = value;

    // Apply age limit first (removes old items)
    if (config.strategy === 'age_limit' || config.strategy === 'both') {
      if (config.maxAge) {
        cleaned = cleanupByAge(cleaned, config.maxAge, getTimestamp) as T;
      }
    }

    // Then apply size limit (keeps most recent items)
    if (config.strategy === 'size_limit' || config.strategy === 'both') {
      if (config.maxSize) {
        cleaned = cleanupBySize(cleaned, config.maxSize, getTimestamp) as T;
      }
    }

    return cleaned;
  }

  // Handle normalized state (byId + allIds)
  if (
    typeof value === 'object' &&
    value !== null &&
    'byId' in value &&
    'allIds' in value
  ) {
    const normalized = value as { byId: Record<string, unknown>; allIds: string[] };
    const getTimestamp = (item: unknown): number => {
      if (typeof item === 'object' && item !== null) {
        const obj = item as Record<string, unknown>;
        if ('created_at' in obj && typeof obj.created_at === 'string') {
          return new Date(obj.created_at).getTime();
        }
        if ('timestamp' in obj && typeof obj.timestamp === 'number') {
          return obj.timestamp;
        }
        if ('updated_at' in obj && typeof obj.updated_at === 'string') {
          return new Date(obj.updated_at).getTime();
        }
      }
      return Date.now();
    };

    let cleaned = normalized;

    // Apply age limit first (removes old items)
    if (config.strategy === 'age_limit' || config.strategy === 'both') {
      if (config.maxAge) {
        cleaned = cleanupNormalizedByAge(
          cleaned as { byId: Record<string, { id: string }>; allIds: string[] },
          config.maxAge,
          getTimestamp,
        ) as typeof normalized;
      }
    }

    // Then apply size limit (keeps most recent items)
    if (config.strategy === 'size_limit' || config.strategy === 'both') {
      if (config.maxSize) {
        cleaned = cleanupNormalizedBySize(
          cleaned as { byId: Record<string, { id: string }>; allIds: string[] },
          config.maxSize,
          getTimestamp,
        ) as typeof normalized;
      }
    }

    return cleaned as T;
  }

  // Handle objects with nested arrays (e.g., messages: { [conversationId]: Message[] })
  if (typeof value === 'object' && value !== null) {
    const obj = value as Record<string, unknown>;
    const cleaned: Record<string, unknown> = {};

    for (const [key, val] of Object.entries(obj)) {
      if (Array.isArray(val)) {
        const getTimestamp = (item: unknown): number => {
          if (typeof item === 'object' && item !== null) {
            const itemObj = item as Record<string, unknown>;
            if ('created_at' in itemObj && typeof itemObj.created_at === 'string') {
              return new Date(itemObj.created_at).getTime();
            }
            if ('timestamp' in itemObj && typeof itemObj.timestamp === 'number') {
              return itemObj.timestamp;
            }
          }
          return Date.now();
        };

        let cleanedArray = val;

        // Apply age limit first (removes old items)
        if (config.strategy === 'age_limit' || config.strategy === 'both') {
          if (config.maxAge) {
            cleanedArray = cleanupByAge(cleanedArray, config.maxAge, getTimestamp);
          }
        }

        // Then apply size limit (keeps most recent items)
        if (config.strategy === 'size_limit' || config.strategy === 'both') {
          if (config.maxSize) {
            cleanedArray = cleanupBySize(cleanedArray, config.maxSize, getTimestamp);
          }
        }

        cleaned[key] = cleanedArray;
      } else {
        cleaned[key] = val;
      }
    }

    return cleaned as T;
  }

  return value;
}

/**
 * Clean up state based on configuration
 */
export function cleanupState<T extends object>(
  state: T,
  configs: Record<string, CleanupConfig>,
): T {
  const cleaned = { ...state } as Record<string, unknown>;

  for (const [field, config] of Object.entries(configs)) {
    if (field in cleaned) {
      try {
        cleaned[field] = applyCleanup(cleaned[field], config);
      } catch (error) {
        logger.warn(`[StateCleanup] Failed to clean field ${field}:`, error);
      }
    }
  }

  return cleaned as T;
}

/**
 * Zustand middleware for automatic state cleanup
 * 
 * @example
 * ```typescript
 * export const useMyStore = create<MyState>()(
 *   cleanupMiddleware(
 *     (set, get) => ({
 *       // store implementation
 *     }),
 *     {
 *       storeName: 'MyStore',
 *       configs: {
 *         messages: {
 *           strategy: 'both',
 *           maxSize: 100,
 *           maxAge: 24 * 60 * 60 * 1000, // 24 hours
 *         },
 *       },
 *     }
 *   )
 * );
 * ```
 */
export function cleanupMiddleware<T extends object>(
  config: StateCreator<T>,
  options: CleanupOptions,
): StateCreator<T> {
  const {
    storeName,
    configs,
    enabled = true,
    autoCleanup = false,
    cleanupInterval = 5 * 60 * 1000, // 5 minutes default
  } = options;

  let cleanupIntervalId: ReturnType<typeof setInterval> | null = null;

  return (set, get, api) => {
    const store = config(set, get, api);

    if (!enabled) {
      return store;
    }

    // Set up automatic cleanup if enabled
    if (autoCleanup && typeof window !== 'undefined') {
      cleanupIntervalId = setInterval(() => {
        try {
          const currentState = get();
          const cleanedState = cleanupState(currentState, configs);
          const stateChanged = JSON.stringify(currentState) !== JSON.stringify(cleanedState);
          
          if (stateChanged) {
            logger.debug(`[StateCleanup:${storeName}] Automatic cleanup performed`);
            set(cleanedState as Partial<T>);
          }
        } catch (error) {
          logger.error(`[StateCleanup:${storeName}] Automatic cleanup failed:`, error);
        }
      }, cleanupInterval);

      // Clean up interval on unmount (if store is destroyed)
      if (typeof window !== 'undefined') {
        window.addEventListener('beforeunload', () => {
          if (cleanupIntervalId) {
            clearInterval(cleanupIntervalId);
          }
        });
      }
    }

    // Add cleanup method to store
    return {
      ...store,
      // Add cleanup method if it doesn't exist
      cleanup: () => {
        performCleanup(get, set, configs, storeName);
      },
    } as T & { cleanup: () => void };
  };
}

/**
 * Manual cleanup function for stores
 */
export function performCleanup<T extends object>(
  getState: () => T,
  setState: (state: Partial<T>) => void,
  configs: Record<string, CleanupConfig>,
  storeName: string = 'Unknown',
): void {
  try {
    const currentState = getState();
    const cleanedState = cleanupState(currentState, configs);
    const stateChanged = JSON.stringify(currentState) !== JSON.stringify(cleanedState);

    if (stateChanged) {
      logger.info(`[StateCleanup:${storeName}] Manual cleanup performed`);
      setState(cleanedState);
    } else {
      logger.debug(`[StateCleanup:${storeName}] No cleanup needed`);
    }
  } catch (error) {
    logger.error(`[StateCleanup:${storeName}] Manual cleanup failed:`, error);
  }
}

