import { get } from "svelte/store"
import { apiRequest, obj2str } from "../../api/hooks/lib/ssr"
import * as sentry from "./sentry"
import { apiBaseOverride } from "./lib/store"

// fetch polyfill
// [MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com/)
const _f = function (url: string, options?: { [key: string]: any }) {
    if (typeof XMLHttpRequest === "undefined") {
        return Promise.resolve(null)
    }

    options = options || {}
    return new Promise((resolve, reject) => {
        const request = new XMLHttpRequest()
        const keys: string[] = []
        // @ts-ignore
        const all = []
        const headers = {}

        const response = () => ({
            ok: ((request.status / 100) | 0) == 2, // 200-299
            statusText: request.statusText,
            status: request.status,
            url: request.responseURL,
            text: () => Promise.resolve(request.responseText),
            json: () => Promise.resolve(request.responseText).then(JSON.parse),
            blob: () => Promise.resolve(new Blob([request.response])),
            clone: response,
            headers: {
                // @ts-ignore
                keys: () => keys,
                // @ts-ignore
                entries: () => all,
                // @ts-ignore
                get: (n) => headers[n.toLowerCase()],
                // @ts-ignore
                has: (n) => n.toLowerCase() in headers,
            },
        })

        request.open(options.method || "get", url, true)

        request.onload = () => {
            request
                .getAllResponseHeaders()
                // @ts-ignore
                .replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, (m, key, value) => {
                    keys.push((key = key.toLowerCase()))
                    all.push([key, value])
                    // @ts-ignore
                    headers[key] = headers[key] ? `${headers[key]},${value}` : value
                })
            resolve(response())
        }

        request.onerror = reject

        request.withCredentials = options.credentials == "include"

        for (const i in options.headers) {
            request.setRequestHeader(i, options.headers[i])
        }

        request.send(options.body || null)
    })
}

// fetch must be declared after sentry import to get the hijacked fetch
// @ts-ignore
export const _fetch: typeof fetch =
    typeof fetch === "undefined" ? (typeof window === "undefined" ? _f : window.fetch || _f) : fetch

export const api = async <T>(
    endpoint: string,
    options?: ApiOptions,
    body?: any
): Promise<{ data: T; count: number } | any> => {
    const _apiBaseOverride = get(apiBaseOverride) || ""
    let data = await apiRequest(_apiBaseOverride + endpoint, options, body, sentry, _fetch)
    // @ts-ignore
    // console.log(data, "data")
    return data
}

const cache: {
    [key: string]: {
        expire: number
        data: any
    }
} = {}

type CollectionNameT = "medialib" | "content" | "product"

type EntryTypeSwitch<T> = T extends "medialib"
    ? MedialibEntry
    : T extends "content"
      ? ContentEntry
      : T extends "product"
        ? ProductEntry
        : never

export async function getDBEntries<T extends CollectionNameT>(
    collectionName: T,
    filter?: { [key: string]: any },
    sort: string = "sort",
    limit?: number,
    offset?: number,
    projection?: string,
    params?: { [key: string]: string }
): Promise<EntryTypeSwitch<T>[]> {
    const c = await api<EntryTypeSwitch<T>[]>(collectionName, {
        filter,
        sort,
        limit,
        offset,
        projection,
        params,
    })
    return c.data
}

export async function getCachedEntries<T extends CollectionNameT>(
    collectionName: T,
    filter?: { [key: string]: any },
    sort: string = "sort",
    limit?: number,
    offset?: number,
    projection?: string,
    params?: { [key: string]: string }
): Promise<EntryTypeSwitch<T>[]> {
    const filterStr = obj2str({ collectionName, filter, sort, limit, offset, projection, params })
    if (cache[filterStr] && cache[filterStr].expire >= Date.now()) {
        return cache[filterStr].data
    }
    const entries = await getDBEntries<T>(collectionName, filter, sort, limit, offset, projection, params)
    // expire in 1h
    cache[filterStr] = { expire: Date.now() + 1000 * 60 * 60, data: entries }
    return entries
}

export async function getDBEntry<T extends CollectionNameT>(
    collectionName: T,
    filter: { [key: string]: any },
    params?: { [key: string]: string }
) {
    return (await getDBEntries<T>(collectionName, filter, "_id", null, null, null, params))?.[0]
}

export async function getCachedEntry<T extends CollectionNameT>(
    collectionName: T,
    filter: { [key: string]: any },
    params?: { [key: string]: string }
) {
    return (await getCachedEntries<T>(collectionName, filter, "_id", null, null, null, params))?.[0]
}

export async function postDBEntry<T extends CollectionNameT>(collectionName: T, entry: EntryTypeSwitch<T>) {
    return api<EntryTypeSwitch<T>>(collectionName, { method: "POST" }, entry)
}
