import { {{ spec.title | caseUcfirst}}Exception } from './exception.ts';

export interface Payload {
    [key: string]: any;
}

export class Client {
    static CHUNK_SIZE = 5*1024*1024; // 5MB
    static DENO_READ_CHUNK_SIZE = 16384; // 16kb; refference: https://github.com/denoland/deno/discussions/9906
    
    endpoint: string = '{{spec.endpoint}}';
    headers: Payload = {
        'content-type': '',
        'user-agent' : `{{spec.title | caseUcfirst}}{{ language.name | caseUcfirst }}SDK/{{ sdk.version }} (${Deno.build.os}; ${Deno.build.arch})`,
        'x-sdk-name': '{{ sdk.name }}',
        'x-sdk-platform': '{{ sdk.platform }}',
        'x-sdk-language': '{{ language.name | caseLower }}',
        'x-sdk-version': '{{ sdk.version }}',
{% for key,header in spec.global.defaultHeaders %}
        '{{key}}':'{{header}}',
{% endfor %}
    };

{% for header in spec.global.headers %}
    /**
     * Set {{header.key | caseUcfirst}}
     *
{% if header.description %}
     * {{header.description}}
     *
{% endif %}
     * @param string value
     *
     * @return self
     */
    set{{header.key | caseUcfirst}}(value: string): this {
        this.addHeader('{{header.name}}', value);

        return this;
    }

{% endfor %}

    /***
     * @param endpoint
     * @return this
     */
    setEndpoint(endpoint: string): this {
        this.endpoint = endpoint;

        return this;
    }

    /**
     * @param key string
     * @param value string
     */
    addHeader(key: string, value: string): this {
        this.headers[key.toLowerCase()] = value;

        return this;
    }

    async call(method: string, path: string = "", headers: Payload = {}, params: Payload = {}, responseType: string = "json") {
        headers = {...this.headers, ...headers};
        const url = new URL(this.endpoint + path);

        let body: string | FormData | undefined = undefined;

        if (method.toUpperCase() === "GET") {
            url.search = new URLSearchParams(Client.flatten(params)).toString();
        } else if (headers["content-type"]?.toLowerCase().startsWith("multipart/form-data")) {
            delete headers["content-type"];
            const formData = new FormData();

            const flatParams = Client.flatten(params);

            for (const [key, value] of Object.entries(flatParams)) {
                if (value && value.type && value.type === "file") {
                    formData.append(key, value.file, value.filename);
                } else {
                    formData.append(key, value);
                }
            }

            body = formData;
        } else {
            body = JSON.stringify(params);
        }

        let response = undefined;
        try {
            response = await fetch(url.toString(), {
                redirect: responseType === "location" ? "manual" : "follow",
                method: method.toUpperCase(),
                headers,
                body
            });

            const warnings = response.headers.get('x-{{ spec.title | lower }}-warning');
            if (warnings) {
                warnings.split(';').forEach((warning: string) => console.warn('Warning: ' + warning));
            }
        } catch (error) {
            throw new {{spec.title | caseUcfirst}}Exception(error.message);
        }

        if (response.status >= 400) {
            const text = await response.text();
            let json = undefined;
            try {
                json = JSON.parse(text);
            } catch (error) {
                throw new {{spec.title | caseUcfirst}}Exception(text, response.status, "", text);
            }
            throw new {{spec.title | caseUcfirst}}Exception(json.message, json.code, json.type, json);
        }

        if (responseType === "arraybuffer") {
            const data = await response.arrayBuffer();
            return data;
        }

        if (responseType === "location") {
            return response.headers.get("location");
        }

        const text = await response.text();
        let json = undefined;
        try {
            json = JSON.parse(text);
        } catch (error) {
            return text;
        }
        return json;
    }

    static flatten(data: Payload, prefix = ''): Payload {
        let output: Payload = {};

        for (const [key, value] of Object.entries(data)) {
            let finalKey = prefix ? prefix + '[' + key +']' : key;
            if (Array.isArray(value)) {
                output = { ...output, ...Client.flatten(value, finalKey) };
            } else {
                output[finalKey] = value;
            }
        }

        return output;
    }
}