/**
 * `FloatingRootStore` — Solid-native.
 *
 * Backed by per-key `createSignal`s exposed via a getter-bag
 * `state` proxy. Property reads on `store.state` track per-key
 * when invoked inside a reactive context; outside one, they
 * return the current value non-reactively. Writes do NOT flush:
 * they batch to the next microtask per Solid 2.0's default, and
 * every consumer reads through a reactive surface that settles on
 * that flush. Imperative call sites needing same-tick visibility
 * (chiefly tests) must call `flush()` themselves before reading
 * back. Each signal is constructed with
 * `ownedWrite: true` because writers are routinely invoked from
 * inside reactive owners (Solid 2.0 dev would otherwise raise
 * `SIGNAL_WRITE_IN_OWNED_SCOPE`).
 *
 * ### Public contract
 *
 *   - `state` — reactive proxy. Read `state.open` etc.; tracks
 *     per-key in reactive scopes.
 *   - `context` — non-reactive bag (`dataRef`, `events`, `nested`,
 *     `triggerElements`). Fields here are intentionally imperative.
 *   - `useState(key)` — convenience wrapper. Returns `state[key]`
 *     (with the `referenceElement → positionReference` fallback).
 *     Equivalent in tracking behavior to a direct `state.*` read.
 *   - `useStateAccessor(key)` — returns `() => useState(key)`.
 *   - `sync(key, value)` / `set(key, value)` — identity-compare
 *     setters. No-op on unchanged.
 *   - `update(partial)` — shallow merge; no-op if nothing changed.
 *   - `getSnapshot()` — alias for `state`. Same reference;
 *     tracking still depends on the caller's context.
 *   - `syncOpenEvent` / `dispatchOpenChange` / `setOpen` —
 *     open-change dispatch.
 */

import { createSignal, untrack } from "solid-js";
import { createEventEmitter } from "../utils/create-event-emitter.jsx";
import { isClickLikeEvent } from "../utils/event.jsx";

// ---------------------------------------------------------------------------
// State + context shapes
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// `FloatingRootStore`
// ---------------------------------------------------------------------------

export class FloatingRootStore {
  // Per-key signals. `ownedWrite: true` because writers are routinely
  // invoked from inside reactive owners (effects, computeds) that observe
  // other parts of state — Solid 2.0's dev runtime would otherwise raise
  // `SIGNAL_WRITE_IN_OWNED_SCOPE`.

  constructor(options) {
    const {
      syncOnly,
      nested,
      onOpenChange,
      triggerElements,
      referenceElement,
      floatingElement,
      floatingId,
      open,
      transitionStatus
    } = options;
    [this._open, this._setOpenSignal] = createSignal(open, {
      name: "floating-root-store.open",
      ownedWrite: true
    });
    [this._transitionStatus, this._setTransitionStatus] = createSignal(transitionStatus, {
      name: "floating-root-store.transitionStatus",
      ownedWrite: true
    });
    [this._referenceElement, this._setReferenceElement] = createSignal(referenceElement, {
      name: "floating-root-store.referenceElement",
      ownedWrite: true
    });
    [this._floatingElement, this._setFloatingElement] = createSignal(floatingElement, {
      name: "floating-root-store.floatingElement",
      ownedWrite: true
    });
    [this._positionReference, this._setPositionReference] = createSignal(referenceElement, {
      name: "floating-root-store.positionReference",
      ownedWrite: true
    });
    [this._domReferenceElement, this._setDomReferenceElement] = createSignal(referenceElement, {
      name: "floating-root-store.domReferenceElement",
      ownedWrite: true
    });
    [this._floatingId, this._setFloatingId] = createSignal(floatingId, {
      name: "floating-root-store.floatingId",
      ownedWrite: true
    });
    const self = this;
    this._stateProxy = {
      get open() {
        return self._open();
      },
      get transitionStatus() {
        return self._transitionStatus();
      },
      get referenceElement() {
        return self._referenceElement();
      },
      get floatingElement() {
        return self._floatingElement();
      },
      get positionReference() {
        return self._positionReference();
      },
      get domReferenceElement() {
        return self._domReferenceElement();
      },
      get floatingId() {
        return self._floatingId();
      }
    };
    this.context = {
      onOpenChange,
      dataRef: {
        current: {}
      },
      events: createEventEmitter(),
      nested,
      triggerElements
    };
    this.syncOnly = syncOnly;
  }
  _getAccessor(key) {
    switch (key) {
      case "open":
        return this._open;
      case "transitionStatus":
        return this._transitionStatus;
      case "referenceElement":
        return this._referenceElement;
      case "floatingElement":
        return this._floatingElement;
      case "positionReference":
        return this._positionReference;
      case "domReferenceElement":
        return this._domReferenceElement;
      case "floatingId":
        return this._floatingId;
      default:
        {
          const _exhaustive = key;
          throw new Error(`Unknown key: ${String(_exhaustive)}`);
        }
    }
  }
  _writeKey(key, value) {
    switch (key) {
      case "open":
        this._setOpenSignal(value);
        return;
      case "transitionStatus":
        this._setTransitionStatus(value);
        return;
      case "referenceElement":
        this._setReferenceElement(value);
        return;
      case "floatingElement":
        this._setFloatingElement(value);
        return;
      case "positionReference":
        this._setPositionReference(value);
        return;
      case "domReferenceElement":
        this._setDomReferenceElement(value);
        return;
      case "floatingId":
        this._setFloatingId(value);
        return;
      default:
        {
          const _exhaustive = key;
          throw new Error(`Unknown key: ${String(_exhaustive)}`);
        }
    }
  }

