import { cssStateVariables } from '@graphprotocol/gds-css';
import { useMapWithInit } from "./useMapWithInit.js";
import { useRefWithInit } from "./useRefWithInit.js";
import { useStyleObserver } from "./useStyleObserver.js";
/**
 * This hook can be used in three different cases (which some components may need simultaneously):
 *
 * - Until Firefox supports style queries, every component that uses CSS states internally (i.e.
 *   contains `@state-*` classes) needs to use this hook and also spread `polyfillAttributes` on the
 *   root element, regardless of what follows.
 * - When a component exposes CSS states from a nested element (e.g.
 *   `u:has-nested-hover/ref:expose-hover` on the root element instead of `u:hover:state-hover`), it
 *   has to call this hook with all the state variables it exposes, even if it's only a particular
 *   state in a particular condition. For instance, it may expose the `active` state when a nested
 *   input is `:active` so it's also triggered with the keyboard, without needing to customize when
 *   `hover` is exposed; regardless, `{ pointer: undefined }` should be passed as the first
 *   argument, `variableInitialValues`. The "initial values" in that object are used during SSR and
 *   on the first render, and should be passed instead of `undefined` for variables whose initial
 *   state may differ from their default value (often the case for `checked` and `disabled`). Not
 *   doing so may result in a flash of incorrect styles, since we need to wait for the DOM to get
 *   style values (including the state variables). The hook returns a tuple with two values: the
 *   first one is a `ref` which should be passed to the root element, and the second one is an
 *   object with an `exposedAttributes` property, which you should spread on the root as well. This
 *   ensures that a class like `active:opacity-50` set by a consumer takes effect when the nested
 *   element is active, rather than when the root element is.
 * - When a component needs to know the state of a nested component or element that supports CSS
 *   states (that is, one that has classes like `u:hover:state-hover` or
 *   `u:has-nested-hover/ref:expose-hover`), then you can call this hook with the state variables
 *   you're interested in, pass the returned `ref` to the component/element in question, and check
 *   the returned `state` (tuple's second value).
 */
export function useCSSState(variableInitialValues, options = {}) {
    const { ref, onChange } = options;
    const variables = useRefWithInit(() => Object.keys(variableInitialValues).map((variableName) => {
        const isArbitrary = !(variableName in cssStateVariables);
        const defaultValue = !isArbitrary
            ? cssStateVariables[variableName].defaultValue
            : undefined;
        return {
            name: variableName,
            defaultValue,
            initialValue: variableInitialValues[variableName] ?? defaultValue,
            observedProperty: isArbitrary
                ? `--gds-state-${variableName}`
                : `--gds-observed-${variableName}`,
            exposedProperty: `--gds-exposed-${variableName}`,
            exposedAttribute: `data-gds-exposed-${variableName}`,
            polyfillAttribute: `data-gds-state-polyfill-${variableName}`,
        };
    })).current;
    const properties = useRefWithInit(() => variables.flatMap((variable) => [variable.observedProperty, variable.exposedProperty])).current;
    const propertyValues = useMapWithInit(() => variables.flatMap((variable) => [
        [variable.observedProperty, String(variable.initialValue ?? '')],
        [variable.exposedProperty, String(variable.initialValue ?? '')],
    ]));
    const getStateValue = (variable, type) => {
        const value = propertyValues.get(type === 'observed' ? variable.observedProperty : variable.exposedProperty);
        if (value === 'true')
            return true;
        if (value === 'false')
            return false;
        if (value === '')
            return undefined;
        return value;
    };
    const getState = () => Object.fromEntries(variables.map((variable) => [variable.name, getStateValue(variable, 'observed')]));
    const callbackRef = useStyleObserver(properties, (records) => {
        let statePropertiesChanged = false;
        for (const record of records) {
            if (propertyValues.get(record.property) !== record.value) {
                propertyValues.set(record.property, record.value);
                if (record.property.startsWith('--gds-state-') ||
                    record.property.startsWith('--gds-observed-')) {
                    statePropertiesChanged = true;
                }
            }
        }
        if (statePropertiesChanged && onChange) {
            onChange(getState());
        }
    }, [], { ...(ref && { ref }), immediate: true });
    const state = getState();
    const { exposedAttributes, polyfillAttributes } = variables.reduce((result, variable) => {
        const exposedValue = getStateValue(variable, 'exposed');
        if (exposedValue !== undefined) {
            result.exposedAttributes[variable.exposedAttribute] = String(exposedValue);
        }
        const observedValue = getStateValue(variable, 'observed');
        if (observedValue !== undefined) {
            result.polyfillAttributes[variable.polyfillAttribute] = String(observedValue);
        }
        return result;
    }, { exposedAttributes: {}, polyfillAttributes: {} });
    return [callbackRef, { ...state, exposedAttributes, polyfillAttributes }];
}
//# sourceMappingURL=useCSSState.js.map