/**
 * @typedef {Object} MarkerData
 * @property {Blob} background - фон
 * @property {Array<Blob>} images - изображения
 * @property {Array<Blob>} slider - слайды
 */

/** Путь к php-прослойке, работающая с бд и внутренним хранилищем */
const db_get_filepath = '../db_get.php'
/** Дефолтный срок годности кэша : 2 часа */
const CACHE_DEFAULT_TTL_MS = 2 * 60 * 60 * 1000;

/**
 * Излечь данные из ответа
 * @param {string} type - тип извлекаемых данных
 * @param {Response} response - ответ
 * @returns {Promise<Object|Blob>}
 */
const extract_file = recovery(async (type, response) => {
    switch(type) {
        case 'file':
            return await response.blob()
        case 'json':
            return await response.json()
        default:
            throw new Error('Не определен type')
    }
}, (...e) => {throw Error(`Ошибки при извлечении (extract_file): ${e.join(', ')}`)} )

/**
 * @param {string} path - путь (либо локальный или https)
 * @param {object} options - параметры fetch
 * @param {int} timeout - секундомер
 */
const fetch_with_timeout = recovery(async (path, options = {}, timeout = 10 * 1000) => {
    /** Штука для преждевременного завершения fetch */
    const controller = new AbortController()
    /** Как я понял, якорь для fetch */
    const signal = controller.signal

    /** Сработает завершение после истечения timeout */
    const timeoutId = setTimeout(() => controller.abort(), timeout)
    /** Запрос с привязкой якоря */
    const response = await fetch(path, {...options, signal})

    try {return response}
    finally {clearTimeout(timeoutId)}
}, (e) => {
    if(e.name === 'AbortError')
        console.log('Запрос прерван по таймауту:', e)
    else console.log(e)
}, (...e) => {throw Error(`Ошибки при запросе с таймером (fetch_with_timeout): ${e.join(', ')}`)} )

/**
 * Вернуть данные из ответа, кэшируя перед этим
 * @param {string} type - тип извлекаемых данных
 * @param {string} path - путь (либо локальный или https)
 * @param {Object} body_req - тело запроса
 * @returns {Promise<Object|Blob>}
 */
const return_fetch = recovery(async (type, path, cache_key, body_req) => {
    const cache = await caches.open('cache')

    /** Запрос по пути (если есть тело, то локальное обращение к файлу) */
    const response = !body_req ? await fetch_with_timeout(path) : await fetch_with_timeout(path, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json'},
        body: JSON.stringify(body_req)
    })

    /** Клонирование, т.к blob/json необратимый процесс. */
    const cloned = response.clone()
    const blob = await cloned.blob()

    const headers = new Headers(cloned.headers)
    /** Добавление даты создания в заголовок 'date' кешируемого ответа. */
    headers.set('date', new Date().toUTCString())

    /** Процесс кэширования */
    await cache.put(
        cache_key,
        new Response(blob, {
            status: cloned.status,
            statusText: cloned.statusText,
            headers
        })
    )

    /** Возвращаем оригинальный (первый) ответ */
    return await extract_file(type, response)
}, (...e) => {throw Error(`Ошибки при запросе и кэшировании (return_fetch): ${e.join(', ')}`)} )

/**
 * Получить данные из пути(локальный или https)
 * @param {*} type - тип извлекаемых данных
 * @param {*} path - путь (либо локальный или https)
 * @param {*} body_req - тело запроса
 * @returns {Promise<Object|Blob>}
 */
const get = recovery(async (type, path, body_req = null) => {
    const cache = await caches.open('cache')

    /** Ключ для добавления или получения кэш-данных. */
    const cache_key = `${path}?type=${type}&file=${encodeURIComponent(JSON.stringify(body_req || {}))}`

    /** Результат поиска ответа в кэше */
    const cached_response = await cache.match(cache_key)

    if (cached_response) {
        /** Срок годности кэша (если задан) */
        const expires_value = cached_response.headers.get('expires')
        /** Дата создания кэша */
        const date_value = cached_response.headers.get('date')
        /** До какого числа годен */
        const exp = expires_value && expires_value !== '-1'
            ? Date.parse(expires_value)
            : Date.parse(date_value ?? '') + CACHE_DEFAULT_TTL_MS
        /** Если exp больше, вернуть ответ из кэша, иначе удалить протухшие данные */
        if (Date.now() < exp) {
            return await extract_file(type, cached_response )
        }
        await cache.delete(cache_key)
    }
    /** Формирование нового кэша, т.к предыдущий протух или его нет */
    return await return_fetch(type, path, cache_key, body_req)
}, (...e) => {throw Error(`Ошибки при получении (get): ${e.join(', ')}`)} )

/**
 * Получить данные на диске (абсолютный/относительный путь)
 * @param {string} filepath - путь к файлу
 * @returns {Promise<Blob>} - без json
 */
const get_local = recovery(async (filepath) => {
    return await get('file', db_get_filepath, {
        mode: 'file',
        path: filepath
    })
}, (...e) => {throw Error(`Ошибки при локальном получении (get_local): ${e.join(', ')}`)} )

/**
 * Получить данные без кэширования
 * @param {string} type - тип извлекаемых данных
 * @param {string} path - путь (либо локальный или https)
 * @returns {Promise<Object|Blob>}
 */
const get_no_cached = recovery(async (type, path) => {
    const file = await fetch_with_timeout(path)
    return await extract_file(type, file)
}, (...e) => {throw Error(`Ошибки при получении без кэширования (get_no_cached): ${e.join(', ')}`)} )

/**
 * Получить метериалы поселения для инфо. страницы
 * @param {MarkerData} properties 
 *      - нужные свойства (пути) из свойств feature.
 * @returns {Promise<{ background: Blob, images: Blob[], slider: Blob[]}>}
 */
const get_for_info_page = recovery(async (properties) => {
    /** Фон инфо. страницы */
    const background = await get_local(properties.background)

    /** Фотографи инфо. страницы */
    const images = await Promise.all(
        (properties.images || []).map(i_path => get_local(i_path))
    )

    /** Слайды инфо. страницы (если есть) */
    const slider = Array.isArray(properties.slider)
        ? await Promise.all(properties.slider.map(s_path => get_local(s_path)))
        : []

    return {
        background,
        images,
        slider,
    }
}, (...e) => {throw Error(`Ошибки при получении материалов для страницы (get_for_info_page): ${e.join(', ')}`)} )

/**
 * Получить иконки для стилизации страниц.
 * @returns {Promise<Object|Blob>}
 */
const get_icons = recovery(async () => await get_no_cached ('json', './data/icons.json')
, (...e) => {throw Error(`Ошибки при получении данных для стилизации (get_icons): ${e.join(', ')}`)} )
/**
 * Получить все geojson'ы из базы данных 
 * @returns {Promise<Object>}
 */
const get_all_geojsons = recovery(async () => await get('json', db_get_filepath, {'mode': 'geojsons'})
, (...e) => {throw Error(`Ошибки при получении geojson'ов из бд (get_all_geojsons): ${e.join(', ')}`)} )