export const apiPrefix = import.meta.env.VITE_API_PREFIX || "";
export const apiBase = (import.meta.env.VITE_API_URL || "") + apiPrefix;

/** Go JSON encodes nil slices as null — normalize before .map() */
export function itemsOrEmpty<T>(items: T[] | null | undefined): T[] {
  return Array.isArray(items) ? items : [];
}

export function assetUrl(path: string | undefined | null) {
  if (!path) return "";
  if (path.startsWith("http")) return path;
  return `${apiBase}${path}`;
}

type JsonCacheEntry<T> = {
  cachedAt: number;
  value: T;
};

const jsonMemoryCache = new Map<string, JsonCacheEntry<unknown>>();
const jsonStoragePrefix = "ark-json-cache:v1:";
const jsonCacheTtlMs = 5 * 60 * 1000;
const jsonCacheMaxEntries = 80;

function jsonCacheKey(path: string): string {
  return `${apiBase}${path}`;
}

function jsonStorageKey(key: string): string {
  return `${jsonStoragePrefix}${key}`;
}

function isFreshJSONCacheEntry(entry: JsonCacheEntry<unknown>): boolean {
  return Date.now() - entry.cachedAt <= jsonCacheTtlMs;
}

function removeStoredJSONCache(key: string): void {
  jsonMemoryCache.delete(key);
  if (typeof window === "undefined") return;
  try {
    window.localStorage.removeItem(jsonStorageKey(key));
  } catch {
    // Ignore privacy-mode storage failures.
  }
}

function readStoredJSONCacheEntry<T>(key: string): JsonCacheEntry<T> | null {
  if (typeof window === "undefined") return null;

  try {
    const raw = window.localStorage.getItem(jsonStorageKey(key));
    if (!raw) return null;
    const entry = JSON.parse(raw) as JsonCacheEntry<T>;
    if (typeof entry.cachedAt !== "number") {
      removeStoredJSONCache(key);
      return null;
    }
    return entry;
  } catch {
    removeStoredJSONCache(key);
    return null;
  }
}

function cleanupExpiredJSONCache(): void {
  for (const [key, entry] of jsonMemoryCache) {
    if (!isFreshJSONCacheEntry(entry)) jsonMemoryCache.delete(key);
  }

  if (typeof window === "undefined") return;

  try {
    const keys: string[] = [];
    for (let index = 0; index < window.localStorage.length; index += 1) {
      const key = window.localStorage.key(index);
      if (key?.startsWith(jsonStoragePrefix)) keys.push(key);
    }

    keys.forEach((storageKey) => {
      const key = storageKey.slice(jsonStoragePrefix.length);
      const entry = readStoredJSONCacheEntry(key);
      if (!entry || !isFreshJSONCacheEntry(entry)) removeStoredJSONCache(key);
    });
  } catch {
    // Ignore storage access failures; memory cleanup above still happened.
  }
}

function pruneJSONCache(): void {
  if (jsonMemoryCache.size > jsonCacheMaxEntries) {
    [...jsonMemoryCache.entries()]
      .sort(([, a], [, b]) => a.cachedAt - b.cachedAt)
      .slice(0, jsonMemoryCache.size - jsonCacheMaxEntries)
      .forEach(([key]) => jsonMemoryCache.delete(key));
  }

  if (typeof window === "undefined") return;

  try {
    const entries: Array<{ key: string; cachedAt: number }> = [];
    for (let index = 0; index < window.localStorage.length; index += 1) {
      const storageKey = window.localStorage.key(index);
      if (!storageKey?.startsWith(jsonStoragePrefix)) continue;
      const key = storageKey.slice(jsonStoragePrefix.length);
      const entry = readStoredJSONCacheEntry(key);
      if (entry) entries.push({ key, cachedAt: entry.cachedAt });
    }

    entries
      .sort((a, b) => a.cachedAt - b.cachedAt)
      .slice(0, Math.max(0, entries.length - jsonCacheMaxEntries))
      .forEach(({ key }) => removeStoredJSONCache(key));
  } catch {
    // Ignore storage access failures.
  }
}

