{% macro get_generics(definition, spec, output = false, first = false) %}
{% apply spaceless %}
    {% if first and definition.additionalProperties %}
        {{definition.name | caseUcfirst}} extends Models.{{definition.name | caseUcfirst}}
    {% endif %}
    {% for property in definition.properties %}
        {% if spec.definitions[property.sub_schema].additionalProperties and output %}
            {{property.sub_schema | caseUcfirst}} extends Models.{{property.sub_schema | caseUcfirst}}
        {% endif %}
        {% if spec.definitions[property.sub_schema] %}
            {{_self.get_generics(spec.definitions[property.sub_schema], spec, true)}}
        {% endif %}
    {% endfor %}
{% endapply %}
{% endmacro %}
{% macro get_generics_return(definition, spec) %}
{% apply spaceless %}
    {% for property in definition.properties %}
        {% if spec.definitions[property.sub_schema].additionalProperties %}
            {{property.sub_schema | caseUcfirst}}
        {% endif %}
        {% if spec.definitions[property.sub_schema] %}
            {{_self.get_generics_return(spec.definitions[property.sub_schema], spec)}}
        {% endif %}
    {% endfor %}
{% endapply %}
{% endmacro %}
import { basename } from "https://deno.land/std@0.122.0/path/mod.ts";
import { Service } from '../service.ts';
import { Payload, Client } from '../client.ts';
import { InputFile } from '../inputFile.ts';
import { AppwriteException } from '../exception.ts';
import type { Models } from '../models.d.ts';
import { Query } from '../query.ts';
{% set added = [] %}
{% for method in service.methods %}
{% for parameter in method.parameters.all %}
{% if parameter.enumValues is not empty %}
{% if parameter.enumName is not empty %}
{% set name = parameter.enumName %}
{% else %}
{% set name = parameter.name %}
{% endif %}
{% if name not in added %}
import { {{ name | caseUcfirst }} } from '../enums/{{ name | caseDash }}.ts';
{% set added = added|merge([name]) %}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}

export type UploadProgress = {
    $id: string;
    progress: number;
    sizeUploaded: number;
    chunksTotal: number;
    chunksUploaded: number;
}

export class {{ service.name | caseUcfirst }} extends Service {

     constructor(client: Client)
     {
        super(client);
     }

{% for method in service.methods %}
{% set generics = _self.get_generics(spec.definitions[method.responseModel], spec, true, true) %}
{% set generics_return = _self.get_generics_return(spec.definitions[method.responseModel], spec) %}
    /**
     * {{ method.title }}
     *
{% if method.description %}
{{ method.description|comment1 }}
     *
{% endif %}
{% for parameter in method.parameters.all%}
     * @param {{ '{' }}{{ parameter | typeName }}{{ '}' }} {{ parameter.name | caseCamel | escapeKeyword }}
{% endfor %}
     * @throws {AppwriteException}
     * @returns {Promise}
     */
    async {{ method.name | caseCamel }}{% if generics %}<{{generics}}>{% endif %}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | typeName }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, onProgress = (progress: UploadProgress) => {}{% endif %}): Promise<{% if method.type == 'webAuth' %}string{% elseif method.type == 'location' %}ArrayBuffer{% else %}{% if method.responseModel and method.responseModel != 'any' %}{% if not spec.definitions[method.responseModel].additionalProperties %}Models.{% endif %}{{method.responseModel | caseUcfirst}}{% if generics_return %}<{{generics_return}}>{% endif %}{% else %}Response{% endif %}{% endif %}> {
{% for parameter in method.parameters.all %}
{% if parameter.required %}
        if (typeof {{ parameter.name | caseCamel | escapeKeyword }} === 'undefined') {
            throw new {{spec.title | caseUcfirst}}Exception('Missing required parameter: "{{ parameter.name | caseCamel | escapeKeyword }}"');
        }

{% endif %}
{% endfor %}
        const apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};
        const payload: Payload = {};

{% for parameter in method.parameters.query %}
        if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
            payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }}{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" and parameter.type != "file" ) %}.toString(){% endif %};
        }

