$(() => {
    // global lets
    let cachedMethods = new Array;
    let ws_url = "http://";

    // automatic detection of ws_url
    match = document.location.toString().match(/^(https?.*\/)tools\/ws\.html?/);
    if (match == null) {
        askForUrl();
    }
    else {
        ws_url = match[1] + 'ws.php';
        getMethodList();
    }

    // manual set of ws_url
    $("#urlForm").submit(function () {
        ws_url = $(this).children("input[name='ws_url']").val();
        getMethodList();
        return false;
    });

    // invoke buttons
    $("#invokeMethod").click(function () {
        invokeMethod($("#methodName").html(), false);
        return false;
    });
    $("#invokeMethodBlank").click(function () {
        invokeMethod($("#methodName").html(), true);
        return false;
    });

    // resizable iframe
    $("#increaseIframe").click(function () {
        $("#resultWrapper").css('height', $("#resultWrapper").height() + 100);
    });
    $("#decreaseIframe").click(function () {
        if ($("#resultWrapper").height() > 200) {
            $("#resultWrapper").css('height', $("#resultWrapper").height() - 100);
        }
    });

    // mask all wrappers
    function resetDisplay() {
        $("#errorWrapper").hide();
        $("#methodWrapper").hide();
        $("#methodName").hide();
        $("#urlForm").hide();
        $("#methodDescription blockquote").empty();
        $("#methodDescription").hide();
        $("#requestURLDisplay").hide();
        $("#requestResultDisplay").hide();
        $("#invokeFrame").attr('src', '');
    }

    // display error wrapper
    function displayError(error) {
        resetDisplay();
        $("#errorWrapper").html("<b>Error:</b> " + error).show();
    }

    // display ws_url form
    function askForUrl() {
        displayError("can't contact web-services, please give absolute url to 'ws.php'");
        if ($("#urlForm input[name='ws_url']").val() == "") {
            $("#urlForm input[name='ws_url']").val(ws_url);
        }
        $("#urlForm").show();
    }

    // parse Piwigo JSON
    function parsePwgJSON(json) {
        try {
            resp = jQuery.parseJSON(json);
            if (resp == null | resp.result == null | resp.stat == null | resp.stat != 'ok') {
                throw new Error();
            }
        }
        catch (e) {
            displayError("unable to parse JSON string");
            resp = { "stat": "ko", "result": "null" };
        }

        return resp.result;
    }

    // fetch methods list
    function getMethodList() {
        resetDisplay();

        $.ajax({
            type: "GET",
            url: ws_url,
            data: { format: "json", method: "reflection.getMethodList" }
        }).done(function (result) {
            // for debug
            //console.log(result);
            result = parsePwgJSON(result);

            if (result != null) {
                methods = result.methods;

                let methodTree = {};
                for (let i = 0; i < methods.length; i++) {
                    addMethodToNode(methodTree, methods[i].split('.'))
                }

                $("#methodsList").html(displayMethodNode(methodTree, [])).show();

                // trigger method selection
                $("#methodsList .method-link").click(function () {
                    selectMethod($(this).data('method'));
                });

                if ($.cookie('wse-menu-state')) {
                    $.cookie('wse-menu-state').split(',').forEach(id => $(`input[id="${id}"]`).attr('checked', true));
                }

                setStateMenu();

                $('.method-node-content').each((i, node) => {
                    let content = $(node);
                    let checkbox = content.parent().children('input');

                    checkbox.on('change', function() {

                        let id = checkbox.attr('id');

                        let menustate = $.cookie('wse-menu-state')?.split(',') ?? [];

                        if (this.checked) {
                            content.slideDown(200);

                            menustate.push(id)
                        } else {
                            content.slideUp(200);
                            menustate = menustate.filter(str => str !== id);
                        }

                        $.cookie('wse-menu-state', menustate.join(','))

                    })
                })
            }
        }).error(function (jqXHR, textStatus, errorThrown) {
            askForUrl();
        });
    }

    function addMethodToNode(methodNode, methodRoute) {
        if (methodRoute.length > 1) {
            let node = methodRoute.shift();
            if (!methodNode[node])
                methodNode[node] = {};
            addMethodToNode(methodNode[node], methodRoute);
        } else 
            methodNode[methodRoute[0]] = 1;
    }

    function displayMethodNode(methodNode, route) {
        let html = '';

        if (methodNode === 1) {
            html = `<a 
                class="method-link" 
                data-method="${route.join('.')}"
                title="${route[route.length - 1]}"
            >
                ${route[route.length - 1]}
            </a>`
        } else {
            html = route.length === 0 ? '' : `<div class="method-node">
                <input type="checkbox" id="method-node-input-${route.join('.')}">
                <label for="method-node-input-${route.join('.')}" title="${route[route.length - 1]}">
                    <i class="icon-down-open"></i>
                    <span>${route[route.length - 1]}</span>
                </label>
                <div class="method-node-content">`;

            for (const node in methodNode) {
                html += displayMethodNode(methodNode[node], [...route, node]);
            }
            
            html += route.length === 0 ? '' :'</div></div>';
        }

        return html;
    }

    function setStateMenu() {
        $('.method-node').each((i, n) => {
            let node = $(n);
            let content = node.children('.method-node-content')
            let checkbox = node.children('input');
            
            if (checkbox.prop('checked')) {
                content.show();
            } else {
                content.hide();
            }
        })
    }

    // select method
    function selectMethod(methodName) {
        $("#introMessage").hide();
        $("#tiptip_holder").fadeOut(200);

        if (cachedMethods[methodName]) {
            fillNewMethod(methodName);
        }
        else {
            $.ajax({
                type: "GET",
                url: ws_url,
                data: { format: "json", method: "reflection.getMethodDetails", methodName: methodName }
            }).done(function (result) {
                result = parsePwgJSON(result);

                if (result != null) {
                    let onlys = [];
                    if (result.options.post_only || result.options.admin_only) {
                        if (result.options.post_only) {
                            onlys.push('POST only');
                        }
                        if (result.options.admin_only) {
                            onlys.push('Admin only');
                        }

                    }
                    result.onlys = onlys;
                    cachedMethods[methodName] = result;
                    fillNewMethod(methodName);
                }
            }).error(function (jqXHR, textStatus, errorThrown) {
                displayError("unknown error");
            });
        }
    }

    // display method details
    function fillNewMethod(methodName) {
        resetDisplay();

        method = cachedMethods[methodName];

        $("#methodName").html(method.name).show();

        $('#onlys').html('');
        method.onlys.forEach((text) => {
            $('#onlys').append($(`<span class="only">${text}</span>`));
        })

        if (method.description != "") {
            $("#methodDescription blockquote").html(method.description);
            $("#methodDescription").show();
        }

        $("#requestFormat").val(method.options.post_only ? 'post' : 'get');

        let methodParams = '';
        if (method.params && method.params.length > 0) {

            $('.no-params').hide();
            $("#methodParams table").show();
            
            for (let i = 0; i < method.params.length; i++) {
                let param = method.params[i],
                    isOptional = param.optional,
                    acceptArray = param.acceptArray,
                    defaultValue = param.defaultValue == null ? '' : param.defaultValue,
                    info = param.info == null ? '' : '<i class="methodInfo icon-info-circled-1" title="' + param.info.replace(/"/g, '&quot;') + '"></i>',
                    type = '',
                    subtype = '',
                    optional = '<span class="required" title = "This parameter is required" >*</span >',
                    array = '<span class="type-badge icon-clone" title="Can be an array"></span >';

                if (param.type.match(/bool/)) type += '<span class="type-badge" title="Boolean">B<span>';
                if (param.type.match(/int/)) type += '<span class="type-badge" title="Integer">I</span>';
                if (param.type.match(/float/)) type += '<span class="type-badge" title="Float">F</span>';

                if (param.type.match(/positive/)) subtype += '<span class="type-badge icon-plus" title="Positive"></span>';
                if (param.type.match(/notnull/)) subtype += '<span class="type-badge" title="Not null"><span style="transform:translateY(-3px)">&oslash;</span></span>';


                // if an array is direclty printed, the delimiter is a comma where we use a pipe
                if (typeof defaultValue == 'object') {
                    defaultValue = defaultValue.join('|');
                }

                methodParams += `<tr>
                    <td>${param.name + (isOptional ? '' : optional ) + info }</td>
                    <td class="mini">${(acceptArray ? array : '') + type + subtype}</td>
                    <td class="input"><input type="text" class="methodParameterValue" data-id="${i}" value="${defaultValue}"></td>
                    <td class="mini">
                        <input type="checkbox" id="parameter-send-${i}" class="methodParameterSend" data-id="${i}" ${(isOptional ? '' : 'checked="checked"')}>
                        <label class="methodParameterSendCheckbox" for="parameter-send-${i}"><i class="icon-ok"></i></label>
                    </td>
                    </tr>`;
            }
            $("#methodParams tbody").html(methodParams);
        }
        else {
            $('.no-params').show();
            $("#methodParams table").hide();
        }

        $("#methodWrapper").show();

        // trigger field modification
        $("input.methodParameterValue").change(function () {
            $("input.methodParameterSend[data-id='" + $(this).data('id') + "']").attr('checked', 'checked');
        });

        // tiptip
        $(".methodInfo").tipTip({
            maxWidth: "300px",
            defaultPosition: "bottom",
            delay: 0
        });

        $(".required, .type-badge").tipTip({
            maxWidth: "100px",
            defaultPosition: "bottom",
            delay: 0
        });
    }

    // invoke method
    function invokeMethod(methodName, newWindow) {

        $('#json-viewer').jsonViewer({});

        $('#requestURLDisplay').show();
        $('#requestResultDisplay').show();

        let method = cachedMethods[methodName];

        let reqUrl = ws_url + "?format=" + $("#responseFormat").val();

        const isJson = $("#responseFormat").val() === 'json';
        const authorization = $('#apiKey').val();
        const useCookie = '' === authorization;

        let fetchOption = {};
        if (!useCookie) {
            fetchOption.credentials = 'omit';
            fetchOption.headers = {
                "X-PIWIGO-API": authorization
            }
        }

        // GET
        if ($("#requestFormat").val() == 'get') {
            reqUrl += "&method=" + methodName;

            for (let i = 0; i < method.params.length; i++) {
                if (!$("input.methodParameterSend[data-id='" + i + "']").is(":checked")) {
                    continue;
                }

                let paramValue = $("input.methodParameterValue[data-id='" + i + "']").val();

                let paramSplitted = paramValue.split('|');
                if (method.params[i].acceptArray && paramSplitted.length > 1) {
                    $.each(paramSplitted, function (v) {
                        reqUrl += '&' + method.params[i].name + '[]=' + paramSplitted[v];
                    });
                }
                else {
                    reqUrl += '&' + method.params[i].name + '=' + paramValue;
                }
            }

            if (newWindow) {
                window.open(reqUrl);
            }
            else {
                fetch(reqUrl, fetchOption)
                .then(data => data.text())
                .then(data => {
                    showResponseData(data);
                })
            }

            $('#requestURLDisplay').find('.url').html(reqUrl).end()
                .find('.params').hide();
        }
        // POST
        else {
            let params = {};

            let form = $("#invokeForm");
            form.attr('action', reqUrl);

            let t = '<input type="hidden" name="method" value="' + methodName + '">';

            for (let i = 0; i < method.params.length; i++) {
                if (!$("input.methodParameterSend[data-id='" + i + "']").is(":checked")) {
                    continue;
                }

                let paramValue = $("input.methodParameterValue[data-id='" + i + "']").val(),
                    paramName = method.params[i].name,
                    paramSplitted = paramValue.split('|');

                if (method.params[i].acceptArray && paramSplitted.length > 1) {
                    params[paramName] = [];

                    $.each(paramSplitted, function (i, value) {
                        params[paramName].push(value);
                        t += '<input type="hidden" name="' + paramName + '[]" value="' + value + '">';
                    });
                }
                else {
                    params[paramName] = paramValue;
                    t += '<input type="hidden" name="' + paramName + '" value="' + paramValue + '">';
                }
            }

            if (!newWindow) {
                $("#invokeFrame").hide();
                $('#json-viewer').show();

                const formData = new URLSearchParams();
                formData.append('method', methodName);
                for (const key in params) {
                    formData.append(key, params[key]);
                }

                fetchOption.headers ??= {};
                fetchOption.headers['Content-Type'] = 'application/x-www-form-urlencoded';

                fetch(reqUrl, {
                    ...fetchOption,
                    method: 'POST', 
                    body: formData
                })
                .then(data => {
                    return data.text();
                })
                .then(data => {
                    showResponseData(data);
                });
            } else {
                $("#invokeFrame").show();
                $('#json-viewer').hide();

                form.html(t);
                form.attr('target', newWindow ? "_blank" : "invokeFrame");
                form.submit();
            }

            $('#requestURLDisplay').find('.url').html(reqUrl).end()
                .find('.params').show().html(JSON.stringify(params, null, 4));
        }

        return false;
    }

    $('#search input').val('');

    $('#search input').on('input', function () {
        if ($(this).val()) {
            $('.method-node, .method-link').hide();
            if (!$('#methodsList').hasClass('onSearch')) {
                $('#methodsList').addClass('onSearch');
                $('.method-node-content').show();
            }

            function showBranch(methodNode) {
                methodNode.show();
                while (methodNode.parent().parent().hasClass("method-node")) {
                    methodNode = methodNode.parent().parent();
                    methodNode.show();
                }
            }

            $('.method-link').each((i, n) => {
                if ($(n).data('method').toLowerCase().search($(this).val().toLowerCase())!= -1) {
                    showBranch($(n));
                }
            })

        } else {
            $('.method-node, .method-link').show();
            $('#methodsList').removeClass('onSearch');
            setStateMenu()
        }
    })

    if ($.cookie('wse-dark-mode')) {
        $('#the_body').addClass('dark-mode');
        $('.darkModeButton').addClass('icon-sun-inv');
    }

    $('.darkModeButton').click(() => {
        if ($.cookie('wse-dark-mode')) {
            $('.darkModeButton').removeClass('icon-sun-inv').addClass('icon-moon-inv');
            $.removeCookie('wse-dark-mode');
            $('#the_body').removeClass('dark-mode');
        } else {
            $('.darkModeButton').removeClass('icon-moon-inv').addClass('icon-sun-inv');
            $.cookie('wse-dark-mode', true);
            $('#the_body').addClass('dark-mode');
        }
    })
})

function showResponseData(data) {
    const isJson = $("#responseFormat").val() === 'json';
    if (isJson) {
        try {
            const json = JSON.parse(data);
            $('#json-viewer').jsonViewer(json);
            $("#invokeFrame").hide();
            $('#json-viewer').show();
        } catch (error) {
            const iframe = $('#invokeFrame');
            const iframeDoc = iframe[0].contentDocument || iframe[0].contentWindow.document;

            iframeDoc.open();
            iframeDoc.write(`<pre>${data}</pre>`);
            iframeDoc.close();
            $("#invokeFrame").show();
            $('#json-viewer').hide();
        }
        return;
    }
    const iframe = $('#invokeFrame');
    const iframeDoc = iframe[0].contentDocument || iframe[0].contentWindow.document;

    iframeDoc.open();
    iframeDoc.write(data);
    iframeDoc.close();

    $("#invokeFrame").show();
    $('#json-viewer').hide();
                
}