"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchEncoderConfigurations = exports.fetchMotorConfigurations = exports.fetchSupportedDios = exports.fetchSupportedAis = exports.fetchEncoderConfigurationsUsage = exports.fetchSupportedEncoderConfigurationTypes = exports.listOblacDrivesUpdateServiceVersions = exports.listFirmwarePackages = exports.fetchOblacDrivesReleases = exports.fetchFirmwareChangelog = exports.fetchProductImage = exports.fetchFirmwarePackage = exports.fetchEsi = exports.fetchBrandData = exports.fetchAndResolve = exports.fetchFromBox = exports.makeCdnUrl = exports.compareBundlesByVersionDesc = exports.cacheOnBox = exports.cacheApiBasePath = exports.cacheS3BasePath = exports.cacheOrigin = exports.cacheHostname = exports.cdnBasePath = exports.oblacDrivesApiGatewayOrigin = exports.hostname = exports.oblacDrivesBrandId = void 0;
const tslib_1 = require("tslib");
const rxjs_1 = require("rxjs");
const semver_1 = require("semver");
const online_1 = require("./online");
const util_1 = require("./util");
/* eslint-disable max-len */
/**
 * The brand ID for OBLAC Drives.
 */
exports.oblacDrivesBrandId = 'a7e925fa-57b7-4741-9126-0840a63cdb71';
/**
 * The hostname of the current environment.
 * Defaults to 'localhost' if running outside a browser (e.g., Node.js).
 */
// @ts-ignore
exports.hostname = typeof window === 'undefined' ? 'localhost' : window.location.hostname;
/**
 * AWS API Gateway endpoint for OBLAC Drives services.
 * S3 URI: s3://synapticon/oblac-drives/
 */
exports.oblacDrivesApiGatewayOrigin = 'https://pc27e3jixd.execute-api.us-east-1.amazonaws.com';
/**
 * Base URL for the CDN hosting OBLAC Drives assets.
 */
exports.cdnBasePath = 'https://d9k1r8ajdoczx.cloudfront.net';
/**
 * Hostname and port for the local cache server (OBLAC Drives Update Service).
 */
exports.cacheHostname = `${exports.hostname}:64000`;
/**
 * Base origin URL for the local cache server.
 */
exports.cacheOrigin = `http://${exports.cacheHostname}`;
/**
 * Base path for accessing cached S3 resources via the local cache server.
 */
exports.cacheS3BasePath = `${exports.cacheOrigin}/s3`;
/**
 * Base path for the local cache API.
 */
exports.cacheApiBasePath = `${exports.cacheOrigin}/api`;
/**
 * Caches the specified object (file) locally, either on the box or within the native Electron app.
 *
 * @param path - The destination path where the object should be cached.
 * @param body - The object (file) data to cache, provided as a Blob.
 * @returns A promise that resolves when caching completes (only resolves in Electron;
 *          for other environments, caching is attempted but not awaited).
 */
function cacheOnBox(path, body) {
    var _a;
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        if ((0, util_1.isElectronContext)()) {
            const base64Data = yield (0, util_1.convertBlobToBase64)(body);
            // @ts-ignore
            return yield ((_a = window.electron) === null || _a === void 0 ? void 0 : _a.cacheFile(path, base64Data));
        }
        else {
            fetch(`${exports.cacheApiBasePath}/cache${path}`, {
                method: 'POST',
                body,
            }).catch((err) => console.warn(`Failed to cache ${path}`, err));
        }
    });
}
exports.cacheOnBox = cacheOnBox;
/**
 * Compares two OBLAC Drives bundle filenames by their embedded version strings,
 * sorting them in descending order based on version.
 *
 * The version is extracted from the filename suffix matching the pattern `_<version>.odbx`.
 *
 * @param a - The first bundle filename to compare.
 * @param b - The second bundle filename to compare.
 * @returns A negative number if `b` has a newer version than `a`, positive if `a` is newer,
 *          or 0 if they are equal or the version cannot be determined.
 */
function compareBundlesByVersionDesc(a, b) {
    const re = /_(.*).odbx$/;
    const m1 = a.match(re);
    const m2 = b.match(re);
    if (m1 && m2) {
        return (0, semver_1.rcompare)(m1[1], m2[1]);
    }
    return 0;
}
exports.compareBundlesByVersionDesc = compareBundlesByVersionDesc;
/**
 * Maps resolution types to their corresponding MIME types used for data conversion.
 */
