import {DocumentRegistry} from "@jupyterlab/docregistry";
import {INotebookModel, Notebook, NotebookPanel} from "@jupyterlab/notebook";
import {IDisposable} from "@lumino/disposable";
import {LintHelper} from "./lint-helper";
import {Cell, ICellModel} from "@jupyterlab/cells";

export class VadalogLinter implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {

    // the delay in milliseconds until linting is triggered after a change in a code cell
    public static readonly LINTING_DELAY = 1500;

    /**
     * Applies linting to the given notebook using the kernel of the specified context.
     * Note: CodeMirror 6 linting is handled differently - this is a simplified version
     *
     * @param context a DocumentRegistry.IContext<INotebookModel> object, used for accessing kernel and saving notebook
     * @param notebook the Notebook object to lint
     */
    public static async lintNotebook(context: DocumentRegistry.IContext<INotebookModel>, notebook: Notebook): Promise<boolean> {
        if (context.sessionContext.session && context.sessionContext.session.kernel) {
            context.save().then(async (_msg: any) => {
                        let lintHelper = new LintHelper(context.sessionContext.session.kernel, context.sessionContext.session.path);
                        await lintHelper.setup(); // wait until the lintHelper gathered the Annotations from the kernel

                        // In CodeMirror 6, linting is handled via extensions
                    });
            return Promise.resolve(true);
        }
        return Promise.resolve(false);
    }


    // helper function for delaying execution (similar to 'Thread.sleep()')
    private static delay(ms: number) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }


    /**
     * Applies linting to the given context and notebook.
     * Waits a few seconds until no further changes to the active cell happen (specified in VadalogLinter.LINTING_DELAY).
     *
     * @param cellModel an ICellModel object of the current active cell
     * @param _ void
     */
    static _onCodeCellContentChanged(this: { context: DocumentRegistry.IContext<INotebookModel>, notebook: Notebook }, cellModel: ICellModel, _: void): void {
        (async () => {
            // assign each change an id and only apply linting for the last change after some delay
            // dynamically set integer object variable as counter on code cell model for that
            // @ts-ignore
            if (!cellModel.waitingForLint) {
                // @ts-ignore
                cellModel.waitingForLint = 0;
            }
            // @ts-ignore
            let id: number = ++cellModel.waitingForLint;

            // wait a bit if more changes happen
            await VadalogLinter.delay(VadalogLinter.LINTING_DELAY);

            // if some other change of the code cell triggered the linting in the meantime, abort
            // @ts-ignore
            if (id !== cellModel.waitingForLint) return;
            else VadalogLinter.lintNotebook(this.context, this.notebook).then();
        })();
    }

    /**
     * Activates code linting on-change if the given cell is a code cell and it not has already been activated.
     *
     * @param notebook Notebook object the cell is in
     * @param cell Cell object representing the new active cell
     */
    static _onActiveCellChanged(this: DocumentRegistry.IContext<INotebookModel>, notebook: Notebook, cell: Cell): void {
        // verify that there's a code cell
        if (!cell || !cell.model || cell.model.type !== 'code') return;

        // check if linting listener is already registered for this cell
        // dynamically set boolean object variable on code cell for that
        // @ts-ignore
        if (!cell.model.lintingActivated) {
            // @ts-ignore
            cell.model.lintingActivated = true;
            cell.model.contentChanged.connect(
                VadalogLinter._onCodeCellContentChanged,
                {context: this, notebook: notebook});
        }
    }


    /**
     * A simple listener widget that applies linting to the whole notebook when fully loaded/disposed.
     * Then continuously adds a signal listener to the active code cell such that code linting is triggered automatically on change.
     *
     * @param panel a NotebookPanel object, containing also the Notebook object
     * @param context context of the notebook (kernel, model, etc.)
     */
    createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
        // wait for the kernel to be ready then lint the notebook initially once
        context.ready.then(() => {
            context.sessionContext.ready.then(() => {
                VadalogLinter.lintNotebook(context, panel.content).then()
            });
        });

        // when the active cell changes, activate code linting on-change in the new active cell
        // hand over notebook context as 'this' context to _onActiveCellChanged function
        panel.content.activeCellChanged.connect(VadalogLinter._onActiveCellChanged, context);

        return undefined; // no disposable returned by this widget
    }

}
