import { isServer } from "@solidjs/web";
import { createEffect, createMemo, createSignal } from "solid-js";

/**
 * Image loading status reported by the probe. Mirrors Base UI React's
 * `ImageLoadingStatus` union (`packages/react/src/avatar/image/useImageLoadingStatus.ts`).
 *
 * It survives the move to async-native (`solidify.md` §4) because the Avatar
 * compound coordinates `<Avatar.Image>` and its sibling `<Avatar.Fallback>`
 * through a shared status signal in root context — sibling parts, not nested
 * subtrees, so a `<Loading>`/`<Errored>` boundary can't replace it. The union
 * is a *derived view* of the async probe below, not a hand-rolled state
 * machine: a failed image is a domain state (→ fallback), not an exception
 * that should escape to `<Errored>` — the same messages-vs-exceptions split
 * `createValidator` draws.
 */

/**
 * Inputs to {@link useImageLoadingStatus}. Each entry is an accessor so the
 * primitive can react to upstream prop changes without being re-created.
 * Matching the React helper, only `src`, `referrerPolicy`, and `crossOrigin`
 * participate in re-probing.
 */

/**
 * Probe a single image off-screen via `new window.Image()`, returning a
 * Promise that resolves once the resource loads and rejects when it errors.
 * This lets a consumer decide whether to actually mount a real `<img>` based
 * on the outcome — avoiding the broken-image flash that happens when the
 * visible `<img>` itself is asked to load a 404.
 *
 * The Promise is the unit the async `createMemo` in {@link useImageLoadingStatus}
 * hands to the reactive graph; recomputing the memo (on a `src` change) simply
 * abandons the previous probe — its eventual `onload`/`onerror` resolves an
 * orphaned Promise nobody is awaiting, so there's no manual "is this still the
 * active probe?" latch to maintain.
 */
function loadImage(src, referrerPolicy, crossOrigin) {
  return new Promise((resolve, reject) => {
    const image = new window.Image();
    image.onload = () => resolve();
    image.onerror = () => reject(new Error(`Failed to load image: ${src}`));
    if (referrerPolicy) {
      image.referrerPolicy = referrerPolicy;
    }
    // `crossOrigin` stays camelCase here — we're writing to a DOM property
    // on the probe element, not a JSX attribute. The JSX prop is
    // `crossorigin`; the IDL property is `crossOrigin`.
    image.crossOrigin = crossOrigin === undefined ? null : crossOrigin;
    image.src = src;

    // Fast path for cached/decoded images — `complete` is synchronously
    // true if the resource is already in the browser cache, in which case
    // `onload`/`onerror` may never fire.
    if (image.complete) {
      if (image.naturalWidth > 0) {
        resolve();
      } else {
        reject(new Error(`Failed to load image: ${src}`));
      }
    }
  });
}

/**
 * Runs a standalone image probe whose async outcome drives a discrete
 * `ImageLoadingStatus` accessor.
 *
 * Solid 2 shape (`solidify.md` §4 — RFC 05 async-native):
 *
 * - `probe` is an async `createMemo` returning {@link loadImage}'s Promise.
 *   It re-runs whenever `src`/`referrerPolicy`/`crossOrigin` flip; the prior
 *   in-flight probe is discarded by the graph (last-write-wins), so no manual
 *   `isActive` latch is needed.
 * - The bundled `createEffect(compute, { effect, error })` reads `probe()` in
 *   its compute half. While the Promise is pending the compute *suspends* —
 *   the `effect` (apply) half does not run, and no `ASYNC_OUTSIDE_LOADING_BOUNDARY`
 *   diagnostic fires (verified: a suspended *computation* without a `<Loading>`
 *   ancestor is silent; only a pending *read in render* requires the boundary).
 *   On resolve the apply half lands `"loaded"`; on reject the `error` half
 *   lands `"error"` — caught from the compute phase per RFC 01's bundle
 *   contract, so the rejection is handled here rather than escaping upward.
 * - A separate split effect tracks the probe inputs and lands `"loading"`
 *   synchronously the moment they change to a non-empty `src` (the bundle
 *   effect can't — it's suspended until the probe settles). An empty `src`
 *   lands `"error"` directly, preserving the React helper's "no loading flash
 *   for a missing src" behaviour (the memo also rejects for empty `src`, so
 *   the two agree).
 *
 * The returned `status` accessor is a plain signal read; callers can drive
 * further effects off it (mirror into a root context, forward into a consumer
 * callback).
 */
export function useImageLoadingStatus(options) {
  // `ownedWrite: true` — `setStatus` is called from the apply/error halves of
  // `createEffect` (owned scope). Without it, dev-mode emits
  // SIGNAL_WRITE_IN_OWNED_SCOPE.
  const [status, setStatus] = createSignal("idle", {
    name: "useImageLoadingStatus.status",
    ownedWrite: true
  });

  // The RC's synchronous server renderer evaluates memos during render. Image
  // probing is browser-only; keep the initial idle status so the fallback is
  // rendered on the server and the client owns the first real probe.
  if (isServer) return status;

  // Async probe — source of truth. A non-empty `src` resolves on load /
  // rejects on error; an empty `src` is a domain "error" state, surfaced as a
  // rejected Promise so the bundle's `error` half routes it to `"error"`.
  const probe = createMemo(() => {
    const src = options.src();
    if (!src) {
      return Promise.reject(new Error("Avatar image: missing src"));
    }
    return loadImage(src, options.referrerPolicy(), options.crossOrigin());
  }, {
    name: "useImageLoadingStatus.probe"
  });

  // Land "loading" (or "error" for a missing src) synchronously when the
  // inputs change. The bundle effect below is suspended while the probe is
  // pending, so it can't surface the in-flight state itself.
  createEffect(() => options.src(), src => {
    setStatus(src ? "loading" : "error");
  });

  // Resolve the settled probe into the terminal status. Reading `probe()`
  // suspends the compute while pending; the apply half fires only once the
  // image has loaded, the error half only on failure.
  createEffect(() => {
    probe();
    return undefined;
  }, {
    effect: () => {
      setStatus("loaded");
    },
    error: (_err, cleanup) => {
      setStatus("error");
      cleanup();
    }
  });
  return status;
}
//# sourceMappingURL=use-image-loading-status.jsx.map
