'use strict'

import { athenaConfig } from '../athena-config.js';

const OmRangeSliderInputValueStyles = {
    DEFAULT_COMMA_SEPARATED: 0,
    PHP_ARRAY: 1,
    ASP_ARRAY: 2
}

const formatNumber = function(number) {
    return parseFloat(number)
        .toFixed(parseInt(athenaConfig.decimals))
        .replace('.', athenaConfig.decimalPoint)
        .replace(/\B(?=(\d{3})+(?!\d))/g, athenaConfig.separator);
}

export function OmRangeSlider(inputElement, inputValueStyle) {
    const that = this;

    let input = inputElement;
    let inputValueEnd = undefined;
    let elementRange = undefined;
    let buttonStart = undefined;
    let buttonEnd = undefined;
    let rangeIndicator = undefined;
    let displayValueStart = undefined;
    let displayValueEnd = undefined;

    let rangeValue = undefined;

    let debug = false;

    let settings = {
        min: 0,
        max: 10,
        minAll: 0,
        maxAll: 10,
        unit: '',
        unitPlacement: '',
        inputValueStyle: inputValueStyle
    };

    this.setRange = function (range) {
        rangeValue = range;

        range = recalculateRange(range);

        let rangeOne = formatNumber(range[0]);
        let rangeTwo = formatNumber(range[1]);

        displayValueStart.textContent = settings.unitPlacement === 'before' ? settings.unit + rangeOne: rangeOne + settings.unit;
        displayValueEnd.textContent = settings.unitPlacement === 'before' ? settings.unit + rangeTwo: rangeTwo + settings.unit;
        triggerRangeChangeEvent(range);

        switch (settings.inputValueStyle) {
            default:
            case OmRangeSliderInputValueStyles.DEFAULT_COMMA_SEPARATED:
                input.value = rangeValue.join(',');
                break;
            case OmRangeSliderInputValueStyles.PHP_ARRAY:
            case OmRangeSliderInputValueStyles.ASP_ARRAY:
                input.value = rangeValue[0];
                inputValueEnd.value = rangeValue[1];
                break;
        }
        return that;
    };

    this.setDebug = function (enabled) {
        debug = !!enabled;
        return that;
    }

    function initEventSubscriptions() {
        let previousWindowWidth = document.documentElement.clientWidth;

        window.addEventListener('resize', (e) => {
            if (sliderEventHelper.getActiveButton() === undefined || previousWindowWidth !== document.documentElement.clientWidth.toFixed()) {
                previousWindowWidth = document.documentElement.clientWidth.toFixed();
                refreshButtonPositions();
                refreshRangeIndicator();
            }
        }, {passive: false});

        const sliderEventHelper = new SliderEventHelper(buttonStart, buttonEnd);
        sliderEventHelper.validateNewPosition = (button, positionX) => {
            return getValidX(button, positionX)
        };
        sliderEventHelper.onButtonPositionChanged = (button) => {
            updateValues();
            refreshRangeIndicator();
        };
    }

    /**
     * Generate Numbers
     *
     * @param min
     * @param max
     * @returns {[]}
     */
    function generateNumbers(
        min,
        max
    )
    {
        min = parseInt(min);
        max = parseInt(max);

        const interval = (max - min) / 5.5;
        const numbers = [];
        for(let i = 1; i <= 5; i++) {
            const number = parseInt(Math.round(min + i * interval));
            numbers.push(number);
        }

        numbers.sort((a, b) => a - b);
        const middleIndex = Math.floor(numbers.length / 2);
        const middleNumber = numbers[middleIndex];

        const displayNumbers = [];
        displayNumbers.unshift(min);
        displayNumbers.push(middleNumber, max);
        return displayNumbers;
    }

    /**
     * Render Bottom Elements
     *
     * @param bottomContainer
     * @param bottomPriceContainer
     */
    function renderBottomElements(
        bottomContainer,
        bottomPriceContainer
    )
    {
        const displayNumbers = generateNumbers(
            settings.min,
            settings.max
        );

        for(let i = 0; i <= 5; i++) {
            const divElement = document.createElement('div');
            divElement?.classList.add('om-min-price-range__div');

            for(let j = 0; j <= 3; j++) {
                const spanElement = document.createElement('span');
                spanElement?.classList.add('om-min-price-range__span');
                divElement.append(spanElement);
            }

            bottomContainer.append(divElement);
        }

        displayNumbers.forEach((displayNumber, index) => {
            const spanElement = document.createElement('span');
            spanElement?.classList.add('om-min-price-range__show-span');

            const formattedNumber = formatNumber(displayNumber);

            spanElement.innerText =  settings.unitPlacement === 'before' ? settings.unit + formattedNumber : formattedNumber + settings.unit;
            if(spanElement) spanElement.style.cssText = `color: #525F7F; font-size: 12px; opacity: 1;`;

            bottomPriceContainer.append(spanElement);
        });
    }

    function initVisuals() {

        const visualSliderContainer = document.createElement('div');
        visualSliderContainer.className = input.className;

        if (!visualSliderContainer.style.height) {
            visualSliderContainer.style.height = input.offsetHeight + 'px';
        }

        visualSliderContainer?.classList.add('om-sliderrange');
        const visualSliderRange = document.createElement('div');
        visualSliderRange?.classList.add('om-sliderrange-range');
        const visualSliderRangeIndicator = document.createElement('div');
        visualSliderRangeIndicator?.classList.add('om-sliderrange-range-indicator');
        const visualSliderButtonStart = document.createElement('div');
        visualSliderButtonStart.role = 'button';
        visualSliderButtonStart.setAttribute('tabindex', '0');
        visualSliderButtonStart?.classList.add('om-sliderrange-button-start');
        visualSliderButtonStart.innerHTML = '<div class="om-slider-range-left-circle-parent" style="position: relative; height: 100%; width: 100%;"><div class="om-slider-range-left-circle" style="position: absolute; height: 48px; width: 48px; top: 50%; left: 50%; transform: translateY(-50%) translateX(-50%);' + (debug ? ' background: rgba(71,178,71,0.6);' : '') + '"></div></div>';
        const visualSliderButtonEnd = document.createElement('div');
        visualSliderButtonEnd.role = 'button';
        visualSliderButtonEnd.setAttribute('tabindex', '0');
        visualSliderButtonEnd?.classList.add('om-sliderrange-button-end');
        visualSliderButtonEnd.innerHTML = '<div class="om-slider-range-right-circle-parent" style="position: relative; height: 100%; width: 100%;"><div class="om-slider-range-right-circle" style="position: absolute; height: 48px; width: 48px; top: 50%; left: 50%; transform: translateY(-50%) translateX(-50%);' + (debug ? '  background: rgba(178,71,71,0.6);' : '') + '"></div></div>';

        visualSliderRange.appendChild(visualSliderRangeIndicator);
        visualSliderRange.appendChild(visualSliderButtonStart);
        visualSliderRange.appendChild(visualSliderButtonEnd);

        visualSliderContainer.appendChild(visualSliderRange);

        const visualDisplayContainer = document.createElement('div');
        visualDisplayContainer.className = 'om-sliderrange-display';
        const midPriceContainer = document.createElement('div');
        midPriceContainer.className = 'om-mid-price-container';
        const bottomMidPriceContainer = document.createElement('div');
        bottomMidPriceContainer.className = 'om-mid-price-show-container';
        const minValue = document.createElement('span');
        minValue.textContent = '0';
        const maxValue = document.createElement('span');
        maxValue.textContent = '10';
        const visualDisplayValueStart = document.createElement('span');
        visualDisplayValueStart.style.cssText = `background: #32325D 0 0 no-repeat padding-box; color: #FFFFFF; border-radius: 6px; padding: 3px 5px 3px 5px;`;
        visualDisplayValueStart.textContent = '0';
        const visualDisplayValueEnd = document.createElement('span');
        visualDisplayValueEnd.textContent = '10';
        visualDisplayValueEnd.style.cssText = `background: #32325D 0 0 no-repeat padding-box; color: #FFFFFF; border-radius: 6px; padding: 3px 5px 3px 5px;`;

        midPriceContainer.appendChild(visualDisplayValueStart);
        midPriceContainer.appendChild(visualDisplayValueEnd);

        visualDisplayContainer.appendChild(visualDisplayValueStart);
        visualDisplayContainer.appendChild(visualDisplayValueEnd);

        visualSliderContainer.prepend(visualDisplayContainer);
        visualSliderContainer.append(midPriceContainer);
        visualSliderContainer.append(bottomMidPriceContainer);

        input.parentNode.insertBefore(visualSliderContainer, input.nextSibling);

        visualSliderButtonEnd.style.left = (visualSliderRange.getBoundingClientRect().width - visualSliderButtonEnd.getBoundingClientRect().width) + 'px';

        elementRange = visualSliderRange;
        buttonStart = visualSliderButtonStart;
        buttonEnd = visualSliderButtonEnd;
        rangeIndicator = visualSliderRangeIndicator;
        displayValueStart = visualDisplayValueStart;
        displayValueEnd = visualDisplayValueEnd;
        settings.min = input.getAttribute('min')
            ? input.getAttribute('min') : 0;
        settings.max = input.getAttribute('max')
            ? input.getAttribute('max') : 10;
        settings.unit = input.getAttribute('unit') ?? '';
        settings.minAll = input.getAttribute('minAll')
            ? input.getAttribute('minAll') : 0;
        settings.maxAll = input.getAttribute('maxAll')
            ? input.getAttribute('maxAll') : 10;

        //Unit Placement Functionality
        const unitPlacement = input.getAttribute('unitPlacement');
        if(unitPlacement && unitPlacement === 'before' || unitPlacement === 'after') {
            settings.unitPlacement = input.getAttribute('unitPlacement') ?? '';
        } else {
            throw new Error('Unit placement needs to be "before" or "after"');
        }

        renderBottomElements(midPriceContainer, bottomMidPriceContainer);

        settings.min = parseFloat(settings.min).toFixed(2);
        settings.max = parseFloat(settings.max).toFixed(2);

        //Setting min and max values
        minValue.textContent = settings.unitPlacement === 'before' ? settings.unit + settings.min: settings.min + settings.unit;
        maxValue.textContent = settings.unitPlacement === 'before' ? settings.unit + settings.max: settings.max + settings.unit;

        input.type = 'hidden';

        const range = input.value ? input.value.split(',').map(x => +x.trim()) : [settings.min, settings.max];

        switch (settings.inputValueStyle) {
            default:
            case OmRangeSliderInputValueStyles.DEFAULT_COMMA_SEPARATED:
                break;
            case OmRangeSliderInputValueStyles.PHP_ARRAY:
                inputValueEnd = document.createElement('input');
                inputValueEnd.hidden = true;
                inputValueEnd.name = input.name + '[]';
                input.parentNode.insertBefore(inputValueEnd, input.nextSibling);
                input.name = input.name + '[]';
                break;
            case OmRangeSliderInputValueStyles.ASP_ARRAY:
                inputValueEnd = document.createElement('input');
                inputValueEnd.hidden = true;
                inputValueEnd.name = input.name;
                input.parentNode.insertBefore(inputValueEnd, input.nextSibling);
                break;
        }

        let rangeStart;
        let rangeEnd;

        let dataMin = parseFloat(settings.minAll).toFixed(2);
        let dataMax = parseFloat(settings.maxAll).toFixed(2);

        rangeStart = dataMin && parseInt(dataMin) > parseInt(range[0]) ? dataMin.toString() : range[0];
        rangeEnd = dataMax && parseInt(dataMax) < parseInt(range[1]) ? dataMax.toString() : range[1];

        that.setRange([rangeStart, rangeEnd]);
        refreshButtonPositions();
        refreshRangeIndicator();
    }

    function refreshButtonPositions() {
        const elementRangeWidth = elementRange.getBoundingClientRect().width;
        const buttonStartWidth = buttonStart.getBoundingClientRect().width;
        const buttonEndWidth = buttonEnd.getBoundingClientRect().width;

        const range = settings.max - settings.min;
        const factor = (elementRangeWidth - buttonStartWidth - buttonEndWidth) / range;

        const valueStart = rangeValue[0] - settings.min;
        const valueEnd = rangeValue[1] - settings.min;

        const buttonStartLeft = Math.floor(valueStart * factor);
        const buttonEndLeft = buttonStartWidth + Math.floor(valueEnd * factor);

        buttonStart.style.left = buttonStartLeft + 'px';
        buttonEnd.style.left = buttonEndLeft + 'px';
    }

    function refreshRangeIndicator() {
        const elementRangeWidth = elementRange.getBoundingClientRect().width;
        const buttonStartWidth = buttonStart.getBoundingClientRect().width;
        const buttonEndWidth = buttonEnd.getBoundingClientRect().width;

        const buttonStartMiddle = Math.round(parseInt(buttonStart.style.left) + (buttonStartWidth / 2));
        const buttonEndMiddle = Math.round(elementRangeWidth - (parseInt(buttonEnd.style.left) + (buttonEndWidth / 2)));
        rangeIndicator.style.left = buttonStartMiddle + 'px';
        rangeIndicator.style.right = buttonEndMiddle + 'px';
    }

    function recalculateRange(range) {
        const maxAll = parseInt(settings.max);
        rangeValue[0] = rangeValue[0] > maxAll ? maxAll : rangeValue[0];
        rangeValue[1] = rangeValue[1] > maxAll ? maxAll : rangeValue[1];

        range[0] = range[0] > maxAll ? maxAll : range[0];
        range[1] = range[1] > maxAll ? maxAll : range[1];

        return range;
    }

    function triggerRangeChangeEvent(range) {
        range = recalculateRange(range);

        const event = new CustomEvent('rangechange', {detail: rangeValue});
        input.value = range ? range.join(',') : '';
        document.dispatchEvent(event);
        input.dispatchEvent(event);
    }

    function updateValues() {
        const rangePx = elementRange.offsetWidth - buttonStart.offsetWidth - buttonEnd.offsetWidth - 1;
        const startPx = parseInt(buttonStart.style.left);
        const endPx = parseInt(buttonEnd.style.left) - buttonEnd.offsetWidth;

        const isFloat = parseFloat(settings.min) % 1 !== 0 || parseFloat(settings.max) % 1 !== 0;

        const diffRange = settings.max - settings.min;
        let resultStart = (diffRange / rangePx * startPx) + parseFloat(settings.min);
        let resultEnd = (diffRange / rangePx * endPx) + parseFloat(settings.min);

        if (!isFloat) {
            resultStart = +Math.ceil(resultStart);
            resultEnd = +Math.ceil(resultEnd);
        }

        refreshRangeIndicator();

        that.setRange([resultStart, resultEnd]);
    }

    function getValidX(button, positionX) {
        let newX = positionX;

        if (button === buttonStart) {
            if (newX <= 0) {
                newX = 0;
            } else if (newX + buttonStart.getBoundingClientRect().width >= parseInt(buttonEnd.style.left)) {
                newX = parseInt(buttonEnd.style.left) - buttonStart.getBoundingClientRect().width;
            }
        } else if (button === buttonEnd) {
            if (newX >= elementRange.getBoundingClientRect().width - buttonEnd.getBoundingClientRect().width) {
                newX = elementRange.getBoundingClientRect().width - buttonEnd.getBoundingClientRect().width;
            } else if (newX <= buttonStart.getBoundingClientRect().width + parseInt(buttonStart.style.left)) {
                newX = buttonStart.getBoundingClientRect().width + parseInt(buttonStart.style.left);
            }
        }
        return parseInt(newX);
    }

    this.initialize = function () {
        initVisuals();
        initEventSubscriptions();
    };
}