export function readJSONCache<T>(path: string): T | null {
  cleanupExpiredJSONCache();

  const key = jsonCacheKey(path);
  const memoryEntry = jsonMemoryCache.get(key) as JsonCacheEntry<T> | undefined;
  if (memoryEntry) {
    if (isFreshJSONCacheEntry(memoryEntry)) return memoryEntry.value;
    removeStoredJSONCache(key);
    return null;
  }

  const entry = readStoredJSONCacheEntry<T>(key);
  if (!entry) return null;
  if (!isFreshJSONCacheEntry(entry)) {
    removeStoredJSONCache(key);
    return null;
  }

  jsonMemoryCache.set(key, entry as JsonCacheEntry<unknown>);
  return entry.value;
}

function writeJSONCache<T>(path: string, value: T): void {
  const key = jsonCacheKey(path);
  const entry: JsonCacheEntry<T> = { cachedAt: Date.now(), value };
  jsonMemoryCache.set(key, entry as JsonCacheEntry<unknown>);

  if (typeof window !== "undefined") {
    try {
      window.localStorage.setItem(jsonStorageKey(key), JSON.stringify(entry));
    } catch {
      // Ignore storage quota / privacy-mode failures; memory cache still works.
    }
  }

  cleanupExpiredJSONCache();
  pruneJSONCache();
}

export async function getJSON<T>(path: string): Promise<T> {
  const res = await fetch(`${apiBase}${path}`);
  if (!res.ok) throw new Error(await res.text());
  const data = (await res.json()) as T;
  writeJSONCache(path, data);
  return data;
}

export async function getJSONAuth<T>(path: string, token: string): Promise<T> {
  const res = await fetch(`${apiBase}${path}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json() as Promise<T>;
}

export async function postJSON<T>(
  path: string,
  body: unknown,
  token?: string,
): Promise<T> {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
  };
  if (token) headers.Authorization = `Bearer ${token}`;
  const res = await fetch(`${apiBase}${path}`, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json() as Promise<T>;
}

export async function postNoBody(path: string): Promise<void> {
  const res = await fetch(`${apiBase}${path}`, { method: "POST" });
  if (!res.ok) throw new Error(await res.text());
}

export async function putJSON<T>(
  path: string,
  body: unknown,
  token: string,
): Promise<T> {
  const res = await fetch(`${apiBase}${path}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json() as Promise<T>;
}

export async function del(path: string, token: string) {
  const res = await fetch(`${apiBase}${path}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new Error(await res.text());
}

export async function uploadFile(
  file: File,
  token: string,
): Promise<{ url: string }> {
  const fd = new FormData();
  fd.append("file", file);
  const res = await fetch(`${apiBase}/api/admin/upload`, {
    method: "POST",
    headers: { Authorization: `Bearer ${token}` },
    body: fd,
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json();
}

export type Category = {
  id: number;
  slug: string;
  name: string;
  description?: string;
  iconKey: string;
  sortOrder: number;
};

export type Resource = {
  id: string;
  title: string;
  description?: string;
  type: string;
  language: string;
  categoryId: number;
  categorySlug: string;
  categoryName: string;
  coverImage?: string;
  fileUrl?: string;
  previewUrl?: string;
  externalUrl?: string;
  bodyText?: string;
  badgeLabel?: string;
  isDownloadable: boolean;
  isRecommended: boolean;
  publishedAt?: string;
  updatedAt: string;
  favoriteCount?: number;
  availability?: "available" | "unavailable";
  tags?: string[];
};

export type AdminResource = Resource & {
  isPublic: boolean;
  sortOrder: number;
  status: string;
  publishedAt?: string;
  viewCount?: number;
  downloadCount?: number;
};