{% endfor %}
{% for parameter in method.parameters.body %}
        if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
            payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }}{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" and parameter.type != "file" ) %}.toString(){% endif %};
        }
{% endfor %}
{% if 'multipart/form-data' in method.consumes %}
{% for parameter in method.parameters.all %}
{% if parameter.type == 'file' %}

        const size = {{ parameter.name | caseCamel | escapeKeyword }}.size;
        
        const apiHeaders: { [header: string]: string } = {
{% for parameter in method.parameters.header %}
            '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
{% endfor %}
{% for key, header in method.headers %}
            '{{ key }}': '{{ header }}',
{% endfor %}
        };

        let id: string | undefined = undefined;
        let response: any = undefined;

        let chunksUploaded = 0;

{% for parameter in method.parameters.all %}
{% if parameter.isUploadID %}
        if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') {
            try {
                response = await this.client.call(
                    'get',
                    apiPath + '/' + {{ parameter.name }},
                    apiHeaders
                );
                chunksUploaded = response.chunksUploaded;
            } catch(e) {
            }
        }
{% endif %}
{% endfor %}

        let currentChunk = 1;
        let currentPosition = 0;
        let uploadableChunk = new Uint8Array(Client.CHUNK_SIZE);

        const uploadChunk = async (lastUpload = false) => {
            if(currentChunk <= chunksUploaded) {
                return;
            }

            const start = ((currentChunk - 1) * Client.CHUNK_SIZE);
            let end = start + currentPosition;

            if (end === size) {
                end -= 1;
            }

            if(!lastUpload || currentChunk !== 1) {
                apiHeaders['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
            }

            let uploadableChunkTrimmed: Uint8Array;
            
            if(currentPosition + 1 >= Client.CHUNK_SIZE) {
                uploadableChunkTrimmed = uploadableChunk;
            } else {
                uploadableChunkTrimmed = new Uint8Array(currentPosition);
                for(let i = 0; i <= currentPosition; i++) {
                    uploadableChunkTrimmed[i] = uploadableChunk[i];
                }
            }

            if (id) {
                apiHeaders['x-{{spec.title | caseLower }}-id'] = id;
            }

            payload['{{ parameter.name }}'] = { type: 'file', file: new File([uploadableChunkTrimmed], {{ parameter.name | caseCamel | escapeKeyword }}.filename), filename: {{ parameter.name | caseCamel | escapeKeyword }}.filename };

            response = await this.client.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% elseif method.type == 'webAuth' %}, 'location'{% endif %});

            if (!id) {
                id = response['$id'];
            }

            if (onProgress !== null) {
                onProgress({
                    $id: response['$id'],
                    progress: Math.min((currentChunk) * Client.CHUNK_SIZE, size) / size * 100,
                    sizeUploaded: end+1,
                    chunksTotal: response['chunksTotal'],
                    chunksUploaded: response['chunksUploaded']
                });
            }

            uploadableChunk = new Uint8Array(Client.CHUNK_SIZE);
            currentPosition = 0;
            currentChunk++;
        }

        for await (const chunk of {{ parameter.name | caseCamel | escapeKeyword }}.stream) {
            let i = 0;
            for(const b of chunk) {
                uploadableChunk[currentPosition] = chunk[i];

                if(currentPosition + 1 >= Client.CHUNK_SIZE) {
                    await uploadChunk();
                    currentPosition--;
                }

                i++;
                currentPosition++;
            }
        }

        await uploadChunk(true);

        return response;
{% endif %}
{% endfor %}
{% else %}
        return await this.client.call(
            '{{ method.method | caseLower }}',
            apiPath,
            {
                {%~ for parameter in method.parameters.header -%}
                '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
                {%~ endfor -%}
                {%~ for key, header in method.headers %}
                '{{ key }}': '{{ header }}',
                {%~ endfor %}
            },
            payload,
            {%~ if method.type == 'location' %}
            'arraybuffer'
            {%~ elseif method.type == 'webAuth' %}
            'location'
            {%~ else %}
            'json'
            {%~ endif %}
        );
{% endif %}
    }
{% endfor %}
}