import type { Ref } from 'react'

import { useMapWithInit } from './useMapWithInit.ts'
import { useStyleObserver } from './useStyleObserver.ts'

/**
 * Hook that returns a style object with observed CSS property values. Automatically updates when
 * the observed properties change.
 */
export function useStyle<T extends string>(
  properties: T[],
  options: {
    ref?: Ref<HTMLElement> | undefined
    initialValues?: Record<T, string>
    onChange?: (values: Record<T, string>) => void
  } = {},
) {
  const { ref, initialValues, onChange } = options

  const propertyValues = useMapWithInit(() =>
    initialValues
      ? (Object.entries(initialValues) as [T, string][])
      : properties.map((property) => [property, undefined]),
  )

  const callbackRef = useStyleObserver(
    properties,
    (records) => {
      let changed = false
      for (const record of records) {
        if (propertyValues.get(record.property) !== record.value) {
          propertyValues.set(record.property, record.value)
          changed = true
        }
      }
      if (changed) {
        /**
         * This assertion is safe because `immediate: true` means that all watched properties will
         * be included in the first call (and any property that is not set will be an empty string
         * rather than `undefined`; see `record.value`'s type).
         */
        onChange?.(Object.fromEntries(propertyValues) as Record<T, string>)
      }
    },
    [],
    { ...(ref !== undefined && { ref }), immediate: true },
  )

  return [callbackRef, Object.fromEntries(propertyValues) as Record<T, string | undefined>] as const
}
