var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { md5 } from '../../md5/md5';
import { ApiError } from '../utils';
export class ApiService {
    constructor(app, instanceData = {}) {
        this.app = app;
        // dafault instance data
        const { apiKey } = this.app.options;
        this.baseEndpoint = '';
        this.predefinedQuery = !!apiKey ? { apiKey } : {};
        this.predefinedBody = {};
        this.beforeRequestHooks = [];
        // set custom data
        this.setData(instanceData);
    }
    extend() {
        return new ApiService(this.app, {
            endpoint: this.baseEndpoint,
            query: this.predefinedQuery,
            body: this.predefinedBody,
            beforeHooks: this.beforeRequestHooks,
        });
    }
    setData(data) {
        const { endpoint, query, body, beforeHooks } = data;
        if (!!endpoint) {
            this.baseEndpoint = endpoint;
        }
        if (!!query) {
            this.predefinedQuery = query;
        }
        if (!!body) {
            this.predefinedBody = body;
        }
        if (!!beforeHooks) {
            this.beforeRequestHooks = beforeHooks;
        }
        return this;
    }
    setEndpoint(endpoint) {
        this.baseEndpoint = endpoint;
        return this;
    }
    addQuery(query) {
        this.predefinedQuery = Object.assign({}, this.predefinedQuery, query);
        return this;
    }
    addBody(body) {
        this.predefinedBody = Object.assign({}, this.predefinedBody, body);
        return this;
    }
    addBeforeHooks(hooks) {
        if (hooks instanceof Function) {
            hooks = [hooks];
        }
        this.beforeRequestHooks = [
            ...this.beforeRequestHooks,
            ...hooks,
        ];
        return this;
    }
    buildEndpoint(endpoint = '') {
        endpoint = '/' + this.baseEndpoint + '/' + endpoint; // add base
        endpoint = endpoint.replace(/\/+$/, ''); // remove trailing slash
        endpoint = endpoint.replace(/\/+/g, '/'); // remove repeated slashs
        return !!endpoint ? endpoint : '/';
    }
    buildQuery(query = {}) {
        query = Object.assign({}, this.predefinedQuery, query);
        // make the string
        let queryStr = '';
        for (const key of Object.keys(query)) {
            queryStr = queryStr + '&' + key + '=' + query[key];
        }
        return queryStr.replace(/^&/, '');
    }
    buildBody(body = {}) {
        return Object.assign({}, this.predefinedBody, body);
    }
    buildUrl(endpoint = '', query = '') {
        let { backendUrl: url } = this.app.options;
        url += !!endpoint ? ('?e=' + endpoint) : (!!query ? '?' : '');
        return (!!query ? (url + '&' + query) : url).replace('?&', '?');
    }
    runHooks(hook, data) {
        return __awaiter(this, void 0, void 0, function* () {
            const hooks = hook === 'before' ? this.beforeRequestHooks : [];
            for (let i = 0; i < hooks.length; i++) {
                data = yield hooks[i](data);
            }
            return data;
        });
    }
    fetch(input, init) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield fetch(input, init);
            if (!response.ok) {
                throw new Error('API fetch failed.');
            }
            const result = yield response.json();
            if (result.error) {
                throw new ApiError(result);
            }
            return result.data;
        });
    }
    request(inputs = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const { method = 'get', endpoint = '/', query = {}, body = {}, cacheTime = 0 } = inputs;
            if (method.toLowerCase() === 'get') {
                return this.get(endpoint, query, cacheTime);
            }
            else if (method.toLowerCase() === 'post') {
                return this.post(endpoint, query, body);
            }
            else {
                return this.post(endpoint, Object.assign({ method }, query), body);
            }
        });
    }
    get(endpoint, query = {}, cacheTime = 0) {
        return __awaiter(this, void 0, void 0, function* () {
            const originalUrl = this.buildUrl(this.buildEndpoint(endpoint), this.buildQuery(query));
            // run hooks
            const beforeHookResult = yield this.runHooks('before', {
                endpoint, query, body: {},
            });
            endpoint = beforeHookResult.endpoint;
            query = beforeHookResult.query;
            // build url
            const url = this.buildUrl(this.buildEndpoint(endpoint), this.buildQuery(query));
            return yield this.app.Cache.getRefresh('api_' + md5(originalUrl), cacheTime, () => __awaiter(this, void 0, void 0, function* () { return yield this.fetch(url, { method: 'GET' }); }));
        });
    }
    post(endpoint, query = {}, body = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const beforeHookResult = yield this.runHooks('before', {
                endpoint, query, body,
            });
            endpoint = beforeHookResult.endpoint;
            query = beforeHookResult.query;
            body = beforeHookResult.body;
            // build url
            const url = this.buildUrl(this.buildEndpoint(endpoint), this.buildQuery(query));
            // send request
            return yield this.fetch(url, {
                method: 'POST',
                body: JSON.stringify(this.buildBody(body)),
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
            });
        });
    }
    put(endpoint, query = {}, body = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.post(endpoint, Object.assign({}, query, { method: 'PUT' }), body);
        });
    }
    patch(endpoint, query = {}, body = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.post(endpoint, Object.assign({}, query, { method: 'PATCH' }), body);
        });
    }
    delete(endpoint, query = {}, body = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.post(endpoint, Object.assign({}, query, { method: 'DELETE' }), body);
        });
    }
}
