type CacheEntry<T> = {
  cachedAt: string;
  data: T;
  etag?: string;
};

type CacheStore = {
  version: 1;
  entries: Record<string, CacheEntry<unknown>>;
};

const STORAGE_KEY = "walkup.offlineCache:v1";

function safeLocalStorage(): Storage | null {
  if (typeof window === "undefined") {
    return null;
  }

  try {
    const { localStorage } = window;
    const probeKey = "__walkup_cache_probe__";
    localStorage.setItem(probeKey, "1");
    localStorage.removeItem(probeKey);
    return localStorage;
  } catch {
    return null;
  }
}

function readStore(): CacheStore {
  const storage = safeLocalStorage();
  if (!storage) {
    return { version: 1, entries: {} };
  }

  const raw = storage.getItem(STORAGE_KEY);
  if (!raw) {
    return { version: 1, entries: {} };
  }

  try {
    const parsed = JSON.parse(raw) as Partial<CacheStore>;
    if (parsed.version !== 1 || !parsed.entries || typeof parsed.entries !== "object") {
      return { version: 1, entries: {} };
    }
    return {
      version: 1,
      entries: parsed.entries as Record<string, CacheEntry<unknown>>,
    };
  } catch {
    return { version: 1, entries: {} };
  }
}

function writeStore(store: CacheStore): void {
  const storage = safeLocalStorage();
  if (!storage) {
    return;
  }

  try {
    storage.setItem(STORAGE_KEY, JSON.stringify(store));
  } catch {
    // Ignore quota and storage errors. The app can still operate online.
  }
}

function cacheKeyFromParts(parts: readonly unknown[]): string {
  return JSON.stringify(parts);
}

export function readCachedValue<T>(parts: readonly unknown[]): CacheEntry<T> | null {
  const store = readStore();
  const entry = store.entries[cacheKeyFromParts(parts)];
  return entry ? (entry as CacheEntry<T>) : null;
}

export function writeCachedValue<T>(parts: readonly unknown[], data: T, etag?: string): void {
  const store = readStore();
  store.entries[cacheKeyFromParts(parts)] = {
    cachedAt: new Date().toISOString(),
    data,
    etag,
  };
  writeStore(store);
}

export function clearOfflineCache(): void {
  const storage = safeLocalStorage();
  if (!storage) {
    return;
  }
  storage.removeItem(STORAGE_KEY);
}

async function readResponseText(response: Response): Promise<string> {
  try {
    return await response.text();
  } catch {
    return "";
  }
}

export async function cachedJsonRequest<T>(
  parts: readonly unknown[],
  url: string,
  init?: RequestInit,
  options?: {
    onUnauthorized?: () => T;
  },
): Promise<T> {
  const cached = readCachedValue<T>(parts);
  const headers = new Headers(init?.headers);
  if (cached?.etag) {
    headers.set("If-None-Match", cached.etag);
  }

  try {
    const response = await fetch(url, {
      ...init,
      headers,
      cache: "no-cache",
      credentials: "include",
    });

    if (response.status === 304) {
      if (!cached) {
        throw new Error("Cache validation returned 304 without a cached response.");
      }
      return cached.data;
    }

    if (!response.ok) {
      if ((response.status === 401 || response.status === 403) && options?.onUnauthorized) {
        return options.onUnauthorized();
      }
      throw new Error((await readResponseText(response)) || `Request failed: ${response.status}`);
    }

    const data = (await response.json()) as T;
    writeCachedValue(parts, data, response.headers.get("etag") ?? undefined);
    return data;
  } catch (error) {
    if (cached && error instanceof TypeError) {
      return cached.data;
    }
    throw error;
  }
}
