"use strict";

import * as USIMP from "usimp";

export class App {
    account: USIMP.Account | null;
    defaultLocation: string;
    main: HTMLElement;
    windows: HTMLElement;
    session: USIMP.Session | null;

    constructor() {
        this.defaultLocation = '/welcome';
        this.account = null;
        this.session = null;

        const main = document.getElementsByTagName("main")[0];
        if (!main) throw new Error("Element <main> not found");
        this.main = main;

        const windows = document.getElementById("windows");
        if (!windows) throw new Error("Element #windows not found");
        this.windows = windows;

        window.addEventListener("hashchange", event => {
            this.handleUrl(event.newURL);
        });

        const domainName = window.localStorage['domainName'];
        const sessionToken = window.localStorage['sessionToken'];
        if (domainName && sessionToken) {
            USIMP.Domain.fromName(domainName).then((domain) => {
                const session = new USIMP.Session(domain);
                session.chooseDomainServer().then(() => {
                    session.token = sessionToken;
                    session.ping().then(() => {
                        this.session = session;
                        this.defaultLocation = '/';
                        this.handleUrl(document.URL);
                    })
                });
            }, (error) => {
                if (error instanceof DOMException) {
                    throw new Error("Connection timed out");
                } else {
                    throw new Error("Invalid USIMP domain");
                }
            });
        } else {
            this.handleUrl(document.URL);
        }
    }

    quit() {
        if (this.session) this.session.close().then();
    }

    sleep() {
        if (this.session) this.session.sleep().then();
    }

    async wakeup() {
        if (this.session) await this.session.wakeup();
    }

    setHash(hash: string) {
        const url = new URL(document.URL);
        url.hash = hash;
        location.href = url.toString();
    }

    handleUrl(url: string) {
        this.handleHash(new URL(url).hash);
    }

    handleHash(hash: string) {
        if (hash[0] === '#') hash = hash.substring(1);

        const defaultCase = () => {
            history.replaceState(null, document.title, `#${this.defaultLocation}`);
            this.handleHash(this.defaultLocation);
        }

        switch (hash) {
            case "/":
                if (!this.session) {
                    return defaultCase();
                }
                this.hideWindows();
                this.showMain();
                this.removeAllWindows();
                this.setupMain();
                break;
            case "/welcome":
                this.hideMain();
                this.showWindows();
                this.removeAllWindows();
                this.addWelcomeWindow();
                break;
            case "/login":
                if (this.session) {
                    return defaultCase();
                }
                this.hideMain();
                this.showWindows();
                this.removeAllWindows();
                this.addLoginWindow();
                break;
            default:
                console.warn(`Invalid url hash #${hash}`);
                return defaultCase();
        }
    }

    async login(accountName: string, domainName: string, password: string) {
        let domain;
        try {
            domain = await USIMP.Domain.fromName(domainName);
        } catch (error) {
            if (error instanceof DOMException) {
                throw new Error("Connection timed out");
            } else {
                throw new Error("Invalid USIMP domain");
            }
        }

        const session = new USIMP.Session(domain);
        const rtt = await session.chooseDomainServer();
        console.log(rtt);

        const response = await session.authenticate(accountName, password);
        if (response.status === "success") {
            this.session = session;
            this.defaultLocation = "/";
            return true;
        } else {
            console.error(response.error);
            throw new Error(response.error.message || response.error.code);
        }
    }

    hideMain() {
        this.main.style.visibility = "hidden";
    }

    showMain() {
        this.main.style.visibility = "visible";
    }

    hideWindows() {
        this.windows.style.visibility = "hidden";
    }

    showWindows() {
        this.windows.style.visibility = "visible";
    }

    removeAllWindows() {
        while (this.windows.lastChild) this.windows.removeChild(this.windows.lastChild);
    }

    addWelcomeWindow() {
        const win = document.createElement("div");
        win.classList.add("window-welcome");

        win.innerHTML = `
            <h1>Welcome to Locutus!</h1>
            <a href="#/login" class="button">Login</a>`;

        this.windows.appendChild(win);
    }

    addMessage(id: string, message: string, delivered: boolean = true) {
        const test = document.getElementById(`msg-${id}`);
        if (test) return;

        const msg = document.createElement("div");
        msg.id = `msg-${id}`;
        msg.classList.add("message");
        if (delivered) {
            msg.classList.add("delivered");
        } else {
            msg.classList.add("pending");
        }

        msg.innerText = message;

        const chat = this.main.getElementsByClassName("chat-history")[0];
        if (!chat) throw new Error("Element .chat-history not found");

        chat.appendChild(msg);
        chat.scrollTop = chat.scrollHeight;
    }

