"use client";

import { useCallback, useEffect, useRef, useState } from "react";

export const useControllableState = <T,>(
  externalValue: T | undefined,
  onExternalChange: ((value: T) => void) | undefined,
  defaultValue: T,
) => {
  const [internalValue, setInternalValue] = useState<T>(defaultValue);
  const value = externalValue !== undefined ? externalValue : internalValue;
  const valueRef = useRef(value);
  const isControlledRef = useRef(externalValue !== undefined);
  const onExternalChangeRef = useRef(onExternalChange);

  useEffect(() => {
    valueRef.current = value;
    isControlledRef.current = externalValue !== undefined;
    onExternalChangeRef.current = onExternalChange;
  }, [externalValue, onExternalChange, value]);

  const setValue = useCallback(
    (next: T | ((previous: T) => T)) => {
      const nextValue =
        typeof next === "function"
          ? (next as (previous: T) => T)(valueRef.current)
          : next;

      if (Object.is(valueRef.current, nextValue)) {
        return;
      }

      valueRef.current = nextValue;
      if (!isControlledRef.current) {
        setInternalValue(nextValue);
      }
      onExternalChangeRef.current?.(nextValue);
    },
    [],
  );

  return [value, setValue] as const;
};

export const useControllableObjectState = <T extends object>(
  externalValue: T | undefined,
  onExternalChange: ((value: T) => void) | undefined,
  defaultValue: T,
) => {
  const [value, setValue] = useControllableState(
    externalValue,
    onExternalChange,
    defaultValue,
  );

  const setField = useCallback(
    <K extends keyof T>(key: K, nextValue: T[K]) => {
      setValue((previous) =>
        Object.is(previous[key], nextValue)
          ? previous
          : { ...previous, [key]: nextValue },
      );
    },
    [setValue],
  );

  return [value, setValue, setField] as const;
};