const resolveToMimeType = {
    blob: 'application/octet-stream',
    json: 'application/json',
    text: 'text/plain',
};
/**
 * Converts a base64-encoded string into the specified data type: Blob, JSON, or text.
 *
 * @param base64 - The base64-encoded string to convert.
 * @param resolveTo - The desired output format: `'blob'`, `'json'`, or `'text'`.
 * @returns A promise that resolves to the decoded data in the specified format.
 */
function base64ToData(base64, resolveTo) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        return fetch(`data:${resolveToMimeType[resolveTo]};base64,${base64}`).then((response) => response[resolveTo]());
    });
}
/**
 * Constructs a full CDN URL by appending the given path to the base CDN URL.
 *
 * @param path - The relative path to the object (file) on the CDN.
 * @returns The complete URL pointing to the resource on the CDN.
 */
function makeCdnUrl(path) {
    return `${exports.cdnBasePath}${path}`;
}
exports.makeCdnUrl = makeCdnUrl;
/**
 * Retrieves a previously cached object (file) from the OBLAC box or native app cache.
 *
 * When running in an Electron app, it uses the native API to get the cached file,
 * otherwise it fetches from a local cache HTTP path.
 *
 * @param path - The path to the cached object within the box or app cache.
 * @param resolveTo - The format to resolve the fetched object to: `'blob'`, `'json'`, or `'text'`.
 * @returns A promise that resolves to the cached object in the specified format,
 *          or `undefined` if the file could not be retrieved.
 */
function fetchFromBox(path, resolveTo) {
    var _a;
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        if ((0, util_1.isElectronContext)()) {
            // @ts-ignore
            const file = yield ((_a = window.electron) === null || _a === void 0 ? void 0 : _a.getCachedFile(path));
            return base64ToData(file, resolveTo);
        }
        else {
            const response = yield fetch(`${exports.cacheS3BasePath}${path}`);
            if (response.ok) {
                return response[resolveTo]();
            }
        }
    });
}
exports.fetchFromBox = fetchFromBox;
/**
 * Fetches an object (file) from S3, resolves it to the specified format (JSON, text, or binary),
 * and optionally caches it locally (on the box or within the native app).
 *
 * **Note:** Cached data is stored under the `/synapticon/oblac-drives/` prefix to align with the bundle's cache location.
 *
 * If offline or if the fetch fails (including HTTP errors), the function falls back to retrieving the cached version.
 *
 * @param path - The S3 path to the object.
 * @param resolveTo - The desired response format: `'blob'`, `'json'`, or `'text'`.
 * @param cache - Whether to cache the fetched object locally. Defaults to false if not specified.
 * @returns A promise that resolves to the fetched object in the specified format.
 */
function fetchAndResolve(path, resolveTo, cache) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        yield (0, rxjs_1.firstValueFrom)(online_1.onlineInitialized$);
        if (!online_1.online$.value) {
            return fetchFromBox(`/synapticon/oblac-drives${path}`, resolveTo);
        }
        try {
            // the fetch() function will automatically throw an error for network errors but not for HTTP errors such as 4xx or 5xx responses
            const url = makeCdnUrl(path);
            const response = yield fetch(url);
            if (response.ok) {
                if (cache) {
                    const body = yield response.clone().blob();
                    cacheOnBox(`/synapticon/oblac-drives${path}`, body); // cacheOnBox will catch network error and warn in the console if it fails
                }
                return response[resolveTo]();
            }
            else {
                return fetchFromBox(`/synapticon/oblac-drives${path}`, resolveTo);
            }
        }
        catch (_err) {
            return fetchFromBox(`/synapticon/oblac-drives${path}`, resolveTo);
        }
    });
}
exports.fetchAndResolve = fetchAndResolve;
/**
 * Fetches the brand JSON data, then retrieves and adds the logo and Bootstrap theme URLs to the object.
 *
 * @param brandJsonUrl - The URL of the brand JSON file. Defaults to `'brand.json'`.
 * @param themeVersion - The version number of the Bootstrap theme to fetch. Defaults to `1`.
 * @param cache - If `true`, caches the fetched resources locally. Defaults to `true`.
 * @returns A promise that resolves to the brand object extended with `logo` and `bootstrap` CSS content,
 *          or `undefined` if fetching or parsing fails.
 */
