/*
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
 *
 *  Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import {Controller} from "@hotwired/stimulus";

import * as bootbox from "bootbox";
import "../../css/components/bootbox_extensions.css";
import accept from "attr-accept";

export default class extends Controller {
    static values = {
        deleteMessage: String,
        prototype: String,
        rowsToDelete: Number, //How many rows (including the current one) shall be deleted after the current row
        fieldPlaceholder: String
    }

    static targets = ["target"];

    /**
     * Decodes escaped HTML entities
     * @param {string} input
     * @returns {string}
     */
    htmlDecode(input) {
        const doc = new DOMParser().parseFromString(input, "text/html");
        return doc.documentElement.textContent;
    }

    /**
     * Generates a unique ID to be used for the new element
     * @returns {string}
     */
    generateUID() {
        const long = (performance.now().toString(36)+Math.random().toString(36)).replace(/\./g,"");
        return long.slice(0, 6); // 6 characters is enough for our unique IDs here
    }


    /**
     * Create a new entry in the target using the given prototype value
     */
    createElement(event) {
        const targetTable = this.targetTarget;
        const prototype = this.prototypeValue

        if(!prototype) {
            console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions.");
            bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)");
            return;
        }


        const regexString = this.fieldPlaceholderValue || "__name__";
        const regex = new RegExp(regexString, "g");

        //Apply the index to prototype to create our element to insert
        const newElementStr = this.htmlDecode(prototype.replace(regex, this.generateUID()));


        let ret = null;

        //Insert new html after the last child element
        //If the table has a tbody, insert it there
        //Afterwards return the newly created row
        if(targetTable.tBodies[0]) {
            targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr);
            ret = targetTable.tBodies[0].lastElementChild;
        } else { //Otherwise just insert it
            targetTable.insertAdjacentHTML('beforeend', newElementStr);
            ret = targetTable.lastElementChild;
        }

        //Trigger an event to notify other components that a new element has been created, so they can for example initialize select2 on it
        targetTable.dispatchEvent(new CustomEvent("collection:elementAdded", {bubbles: true}));

        this.focusNumberInput(ret);

        return ret;

    }

    focusNumberInput(element) {
        const fields = element.querySelectorAll("input[type=number]");
        //Focus the first available number input field to open the numeric keyboard on mobile devices
        if(fields.length > 0) {
            fields[0].focus();
        }
    }

    /**
     * This action opens a file dialog to select multiple files and then creates a new element for each file, where
     * the file is assigned to the input field.
     * This should only be used for attachments collection types
     * @param event
     */
    uploadMultipleFiles(event) {
        //Open a file dialog to select multiple files
        const input = document.createElement('input');
        input.type = 'file';
        input.multiple = true;
        input.click();

        input.addEventListener('change', (event) => {
            //Create a element for each file

            for (let i = 0; i < input.files.length; i++) {
                const file = input.files[i];

                const newElement = this.createElement(event);
                const rowInput = newElement.querySelector("input[type='file']");

                //We can not directly assign the file to the input, so we have to create a new DataTransfer object
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(file);

                rowInput.files = dataTransfer.files;

                //Check the file extension and find the corresponding attachment type based on the data-filetype_filter attribute
                const attachmentTypeSelect = newElement.querySelector("select");
                if (attachmentTypeSelect) {
                    let foundMatch = false;
                    for (let j = 0; j < attachmentTypeSelect.options.length; j++) {
                        const option = attachmentTypeSelect.options[j];
                        //skip disabled options
                        if (option.disabled) {
                            continue;
                        }

                        const filter = option.getAttribute('data-filetype_filter');
                        if (filter) {
                            if (accept({name: file.name, type: file.type}, filter)) {
                                attachmentTypeSelect.value = option.value;
                                foundMatch = true;
                                break;
                            }
                        } else { //If no filter is set, chose this option until we find a better match
                            if (!foundMatch) {
                                attachmentTypeSelect.value = option.value;
                                foundMatch = true;
                            }
                        }
                    }
                }
            }

        });

    }

    /**
     * Similar to createEvent Pricedetails need some special handling to fill min amount
     * @param event
     */
    createPricedetail(event) {
        //First insert our new element
        this.createElement(event);

        const extractElementsFromRow = (row) => {
            const priceRelated = row.querySelector("input[id$='price_related_quantity_value']");
            const minDiscount = row.querySelector("input[id$='min_discount_quantity_value']");

            return [priceRelated, minDiscount];
        }

        const targetTable = this.targetTarget;
        const targetRows = targetTable.tBodies[0].rows;
        const targetRowsCount = targetRows.length;

        //If we just have one element we dont have to do anything as 1 is already the default
        if(targetRowsCount <= 1) {
            return;
        }

        //Our new element is the last child of the table
        const newlyCreatedRow = targetRows[targetRowsCount - 1];
        const [newPriceRelated, newMinDiscount] = extractElementsFromRow(newlyCreatedRow);

        const oldRow = targetRows[targetRowsCount - 2];
        const [oldPriceRelated, oldMinDiscount] = extractElementsFromRow(oldRow);

        //Use the old PriceRelated value to determine the next 10 decade value for the new row
        const oldMinAmount = parseInt(oldMinDiscount.value)
        //The next 10 power can be achieved by creating a string beginning with "1" and adding 0 times the length of the old string
        const oldMinAmountLength = oldMinAmount.toString().length;
        const newMinAmountStr = '1' + '0'.repeat(oldMinAmountLength);
        //Parse the sting back to an integer and we have our new min amount
        const newMinAmount = parseInt(newMinAmountStr);


        //Assign it to our new element
        newMinDiscount.value = newMinAmount;
    }

    deleteElement(event) {
        const del = () => {
            const target = event.target;
            //Remove the row element from the table
            const current_row = target.closest("tr");
            for(let i = this.rowsToDeleteValue; i > 1; i--) {
                let nextSibling = current_row.nextElementSibling;
                //Ensure that nextSibling is really a tr
                if (nextSibling && nextSibling.tagName === "TR") {
                    nextSibling.remove();
                }
            }

            //Finally delete the current row
            current_row.remove();
        }

        if(this.deleteMessageValue) {
            bootbox.confirm(this.deleteMessageValue, (result) => {
                if (result) {
                    del();
                }
            });
        } else {
            del();
        }
    }
}