  // ---------------------------------------------------------------------
  // Reactive access
  // ---------------------------------------------------------------------

  /**
   * Reactive store proxy. Property reads inside tracking contexts
   * subscribe to the specific key; reads in untracked contexts
   * (event handlers) return the current value without subscribing.
   */
  get state() {
    return this._stateProxy;
  }

  /**
   * Current state view. Same reference as `state`. Property reads
   * outside tracking contexts don't subscribe — use this when the
   * spelling "snapshot" makes intent clearer at the call site.
   */
  getSnapshot = () => this._stateProxy;

  /**
   * Returns the current value for `key`, subscribing the caller's
   * tracking context to updates of that specific key. The
   * `"referenceElement"` selector prefers `positionReference` over
   * the raw slot to match the React selector.
   */
  useState(key) {
    if (key === "referenceElement") {
      return this._positionReference() ?? this._referenceElement();
    }
    return this._getAccessor(key)();
  }

  /**
   * Returns a live `Accessor<T>` for `key`. Equivalent to
   * `() => store.useState(key)` with a stable function identity.
   */
  useStateAccessor(key) {
    return () => this.useState(key);
  }

  // ---------------------------------------------------------------------
  // Writers
  // ---------------------------------------------------------------------

  /**
   * Set a specific key. `createStore` handles the identity-compare
   * skip internally — writes equal to the current value are no-ops.
   */
  set(key, value) {
    const accessor = this._getAccessor(key);
    if (Object.is(untrack(() => accessor()), value)) return;
    this._writeKey(key, value);
    // No per-writer flush: writes batch to the next microtask. Every
    // production consumer reads store state through a reactive surface
    // (getter prop bags, positioning/render effects) that settles on
    // that flush, so synchronous visibility was only ever needed by
    // imperative test reads — those flush() explicitly before asserting.
  }

  /**
   * Alias for `set`. Kept for the call-site spelling that the React
   * source uses (`useSyncedValue`).
   */
  sync(key, value) {
    this.set(key, value);
  }

  /**
   * Merge a partial into state. Uses the draft-first setter form so
   * only the keys that actually changed notify subscribers.
   */
  update(changes) {
    let changed = false;
    for (const key in changes) {
      const k = key;
      const accessor = this._getAccessor(k);
      if (!Object.is(untrack(() => accessor()), changes[k])) {
        changed = true;
        break;
      }
    }
    if (!changed) return;
    for (const key in changes) {
      const k = key;
      this.set(k, changes[k]);
    }
  }

  // ---------------------------------------------------------------------
  // Open-change dispatch
  // ---------------------------------------------------------------------

  /**
   * Syncs the event used by hover logic to distinguish hover-open from
   * click-like interaction. Click events can upgrade a hover-open, but
   * a pending hover-open cannot overwrite a click-open.
   */
  syncOpenEvent = (newOpen, event) => {
    if (!newOpen || !untrack(() => this._open()) || event != null && isClickLikeEvent(event)) {
      this.context.dataRef.current.openEvent = newOpen ? event : undefined;
    }
  };

  /**
   * Runs the root-owned side effects for an open state change: syncs the
   * open-event bookkeeping and emits `openchange` on the events bus.
   */
  dispatchOpenChange = (newOpen, eventDetails) => {
    this.syncOpenEvent(newOpen, eventDetails.event);
    const details = {
      open: newOpen,
      reason: eventDetails.reason,
      nativeEvent: eventDetails.event,
      nested: this.context.nested,
      triggerElement: eventDetails.trigger
    };
    this.context.events.emit("openchange", details);
  };

  /**
   * Emits `openchange` and calls the context `onOpenChange` callback.
   * In `syncOnly` mode the dispatch step is skipped — the owning popup
   * store is responsible for emitting the event itself.
   */
  setOpen = (newOpen, eventDetails) => {
    if (this.syncOnly) {
      this.context.onOpenChange?.(newOpen, eventDetails);
      return;
    }
    this.dispatchOpenChange(newOpen, eventDetails);
    this.context.onOpenChange?.(newOpen, eventDetails);
  };
}
//# sourceMappingURL=floating-root-store.jsx.map
