import { IgniteTemplate } from "../ignite-html/ignite-template.js";
import { IgniteProperty } from "../ignite-html/ignite-html.js";
import { IgniteElement } from "../ignite-html/ignite-element.js";
/**
 * Creates and shows a notification element on a target element.
 * @param {HTMLElement} target Target element to show the notification on.
 * @param {string} type Type of notification to show (Error|Warning|Success|Info)
 * @param {string|Function|IgniteProperty} msg The message to show
 * @param {Number} duration The number of miliseconds to show the notification for.
 * @returns {HTMLElement} The created notification HTMLElement.
 */
function notify(target, type, msg, duration) {
    var color = "";
    if (type == "error") {
        color = "#ff7979";
    } else if (type == "warning") {
        color = "#ffc107";
    } else if (type == "info") {
        color = "#2980b9";
    } else if (type == "success") {
        color = "#10c469";
    }
    if (target._validation && target._validation.isConnected) {
        target._validation.remove();
    }
    var originalPosition = window.getComputedStyle(target.parentNode).position;
    target.parentNode.style.setProperty("position", "relative");
    var notification = document.createElement("div");
    notification.classList.add("ignite-html-validate");
    notification.classList.add(type);
    notification.style.setProperty("left", "50%");
    notification.style.setProperty("top", "100%");
    notification.style.setProperty("position", "absolute");
    notification.style.setProperty("transform", "translate(-50%, 0)");
    notification.style.setProperty("display", "flex");
    notification.style.setProperty("align-items", "center");
    notification.style.setProperty("flex-direction", "column");
    notification.style.setProperty("z-index", 999999);
    notification.style.setProperty("width", "100%");
    var pointer = document.createElement("div");
    pointer.classList.add("pointer");
    pointer.style.setProperty("width", 0);
    pointer.style.setProperty("height", 0);
    pointer.style.setProperty("background-color", "transparent");
    pointer.style.setProperty("border-left", "0.5em solid transparent");
    pointer.style.setProperty("border-right", "0.5em solid transparent");
    pointer.style.setProperty("border-bottom", `0.5em solid ${color}`);
    var content = document.createElement("div");
    content.classList.add("content");
    content.style.setProperty("background-color", color);
    content.style.setProperty("padding", "0.5em");
    content.style.setProperty("border-radius", "0.5em");
    content.style.setProperty("box-shadow", "0 2px 2px rgba(0,0,0,0.4)");
    content.style.setProperty("color", "#fff");
    content.style.setProperty("text-align", "center");
    if (msg instanceof Function) {
        content.innerHTML = msg();
    } else if (msg instanceof IgniteProperty) {
        content.innerHTML = msg.value;
    } else {
        content.innerHTML = msg.toString();
    }
    notification.appendChild(pointer);
    notification.appendChild(content);
    if (target.nextSibling) {
        target.parentNode.insertBefore(notification, target.nextSibling);
    } else {
        target.parentNode.appendChild(notification);
    }
    target._validation = notification;
    if (duration != -1) {
        setTimeout(() => {
            if (notification && notification.isConnected) {
                notification.remove();
                target._validation = null;
            }
            target.parentNode.style.setProperty("position", originalPosition);
        }, duration);
    }
    notification.scrollIntoView({ behavior: "smooth", block: "center" });
    return notification;
}
/**
 * Shows an error message on a given HTMLElement.
 * @param {string|Function} msg The error message to show.
 * @param {Number} duration The number of miliseconds to show the error. Default is 4000. -1 is infinite.
 * @returns {HTMLElement} The created notification HTMLElement.
 */
HTMLElement.prototype.error = function (msg, duration = 4000) {
    return notify(this, "error", msg, duration);
}
/**
 * Shows a warning message on a given HTMLElement.
 * @param {string|Function} msg The warning message to show.
 * @param {Number} duration The number of miliseconds to show the warning. Default is 4000. -1 is infinite.
 * @returns {HTMLElement} The created notification HTMLElement.
 */
HTMLElement.prototype.warning = function (msg, duration = 4000) {
    return notify(this, "warning", msg, duration);
}
/**
 * Shows a success message on a given HTMLElement.
 * @param {string|Function} msg The success message to show.
 * @param {Number} duration The number of miliseconds to show the notification. Default is 4000. -1 is infinite.
 * @returns {HTMLElement} The created notification HTMLElement.
 */
