import Component from "../Component";
import DataEndNode from "../../Data/DataEndNode";
import EventHandler from "../../Utility/EventHandler";

export default abstract class Input<T> extends Component{
    protected dataNode: DataEndNode<T>;
    public name: string = "";
    public autocomplete: string = "";
    public description: string = "";
    public index: string = "";
    protected questionId: string = "";
    protected _value: T|null|undefined = undefined;

    public onChange: EventHandler<ChangeEvent<T>> = new EventHandler();
    public onReset: EventHandler<null> = new EventHandler();

    protected constructor(params: Partial<Input<T>>) {
        super(params);
        Object.assign(this, params);
        this.questionId = `pad-${Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)}`;
    }

    protected getFullPathName(): string {
        let lastIndex: string = this.index;
        let path = "";
        let parent = this.parent;
        while (parent != null) {
            if (parent.index != null) {
                path = `[${lastIndex}]${path}`;
                lastIndex = parent.index;
            }

            parent = parent.parent;
        }

        return lastIndex + path;
    }

    protected getQuestionContainer(): HTMLElement {
        const questionContainer = document.createElement("label");
        questionContainer.classList.add(
            "pad-input",
            "pad-form-index-" + this.index
        );
        this.dataNode.onValidChange.add(event => {
            questionContainer.classList.toggle("pad-invalid", !event.isValid);
        });

        this.dataNode.onServerSideErrorsChange.add(event => {
            questionContainer.classList.toggle("pad-server-side-errors", (event.errors.length) !== 0);
        });

        questionContainer.append(
            this.getWarningsAreaElement()
        );

        if ( this.parent?.isDisplayDefaultNotice ?? false )
            questionContainer.append( this.getNoticesElement() );

        const inputElement = this.getInputElement();
        inputElement.classList.add("pad-input-field");
        inputElement.id = this.questionId;

        questionContainer.append(
            this.getLabelElement(),
            inputElement,
            this.getDescriptionElement()
        );

        return questionContainer;
    }

    protected getLabelElement(): HTMLElement {
        const labelElement = document.createElement("label");
        labelElement.innerText = this.name;
        labelElement.htmlFor = this.questionId;
        labelElement.classList.add("pad-label");
        return labelElement;
    }

    protected getDescriptionElement(): HTMLElement {
        const descriptionElement = document.createElement("p");
        descriptionElement.innerText = this.description;
        descriptionElement.classList.add("pad-description");
        return descriptionElement;
    }

    protected getNoticesElement(): HTMLElement {
        const inputNoticeArea = document.createElement("p");
        inputNoticeArea.classList.add("pad-notice-area");

        const inputNotice = document.createElement("span");
        inputNotice.classList.add("pad-notice");

        const resetText = document.createElement("a");
        resetText.innerText = this.getOwnerForm().resetButtonText;
        resetText.href = "#";
        resetText.classList.add("pad-reset-text");
        resetText.addEventListener("click", () => {
            this.dataNode.resetValue();
        })

        inputNoticeArea.append(inputNotice);
        inputNoticeArea.append(resetText);

        this.dataNode.onChange.add(() => {
            if (this.dataNode.isChanged){
                inputNotice.innerText = this.getOwnerForm().isChangedText;
                resetText.classList.remove("pad-hidden");
            } else {
                inputNotice.innerText = this.getOwnerForm().isDefaultText;
                resetText.classList.add("pad-hidden");
            }
        });

        if (this.dataNode.isChanged){
            inputNotice.innerText = this.getOwnerForm().isChangedText;
            resetText.classList.remove("pad-hidden");
        } else {
            inputNotice.innerText = this.getOwnerForm().isDefaultText;
            resetText.classList.add("pad-hidden");
        }

        return inputNoticeArea;
    }

    protected getWarningsAreaElement(): HTMLElement {
        const warningElement = document.createElement("ul");
        warningElement.classList.add(
            "pad-warning",
            "pad-hidden"
        );

        let dataMessages: string[] = [];
        let serverSideErrors: string[] = [];

        const setMessageElements = () => {
            warningElement.innerHTML = "";
            dataMessages.forEach(message => {
                const listElement = document.createElement("li");
                listElement.classList.add("pad-data-error");
                listElement.innerText = message;
                warningElement.append(listElement);
            });

            serverSideErrors.forEach(message => {
                const listElement = document.createElement("li");
                listElement.classList.add("pad-server-side-error");
                listElement.innerText = message;
                warningElement.append(listElement);
            });

            warningElement.classList.toggle("pad-hidden", dataMessages.length + serverSideErrors.length === 0);
        };

        this.dataNode.onMessagesChange.add(event => {
            dataMessages = [];
            event.messages.forEach(message => {
                dataMessages.push(message);
            });

            setMessageElements();
        });

        this.dataNode.onServerSideErrorsChange.add(event => {
            serverSideErrors = [];
            event.errors.forEach(message => {
                serverSideErrors.push(message);
            });

            setMessageElements();
        });

        return warningElement;
    }

    protected abstract getInputElement(): HTMLElement;

    public setDataNode(dataNode: DataEndNode<T>): this {
        this.dataNode = dataNode;
        return this;
    }

    public getDataNode(): DataEndNode<T> {
        return this.dataNode;
    }

    public saveValue(): void {
        this.dataNode.value = this._value;
    }

    protected set value(value: T|null|undefined) {
        if (this._value === value) return;
        this._value = value;
        this.onChange.emit(new ChangeEvent(value));
    }

    public updateVisibility(): void {
        super.updateVisibility();
        this.dataNode.isVisible = this.visible;
    }
}

export class ChangeEvent<T> {
    public constructor(
        public readonly value: T|null|undefined,
    ) {}
}