import { resolver } from "@rocicorp/resolver";
import React, { useSyncExternalStore } from "react";
import { deepClone } from "../../shared/src/deep-clone.js";
import "compare-utf8";
import "../../zero-protocol/src/ast.js";
import "js-xxhash";
import { asQueryInternals } from "../../zql/src/query/query-internals.js";
import { addContextToQuery } from "../../zql/src/query/query-registry.js";
import { DEFAULT_TTL_MS } from "../../zql/src/query/ttl.js";
import { useZero } from "./zero-provider.js";
const reactUse = React.use;
const suspend = reactUse ? reactUse : (p) => {
  throw p;
};
function useQuery(query, options) {
  let enabled = true;
  let ttl = DEFAULT_TTL_MS;
  if (typeof options === "boolean") {
    enabled = options;
  } else if (options) {
    ({ enabled = true, ttl = DEFAULT_TTL_MS } = options);
  }
  const zero = useZero();
  const q = query ? addContextToQuery(query, zero.context) : void 0;
  const view = q ? viewStore.getView(zero, q, enabled, ttl) : void 0;
  return useSyncExternalStore(
    view?.subscribeReactInternals ?? disabledSubscriber,
    view?.getSnapshot ?? getDisabledSnapshot,
    view?.getSnapshot ?? getDisabledSnapshot
  );
}
function useSuspenseQuery(query, options) {
  let enabled = true;
  let ttl = DEFAULT_TTL_MS;
  let suspendUntil = "partial";
  if (typeof options === "boolean") {
    enabled = options;
  } else if (options) {
    ({
      enabled = true,
      ttl = DEFAULT_TTL_MS,
      suspendUntil = "complete"
    } = options);
  }
  const zero = useZero();
  const q = query ? addContextToQuery(query, zero.context) : void 0;
  const view = q ? viewStore.getView(zero, q, enabled, ttl) : void 0;
  const snapshot = useSyncExternalStore(
    view?.subscribeReactInternals ?? disabledSubscriber,
    view?.getSnapshot ?? getDisabledSnapshot,
    view?.getSnapshot ?? getDisabledSnapshot
  );
  if (view && enabled) {
    if (suspendUntil === "complete" && !view.complete) {
      suspend(view.waitForComplete());
    }
    if (suspendUntil === "partial" && !view.nonEmpty) {
      suspend(view.waitForNonEmpty());
    }
  }
  return snapshot;
}
const emptyArray = [];
const disabledSubscriber = () => () => {
};
const resultTypeUnknown = { type: "unknown" };
const resultTypeComplete = { type: "complete" };
const resultTypeError = { type: "error" };
const disabledQuerySnapshot = [void 0, resultTypeUnknown];
const getDisabledSnapshot = () => disabledQuerySnapshot;
const emptySnapshotSingularUnknown = [void 0, resultTypeUnknown];
const emptySnapshotSingularComplete = [void 0, resultTypeComplete];
const emptySnapshotSingularErrorUnknown = [void 0, resultTypeError];
const emptySnapshotPluralUnknown = [emptyArray, resultTypeUnknown];
const emptySnapshotPluralComplete = [emptyArray, resultTypeComplete];
const emptySnapshotErrorUnknown = [emptyArray, resultTypeError];
function getDefaultSnapshot(singular) {
  return singular ? emptySnapshotSingularUnknown : emptySnapshotPluralUnknown;
}
function getSnapshot(singular, data, resultType, retryFn, error) {
  if (singular && data === void 0) {
    switch (resultType) {
      case "error":
        if (error) {
          return [
            void 0,
            makeError(retryFn, error)
          ];
        }
        return emptySnapshotSingularErrorUnknown;
      case "complete":
        return emptySnapshotSingularComplete;
      case "unknown":
        return emptySnapshotSingularUnknown;
    }
  }
  if (!singular && data.length === 0) {
    switch (resultType) {
      case "error":
        if (error) {
          return [
            emptyArray,
            makeError(retryFn, error)
          ];
        }
        return emptySnapshotErrorUnknown;
      case "complete":
        return emptySnapshotPluralComplete;
      case "unknown":
        return emptySnapshotPluralUnknown;
    }
  }
  switch (resultType) {
    case "error":
      if (error) {
        return [data, makeError(retryFn, error)];
      }
      return [
        data,
        makeError(retryFn, {
          error: "app",
          message: "An unknown error occurred"
        })
      ];
    case "complete":
      return [data, resultTypeComplete];
    case "unknown":
      return [data, resultTypeUnknown];
  }
}
function makeError(retry, error) {
  const message = error.message ?? "An unknown error occurred";
  return {
    type: "error",
    retry,
    refetch: retry,
    error: {
      type: error.error,
      message,
      ...error.details ? { details: error.details } : {}
    }
  };
}
class ViewStore {
  #views = /* @__PURE__ */ new Map();
  constructor() {
  }
  getView(zero, q, enabled, ttl) {
    const qi = asQueryInternals(q);
    if (!enabled) {
      return {
        getSnapshot: () => getDefaultSnapshot(qi.format.singular),
        subscribeReactInternals: disabledSubscriber,
        updateTTL: () => {
        },
        waitForComplete: () => Promise.resolve(),
        waitForNonEmpty: () => Promise.resolve(),
        complete: false,
        nonEmpty: false
      };
    }
    const hash = qi.hash() + zero.clientID;
    let existing = this.#views.get(hash);
    if (!existing) {
      existing = new ViewWrapper(q, zero, ttl, (view) => {
        const currentView = this.#views.get(hash);
        if (currentView && currentView !== view) {
          return;
        }
        this.#views.delete(hash);
      });
      this.#views.set(hash, existing);
    } else {
      existing.updateTTL(ttl);
    }
    return existing;
  }
}
const viewStore = new ViewStore();
class ViewWrapper {
  #view;
  #onDematerialized;
  #query;
  #snapshot;
  #reactInternals = /* @__PURE__ */ new Set();
  #ttl;
  #complete = false;
  #completeResolver = resolver();
  #nonEmpty = false;
  #nonEmptyResolver = resolver();
  #zero;
  #singular;
  #destroyTimer;
  constructor(query, zero, ttl, onDematerialized) {
    this.#query = query;
    this.#zero = zero;
    this.#ttl = ttl;
    this.#onDematerialized = onDematerialized;
    const { singular } = asQueryInternals(query).format;
    this.#singular = singular;
    this.#snapshot = getDefaultSnapshot(singular);
    this.#materializeIfNeeded();
  }
  #onData = (snap, resultType, error) => {
    const data = snap === void 0 ? snap : deepClone(snap);
    this.#snapshot = getSnapshot(
      this.#singular,
      data,
      resultType,
      this.#retry,
      error
    );
    if (resultType === "complete" || resultType === "error") {
      this.#complete = true;
      this.#completeResolver.resolve();
      this.#nonEmpty = true;
      this.#nonEmptyResolver.resolve();
    }
    if (this.#singular ? this.#snapshot[0] !== void 0 : this.#snapshot[0].length !== 0) {
      this.#nonEmpty = true;
      this.#nonEmptyResolver.resolve();
    }
    for (const internals of this.#reactInternals) {
      internals();
    }
  };
  /**
   * Called by the user to force a retry of the query
   * in the case the query errored.
   */
  #retry = () => {
    this.#view?.destroy();
    this.#view = void 0;
    this.#materializeIfNeeded();
  };
  #materializeIfNeeded = () => {
    if (this.#view) {
      return;
    }
    this.#view = this.#zero.materialize(this.#query, {
      ttl: this.#ttl
    });
    this.#view.addListener(this.#onData);
  };
  getSnapshot = () => this.#snapshot;
  subscribeReactInternals = (internals) => {
    this.#reactInternals.add(internals);
    if (this.#destroyTimer !== void 0) {
      clearTimeout(this.#destroyTimer);
      this.#destroyTimer = void 0;
    }
    this.#materializeIfNeeded();
    return () => {
      this.#reactInternals.delete(internals);
      if (this.#reactInternals.size === 0) {
        this.#destroyTimer = setTimeout(() => {
          this.#destroyTimer = void 0;
          if (this.#view === void 0) {
            return;
          }
          if (this.#reactInternals.size > 0) {
            return;
          }
          this.#view.destroy();
          this.#view = void 0;
          this.#complete = false;
          this.#completeResolver = resolver();
          this.#nonEmpty = false;
          this.#nonEmptyResolver = resolver();
          this.#onDematerialized(this);
        }, 10);
      }
    };
  };
  updateTTL(ttl) {
    this.#ttl = ttl;
    this.#view?.updateTTL(ttl);
  }
  get complete() {
    return this.#complete;
  }
  waitForComplete() {
    return this.#completeResolver.promise;
  }
  get nonEmpty() {
    return this.#nonEmpty;
  }
  waitForNonEmpty() {
    return this.#nonEmptyResolver.promise;
  }
}
export {
  ViewStore,
  useQuery,
  useSuspenseQuery
};
//# sourceMappingURL=use-query.js.map