HTMLElement.prototype.success = function (msg, duration = 4000) {
    return notify(this, "success", msg, duration);
}
/**
 * Shows a info message on a given HTMLElement.
 * @param {string|Function} msg The info message to show.
 * @param {Number} duration The number of miliseconds to show the notification. Default is 4000. -1 is infinite.
 * @returns {HTMLElement} The created notification HTMLElement.
 */
HTMLElement.prototype.info = function (msg, duration = 4000) {
    notify(this, "info", msg, duration);
}
/**
 * Validates an input when changed.
 * @param {Function|IgniteProperty} callback The callback function to invoke in order to validate changes to an input.
 * @example
 * .validate((value, error, warning, success, info) => {
 *  if (value == null) {
 *      error("Value cannot be null");
 *  }
 * })
 * @returns {IgniteTemplate} This ignite template.
 */
IgniteTemplate.prototype.validate = function (callback) {
    //Setup the validators array if it doesn't exist.
    if (!this._validators) {
        this._validators = [];
        //Setup an event for change to run the validators.
        this.on("change", () => {
            for (var i = 0; i < this._validators.length; i++) {
                if (!this._validators[i]()) {
                    return false;
                }
            }
        });
    }
    //Register a new validator
    this._validators.push(() => {
        //Get the elements current value
        var type = this.element.hasAttribute("type") ? this.element.getAttribute("type").toLowerCase().trim() : null;
        
        //Get the value from the element.
        var value = null;
        if (type == "checkbox") {
            value = this.element.checked;
        } else if (type == "radio") {
            value = this.element.checked;
        } else if (type == "number") {
            value = Number(this.element.value);
        } else if (type == "file") {
            value = this.element.files;
        } else if (this.element.hasAttribute("contenteditable") && this.element.getAttribute("contenteditable").toLowerCase().trim() == "true") {
            value = this.element.textContent;
        } else {
            value = this.element.value;
        }
        //If the element already has a validation element remove it.
        if (this.element._validation && this.element._validation.isConnected) {
            this.element._validation.remove();
            this.element._validation = null;
        }
        //Get the target from the callback.
        var target = callback;
        if (target instanceof IgniteProperty) {
            target = target.value;
        }
        //Run the target to see if we passed validation.
        var error = false;
        target(
            value,
            (msg, duration = 4000) => { notify(this.element, "error", msg, duration); error = true; },
            (msg, duration = 4000) => { notify(this.element, "warning", msg, duration); },
            (msg, duration = 4000) => { notify(this.element, "success", msg, duration); },
            (msg, duration = 4000) => { notify(this.element, "info", msg, duration); }
        );
        return !error;
    });
    return this;
}
/**
 * Validates an input to make sure it contains a valid email address.
 * @param {string|Function|IgniteProperty} msg The message to display when the email is incorrect. If null a default one will show. 
 * @returns {IgniteTemplate} This ignite template.
 */
IgniteTemplate.prototype.validateEmail = function (msg) {
    return this.validate((value, error) => {
        if (!value || value.length == 0) {
            return error(msg ? msg : 'Please enter an email address.');
        } else if (value.trim().length < 5) {
            return error(msg ? msg : 'Email address too short.');
        } else if (!value.includes('@')) {
            return error(msg ? msg : 'Email address missing @ symbol.');
        } else if (!value.includes('.')) {
            return error(msg ? msg : 'Email address missing domain.');
        } else if (value.length > 64) {
            return error(msg ? msg : 'Email address too long.');
        } else if (value.includes(' ')) {
            return error(msg ? msg : 'Email address cannot contain spaces.');
        } else if (value.split('@').length > 2) {
            return error(msg ? msg : 'Email address has too many @ symbols.')
        }
    });
}
/**
 * Validates an input to make sure it contains a valid password.
 * @param {string|Function|IgniteProperty} msg The message to display when the email is incorrect. If null a default one will show.
 * @returns {IgniteTemplate} This ignite template.
 */
IgniteTemplate.prototype.validatePassword = function (msg) {
    return this.validate((value, error) => {
        if (!value || value.trim().length < 5) {
            return error(msg ? msg : `Password must be at least 5 characters.`);
        } else if (value.includes(' ')) {
            return error(msg ? msg : 'Password cannot contain spaces.');
        } else if (value.length > 64) {
            return error(msg ? msg : 'Password too long.');
        }
    });
}
/**
 * Validates an input to make sure it contains a min number of characters.
 * @param {Number} min The min number of characters the input can have.
 * @param {string|Function|IgniteProperty} msg The message to display when the input length is too short. If null a default one will show. 
 * @returns {IgniteTemplate} This ignite template.
 */
