export default class API {

    defaultOp = {
        requestURI: "",
        maxRetry: 5,
        initialDelay: 1000, // 1 second
        maxBackoff: 1000 * 30, // 30 seconds
		pingAction: "ping"
    };

    constructor(op){

        this.op = { ...this.defaultOp, ...op };
        this.requestURI = this.op.requestURI;
		this.online = true;
    }

    setBaseURI(uri){

        if( typeof uri === "string" && uri ){
            this.requestURI = uri;
        }
    }

	setRequestURI(uri){ this.setBaseURI(uri); }

    async send(action, payload){

		if(!this.requestURI){
			const error = {
				type: "bad request",
				msg: "Request URI not set",
				status: 0,
				attempt: 0
			}
			console.log(error);
			return { success: false, error };
		}

        if(!this.online && action !== this.op.pingAction){
			const error = {
				type: "offline",
				msg: "API is offline",
				status: 0,
				attempt: 0
			}
			return { success: false, error }
        }

        const reqURI = `${this.requestURI}/action/${action}`;
        const reqBody = {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        };

        let attempt = 0;
        let delay = this.op.initialDelay;
        while(attempt <= this.op.maxRetry){

			attempt++;

			try{
				const res = await fetch(reqURI, reqBody);
				if(res.ok){
					try {
						const data = await res.json();
						return data;
					} catch (parseErr) {
						const error = {
							type: "parse",
							msg: "Failed to parse server response as JSON",
							status: res.status,
							attempt
						}
						console.log(error);
						return { success: false, error };
					}
				} 
				else{
					const error = {
						type: "http",
						msg: `HTTP Error: ${res.status}`,
						status: res.status,
						attempt
					}
					console.log(error);
					return { success: false, error }
				}
			}
			catch(err){
				if(attempt > this.op.maxRetry){
					const error = {
						type: "network",
						msg: err.message,
						status: 0,
						attempt
					}
					console.log(error);
					return { success: false, error }
				}
				// exponential backoff
				await new Promise(resolve => setTimeout(resolve, delay));
                delay = Math.min(delay * 2, this.op.maxBackoff);	
			}
		}
	}
	
   /**
	* Pure utility to check if the server is reachable.
	* Returns Boolean. No state side-effects.
	*/
	async isReachable(){

		try{
			const reqURI = `${this.requestURI}/action/${this.op.pingAction}`;
			const res = await fetch(reqURI, {
				method: 'HEAD',
				cache: 'no-store'
			});
			return res.ok;
		} 
		catch(e){
			return false;
		}
	}
}
