import type { NestedMessages } from './index'

export class BundleLoadError extends Error {
  constructor(
    public readonly locale: string,
    public readonly namespace?: string,
  ) {
    super(
      namespace
        ? `BundleLoadError: no bundle for locale "${locale}" namespace "${namespace}"`
        : `BundleLoadError: no bundle for locale "${locale}"`,
    )
    this.name = 'BundleLoadError'
  }
}

export type BundleLoaderFn<L extends string> = (
  locale: L,
  namespace?: string,
) => Promise<NestedMessages | null | undefined>

export interface BundleLoader<L extends string = string> {
  load(locale: L, namespace?: string): Promise<NestedMessages>
  peek(locale: L, namespace?: string): NestedMessages | undefined
  clear(): void
}

function cacheKey(locale: string, namespace?: string): string {
  return namespace ? `${locale}:${namespace}` : locale
}

export function createBundleLoader<L extends string>(loadFn: BundleLoaderFn<L>): BundleLoader<L> {
  const cache = new Map<string, NestedMessages>()

  return {
    async load(locale, namespace) {
      const key = cacheKey(locale, namespace)
      const hit = cache.get(key)
      if (hit) return hit
      const bundle = await loadFn(locale, namespace)
      if (!bundle) throw new BundleLoadError(locale, namespace)
      cache.set(key, bundle)
      return bundle
    },
    peek(locale, namespace) {
      return cache.get(cacheKey(locale, namespace))
    },
    clear() {
      cache.clear()
    },
  }
}
