// group rows into a Map of key → values, preserving input order
export const groupBy = <T, K, V>(
  rows: readonly T[],
  key: (row: T) => K,
  value: (row: T) => V,
): Map<K, V[]> => {
  const out = new Map<K, V[]>()
  for (const row of rows) {
    const k = key(row)
    const list = out.get(k)
    if (list) list.push(value(row))
    else out.set(k, [value(row)])
  }
  return out
}

// copy with the item at `from` moved to `to`; `to` is clamped into
// range, an out-of-range `from` is a no-op
export const moveItem = <T>(
  items: readonly T[],
  from: number,
  to: number,
): T[] => {
  const out = [...items]
  if (from < 0 || from >= items.length) return out
  const target = Math.max(0, Math.min(to, items.length - 1))
  const [item] = out.splice(from, 1)
  out.splice(target, 0, item as T)
  return out
}

// the by-id list edits every in-memory store repeats over API entities. each
// returns a fresh array but keeps the identity of untouched items, so a store's
// persist subscriber and memo selectors don't fire for unchanged rows

// the item with this id, or undefined
export const findById = <T extends {id: string}>(
  items: readonly T[],
  id: string,
): T | undefined => items.find((item) => item.id === id)

// replace the item with this id outright (a no-op copy if absent)
export const replaceById = <T extends {id: string}>(
  items: readonly T[],
  id: string,
  next: T,
): T[] => items.map((item) => (item.id === id ? next : item))

// apply a patch to the item with this id, leaving the rest by reference
export const patchById = <T extends {id: string}>(
  items: readonly T[],
  id: string,
  patch: (item: T) => T,
): T[] => items.map((item) => (item.id === id ? patch(item) : item))

// copy without the item with this id
export const removeById = <T extends {id: string}>(
  items: readonly T[],
  id: string,
): T[] => items.filter((item) => item.id !== id)

// values for these keys in key order, skipping keys the map lacks — the
// "hydrate ids through a lookup" step every by-id join repeats
export const pickPresent = <K, V>(
  map: Map<K, V>,
  keys: readonly K[],
): V[] => {
  const out: V[] = []
  for (const key of keys) {
    const value = map.get(key)
    if (value !== undefined) out.push(value)
  }
  return out
}

// split an array into fixed-size batches
export const chunk = <T>(
  items: readonly T[],
  size: number,
): T[][] => {
  const out: T[][] = []
  for (let i = 0; i < items.length; i += size)
    out.push(items.slice(i, i + size))
  return out
}
