// modules/react/cache/client-data-cache/index.ts
var CACHE_KEY = "__FW_DATA_CACHE__";
var MAX_CACHE_SIZE = 100;
function getCacheStore() {
  if (typeof window !== "undefined") {
    if (!window[CACHE_KEY]) {
      window[CACHE_KEY] = {
        data: /* @__PURE__ */ new Map(),
        index: /* @__PURE__ */ new Map(),
        lru: []
      };
    }
    return window[CACHE_KEY];
  }
  return {
    data: /* @__PURE__ */ new Map(),
    index: /* @__PURE__ */ new Map(),
    lru: []
  };
}
var cacheStore = getCacheStore();
var dataCache = cacheStore.data;
var pathIndex = cacheStore.index;
var lru = cacheStore.lru;
function extractPathBase(key) {
  return key.split("?")[0];
}
function addToIndex(key) {
  const pathBase = extractPathBase(key);
  if (!pathIndex.has(pathBase)) {
    pathIndex.set(pathBase, /* @__PURE__ */ new Set());
  }
  pathIndex.get(pathBase).add(key);
}
function removeFromIndex(key) {
  const pathBase = extractPathBase(key);
  const keys = pathIndex.get(pathBase);
  if (keys) {
    keys.delete(key);
    if (keys.size === 0) {
      pathIndex.delete(pathBase);
    }
  }
}
function updateLRU(key) {
  const index = lru.indexOf(key);
  if (index !== -1) {
    lru.splice(index, 1);
  }
  lru.push(key);
}
function evictOldest() {
  while (lru.length >= MAX_CACHE_SIZE && lru.length > 0) {
    const oldestKey = lru.shift();
    dataCache.delete(oldestKey);
    removeFromIndex(oldestKey);
  }
}
function setCacheEntry(key, entry) {
  const existingEntry = dataCache.get(key);
  const wasFulfilled = existingEntry?.status === "fulfilled";
  dataCache.set(key, entry);
  if (entry.status === "fulfilled") {
    if (!wasFulfilled) {
      addToIndex(key);
    }
    updateLRU(key);
    evictOldest();
  } else if (wasFulfilled) {
    removeFromIndex(key);
  }
}
function deleteCacheEntry(key) {
  if (dataCache.has(key)) {
    dataCache.delete(key);
    removeFromIndex(key);
    const lruIndex = lru.indexOf(key);
    if (lruIndex !== -1) {
      lru.splice(lruIndex, 1);
    }
  }
}
function buildDataUrl(url) {
  return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
}
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
  const dataUrl = buildDataUrl(url);
  const headers = {
    "x-fw-data": "1",
    Accept: "application/json"
  };
  if (skipLayoutHooks) {
    headers["x-skip-layout-hooks"] = "true";
  }
  const res = await fetch(dataUrl, { headers });
  let json = {};
  try {
    const text = await res.text();
    if (text) {
      json = JSON.parse(text);
    }
  } catch (parseError) {
    console.error(
      "[client][cache] Failed to parse response as JSON:",
      parseError
    );
  }
  const result = {
    ok: res.ok,
    status: res.status,
    json
  };
  return result;
}
function revalidatePath(path, skipAutoRevalidate = false) {
  const normalizedPath = path.split("?")[0];
  const hasQueryParams = path.includes("?");
  const keysForPath = pathIndex.get(normalizedPath);
  if (!keysForPath || keysForPath.size === 0) {
    return;
  }
  let specificQueryParams;
  if (hasQueryParams) {
    const queryPart = path.split("?")[1];
    specificQueryParams = queryPart.split("&").filter((p) => !p.startsWith("__fw_data=")).sort().join("&");
  }
  const keysToDelete = [];
  for (const key of keysForPath) {
    if (hasQueryParams && specificQueryParams) {
      const [, keyQuery = ""] = key.split("?");
      const keyQueryParams = keyQuery.split("&").filter((p) => !p.startsWith("__fw_data=")).sort().join("&");
      if (keyQueryParams === specificQueryParams) {
        keysToDelete.push(key);
      }
    } else {
      keysToDelete.push(key);
    }
  }
  keysToDelete.forEach((key) => {
    deleteCacheEntry(key);
  });
  if (!skipAutoRevalidate && typeof window !== "undefined") {
    const currentPathname = window.location.pathname;
    const currentSearch = window.location.search;
    const matchesCurrentPath = normalizedPath === currentPathname;
    if (matchesCurrentPath) {
      if (hasQueryParams && specificQueryParams) {
        const currentQueryParams = currentSearch.replace("?", "").split("&").filter((p) => !p.startsWith("__fw_data=")).sort().join("&");
        if (currentQueryParams === specificQueryParams) {
          revalidate().catch((err) => {
            console.error(
              "[client][cache] Error revalidating current route:",
              err
            );
          });
        }
      } else {
        revalidate().catch((err) => {
          console.error(
            "[client][cache] Error revalidating current route:",
            err
          );
        });
      }
    }
  }
}
var isRevalidating = false;
async function revalidate() {
  if (typeof window === "undefined") {
    throw new Error("revalidate() can only be called on the client");
  }
  if (isRevalidating) {
    const key = buildDataUrl(window.location.pathname + window.location.search);
    const entry = dataCache.get(key);
    if (entry && entry.status === "pending") {
      return entry.promise;
    }
  }
  isRevalidating = true;
  try {
    const pathname = window.location.pathname + window.location.search;
    revalidatePath(pathname, true);
    const freshData = await getRouteData(pathname, { revalidate: true });
    if (window.__FW_DATA__ && freshData.ok && freshData.json) {
      const currentData = window.__FW_DATA__;
      if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
        window.__FW_LAYOUT_PROPS__ = freshData.json.layoutProps;
      }
      let combinedProps = currentData.props || {};
      if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
        combinedProps = {
          ...freshData.json.layoutProps,
          ...freshData.json.pageProps ?? freshData.json.props ?? {}
        };
      } else if (freshData.json.pageProps !== void 0) {
        const preservedLayoutProps = window.__FW_LAYOUT_PROPS__ || {};
        combinedProps = {
          ...preservedLayoutProps,
          ...freshData.json.pageProps
        };
      } else if (freshData.json.props) {
        combinedProps = freshData.json.props;
      }
      window.__FW_DATA__ = {
        ...currentData,
        pathname: pathname.split("?")[0],
        params: freshData.json.params || currentData.params || {},
        props: combinedProps,
        metadata: freshData.json.metadata ?? currentData.metadata ?? null,
        notFound: freshData.json.notFound ?? false,
        error: freshData.json.error ?? false
      };
      window.dispatchEvent(new CustomEvent("fw-data-refresh", {
        detail: { data: freshData }
      }));
    }
    return freshData;
  } finally {
    isRevalidating = false;
  }
}
function revalidateRouteData(url) {
  revalidatePath(url);
}
function prefetchRouteData(url) {
  const key = buildDataUrl(url);
  const cached = dataCache.get(key);
  if (cached && cached.status !== "rejected") {
    if (cached.status === "fulfilled") {
      updateLRU(key);
    }
    return;
  }
  const promise = fetchRouteDataOnce(url, true).then((value) => {
    setCacheEntry(key, { status: "fulfilled", value });
    return value;
  }).catch((error) => {
    console.error("[client][cache] Error prefetching route data:", error);
    dataCache.set(key, { status: "rejected", error });
    throw error;
  });
  dataCache.set(key, { status: "pending", promise });
}
async function getRouteData(url, options) {
  const key = buildDataUrl(url);
  if (options?.revalidate) {
    deleteCacheEntry(key);
  }
  const entry = dataCache.get(key);
  if (entry && !options?.revalidate) {
    if (entry.status === "fulfilled") {
      updateLRU(key);
      return entry.value;
    }
    if (entry.status === "pending") {
      return entry.promise;
    }
  }
  const skipLayoutHooks = !options?.revalidate;
  const currentEntry = dataCache.get(key);
  if (currentEntry && !options?.revalidate) {
    if (currentEntry.status === "fulfilled") {
      updateLRU(key);
      return currentEntry.value;
    }
    if (currentEntry.status === "pending") {
      return currentEntry.promise;
    }
  }
  const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
    const entryAfterFetch = dataCache.get(key);
    if (!entryAfterFetch || entryAfterFetch.status === "pending") {
      setCacheEntry(key, { status: "fulfilled", value });
    }
    return value;
  }).catch((error) => {
    console.error("[client][cache] Error fetching route data:", error);
    const entryAfterFetch = dataCache.get(key);
    if (!entryAfterFetch || entryAfterFetch.status === "pending") {
      dataCache.set(key, { status: "rejected", error });
    }
    throw error;
  });
  dataCache.set(key, { status: "pending", promise });
  return promise;
}
export {
  getRouteData,
  prefetchRouteData,
  revalidate,
  revalidatePath,
  revalidateRouteData
};
//# sourceMappingURL=cache.mjs.map