/**
 * State Normalization Utilities
 * FE-STATE-009: Normalize nested state structures for better performance
 * 
 * Provides utilities to normalize nested state structures (arrays of objects)
 * into flat structures with indexed lookups (byId + allIds pattern).
 * This improves performance by enabling O(1) lookups instead of O(n) array searches.
 */

/**
 * Normalized state structure
 * Instead of storing items as arrays: [item1, item2, item3]
 * We store them as: { byId: { id1: item1, id2: item2, id3: item3 }, allIds: ['id1', 'id2', 'id3'] }
 */
export interface NormalizedState<T> {
  byId: Record<string, T>;
  allIds: string[];
}

/**
 * Normalize an array of items into a normalized state structure
 * @param items Array of items with an 'id' property
 * @returns Normalized state with byId and allIds
 */
export function normalize<T extends { id: string }>(
  items: T[],
): NormalizedState<T> {
  const byId: Record<string, T> = {};
  const allIds: string[] = [];

  for (const item of items) {
    if (item.id) {
      byId[item.id] = item;
      allIds.push(item.id);
    }
  }

  return { byId, allIds };
}

/**
 * Denormalize a normalized state back into an array
 * @param normalized Normalized state structure
 * @returns Array of items in the order of allIds
 */
export function denormalize<T>(
  normalized: NormalizedState<T>,
): T[] {
  return normalized.allIds
    .map((id) => normalized.byId[id])
    .filter((item): item is T => item !== undefined);
}

/**
 * Add an item to normalized state
 * @param normalized Current normalized state
 * @param item Item to add
 * @param position Optional position to insert at (default: append)
 * @returns New normalized state
 */
export function addToNormalized<T extends { id: string }>(
  normalized: NormalizedState<T>,
  item: T,
  position?: number,
): NormalizedState<T> {
  const { byId, allIds } = normalized;
  const newById = { ...byId, [item.id]: item };
  
  let newAllIds: string[];
  if (position === undefined || position >= allIds.length) {
    // Append to end
    newAllIds = [...allIds, item.id];
  } else if (position <= 0) {
    // Prepend to start
    newAllIds = [item.id, ...allIds];
  } else {
    // Insert at position
    newAllIds = [
      ...allIds.slice(0, position),
      item.id,
      ...allIds.slice(position),
    ];
  }

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

/**
 * Update an item in normalized state
 * @param normalized Current normalized state
 * @param itemId ID of item to update
 * @param updates Partial updates to apply
 * @returns New normalized state
 */
export function updateInNormalized<T extends { id: string }>(
  normalized: NormalizedState<T>,
  itemId: string,
  updates: Partial<T>,
): NormalizedState<T> {
  const existing = normalized.byId[itemId];
  if (!existing) {
    return normalized;
  }

  return {
    ...normalized,
    byId: {
      ...normalized.byId,
      [itemId]: { ...existing, ...updates },
    },
  };
}

/**
 * Remove an item from normalized state
 * @param normalized Current normalized state
 * @param itemId ID of item to remove
 * @returns New normalized state
 */
export function removeFromNormalized<T>(
  normalized: NormalizedState<T>,
  itemId: string,
): NormalizedState<T> {
  const { [itemId]: removed, ...byId } = normalized.byId;
  const allIds = normalized.allIds.filter((id) => id !== itemId);

  return { byId, allIds };
}

/**
 * Replace all items in normalized state
 * @param normalized Current normalized state
 * @param items New array of items
 * @returns New normalized state
 */
export function replaceNormalized<T extends { id: string }>(
  normalized: NormalizedState<T>,
  items: T[],
): NormalizedState<T> {
  return normalize(items);
}

/**
 * Merge items into normalized state (add new, update existing)
 * @param normalized Current normalized state
 * @param items Items to merge
 * @returns New normalized state
 */
export function mergeNormalized<T extends { id: string }>(
  normalized: NormalizedState<T>,
  items: T[],
): NormalizedState<T> {
  const newById = { ...normalized.byId };
  const newAllIds = [...normalized.allIds];
  const existingIds = new Set(normalized.allIds);

  for (const item of items) {
    if (item.id) {
      newById[item.id] = item;
      if (!existingIds.has(item.id)) {
        newAllIds.push(item.id);
        existingIds.add(item.id);
      }
    }
  }

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

/**
 * Get an item by ID from normalized state
 * @param normalized Normalized state
 * @param itemId ID to look up
 * @returns Item or undefined
 */
export function getById<T>(
  normalized: NormalizedState<T>,
  itemId: string,
): T | undefined {
  return normalized.byId[itemId];
}

/**
 * Get multiple items by IDs from normalized state
 * @param normalized Normalized state
 * @param itemIds Array of IDs to look up
 * @returns Array of items (may contain undefined for missing IDs)
 */
export function getByIds<T>(
  normalized: NormalizedState<T>,
  itemIds: string[],
): (T | undefined)[] {
  return itemIds.map((id) => normalized.byId[id]);
}

/**
 * Check if an item exists in normalized state
 * @param normalized Normalized state
 * @param itemId ID to check
 * @returns True if item exists
 */
export function hasId<T>(
  normalized: NormalizedState<T>,
  itemId: string,
): boolean {
  return itemId in normalized.byId;
}

/**
 * Get the count of items in normalized state
 * @param normalized Normalized state
 * @returns Number of items
 */
export function getCount<T>(normalized: NormalizedState<T>): number {
  return normalized.allIds.length;
}

/**
 * Create an empty normalized state
 * @returns Empty normalized state
 */
export function createEmptyNormalized<T>(): NormalizedState<T> {
  return { byId: {}, allIds: [] };
}

/**
 * Reorder items in normalized state
 * @param normalized Current normalized state
 * @param fromIndex Source index
 * @param toIndex Destination index
 * @returns New normalized state with reordered allIds
 */
export function reorderNormalized<T>(
  normalized: NormalizedState<T>,
  fromIndex: number,
  toIndex: number,
): NormalizedState<T> {
  const newAllIds = [...normalized.allIds];
  const [removed] = newAllIds.splice(fromIndex, 1);
  newAllIds.splice(toIndex, 0, removed);

  return {
    ...normalized,
    allIds: newAllIds,
  };
}

