/* ===================== AUTOGENERATED SOURCE IMPORT ===================== */

import Template from "/emcJS/util/html/template/HTMLTemplate.js";

const TPL = new Template(`
    <div id="header"></div>
    <div id="body" class="content"></div>
    
`);

import GlobalStyle from "/emcJS/util/html/template/CSSTemplate.js";

const STYLE = new GlobalStyle(`
    :host {
        position: relative;
        display: table;
        margin: 0.2em;
        color: var(--logic-color-text, black);
        background: var(--logic-color-back, white);
        border: solid 2px var(--logic-color-border, black);
        border-radius: 5px;
        font-family: monospace;
        font-size: 1rem;
        cursor: move;
        user-select: none;
        word-break: break-word;
        box-sizing: border-box;
    }
    
    :host(:focus) {
        box-shadow: 0 0 2px 3px var(--input-focus-color, #06b5ff);
        outline: none;
    }
    
    #header {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        height: 2em;
        padding: 0.5em 1em;
        font-weight: bold;
        user-select: none;
        text-transform: uppercase;
    }
    
    #header[value]::before {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 50px;
        height: 16px;
        font-size: 12px;
        line-height: 10px;
        margin-right: 5px;
        border-radius: 10px;
        border: solid 2px black;
        background-color: #85ff85;
        content: attr(value);
    }
    
    #header[value="0"]::before {
        background-color: #ff8585;
    }
    
    .content {
        display: block;
        padding: 0.2em;
        border-top-width: 2px;
        border-top-style: solid;
        border-color: var(--logic-color-border, black);
    }
    
    .content:empty {
        display: none;
    }
    
    .placeholder {
        display: table;
        margin: 0.2em;
        padding: 0.2em 1.5em;
        color: var(--button-text-color, #000000);
        background-color: var(--button-back-color, #cccccc);
        background-image: var(--button-back-image, none);
        border-style: solid;
        border-width: var(--input-border-width, 2px);
        border-color: var(--input-border-color, #222222);
        border-radius: var(--input-border-radius, 6px);
        font-weight: bold;
        cursor: pointer;
    }
    
    .placeholder:hover {
        color: var(--button-hover-text-color, #ffffff);
        background-color: var(--button-hover-back-color, #000000);
        background-image: var(--button-hover-back-image, none);
    }
    
    .placeholder:disabled {
        color: var(--input-disabled-text-color, #757575);
        background-color: var(--button-disabled-back-color, #999999);
        background-image: var(--button-disabled-back-image, none);
        cursor: default;
    }
    
    .placeholder:focus {
        box-shadow: 0 0 2px 2px var(--input-focus-color, #06b5ff);
        outline: none;
    }
    
    :host([disabled]:not([disabled="false"])),
    :host([readonly]:not([readonly="false"])) {
        cursor: default;
    }
    
    :host([readonly]:not([readonly="false"])) .placeholder {
        display: none;
    }
    
    :host([readonly]:not([readonly="false"])) input,
    :host([readonly]:not([readonly="false"])) select,
    :host([readonly]:not([readonly="false"])) .placeholder,
    :host([template]:not([template="false"])) input,
    :host([template]:not([template="false"])) select,
    :host([template]:not([template="false"])) .placeholder,
    :host([disabled]:not([disabled="false"])) input,
    :host([disabled]:not([disabled="false"])) select,
    :host([disabled]:not([disabled="false"])) .placeholder {
        pointer-events: none;
    }
    
`);
const STYLE_ERROR = new GlobalStyle(`
    :host {
        --logic-color-back: #ff0000;
        --logic-color-border: #770000;
        --logic-color-text: #ffffff;
    }
`);

/* ======================================================================= */

import CustomElement from "../../element/CustomElement.js";
import DragDropMemory from "../../../data/DragDropMemory.js";
import {appUID} from "../../../util/helper/UniqueGenerator.js";

function dragStart(event) {
    DragDropMemory.clear();
    DragDropMemory.add(event.currentTarget);
    event.stopPropagation();
}

const REG = new Map();

export default class AbstractElement extends CustomElement {

    #id;

    #headerEl;

    constructor(caption) {
        if (new.target === AbstractElement) {
            throw new Error("can not construct abstract class");
        }
        /* --- */
        super();
        this.shadowRoot.append(TPL.generate());
        STYLE.apply(this.shadowRoot);
        /* --- */
        this.#headerEl = this.shadowRoot.getElementById("header");
        this.#headerEl.innerText = caption;
        this.#id = appUID("logic-element");
        /* --- */
        this.addEventListener("click", (event) => {
            if (this.editable) {
                event.stopPropagation();
            }
        });
        this.addEventListener("contextmenu", (event) => {
            event.stopPropagation();
            event.preventDefault();
            if (this.editable) {
                const ev = new Event("menu", {
                    bubbles: true,
                    cancelable: true
                });
                ev.id = this.#id;
                ev.left = event.clientX;
                ev.top = event.clientY;
                this.dispatchEvent(ev);
            }
        });
    }

