/**
 * `mergeRootProps(internal, user)` — public slot-function prop merger.
 *
 * Distinct from `mergePropsN` (used by ~20 React-port primitives). This
 * one is for the Solid 2.0-native compound surface where a primitive
 * exposes `c.trigger()` / `c.root()` slot functions and the consumer
 * spreads them onto an element with `<Tag {...c.trigger()} />`.
 *
 * Per-key resolution:
 * - `on*` handlers: internal runs first, user runs after unless the
 *   event was `preventDefault()`-ed by internal. `user[k] === undefined`
 *   does NOT clear internal — it's treated as "absent". This is the key
 *   difference from Solid's `merge` (RFC 04) and what stops consumers
 *   accidentally killing library behaviour by spreading
 *   `{ onClick: undefined }`.
 * - `class`: returns `[internal.class, user.class]` per RFC 07 — Solid's
 *   class binding handles nested clsx shapes per entry. No string concat.
 * - `style`: object declarations merge with user values winning. Mixed
 *   object/string values serialize deterministically with internal
 *   declarations first and user declarations second.
 * - `ref`: array form `[internal.ref, user.ref]` (skip absent sides;
 *   single value if only one side present). Solid's `applyRef` flattens.
 * - everything else (`data-*`, `aria-*`, plain attrs): user wins when
 *   defined, internal provides the reactive default. `user[k] ===
 *   undefined` means internal wins.
 *
 * Reactivity: getters delegate reads back to `internal[k]` / `user[k]` so
 * source-side getters (e.g. `get "data-state"() { return open() ? ... }`)
 * stay live across spreads. The output is a plain object — Solid's
 * compiled spread iterates `Object.keys` once at first spread and binds a
 * render effect per key; each binding then reads through the getter on
 * every relevant flush.
 *
 * Implementation: pre-bake a getter per key in `keys(internal) ∪ keys(user)`
 * at call time. Specialised per which side(s) declared the key — the
 * cheap path (internal-only key) skips per-access branching. Faster than
 * the prior Proxy form (~2.4× by microbench: 123ms → 51ms for 7M reads)
 * and produces an indistinguishable shape for downstream consumers
 * (`Object.keys`, `Object.getOwnPropertyDescriptor`, `in` for known keys
 * all match).
 *
 * Slot prop bags have a static key set at the call site, so pre-baking
 * is correct. Dynamic key sets that grow over time still need `mergePropsN`.
 */

import { untrack } from "solid-js";
const EVENT_HANDLER_RE = /^on[A-Z]/;
function isEventHandlerKey(key) {
  return EVENT_HANDLER_RE.test(key);
}
function composeHandlers(internal, user) {
  return (event, ...rest) => {
    internal(event, ...rest);
    if (event && event.defaultPrevented) return;
    user(event, ...rest);
  };
}

// Lazy resolvers — invoked on every getter access so source-side getters
// stay reactive. Each resolver re-reads its sides; the per-key getter
// installed on the output binds these resolvers via closure.

function resolveClass(internalV, userV) {
  if (internalV === undefined) return userV;
  if (userV === undefined) return internalV;
  return [internalV, userV];
}
function isRemovedStyle(value) {
  return value === undefined || value === null || value === false;
}
function cssPropertyName(name) {
  if (name.startsWith("--") || name.includes("-")) return name;
  const dashed = name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
  return /^ms[A-Z]/.test(name) ? `-${dashed}` : dashed;
}
function normalizeStyleObject(value) {
  const normalized = {};
  for (const [name, declaration] of Object.entries(value)) {
    if (isRemovedStyle(declaration)) continue;
    normalized[cssPropertyName(name)] = declaration;
  }
  return normalized;
}
function serializeStyleObject(value) {
  const declarations = [];
  for (const [name, declaration] of Object.entries(value)) {
    if (isRemovedStyle(declaration)) continue;
    declarations.push(`${cssPropertyName(name)}:${String(declaration)}`);
  }
  return declarations.join(";");
}
function joinSerializedStyles(internal, user) {
  const left = internal.trim().replace(/;+$/, "");
  const right = user.trim().replace(/^;+/, "");
  if (!left) return right;
  if (!right) return left;
  return `${left};${right}`;
}

/**
 * Merge two Solid-native style values with the later value taking precedence.
 * Object/object inputs remain objects. If either side is serialized CSS, the
 * result is serialized with internal declarations first and user declarations
 * second, which is deterministic across browser and server rendering.
 */
