/**
 * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * Adapted from LiteMol (c) David Sehnal
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 */

import { PluginComponent } from '../component';
import { OrderedMap } from 'immutable';
import { PluginContext } from '../context';
import { PluginCommands } from '../command';

export interface PluginToast {
    title: string,
    /**
     * The message can be either a string, html string, or an arbitrary React component.
     */
    message: string | React.ComponentClass,
    /**
     * Only one message with a given key can be shown.
     */
    key?: string,
    /**
     * Specify a timeout for the message in milliseconds.
     */
    timeoutMs?: number
}

export class PluginToastManager extends PluginComponent<{
    entries: OrderedMap<number, PluginToastManager.Entry>
}> {
    readonly events = {
        changed: this.ev()
    };

    private serialNumber = 0;
    private serialId = 0;

    private findByKey(key: string): PluginToastManager.Entry | undefined {
        return this.state.entries.find(e => !!e && e.key === key)
    }

    private show(toast: PluginToast) {
        let entries = this.state.entries;
        let e: PluginToastManager.Entry | undefined = void 0;
        const id = ++this.serialId;
        let serialNumber: number;
        if (toast.key && (e = this.findByKey(toast.key))) {
            if (e.timeout !== void 0) clearTimeout(e.timeout);
            serialNumber = e.serialNumber;
            entries = entries.remove(e.id);
        } else {
            serialNumber = ++this.serialNumber;
        }

        e = {
            id,
            serialNumber,
            key: toast.key,
            title: toast.title,
            message: toast.message,
            timeout: this.timeout(id, toast.timeoutMs),
            hide: () => this.hideId(id)
        };

        if (this.updateState({ entries: entries.set(id, e) })) this.events.changed.next();
    }

    private timeout(id: number, delay?: number) {
        if (delay === void 0) return void 0;

        if (delay < 0) delay = 500;
        return <number><any>setTimeout(() => {
            const e = this.state.entries.get(id);
            e.timeout = void 0;
            this.hide(e);
        }, delay);
    }

    private hideId(id: number) {
        this.hide(this.state.entries.get(id));
    }

    private hide(e: PluginToastManager.Entry | undefined) {
        if (!e) return;
        if (e.timeout !== void 0) clearTimeout(e.timeout);
        e.hide = <any>void 0;
        if (this.updateState({ entries: this.state.entries.delete(e.id) })) this.events.changed.next();
    }

    constructor(plugin: PluginContext) {
        super({ entries: OrderedMap<number, PluginToastManager.Entry>() });

        PluginCommands.Toast.Show.subscribe(plugin, e => this.show(e));
        PluginCommands.Toast.Hide.subscribe(plugin, e => this.hide(this.findByKey(e.key)));
    }
}

export namespace PluginToastManager {
    export interface Entry {
        id: number,
        serialNumber: number,
        key?: string,
        title: string,
        message: string | React.ComponentClass,
        hide: () => void,
        timeout?: number
    }
}