    confirmMessage(id: string) {
        const msg = document.getElementById(`msg-${id}`);
        if (!msg) return;

        msg.classList.remove("pending");
        msg.classList.add("delivered");
    }

    addLoginWindow() {
        const win = document.createElement("div");
        win.classList.add("window-login");

        win.innerHTML = `
            <h1>Login to USIMP Account</h1>
            <form>
                <input name="account" placeholder="Account name" type="text" required/>
                <input name="domain" placeholder="Domain" type="text" pattern="([a-zA-Z0-9_-]+\\.)+[a-zA-Z]{2,}" required/>
                <input name="password" placeholder="Password" type="password" required/>
                <fieldset>
                    <label><input name="saveAccountName" type="checkbox"/> Save Account Name</label>
                </fieldset>
                <fieldset>
                    <label><input name="keepSession" type="checkbox"/> Keep me signed in</label>
                </fieldset>
                <button type="submit">Login</button>
            </form>`;

        const form = win.getElementsByTagName("form")[0];
        if (!form) throw new Error("Element <form> not found");

        form.addEventListener("submit", async event => {
            event.preventDefault();

            for (const e of form.getElementsByTagName("button")) e.disabled = false;
            for (const e of form.getElementsByTagName("input")) {
                e.disabled = true;
                e.removeAttribute("invalid");
            }

            try {
                const accountName: string = form['account'].value;
                const domainName: string = form['domain'].value;
                const keepSession: boolean = form['keepSession'].checked;
                const saveAccountName: boolean = form['saveAccountName'].checked;
                try {
                    if (await this.login(accountName, domainName, form['password'].value)) {
                        window.localStorage.clear();
                        if (keepSession || saveAccountName)
                            window.localStorage['domainName'] = domainName;
                        if (keepSession)
                            window.localStorage['sessionToken'] = this.session?.token;
                        if (saveAccountName)
                            window.localStorage['accountName'] = accountName;
                        this.setHash("/");
                    }
                } finally {
                    for (const d of form.getElementsByTagName("div")) form.removeChild(d);
                    for (const e of form.getElementsByTagName("input")) e.disabled = false;
                    for (const e of form.getElementsByTagName("button")) e.disabled = false;
                }
            } catch (error: any) {
                if (error.toString() === "Error: unable to authenticate") {
                    const account = document.getElementsByName("account")[0];
                    if (account) account.setAttribute("invalid", "invalid");
                } else {
                    const domain = document.getElementsByName("domain")[0];
                    if (domain) domain.setAttribute("invalid", "invalid");
                }

                const div = document.createElement("div");
                div.classList.add("error");
                div.innerText = error.toString();
                form.appendChild(div);
            }
        });

        this.windows.appendChild(win);

        const domain = document.getElementsByName("domain")[0];
        if (!domain) throw new Error("Element name=domain not found");
        domain.addEventListener("input", () => {
            domain.removeAttribute("invalid");
        });

        const account = document.getElementsByName("account")[0];
        if (!account) throw new Error("Element name=account not found");
        account.addEventListener("input", () => {
            account.removeAttribute("invalid");
        });

        const password = document.getElementsByName("password")[0];
        if (!password) throw new Error("Element name=password not found");

        const accountName = window.localStorage['accountName'];
        const domainName = window.localStorage['domainName'];
        if (accountName && domainName) {
            form['account'].value = accountName;
            form['domain'].value = domainName;
            form['saveAccountName'].checked = true;
            password.focus();
        } else {
            account.focus();
        }
    }

    setupMain() {
        if (!this.session) throw new Error("Invalid state");

        this.main.innerHTML = `
            <div class="chat">
                <div class="chat-history"></div>
                <div class="chat-input-wrapper">
                    <input name="message" type="text" autocomplete="off"/>
                </div>
            </div>`;

        const input = document.getElementsByTagName("input")[0];
        if (!input) throw new Error("Element <input> not found");

        input.addEventListener("keydown", event => {
            if (event.key === "Enter" && input.value.length > 0) {
                if (!this.session) throw new Error("No session found");

                const id = crypto.randomUUID();
                const val = input.value;
                this.addMessage(id, input.value, false);

                this.session.newEvent(
                    "24595934-4540-4333-ac2b-78796ac3f25f",
                    {message: val},
                    id
                ).then(() => {
                    this.confirmMessage(id);
                });

                input.value = "";
            }
        });

        this.session.subscribe(event => {
            this.addMessage(event.id, event.data.message);
        }).then();
    }
}
