import { replaceData as p, noop as c, resolveEnabled as y, skipToken as v, resolveStaleTime as m, timeUntilStale as b, ensureQueryFn as S } from "./utils.js";
import { notifyManager as F } from "./notifyManager.js";
import { createRetryer as C, CancelledError as d, canFetch as g } from "./retryer.js";
import { Removable as q } from "./removable.js";
var x = class extends q {
  #i;
  #n;
  #e;
  #r;
  #t;
  #o;
  #a;
  constructor(t) {
    super(), this.#a = !1, this.#o = t.defaultOptions, this.setOptions(t.options), this.observers = [], this.#r = t.client, this.#e = this.#r.getQueryCache(), this.queryKey = t.queryKey, this.queryHash = t.queryHash, this.#i = f(this.options), this.state = t.state ?? this.#i, this.scheduleGc();
  }
  get meta() {
    return this.options.meta;
  }
  get promise() {
    return this.#t?.promise;
  }
  setOptions(t) {
    if (this.options = { ...this.#o, ...t }, this.updateGcTime(this.options.gcTime), this.state && this.state.data === void 0) {
      const e = f(this.options);
      e.data !== void 0 && (this.setState(
        l(e.data, e.dataUpdatedAt)
      ), this.#i = e);
    }
  }
  optionalRemove() {
    !this.observers.length && this.state.fetchStatus === "idle" && this.#e.remove(this);
  }
  setData(t, e) {
    const i = p(this.state.data, t, this.options);
    return this.#s({
      data: i,
      type: "success",
      dataUpdatedAt: e?.updatedAt,
      manual: e?.manual
    }), i;
  }
  setState(t, e) {
    this.#s({ type: "setState", state: t, setStateOptions: e });
  }
  cancel(t) {
    const e = this.#t?.promise;
    return this.#t?.cancel(t), e ? e.then(c).catch(c) : Promise.resolve();
  }
  destroy() {
    super.destroy(), this.cancel({ silent: !0 });
  }
  get resetState() {
    return this.#i;
  }
  reset() {
    this.destroy(), this.setState(this.resetState);
  }
  isActive() {
    return this.observers.some(
      (t) => y(t.options.enabled, this) !== !1
    );
  }
  isDisabled() {
    return this.getObserversCount() > 0 ? !this.isActive() : this.options.queryFn === v || !this.isFetched();
  }
  isFetched() {
    return this.state.dataUpdateCount + this.state.errorUpdateCount > 0;
  }
  isStatic() {
    return this.getObserversCount() > 0 ? this.observers.some(
      (t) => m(t.options.staleTime, this) === "static"
    ) : !1;
  }
  isStale() {
    return this.getObserversCount() > 0 ? this.observers.some(
      (t) => t.getCurrentResult().isStale
    ) : this.state.data === void 0 || this.state.isInvalidated;
  }
  isStaleByTime(t = 0) {
    return this.state.data === void 0 ? !0 : t === "static" ? !1 : this.state.isInvalidated ? !0 : !b(this.state.dataUpdatedAt, t);
  }
  onFocus() {
    this.observers.find((e) => e.shouldFetchOnWindowFocus())?.refetch({ cancelRefetch: !1 }), this.#t?.continue();
  }
  onOnline() {
    this.observers.find((e) => e.shouldFetchOnReconnect())?.refetch({ cancelRefetch: !1 }), this.#t?.continue();
  }
  addObserver(t) {
    this.observers.includes(t) || (this.observers.push(t), this.clearGcTimeout(), this.#e.notify({ type: "observerAdded", query: this, observer: t }));
  }
  removeObserver(t) {
    this.observers.includes(t) && (this.observers = this.observers.filter((e) => e !== t), this.observers.length || (this.#t && (this.#a || this.#h() ? this.#t.cancel({ revert: !0 }) : this.#t.cancelRetry()), this.scheduleGc()), this.#e.notify({ type: "observerRemoved", query: this, observer: t }));
  }
  getObserversCount() {
    return this.observers.length;
  }
  #h() {
    return this.state.fetchStatus === "paused" && this.state.status === "pending";
  }
  invalidate() {
    this.state.isInvalidated || this.#s({ type: "invalidate" });
  }
  async fetch(t, e) {
    if (this.state.fetchStatus !== "idle" && // If the promise in the retryer is already rejected, we have to definitely
    // re-start the fetch; there is a chance that the query is still in a
    // pending state when that happens
    this.#t?.status() !== "rejected") {
      if (this.state.data !== void 0 && e?.cancelRefetch)
        this.cancel({ silent: !0 });
      else if (this.#t)
        return this.#t.continueRetry(), this.#t.promise;
    }
    if (t && this.setOptions(t), !this.options.queryFn) {
      const s = this.observers.find((o) => o.options.queryFn);
      s && this.setOptions(s.options);
    }
    process.env.NODE_ENV !== "production" && (Array.isArray(this.options.queryKey) || console.error(
      "As of v4, queryKey needs to be an Array. If you are using a string like 'repoData', please change it to an Array, e.g. ['repoData']"
    ));
    const i = new AbortController(), r = (s) => {
      Object.defineProperty(s, "signal", {
        enumerable: !0,
        get: () => (this.#a = !0, i.signal)
      });
    }, n = () => {
      const s = S(this.options, e), h = (() => {
        const u = {
          client: this.#r,
          queryKey: this.queryKey,
          meta: this.meta
        };
        return r(u), u;
      })();
      return this.#a = !1, this.options.persister ? this.options.persister(
        s,
        h,
        this
      ) : s(h);
    }, a = (() => {
      const s = {
        fetchOptions: e,
        options: this.options,
        queryKey: this.queryKey,
        client: this.#r,
        state: this.state,
        fetchFn: n
      };
      return r(s), s;
    })();
    this.options.behavior?.onFetch(a, this), this.#n = this.state, (this.state.fetchStatus === "idle" || this.state.fetchMeta !== a.fetchOptions?.meta) && this.#s({ type: "fetch", meta: a.fetchOptions?.meta }), this.#t = C({
      initialPromise: e?.initialPromise,
      fn: a.fetchFn,
      onCancel: (s) => {
        s instanceof d && s.revert && this.setState({
          ...this.#n,
          fetchStatus: "idle"
        }), i.abort();
      },
      onFail: (s, o) => {
        this.#s({ type: "failed", failureCount: s, error: o });
      },
      onPause: () => {
        this.#s({ type: "pause" });
      },
      onContinue: () => {
        this.#s({ type: "continue" });
      },
      retry: a.options.retry,
      retryDelay: a.options.retryDelay,
      networkMode: a.options.networkMode,
      canRun: () => !0
    });
    try {
      const s = await this.#t.start();
      if (s === void 0)
        throw process.env.NODE_ENV !== "production" && console.error(
          `Query data cannot be undefined. Please make sure to return a value other than undefined from your query function. Affected query key: ${this.queryHash}`
        ), new Error(`${this.queryHash} data is undefined`);
      return this.setData(s), this.#e.config.onSuccess?.(s, this), this.#e.config.onSettled?.(
        s,
        this.state.error,
        this
      ), s;
    } catch (s) {
      if (s instanceof d) {
        if (s.silent)
          return this.#t.promise;
        if (s.revert) {
          if (this.state.data === void 0)
            throw s;
          return this.state.data;
        }
      }
      throw this.#s({
        type: "error",
        error: s
      }), this.#e.config.onError?.(
        s,
        this
      ), this.#e.config.onSettled?.(
        this.state.data,
        s,
        this
      ), s;
    } finally {
      this.scheduleGc();
    }
  }
  #s(t) {
    const e = (i) => {
      switch (t.type) {
        case "failed":
          return {
            ...i,
            fetchFailureCount: t.failureCount,
            fetchFailureReason: t.error
          };
        case "pause":
          return {
            ...i,
            fetchStatus: "paused"
          };
        case "continue":
          return {
            ...i,
            fetchStatus: "fetching"
          };
        case "fetch":
          return {
            ...i,
            ...A(i.data, this.options),
            fetchMeta: t.meta ?? null
          };
        case "success":
          const r = {
            ...i,
            ...l(t.data, t.dataUpdatedAt),
            dataUpdateCount: i.dataUpdateCount + 1,
            ...!t.manual && {
              fetchStatus: "idle",
              fetchFailureCount: 0,
              fetchFailureReason: null
            }
          };
          return this.#n = t.manual ? r : void 0, r;
        case "error":
          const n = t.error;
          return {
            ...i,
            error: n,
            errorUpdateCount: i.errorUpdateCount + 1,
            errorUpdatedAt: Date.now(),
            fetchFailureCount: i.fetchFailureCount + 1,
            fetchFailureReason: n,
            fetchStatus: "idle",
            status: "error",
            // flag existing data as invalidated if we get a background error
            // note that "no data" always means stale so we can set unconditionally here
            isInvalidated: !0
          };
        case "invalidate":
          return {
            ...i,
            isInvalidated: !0
          };
        case "setState":
          return {
            ...i,
            ...t.state
          };
      }
    };
    this.state = e(this.state), F.batch(() => {
      this.observers.forEach((i) => {
        i.onQueryUpdate();
      }), this.#e.notify({ query: this, type: "updated", action: t });
    });
  }
};
function A(t, e) {
  return {
    fetchFailureCount: 0,
    fetchFailureReason: null,
    fetchStatus: g(e.networkMode) ? "fetching" : "paused",
    ...t === void 0 && {
      error: null,
      status: "pending"
    }
  };
}
function l(t, e) {
  return {
    data: t,
    dataUpdatedAt: e ?? Date.now(),
    error: null,
    isInvalidated: !1,
    status: "success"
  };
}
function f(t) {
  const e = typeof t.initialData == "function" ? t.initialData() : t.initialData, i = e !== void 0, r = i ? typeof t.initialDataUpdatedAt == "function" ? t.initialDataUpdatedAt() : t.initialDataUpdatedAt : 0;
  return {
    data: e,
    dataUpdateCount: 0,
    dataUpdatedAt: i ? r ?? Date.now() : 0,
    error: null,
    errorUpdateCount: 0,
    errorUpdatedAt: 0,
    fetchFailureCount: 0,
    fetchFailureReason: null,
    fetchMeta: null,
    isInvalidated: !1,
    status: i ? "success" : "pending",
    fetchStatus: "idle"
  };
}
export {
  x as Query,
  A as fetchState
};
