let PreParedReportDataObject = null;

//////////////////////////////////
// Pre render function
//
async function ayPreRender(ayData) {
    if (typeof ayPrepareData === "function") {
        PreParedReportDataObject = await ayPrepareData(ayData);
    } else {
        PreParedReportDataObject = ayData;
    }
    return true;
}

//##################################### HANDLEBARS HELPERS ###################################################

///////////////////////////////////////
// Set our stock handlebars helpers
//
function ayRegisterHelpers() {
    Handlebars.registerHelper("ayCaps", function (ayValue) {
        return ayValue.toUpperCase();
    });

    Handlebars.registerHelper("ayDateTime", function (ayValue) {
        return utcDateToShortDateAndTimeLocalized(ayValue);
    });

    Handlebars.registerHelper("ayDate", function (ayValue) {
        return utcDateToShortDateLocalized(ayValue);
    });

    Handlebars.registerHelper("ayTime", function (ayValue) {
        return utcDateToShortTimeLocalized(ayValue);
    });

    Handlebars.registerHelper("ayDecimal", function (ayValue) {
        return decimalLocalized(ayValue);
    });

    Handlebars.registerHelper("ayCurrency", function (ayValue) {
        return currencyLocalized(ayValue);
    });

    Handlebars.registerHelper("ayWiki", function (ayValue) {
        if (ayValue == null) {
            return "";
        }

        //replace attachment urls with tokenized local urls
        let src = ayValue.replace(/\[ATTACH:(.*)\]/g, function (match, p1) {
            return attachmentDownloadUrl(p1);
        });

        return new Handlebars.SafeString(
            DOMPurify.sanitize(marked.parse(src, { breaks: true }))
        );
    });

    Handlebars.registerHelper("ayJSON", function (obj) {
        return JSON.stringify(obj, null, 3);
    });

    Handlebars.registerHelper("ayLink", function (text, url) {
        var url = Handlebars.escapeExpression(url),
            text = Handlebars.escapeExpression(text);

        return new Handlebars.SafeString(
            "<a href='" + url + "'>" + text + "</a>"
        );
    });

    Handlebars.registerHelper("ayLogo", function (size) {
        if (AYMETA.ayServerMetaData) {
            switch (size) {
                case "small":
                    if (!AYMETA.ayServerMetaData.HasSmallLogo) {
                        return "";
                    }
                    break;
                case "medium":
                    if (!AYMETA.ayServerMetaData.HasMediumLogo) {
                        return "";
                    }
                    break;
                case "large":
                    if (!AYMETA.ayServerMetaData.HasLargeLogo) {
                        return "";
                    }
                    break;
            }
        }
        var url = `${Handlebars.escapeExpression(
            AYMETA.ayServerMetaData.ayApiUrl
        )}logo/${size}`;
        return new Handlebars.SafeString("<img src='" + url + "'/>");
    });

    Handlebars.registerHelper("ayT", function (translationKey) {
        if (ayTranslationKeyCache[translationKey] == undefined) {
            throw `ayT reporting helper error: the key "${translationKey}" is not present in the translation cache, did you forget to include it in your call to "await ayGetTranslations(['ExampleTranslationKey1','ExampleTranslationKey2','etc']);" in ayPrepareData()\nTranslationKeyCache contains: ${JSON.stringify(
                ayTranslationKeyCache,
                null,
                3
            )}?`;
            // return translationKey;
        }
        return ayTranslationKeyCache[translationKey];
    });

    ///////////////////////////////////////////
    // BarCode helper using
    //  https://github.com/metafloor/bwip-js#browser-usage
    //
    Handlebars.registerHelper("ayBC", function (text, options) {
        let canvas = document.getElementById("aybarcode");
        if (canvas == null) {
            canvas = document.createElement("canvas");
            canvas.id = "aybarcode";
        }
        let opt = JSON.parse(options);
        if (text == null) {
            text = "";
        } else {
            text = text.toString();
        }
        opt.text = text;
        opt.textxalign = opt.textxalign || "center";

        bwipjs.toCanvas(canvas, opt);
        var url = canvas.toDataURL("image/png");
        return new Handlebars.SafeString("<img src='" + url + "'/>");
    });
} //eof