function fetchBrandData(brandJsonUrl = 'brand.json', themeVersion = 1, cache = true) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        try {
            const response = yield fetch(brandJsonUrl);
            if (response.ok) {
                const brand = yield response.json();
                brand.bootstrap = yield fetchAndResolve(`/brands/${brand.id}/styles.v${themeVersion}.min.css`, 'text', cache);
                brand.logo = yield fetchAndResolve(`/brands/${brand.id}/logo.svg`, 'text', cache);
                return brand;
            }
        }
        catch (err) {
            console.error(err);
        }
        return;
    });
}
exports.fetchBrandData = fetchBrandData;
/**
 * Fetches the ESI file for a specified brand and firmware version.
 *
 * @param brandId - The unique identifier of the brand.
 * @param version - The firmware version to fetch the ESI file for.
 * @param cache - If `true`, caches the fetched ESI file locally. Defaults to `true`.
 * @returns A promise that resolves to the ESI file content as a string.
 */
function fetchEsi(brandId, version, cache = true) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        version = encodeURIComponent(version);
        const path = `/brands/${brandId}/esis/${version}.xml`;
        return fetchAndResolve(path, 'text', cache);
    });
}
exports.fetchEsi = fetchEsi;
/**
 * Fetches the content of a firmware package as a Blob.
 *
 * @param brandId - The unique identifier of the brand.
 * @param filename - The name of the firmware package file.
 * @param cache - If `true`, caches the fetched package locally. Defaults to `true`.
 * @returns A promise that resolves to the firmware package content as a Blob.
 */
function fetchFirmwarePackage(brandId, filename, cache = true) {
    filename = encodeURIComponent(filename);
    const path = `/brands/${brandId}/packages/${filename}`;
    return fetchAndResolve(path, 'blob', cache);
}
exports.fetchFirmwarePackage = fetchFirmwarePackage;
/**
 * Fetches the SVG product image for a given brand using a product ID or image ID.
 *
 * @param brandId - The unique identifier of the brand.
 * @param id - The product ID or image ID (number or string).
 * @param cache - If `true`, caches the fetched image locally. Defaults to `true`.
 * @returns A promise that resolves to the SVG image as a string.
 */
function fetchProductImage(brandId, id, cache = true) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const filename = `${id}_product-image.svg`;
        const path = `/brands/${brandId}/product-images/${filename}`;
        return fetchAndResolve(path, 'text', cache);
    });
}
exports.fetchProductImage = fetchProductImage;
/**
 * Fetches the firmware changelog in Markdown format for a specified minor version.
 *
 * @param brandId - The unique identifier of the brand.
 * @param minorVersion - The minor firmware version to fetch the changelog for.
 * @param cache - If `true`, caches the fetched changelog locally. Defaults to `true`.
 * @returns A promise that resolves to the changelog content as a Markdown string.
 *          Rejects if the fetched content is invalid or not found.
 */
function fetchFirmwareChangelog(brandId, minorVersion, cache = true) {
    const path = `/brands/${brandId}/changelogs/v${encodeURIComponent(minorVersion)}.md`;
    return new Promise((resolve, reject) => {
        fetchAndResolve(path, 'text', cache).then((text) => {
            if (text && text.indexOf('<?xml version="1.0" encoding="UTF-8"?>') === -1) {
                resolve(text);
            }
            else {
                reject(new Error(`Failed to fetch changelog for ${minorVersion}`));
                return;
            }
        });
    });
}
exports.fetchFirmwareChangelog = fetchFirmwareChangelog;
/**
 * Fetches both production and development OBLAC Drives releases.
 *
 * @param cache - If `true`, caches the fetched data locally. Defaults to `true`.
 * @returns A promise that resolves to a tuple containing two arrays:
 *          - The first array holds production releases (`OblacDrivesRelease[]`).
 *          - The second array holds development releases (`OblacDrivesRelease[]`).
 */
function fetchOblacDrivesReleases(cache = true) {
    return Promise.all([
        fetchAndResolve('/oblac-drives-releases.json', 'json', cache),
        fetchAndResolve('/oblac-drives-development-releases.json', 'json', cache),
    ]);
}
exports.fetchOblacDrivesReleases = fetchOblacDrivesReleases;
/**
 * Lists firmware package filenames for a given brand.
 *
 * @param brandId - The unique identifier of the brand.
 * @param latestMinorVersionsOnly - If `true`, only includes the latest minor versions; otherwise includes all versions. Defaults to `false`.
 * @param cache - If `true`, caches the fetched data locally. Defaults to `true`.
 * @returns A promise that resolves to an array of firmware package filenames.
 */
