import { useCallback, useState } from 'react'

export type KeyedRow<T> = { key: string; value: T }

const toRows = <T>(values: readonly T[]): KeyedRow<T>[] =>
  values.map((value) => ({ key: crypto.randomUUID(), value }))

/**
 * Редактируемый список со стабильными ключами строк.
 *
 * Индекс в качестве `key` не годится там, где строки удаляются из середины или переставляются:
 * React сопоставит уцелевшие DOM-узлы не с теми значениями, и фокус вместе с внутренним состоянием
 * полей переедет в соседнюю строку. Ключ существует только на клиенте и наружу не уезжает —
 * в API отправляется `values`.
 *
 * Заодно правки строк перестают быть замыканиями по индексу внутри JSX: `patch`/`remove` работают
 * по ключу и не зависят от текущей позиции строки в списке.
 */
export function useKeyedList<T>(initial: readonly T[]) {
  const [rows, setRows] = useState<KeyedRow<T>[]>(() => toRows(initial))

  const reset = useCallback((values: readonly T[]) => setRows(toRows(values)), [])

  const add = useCallback((value: T) => setRows((c) => [...c, ...toRows([value])]), [])

  const remove = useCallback(
    (key: string) => setRows((c) => c.filter((row) => row.key !== key)),
    [],
  )

  const patch = useCallback(
    (key: string, next: (value: T) => T) =>
      setRows((c) =>
        c.map((row) => (row.key === key ? { key: row.key, value: next(row.value) } : row)),
      ),
    [],
  )

  return { rows, values: rows.map((row) => row.value), reset, add, remove, patch }
}
