import type { CacheOrGetOptions } from "./cacheOrGetOptions"

const ONE_HOUR_IN_SECONDS = 3_600 as const
const ONE_DAY_IN_SECONDS = 86_400 as const

export const namedCacheTimeMap: Record<NamedCacheTimes, number> = {
  "no cache": 0,
  "1m": 60,
  "5m": 300,
  "10m": 600,
  "1h": ONE_HOUR_IN_SECONDS,
  "3h": ONE_HOUR_IN_SECONDS * 3,
  "6h": ONE_HOUR_IN_SECONDS * 6,
  "1d": ONE_DAY_IN_SECONDS,
  "3d": ONE_DAY_IN_SECONDS * 3,
  max: ONE_DAY_IN_SECONDS * 30,
} as const

export const namedCacheTimes = [
  "no cache",
  "1m",
  "5m",
  "10m",
  "1h",
  "3h",
  "6h",
  "1d",
  "3d",
  "max",
] as const

export type NamedCacheTimes = (typeof namedCacheTimes)[number]

/**
 * Retrieves the cache time in seconds based on the given cache time.
 * @param cacheTime - The time value to determine, either a named cache time or a number of seconds.
 * @returns The cache time in seconds.
 */
export const getCacheTimeInSeconds = (cacheTime: CacheTime): number => {
  if (typeof cacheTime === "number") {
    if (cacheTime < 0 || !Number.isInteger(cacheTime)) {
      return 0
    }

    return cacheTime
  }

  return namedCacheTimeMap[cacheTime] ?? 0
}

export type CacheTime = NamedCacheTimes | number

export type DataCache = {
  /**
   * Type of cache
   */
  type: "edge" | "redis" | "in-memory" | "unstable-cache"

  /**
   * Helper function that retrieves from the cache if it exists, otherwise calls the callback and caches the result.
   * If the call fails, the cache is not updated.
   * @param key The cache key
   * @param getDataFromSource An async function that provides a value to cache
   * @param ttl Time to live, either a named cache time or a number of seconds
   * @param opts Options to control cache behavior when retrieving or storing data.
   * @returns The cached value or the result from the callback
   */
  cacheOrGet: <T>(
    key: string | string[],
    getDataFromSource: (
      overrideTTL?: (cacheTime: CacheTime) => void
    ) => Promise<T>,
    ttl: CacheTime,
    opts?: CacheOrGetOptions
  ) => Promise<T>

  /**
   * Get a value from the cache, if it exists
   * @see `cacheOrGet` for a more convenient way to cache values
   * @param key The cache key to retrieve the value for
   * @returns The cached value or undefined if not found
   */
  get: <T>(key: string) => Promise<T | undefined>

  /**
   * Sets a value in the cache.
   * @see `cacheOrGet` for a more convenient way to cache values
   * @param key CacheKey to set
   * @param obj Value to be cached
   * @param ttl Time to live, either a named cache time or a number of seconds
   * @returns A promise that resolves when the value has been cached
   */
  set: <T>(key: string, obj: T, ttl: CacheTime) => Promise<void>

  /**
   * Deletes a key from the cache
   * @param key CacheKey to delete
   * @param fuzzy If true, does a wildcard delete. *key*
   * @returns
   */
  deleteKey: (key: string, opts?: { fuzzy?: boolean }) => Promise<void>
}