function listFirmwarePackages(brandId, latestMinorVersionsOnly = false, cache = true) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const path = `/brands/${brandId}/firmware-packages${latestMinorVersionsOnly ? '.minor' : ''}.json`;
        return yield fetchAndResolve(path, 'json', cache);
    });
}
exports.listFirmwarePackages = listFirmwarePackages;
/**
 * Retrieves a list of available Oblac Drives Update Service versions.
 *
 * Waits for the online state to initialize, then fetches the version list
 * either from the live API or a local cache depending on connectivity.
 *
 * @returns A promise that resolves to an array of version strings.
 */
function listOblacDrivesUpdateServiceVersions() {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        yield (0, rxjs_1.firstValueFrom)(online_1.onlineInitialized$);
        let url = online_1.online$.value
            ? `${exports.oblacDrivesApiGatewayOrigin}/listOblacDrivesUpdateServiceVersions`
            : `${exports.cacheApiBasePath}/listOblacDrivesUpdateServiceVersions`;
        const response = yield fetch(url, { method: 'GET', mode: 'cors' });
        return yield response.json();
    });
}
exports.listOblacDrivesUpdateServiceVersions = listOblacDrivesUpdateServiceVersions;
/**
 * Retrieves the list of supported encoder configuration types from a JSON resource hosted on AWS S3.
 *
 * @deprecated Use new structure with `fetchEncoderConfigurationsUsage` instead
 * @returns A promise that resolves to a `FirmwareProductSupportedOptions` object
 * containing the supported encoder configuration types.
 */
function fetchSupportedEncoderConfigurationTypes() {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const path = `/supported-encoder-configuration-types.json`;
        return yield fetchAndResolve(path, 'json', true);
    });
}
exports.fetchSupportedEncoderConfigurationTypes = fetchSupportedEncoderConfigurationTypes;
/**
 * Retrieves the list of encoder configuration usage information from a JSON resource hosted on AWS S3.
 *
 * @returns A promise that resolves to a `EncoderConfigurationUsage` object
 * containing the encoder configuration supported types and subindexes usage .
 */
function fetchEncoderConfigurationsUsage() {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const path = `/encoder-configurations-usage.json`;
        return yield fetchAndResolve(path, 'json', true);
    });
}
exports.fetchEncoderConfigurationsUsage = fetchEncoderConfigurationsUsage;
/**
 * Retrieves the list of supported analog inputs from a JSON resource hosted on AWS S3.
 *
 * @returns A promise that resolves to a `FirmwareProductSupportedOptions` object
 * containing the supported analog inputs.
 */
function fetchSupportedAis() {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const path = '/supported-ais.json';
        return yield fetchAndResolve(path, 'json', true);
    });
}
exports.fetchSupportedAis = fetchSupportedAis;
/**
 * Retrieves the list of supported digital input/output from a JSON resource hosted on AWS S3.
 *
 * @returns A promise that resolves to a `FirmwareProductSupportedOptions` object
 * containing the supported digital input/output.
 */
function fetchSupportedDios() {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const path = '/supported-dios.json';
        return yield fetchAndResolve(path, 'json', true);
    });
}
exports.fetchSupportedDios = fetchSupportedDios;
/**
 * Retrieves the motor configuration data from a JSON resource hosted on AWS S3.
 *
 * @returns A promise that resolves to a `PartConfigurationTable` object
 * containing available motor configurations.
 */
function fetchMotorConfigurations() {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const path = '/motor-configurations.json';
        return fetchAndResolve(path, 'json', true);
    });
}
exports.fetchMotorConfigurations = fetchMotorConfigurations;
/**
 * Retrieves the encoder configuration data from a JSON resource hosted on AWS S3.
 *
 * @returns A promise that resolves to a `PartConfigurationTable` object
 * containing available encoder configurations.
 */
function fetchEncoderConfigurations(firmwareMajorVersion) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const path = `/encoder-configurations-v${firmwareMajorVersion}.json`;
        return fetchAndResolve(path, 'json', true);
    });
}
exports.fetchEncoderConfigurations = fetchEncoderConfigurations;
//# sourceMappingURL=fetch.js.map