IgniteTemplate.prototype.validateMin = function (min, msg) {
    return this.validate((value, error) => {
        if (!value || value.toString().trim().length < min) {
            return error(msg ? msg : `Input must contain at least ${min} character(s).`);
        }
    });
}
/**
 * Validates an input to make sure it contains a max number of characters.
 * @param {Number} max The max number of characters the input can have.
 * @param {string|Function|IgniteProperty} msg The message to display when the input length is too long. If null a default one will show. 
 * @returns {IgniteTemplate} This ignite template.
 */
IgniteTemplate.prototype.validateMax = function (max, msg) {
    return this.validate((value, error) => {
        if (value && value.toString().trim().length > max) {
            return error(msg ? msg : `Input must contain less than ${max} character(s).`);
        }
    });
}
/**
 * Validates an input to make sure it it's value is not the given value.
 * @param {Any} expected The value the input cannot have in order to be considered valid.
 * @param {string|Function|IgniteProperty} msg The message to display when the input length is too long. If null a default one will show.
 * @returns {IgniteTemplate} This ignite template. 
 */
IgniteTemplate.prototype.validateNot = function (expected, msg) {
    return this.validate((value, error) => {
        if (!value || value == expected) {
            return error(msg ? msg : `Input cannot be ${expected}.`);
        }
    });
}
/**
 * Validates an input to make sure it includes a string.
 * @param {string} str The string that the input must include. 
 * @param {string|Function|IgniteProperty} msg The message to display when the input doesn't contain the string. If null a default one will show. 
 * @returns {IgniteTemplate} This ignite template.
 */
IgniteTemplate.prototype.validateIncludes = function (str, msg) {
    return this.validate((value, error) => {
        if (!value || !value.toString().includes(str)) {
            return error(msg ? msg : `Input must contain ${str}.`);
        }
    });
}
/**
 * Validates an input to make sure it matches a given value.
 * @param {string|Function|IgniteProperty} compare The value to compare against the input.
 * @param {string|Function|IgniteProperty} msg The message to display when the input doesn't match. If null a default one will show.
 * @returns {IgniteTemplate} This ignite template. 
 */
IgniteTemplate.prototype.validateMatches = function (compare, msg) {
    return this.validate((value, error) => {
        if (compare instanceof Function) {
            if (compare() != value) {
                return error(msg ? msg : `Input does not match: ${compare()}.`);
            }
        } else if (compare instanceof IgniteProperty) {
            if (compare.value != value) {
                return error(msg ? msg : `Input does not match: ${compare.value}.`);
            }
        } else if (value != compare) {
            return error(msg ? msg : `Input does not match: ${str}.`);
        }
    });
}
/**
 * Validates an input to make sure it contains at least one file file.
 * @param {string|Function|IgniteProperty} msg The message to display when a file has not been selected.
 * @returns {IgniteTemplate} This ignite template.
 */
IgniteTemplate.prototype.validateFile = function(msg) {
    return this.validate((value, error) => {
        if (!value || value.length == 0) {
            return error(msg ? msg : 'Must select a file to upload.');
        }
    });
}
/**
 * Validates this element and all the children within the element.
 * @returns {Boolean} Whether or not this element and all children in this element are valid.
 */
HTMLElement.prototype.validate = function () {
    //If this element has a template with validators run them.
    if (this.template && this.template._validators) {
        for (var i = 0; i < this.template._validators.length; i++) {
            if (!this.template._validators[i]()) {
                return false;
            }
        }
    } else {
        //Look for elements that we can try to validate.
        var elements = this.getElementsByTagName("*");
        for (var i = 0; i < elements.length; i++) {
            var element = elements[i];
            //Check if the element is visible.
            if (!element.offsetParent) {
                continue;
            }
            //If this element has a template, and has validators, run them, if any are false, return false.
            if (element.template && element.template._validators) {
                for (var v = 0; v < element.template._validators.length; v++) {
                    if (!element.template._validators[v]()) {
                        return false;
                    }
                }
            }
        }
    }
    return true;
}