///////////////////////////////////////////
// Concat helper using
//  https://stackoverflow.com/a/52571635/8939
//
Handlebars.registerHelper("ayConcat", function () {
    arguments = [...arguments].slice(0, -1);
    return arguments.join("");
});

//##################################### LOCALIZATION & TRANSLATION ###################################################

///////////////////////////////////////////
// Turn a utc date into a displayable
// short date and time
//
function utcDateToShortDateAndTimeLocalized(ayValue) {
    if (!ayValue) {
        return "";
    }

    //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
    let parsedDate = new Date(ayValue);

    //is it a valid date?
    if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
        return "not valid";
    }

    return parsedDate.toLocaleString(
        AYMETA.ayClientMetaData.LanguageName || "en-US",
        {
            timeZone:
                AYMETA.ayClientMetaData.TimeZoneName || "America/Winnipeg",
            dateStyle: "short",
            timeStyle: "short",
            hour12: AYMETA.ayClientMetaData.Hour12
        }
    );
}
///////////////////////////////////////////
// Turn a utc date into a displayable
// short date
function utcDateToShortDateLocalized(ayValue) {
    if (!ayValue) {
        return "";
    }

    //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
    let parsedDate = new Date(ayValue);

    //is it a valid date?
    if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
        return "not valid";
    }

    return parsedDate.toLocaleDateString(
        AYMETA.ayClientMetaData.LanguageName || "en-US",
        {
            timeZone:
                AYMETA.ayClientMetaData.TimeZoneName || "America/Winnipeg",
            dateStyle: "short"
        }
    );
}
///////////////////////////////////////////
// Turn a utc date into a displayable
// short time
function utcDateToShortTimeLocalized(ayValue) {
    if (!ayValue) {
        return "";
    }

    //parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
    let parsedDate = new Date(ayValue);

    //is it a valid date?
    if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
        return "not valid";
    }

    return parsedDate.toLocaleTimeString(
        AYMETA.ayClientMetaData.LanguageName || "en-US",
        {
            timeZone:
                AYMETA.ayClientMetaData.TimeZoneName || "America/Winnipeg",
            timeStyle: "short",
            hour12: AYMETA.ayClientMetaData.Hour12
        }
    );
}

///////////////////////////////////////////
// CURRENCY LOCALIZATION
//
function currencyLocalized(ayValue) {
    if (!ayValue) {
        return "";
    }
    return new Intl.NumberFormat(
        AYMETA.ayClientMetaData.LanguageName || "en-US",
        {
            style: "currency",
            currency: AYMETA.ayClientMetaData.CurrencyName || "USD"
        }
    ).format(ayValue);
}

///////////////////////////////////////////
// DECIMAL LOCALIZATION
//
function decimalLocalized(ayValue) {
    if (!ayValue) {
        return "";
    }
    return new Intl.NumberFormat(
        AYMETA.ayClientMetaData.LanguageName || "en-US"
    ).format(ayValue);
}

//////////////////////////////////
// cache to hold translations keys
//
var ayTranslationKeyCache = {};

///////////////////////////////////
//  GET TRANSLATIONS FROM API SERVER
//
async function ayGetTranslations(keys) {
    if (!keys || keys.length == 0) {
        return;
    }
    try {
        let transData = await ayPostToAPI("translation/subset", keys);
        transData.data.forEach(function storeFetchedTranslationItemsInCache(
            item
        ) {
            ayTranslationKeyCache[item.key] = item.value;
        });
    } catch (error) {
        throw error;
    }
}

//##################################### API UTILITIES ###################################################

///////////////////////////////////
//  GET DATA FROM API SERVER
//
async function ayGetFromAPI(route, token) {
    token = token || AYMETA.ayClientMetaData.Authorization;
    if (route && !route.startsWith("http")) {
        route = AYMETA.ayServerMetaData.ayApiUrl + route;
    }
    try {
        let r = await fetch(route, {
            method: "get",
            mode: "cors",
            headers: {
                Accept: "application/json",
                "Content-Type": "application/json",
                Authorization: token
            }
        });
        return await extractBodyEx(r);
    } catch (error) {
        throw error;
    }
}

