import * as bootstrap from 'bootstrap';

import xslContent from 'bundle-text:../xsl/MessageLog.xsl';
import * as ui from './ui';
import { db } from './db';

const xsltProcessor = createXSLTProcessor(xslContent)!;

function showApplication(toggle: boolean) {
    if (toggle) {
        ui.realBody.classList.remove('d-none');
    } else {
        ui.realBody.classList.add('d-none');
    }
}

function showLoadingIndicator(toggle: boolean) {
    if (toggle) {
        ui.loadingIndicator.classList.remove('d-none');
    } else {
        ui.loadingIndicator.classList.add('d-none');
    }
}

function showLoadedControls() {
    ui.noFileLoadedControls.classList.add('d-none');
    ui.noFileLoadedControls.classList.remove('d-lg-block');
    ui.fileLoadedControls.classList.remove('d-none');
}

function showNotLoadedControls() {
    ui.noFileLoadedControls.classList.remove('d-none');
    ui.noFileLoadedControls.classList.add('d-lg-block');
    ui.fileLoadedControls.classList.add('d-none');
}

function displayError(errorDiv: HTMLElement, category: string, ex: Error) {
    console.error(ex);
    const errorHeading = errorDiv.querySelector('.alert-heading') as HTMLElement | null;

    //clear all error text, but not heading
    const errorMessages = errorDiv.querySelectorAll('span');
    errorMessages.forEach(message => errorDiv.removeChild(message));

    //add new message and show the error alert
    errorDiv.appendChild(<span>{ex.message}</span>);

    if (errorHeading) {
        errorHeading.innerText = category;
    }
    errorDiv.classList.remove('d-none');
}

function displayFatalError(category: string, ex: Error) {
    showApplication(false);
    showLoadingIndicator(false);
    displayError(ui.fatalError, category, ex);
}

function displayNonFatalError(category: string, ex: Error) {
    showApplication(true);
    showLoadingIndicator(false);
    displayError(ui.nonFatalError, category, ex);
}

function hideErrors() {
    document
        .querySelectorAll('#fatal-error, #display-backup-error')
        .forEach(el => el.classList.add('d-none'));
}

function createXSLTProcessor(xmlStylesheet: string): XSLTProcessor | undefined {
    const doc = new DOMParser().parseFromString(xmlStylesheet, "text/xml");
    try {
        const xsltProcessor = new XSLTProcessor();
        xsltProcessor.importStylesheet(doc);
        return xsltProcessor;
    } catch (cause) {
        console.error(cause);
        displayFatalError('Initialiation Failed', new Error('Stylesheet parsing failed'));
    }
}

function removeChildren(element: HTMLElement) {
    while (element.hasChildNodes() && element.lastChild != null) {
        element.removeChild(element.lastChild);
    }
}

function checkOverflow(elem: HTMLElement) {
    const elemHeight = elem.scrollHeight;
    const parentHeight = elem.offsetHeight;
    return elemHeight > parentHeight;
}

function processFragment() {
    document
        .querySelectorAll('[data-bs-toggle="popover"]')
        .forEach(popover => new bootstrap.Popover(popover, { trigger: 'focus' }));

    document.querySelectorAll('.message-content div').forEach(div => {
        if (checkOverflow(div as HTMLElement)) {
            div.parentElement?.classList.add('overflow-icon');
        }
    });
}

async function saveToDatabase(filename: string, xml: string) {
    db.entries
        .put({ filename, backupData: xml })
        .then(_ => toggleLoadPrevious(true))
        .catch(err => console.error('Saving Failed', err));
}

function displayBackup(xsltProcessor: XSLTProcessor, filename: string, xmlText: string): boolean {
    hideErrors();
    removeChildren(ui.chatDisplay);
    showLoadedControls();

    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xmlText, "text/xml");
    const errorNode = xmlDoc.querySelector('parsererror');

    if (!errorNode) {
        try {
            const fragment = xsltProcessor.transformToFragment(xmlDoc, document);
            ui.chatDisplay.appendChild(fragment);
            ui.currentlyViewing.value = filename;
            processFragment();
            if (history.state?.filename != filename) {
                history.pushState({ filename }, '', `#!/${filename}`);
            }

            document.title = `MSN Viewer - ${filename}`;
            return true;
        }
        catch (e) {
            if (e instanceof Error) {
                displayNonFatalError('Could not load backup', e);
            }
            return false;
        }
    } else {
        const err = new Error(errorNode.firstChild?.nodeValue || '');
        displayNonFatalError('Could not load backup', err);
        return false;
    }
}