export function mergeElementStyles(internal, user) {
  if (isRemovedStyle(internal)) return user;
  if (isRemovedStyle(user)) return internal;
  if (typeof internal !== "string" && typeof user !== "string") {
    const merged = normalizeStyleObject(internal);
    for (const [name, declaration] of Object.entries(user)) {
      const property = cssPropertyName(name);
      if (isRemovedStyle(declaration)) delete merged[property];else merged[property] = declaration;
    }
    return merged;
  }
  const internalCss = typeof internal === "string" ? internal : serializeStyleObject(internal);
  const userCss = typeof user === "string" ? user : serializeStyleObject(user);
  return joinSerializedStyles(internalCss, userCss);
}
function resolveStyle(internalV, userV) {
  return mergeElementStyles(internalV, userV);
}
function resolveRef(internalV, userV) {
  if (internalV === undefined) return userV;
  if (userV === undefined) return internalV;
  return [internalV, userV];
}
function resolveHandler(internalV, userV) {
  const internalIsFn = typeof internalV === "function";
  const userIsFn = typeof userV === "function";
  if (!internalIsFn && !userIsFn) return undefined;
  if (!userIsFn) return internalV;
  if (!internalIsFn) return userV;
  return composeHandlers(internalV, userV);
}
function resolveAttr(internalV, userV, userHas) {
  if (userHas && userV !== undefined) return userV;
  return internalV;
}

/**
 * Accepts any two prop bags and returns the merged bag as a plain
 * `Record<string, unknown>` — which is all every call site needs (each one
 * either assigns the result to a `Record<string, unknown>` or spreads it onto
 * a `dynamic()` element). The widening cast lives HERE, once, instead of forcing every
 * call site to write `internal` just to
 * satisfy a generic whose precise return type nothing consumes.
 */
export function mergeRootProps(internal, user) {
  const i = internal;
  const u = user;
  const out = {};

  // Enumerate keys in `untrack`: `Object.keys` on a Solid props proxy fires
  // its `ownKeys` trap, which reads the reactive source. The slot key set is
  // static at the call site (see the header note), so this read must NOT be
  // tracked — otherwise it trips `[STRICT_READ_UNTRACKED]` for every consumer
  // (e.g. <PopoverTitle>) and floods the dev console. The per-key getters
  // defined below stay fully reactive.
  const internalKeys = untrack(() => Object.keys(i));
  const userKeys = u !== undefined ? untrack(() => Object.keys(u)) : [];
  // Membership tests below use this plain Set rather than `key in u`: the
  // `in` operator fires the Solid props-proxy's `has` trap, which reads the
  // reactive source untracked and trips `[STRICT_READ_UNTRACKED]` (the second
  // flood source, alongside the `ownKeys` enumeration above). The key set is
  // static at the call site, so a one-shot snapshot is correct.
  const userKeySet = new Set(userKeys);
  const seen = new Set();
  const define = (key, getter) => {
    if (seen.has(key)) return;
    seen.add(key);
    Object.defineProperty(out, key, {
      enumerable: true,
      configurable: true,
      get: getter
    });
  };

  // Internal-side keys first. Specialise the getter based on whether
  // `user` also declares the key, so the cheap path (internal-only) skips
  // per-access branching.
  for (const key of internalKeys) {
    const userHas = userKeySet.has(key);
    if (key === "class") {
      define(key, userHas ? () => resolveClass(i[key], u[key]) : () => i[key]);
    } else if (key === "style") {
      define(key, userHas ? () => resolveStyle(i[key], u[key]) : () => i[key]);
    } else if (key === "ref") {
      define(key, userHas ? () => resolveRef(i[key], u[key]) : () => i[key]);
    } else if (isEventHandlerKey(key)) {
      define(key, userHas ? () => resolveHandler(i[key], u[key]) : () => i[key]);
    } else {
      define(key, userHas ? () => resolveAttr(i[key], u[key], true) : () => i[key]);
    }
  }

  // User-only keys — specialised but trivial (no internal side to merge).
  // Keep a getter (not a data property) so user-side getters stay reactive
  // in the same way internal-side getters do.
  for (const key of userKeys) {
    if (seen.has(key)) continue;
    define(key, () => u[key]);
  }
  return out;
}
//# sourceMappingURL=merge-root-props.jsx.map