///////////////////////////////////
//  POST  DATA TO API SERVER
//
async function ayPostToAPI(route, data, token) {
    token = token || AYMETA.ayClientMetaData.Authorization;
    if (route && !route.startsWith("http")) {
        route = AYMETA.ayServerMetaData.ayApiUrl + route;
    }
    //api expects custom fields to be a string not an object
    if (data && data.CustomFields && data.CustomFields.constructor === Object) {
        data.CustomFields = JSON.stringify(data.CustomFields);
    }
    try {
        fetchOptions = {
            method: "post",
            mode: "cors",
            headers: {
                Accept: "application/json",
                "Content-Type": "application/json",
                Authorization: token
            },
            body: JSON.stringify(data)
        };
        let r = await fetch(route, fetchOptions);
        return await extractBodyEx(r);
    } catch (error) {
        throw error;
    }
}

///////////////////////////////////
//  PUT  DATA TO API SERVER
//
async function ayPutToAPI(route, data, token) {
    token = token || AYMETA.ayClientMetaData.Authorization;
    if (route && !route.startsWith("http")) {
        route = AYMETA.ayServerMetaData.ayApiUrl + route;
    }

    //api expects custom fields to be a string not an object
    if (data && data.CustomFields && data.CustomFields.constructor === Object) {
        data.CustomFields = JSON.stringify(data.CustomFields);
    }
    try {
        fetchOptions = {
            method: "put",
            mode: "cors",
            headers: {
                Accept: "application/json",
                "Content-Type": "application/json",
                Authorization: token
            },
            body: JSON.stringify(data)
        };
        let r = await fetch(route, fetchOptions);
        return await extractBodyEx(r);
    } catch (error) {
        throw error;
    }
}

/////////////////////////////
// attachment download URL
// (INTERNAL USE NOT DOCUMENTED FOR HELPER USE)
function attachmentDownloadUrl(fileId, ctype) {
    let url =
        "attachment/download/" +
        fileId +
        "?t=" +
        AYMETA.ayClientMetaData.DownloadToken;

    if (ctype && ctype.includes("image")) {
        url += "&i=1";
    }

    return AYMETA.ayServerMetaData.ayApiUrl + url;
}

//##################################### CODE UTILITIES ###################################################

async function extractBodyEx(response) {
    if (response.status == 204) {
        //no content, nothing to process
        return response;
    }
    if (response.status == 202) {
        //Accepted, nothing to process
        return response;
    }

    const contentType = response.headers.get("content-type");

    if (!contentType) {
        return response;
    }
    if (contentType.includes("json")) {
        return await response.json();
    }
    if (contentType.includes("text/plain")) {
        return await response.text();
    }

    if (contentType.includes("application/pdf")) {
        return await response.blob();
    }
    return response;
}

/////////////////////////////////////////////////////////
// Group by function
// reshapes input array into a new array grouped with
// a key named "group" with that group's key value
// and a key named "items" to contain all items
// for that group and also a "count" of items added
//
//
function ayGroupByKey(reportDataArray, groupByKeyName) {
    //array to hold grouped data
    const ret = [];
    //iterate through the raw reprot data
    for (let i = 0; i < reportDataArray.length; i++) {
        //search the ret array for a group with this name and if found return a reference to that group object
        let groupObject = ret.find(
            (z) => z.group == reportDataArray[i][groupByKeyName]
        );
        if (groupObject != undefined) {
            //there is already a matching group in the return array so just push this raw report data record into it
            groupObject.items.push(reportDataArray[i]);
            //update the count for this group's items
            groupObject.count++;
        } else {
            //No group yet, so start a new one in the ret array and push this raw report data record
            ret.push({
                group: reportDataArray[i][groupByKeyName],
                items: [reportDataArray[i]],
                count: 1
            });
        }
    }
    return ret;
}