function toggleLoadPrevious(toggle: boolean) {
    ui.loadPreviousButtons.forEach(btn => {
        if (toggle) {
            btn.classList.remove('disabled');
        } else {
            btn.classList.add('disabled');
            bootstrap.Modal.getOrCreateInstance(ui.previousBackupsModal)?.hide();
        }
    });
}

async function initEvents() {
    db.entries.count()
        .then(count => {
            if (count > 0) {
                toggleLoadPrevious(true);
            }
        })
        .catch(err => {
            //Something wrong with db access, disable buttons anyway.
            console.error(err);
            toggleLoadPrevious(false);
        });

    ui.loadPreviousButtons.forEach(button => button.addEventListener('click', async () => {
        await populateSavedBackups();
    }));

    ui.viewNewButtons.forEach(button => button.addEventListener('click', e => {
        ui.fileSelector.click();
        e.preventDefault();
    }));

    ui.fileSelector.addEventListener("change", async (e) => {
        const target = e.currentTarget as HTMLInputElement | null;
        const file = target?.files?.[0];

        if (file) {
            const xmlText = await file.text();
            if (displayBackup(xsltProcessor, file.name, xmlText)) {
                await saveToDatabase(file.name, xmlText);
            }
        } else {
            displayNonFatalError('Could not load file', new Error('No file found.'));
        }
    });

    window.onpopstate = async (event) => {
        const filename = event?.state?.filename as string | null;

        if (filename) {
            await doLoadBackup(event.state.filename);
        } else {
            reset();
        }
    }
}

function reset() {
    showApplication(true);
    showNotLoadedControls();
    hideErrors();
    removeChildren(ui.chatDisplay);
    history.pushState(null, 'MSN Viewer', '/');
    document.title = 'MSN Viewer';
}

async function deleteBackup(e: MouseEvent) {
    const el = e.currentTarget as HTMLElement;
    const anchor = el.closest('a.backup-entry') as HTMLAnchorElement | null;
    const filename = anchor?.dataset['filename'];

    if (filename) {
        if (window.confirm('Do you want to remove ' + filename + ' from the backup list?')) {
            try {
                await db.entries.delete(filename);
                const count = await db.entries.count();

                if (count > 0) {
                    populateSavedBackups();
                } else {
                    toggleLoadPrevious(false);
                    reset();
                }
            } catch (e) {
                displayNonFatalError('Could not remove backup', e as Error);
            }
        }
    }
}

async function doLoadBackup(filename: string) {
    const entry = await db.entries.get(filename);
    if (entry) {
        displayBackup(xsltProcessor, entry.filename, entry.backupData);
    } else {
        displayNonFatalError(
            'Internal Error',
            new Error(`Could not find data for backup: ${filename}`)
        );
    }

    bootstrap.Modal.getInstance(ui.previousBackupsModal)?.hide();
}

async function loadBackup(e: MouseEvent) {
    const listItem = e.target as HTMLElement | null;
    const filename = listItem?.dataset['filename'];

    if (filename) {
        await doLoadBackup(filename);
    }
}

function BackupListItem(props: { filename: string }) {
    return (
        <a href="javascript:void(0);"
            class="backup-entry list-group-item list-group-item-action"
            onClick={loadBackup} dataset={{ filename: props.filename }}>
            <div class="backup-name pe-none">
                {props.filename}
            </div>
            <div class="backup-delete" onClick={deleteBackup}>
                <i class="bi bi-x-circle pe-none"></i>
            </div>
        </a>
    );
}

async function populateSavedBackups() {
    removeChildren(ui.previousBackupsList);
    const modal = bootstrap.Modal.getOrCreateInstance(ui.previousBackupsModal);
    const entries = await db.entries.orderBy('filename').toArray();

    entries.forEach(entry => {
        ui.previousBackupsList.appendChild(<BackupListItem filename={entry.filename} />);
    });

    modal.show();
}

async function parseURL() {
    if (window.location.hash && window.location.hash.startsWith('#!/')) {
        const split = window.location.hash.split('#!/');
        if (split.length > 1) {
            const filename = split[1];
            console.log(filename);
            await doLoadBackup(filename);
        }
    }
}

window.addEventListener('DOMContentLoaded', async () => {
    await initEvents();
    showLoadingIndicator(false);
    await parseURL();
    showApplication(true);
});