function SliderEventHelper(buttonStart, buttonEnd) {

    const that = this;

    let x = 0;
    let activeButton = null;
    let isMouseButtonDown = false;

    this.getActiveButton = function () {
        return activeButton;
    }

    /**
     * Callback that needs to be implemented to check if the position of the currently moved button is within allowed range.
     *
     * @callback validateNewPosition
     * @param {HtmlButtonElement} button The button the position should be checked of
     * @param {int} positionX The new position of the button
     * @returns {int} The new validated position
     */
    this.validateNewPosition = undefined;
    /**
     * Event callback that gets called when the position of the button has been changed.
     *
     * @callback onButtonPositionChanged
     * @param {HtmlButtonElement} button The button of which the position changed
     */
    this.onButtonPositionChanged = undefined;

    function handleButtonGrabbed(e) {
        e.preventDefault();
        isMouseButtonDown = true;
        // The touch area of the buttons are bigger than they are visually. Because of that, the touch area overlaps, in case that the buttons are positioned
        // for a small range. Do determine, which button on touch/click should be moved, we check, if the X is behind the starting of the endbutton.
        // The end button touch area always overlaps the start button, because of the Z-index (it is the latter added button).
        const buttonStartPosStart = buttonStart.getBoundingClientRect()?.left ?? undefined;
        const buttonEndPosStart = buttonEnd.getBoundingClientRect()?.left ?? undefined;
        let buttonStartPosEnd = undefined;
        let halfDistanceBetweenButtons = undefined;
        if (buttonStartPosStart && buttonEndPosStart) {
            buttonStartPosEnd = buttonStartPosStart + buttonStart.getBoundingClientRect().width;
            halfDistanceBetweenButtons = (buttonEndPosStart - buttonStartPosEnd) / 2;
        }

        if (halfDistanceBetweenButtons !== undefined && e.currentTarget === buttonEnd) {
            if (getXOfMoveEvent(e) < buttonStartPosEnd + halfDistanceBetweenButtons) {
                activeButton = buttonStart;
            } else {
                activeButton = buttonEnd;
            }
        } else {
            activeButton = e.currentTarget;
        }
        activeButton.focus();

        x = activeButton.offsetLeft - getXOfMoveEvent(e);
    }

    function handleButtonRelease(e) {
        isMouseButtonDown = false;
        if (!activeButton)
            return;

        x = activeButton.offsetLeft - getXOfMoveEvent(e);

        activeButton = undefined;
    }

    function handleButtonMove(e) {
        if (!activeButton || !isMouseButtonDown)
            return;

        e.preventDefault();
        e.stopImmediatePropagation();

        let newX = getXOfMoveEvent(e);

        newX = that.validateNewPosition(activeButton, newX + x);

        if (newX !== parseInt(activeButton.style.left)) {
            activeButton.style.left = newX + 'px';
            that.onButtonPositionChanged(activeButton);
        }
    }

    function handleKeyDown(e) {
        isMouseButtonDown = false;
        if (!document.activeElement) {
            return;
        }

        if (document.activeElement === buttonStart) {
            activeButton = buttonStart;
        } else if (document.activeElement === buttonEnd) {
            activeButton = buttonEnd;
        } else {
            return;
        }

        const arrowLeft = 37;
        const arrowRight = 39;

        let newX = 0;
        if (e.keyCode === arrowLeft) {
            newX = that.validateNewPosition(activeButton, activeButton.offsetLeft - 1);
        } else if (e.keyCode === arrowRight) {
            newX = that.validateNewPosition(activeButton, activeButton.offsetLeft + 1);
        }
        else {
            return;
        }

        e.preventDefault();

        activeButton.style.left = newX + 'px';
        that.onButtonPositionChanged(activeButton);
    }

    function getXOfMoveEvent(e) {
        if (e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend' || e.type === 'touchcancel') {
            const evt = !e.originalEvent ? e : e.originalEvent;
            const touch = evt.touches[0] || evt.changedTouches[0];

            return touch.pageX || touch.clientX;
        } else if (e.type === 'mousedown' || e.type === 'mouseup' || e.type === 'mousemove' || e.type === 'mouseover' || e.type === 'mouseout' || e.type === 'mouseenter' || e.type === 'mouseleave') {
            return e.clientX;
        } else {
            return undefined;
        }
    }

    function init() {
        buttonStart.addEventListener('mousedown', handleButtonGrabbed, {passive: false});
        buttonStart.addEventListener('touchstart', handleButtonGrabbed, {passive: false});
        buttonStart.addEventListener('touchend', handleButtonRelease, {passive: false});
        buttonStart.addEventListener('touchcancel', handleButtonRelease, {passive: false});
        buttonEnd.addEventListener('mousedown', handleButtonGrabbed, {passive: false});
        buttonEnd.addEventListener('touchstart', handleButtonGrabbed, {passive: false});
        buttonEnd.addEventListener('touchend', handleButtonRelease, {passive: false});
        buttonEnd.addEventListener('touchcancel', handleButtonRelease, {passive: false});

        window.addEventListener('mouseup', handleButtonRelease, {passive: false});
        window.addEventListener('touchend', handleButtonRelease, {passive: false});
        window.addEventListener('touchcancel', handleButtonRelease, {passive: false});

        window.addEventListener('mousemove', handleButtonMove, {passive: false});
        window.addEventListener('touchmove', handleButtonMove, {passive: false});
        window.addEventListener('mouseout', (e) => {
            if (e.toElement == null && e.relatedTarget == null) {
                handleButtonRelease(e);
            }
        }, {passive: false});

        window.addEventListener('keydown', handleKeyDown, {passive: false});
    }

    init();
}


OmRangeSlider.init = function (settings) {

    const defaultSettings = {
        selector: 'input[type=range][multiple]',
        inputValueStyle: OmRangeSliderInputValueStyles.DEFAULT_COMMA_SEPARATED
    };
    settings = settings ? Object.assign(defaultSettings, settings) : defaultSettings;

    const rangeSliders = document.querySelectorAll(settings.selector);
    for (const rangeSlider of rangeSliders) {
        (new OmRangeSlider(rangeSlider, settings.inputValueStyle))
            .setDebug(false)
            .initialize();
    }
}