    connectedCallback() {
        if (this.getAttribute("tabindex") !== "0") {
            this.setAttribute("tabindex", "0");
        }
        if (this.draggable) {
            this.setAttribute("draggable", "true");
        } else {
            this.removeAttribute("draggable");
        }
        this.setAttribute("id", this.#id);
        this.addEventListener("dragstart", dragStart);
    }

    disconnectedCallback() {
        this.removeEventListener("dragstart", dragStart);
    }

    get draggable() {
        return !this.disabled && !this.readonly && (!this.template || this.template !== "clicked");
    }

    get editable() {
        return !this.disabled && !this.readonly && !this.template;
    }

    get id() {
        return this.#id;
    }

    getHeader() {
        if (this.#headerEl) {
            return this.#headerEl.innerText;
        }
    }

    getElement(forceCopy = false) {
        if (forceCopy || this.template) {
            const node = this.cloneNode(true);
            node.removeAttribute("template");
            return node;
        } else {
            return this;
        }
    }

    calculate(/* state = {} */) {
        throw new Error("can not call abstract method");
    }

    toJSON() {
        throw new Error("can not call abstract method");
    }

    loadLogic() {
        throw new Error("can not call abstract method");
    }

    append(node) {
        if (Array.isArray(node)) {
            node.forEach((e) => this.appendChild(e));
        } else {
            this.appendChild(node);
        }
    }

    prepend(node) {
        const firstEl = this.firstChild;
        if (firstEl == null) {
            this.append(node);
        } else if (Array.isArray(node)) {
            node.forEach((e) => firstEl.before(e));
        } else {
            firstEl.before(node);
        }
    }

    appendChild(node) {
        if (node instanceof AbstractElement && this.editable) {
            const r = super.appendChild(node);

            if (this.hasAttribute("visualize")) {
                r.setAttribute("visualize", this.getAttribute("visualize"));
            } else {
                r.removeAttribute("visualize");
            }
            if (this.hasAttribute("readonly")) {
                r.setAttribute("readonly", this.getAttribute("readonly"));
            } else {
                r.removeAttribute("readonly");
            }

            return r;
        }
    }

    insertBefore(node, ref) {
        if (node instanceof AbstractElement && this.editable) {
            const r = super.insertBefore(node, ref);

            if (this.hasAttribute("visualize")) {
                r.setAttribute("visualize", this.getAttribute("visualize"));
            } else {
                r.removeAttribute("visualize");
            }
            if (this.hasAttribute("readonly")) {
                r.setAttribute("readonly", this.getAttribute("readonly"));
            } else {
                r.removeAttribute("readonly");
            }

            return r;
        }
    }

    before(node) {
        const parentEl = this.parentElement;
        if (node instanceof AbstractElement && parentEl.editable) {
            super.before(node);

            if (parentEl.hasAttribute("visualize")) {
                node.setAttribute("visualize", parentEl.getAttribute("visualize"));
            } else {
                node.removeAttribute("visualize");
            }
            if (parentEl.hasAttribute("readonly")) {
                node.setAttribute("readonly", parentEl.getAttribute("readonly"));
            } else {
                node.removeAttribute("readonly");
            }
        }
    }

    after(node) {
        const parentEl = this.parentElement;
        if (node instanceof AbstractElement && parentEl.editable) {
            super.after(node);

            if (parentEl.hasAttribute("visualize")) {
                node.setAttribute("visualize", parentEl.getAttribute("visualize"));
            } else {
                node.removeAttribute("visualize");
            }
            if (parentEl.hasAttribute("readonly")) {
                node.setAttribute("readonly", parentEl.getAttribute("readonly"));
            } else {
                node.removeAttribute("readonly");
            }
        }
    }

    get childList() {
        return [];
    }

    set value(value) {
        const hdr = this.shadowRoot.querySelector(".header");
        if (typeof value == "undefined") {
            this.removeAttribute("value");
            if (hdr) {
                delete hdr.dataset.value;
            }
        } else if (typeof value == "boolean") {
            this.setAttribute("value", +value);
            if (hdr) {
                hdr.dataset.value = +value;
            }
        } else {
            this.setAttribute("value", parseInt(value) || 0);
            if (hdr) {
                hdr.dataset.value = parseInt(value) || 0;
            }
        }
    }

    get value() {
        const val = this.getAttribute("value");
        if (val == null) {
            return undefined;
        }
        return parseInt(val) || 0;
    }

    set disabled(val) {
        this.setBooleanAttribute("disabled", val);
    }

    get disabled() {
        return this.getBooleanAttribute("disabled");
    }

    set readonly(value) {
        this.setBooleanAttribute("readonly", value);
    }

    get readonly() {
        return this.getBooleanAttribute("readonly");
    }

    set template(value) {
        this.setBooleanAttribute("template", value);
    }

    get template() {
        return this.getBooleanAttribute("template");
    }

    static get observedAttributes() {
        return ["disabled", "readonly", "value", "visualize", "template"];
    }

    attributeChangedCallback(name, oldValue, newValue) {
        switch (name) {
            case "value": {
                if (oldValue != newValue) {
                    const event = new Event("update");
                    event.value = this.value;
                    this.dispatchEvent(event);
                }
            } break;
            case "visualize": {
                if (oldValue != newValue) {
                    const value = newValue != null && newValue != "false";
                    for (const ch of this.children) {
                        ch.visualize = value;
                    }
                }
            } break;
            case "disabled": {
                if (oldValue != newValue) {
                    if (this.draggable) {
                        this.setAttribute("draggable", "true");
                    } else {
                        this.removeAttribute("draggable");
                    }
                    const value = newValue != null && newValue != "false";
                    for (const ch of this.children) {
                        ch.disabled = value;
                    }
                }
            } break;
            case "readonly": {
                if (oldValue != newValue) {
                    if (this.draggable) {
                        this.setAttribute("draggable", "true");
                    } else {
                        this.removeAttribute("draggable");
                    }
                    const value = newValue != null && newValue != "false";
                    for (const ch of this.children) {
                        ch.readonly = value;
                    }
                }
            } break;
            case "template": {
                if (oldValue != newValue) {
                    if (this.draggable) {
                        this.setAttribute("draggable", "true");
                    } else {
                        this.removeAttribute("draggable");
                    }
                }
            } break;
        }
    }

    static registerReference(ref, clazz) {
        if (REG.has(ref)) {
            throw new Error(`reference ${ref} already exists`);
        }
        REG.set(ref, clazz);
    }

    static getReference(...refs) {
        for (const ref of refs) {
            if (REG.has(ref)) {
                return REG.get(ref);
            }
        }
        console.error("logic element not found for references", ...refs);
        return ErrorElement;
    }

    static buildLogic(logic) {
        if (typeof logic === "object" && !!logic) {
            if (Array.isArray(logic)) {
                return new ErrorElement();
            } else {
                let cl;
                if (logic.category) {
                    cl = AbstractElement.getReference(logic.category, logic.type);
                } else {
                    cl = AbstractElement.getReference(logic.type);
                }
                const node = new cl();
                node.loadLogic(logic);
                return node;
            }
        }
        return new (AbstractElement.getReference(`${logic}`));
    }

    static allowDrop(event) {
        const node = event.target.getRootNode().host;
        if (node.editable) {
            event.preventDefault();
            event.stopPropagation();
            return false;
        }
    }

    static dropOnPlaceholder(event) {
        const nodes = DragDropMemory.get();
        if (nodes.length) {
            const node = nodes[0];
            if (!!node && node instanceof AbstractElement) {
                const ne = node.getElement(event.ctrlKey);
                if (ne) {
                    event.target.getRootNode().host.append(ne);
                    const slot = event.target.parentNode;
                    if (slot instanceof HTMLSlotElement && slot.name != null) {
                        ne.setAttribute("slot", slot.name);
                    } else {
                        ne.removeAttribute("slot");
                    }
                }
            }
        }
        DragDropMemory.clear();
        event.preventDefault();
        event.stopPropagation();
        return false;
    }

    checkValidity() {
        return true;
    }

}

class ErrorElement extends AbstractElement {

    constructor() {
        super("ERROR: REFERENCE NOT FOUND");
        STYLE_ERROR.apply(this.shadowRoot);
    }

    getElement() {
        return this;
    }

    get value() {
        return undefined;
    }

    calculate(/* state = {} */) {
        this.shadowRoot.getElementById("header").setAttribute("value", "0");
        return 0;
    }

    toJSON() {}

    loadLogic(logic) {
        this.shadowRoot.getElementById("body").innerHTML = logic.type || "UNKNOWN TYPE";
    }

    checkValidity() {
        return false;
    }

}

customElements.define("emc-logic-error", ErrorElement);
