import {
  ILabShell,
  ILayoutRestorer,
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { CodeCell, MarkdownCell } from '@jupyterlab/cells';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { IEditorTracker } from '@jupyterlab/fileeditor';
import { IMarkdownViewerTracker } from '@jupyterlab/markdownviewer';
import { INotebookTracker } from '@jupyterlab/notebook';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import {
  createLatexGenerator,
  createMarkdownGenerator,
  createNotebookGenerator,
  createPythonGenerator,
  createRenderedMarkdownGenerator,
  INotebookHeading,
  ITableOfContentsRegistry,
  TableOfContents,
  TableOfContentsRegistry
} from './toc';
import { ITranslator } from '@jupyterlab/translation';
import { tocIcon } from './utils/icons';
import { PLUGIN_ID } from './utils/constants';

/**
 * A namespace for command IDs of table of contents plugin.
 */
namespace CommandIDs {
  export const runCells = `${PLUGIN_ID}:run-cells`;

  export const showPanel = `${PLUGIN_ID}:show-panel`;
}

/**
 * Activates the ToC extension.
 *
 * @private
 * @param app - Jupyter application
 * @param docmanager - document manager
 * @param rendermime - rendered MIME registry
 * @param translator - translator
 * @param editorTracker - editor tracker
 * @param restorer - application layout restorer
 * @param labShell - Jupyter lab shell
 * @param markdownViewerTracker - Markdown viewer tracker
 * @param notebookTracker - notebook tracker
 * @param settingRegistry - setting registry
 * @returns table of contents registry
 */
async function activateTOC(
  app: JupyterFrontEnd,
  docmanager: IDocumentManager,
  rendermime: IRenderMimeRegistry,
  translator: ITranslator,
  editorTracker?: IEditorTracker,
  restorer?: ILayoutRestorer,
  labShell?: ILabShell,
  markdownViewerTracker?: IMarkdownViewerTracker,
  notebookTracker?: INotebookTracker,
  settingRegistry?: ISettingRegistry
): Promise<ITableOfContentsRegistry> {
  console.log(`JupyterLab extension ${PLUGIN_ID} is activated!`);

  const trans = translator.load('jupyterlab');
  // Create the ToC widget:
  const toc = new TableOfContents({
    docmanager,
    rendermime,
    translator
  });

  // Create the ToC registry:
  const registry = new TableOfContentsRegistry();

  // Add the ToC to the left area:
  toc.title.icon = tocIcon;
  toc.title.caption = trans.__('Table of Contents with Code');
  toc.id = 'jpcodetoc-id';
  toc.node.setAttribute('role', 'region');
  toc.node.setAttribute('aria-label', trans.__('Table of Contents section'));

  app.shell.add(toc, 'left', { rank: 400 });

  app.commands.addCommand(CommandIDs.runCells, {
    execute: args => {
      if (!notebookTracker) {
        return null;
      }

      const panel = notebookTracker.currentWidget;
      if (panel === null) {
        return;
      }

      const cells = panel.content.widgets;
      if (cells === undefined) {
        return;
      }

      const activeCell = (toc.activeEntry as INotebookHeading).cellRef;

      if (activeCell instanceof MarkdownCell) {
        const level = activeCell.headingInfo.level;
        for (let i = cells.indexOf(activeCell) + 1; i < cells.length; i++) {
          const cell = cells[i];
          if (
            cell instanceof MarkdownCell &&
            cell.headingInfo.level <= level &&
            cell.headingInfo.level > -1
          ) {
            break;
          }

          if (cell instanceof CodeCell) {
            void CodeCell.execute(cell, panel.sessionContext);
          }
        }
      } else {
        if (activeCell instanceof CodeCell) {
          void CodeCell.execute(activeCell, panel.sessionContext);
        }
      }
    },
    label: trans.__('Run Cell(s)')
  });

  app.commands.addCommand(CommandIDs.showPanel, {
    label: trans.__('Table of Contents with Code'),
    execute: () => {
      app.shell.activateById(toc.id);
    }
  });

  if (restorer) {
    // Add the ToC widget to the application restorer:
    restorer.add(toc, `${PLUGIN_ID}:plugin`);
  }

  // Attempt to load plugin settings:
  let settings: ISettingRegistry.ISettings | undefined;
  if (settingRegistry) {
    try {
      settings = await settingRegistry.load(extension.id);
    } catch (error) {
      console.error(
        `Failed to load settings for the Table of Contents extension.\n\n${error}`
      );
    }
  }

  // Create a notebook generator:
  if (notebookTracker) {
    const notebookGenerator = createNotebookGenerator(
      notebookTracker,
      toc,
      rendermime.sanitizer,
      translator,
      settings
    );
    registry.add(notebookGenerator);
  }

  // Create a Markdown generator:
  if (editorTracker) {
    const markdownGenerator = createMarkdownGenerator(
      editorTracker,
      toc,
      rendermime.sanitizer,
      translator,
      settings
    );
    registry.add(markdownGenerator);

    // Create a LaTeX generator:
    const latexGenerator = createLatexGenerator(editorTracker);
    registry.add(latexGenerator);

    // Create a Python generator:
    const pythonGenerator = createPythonGenerator(editorTracker);
    registry.add(pythonGenerator);
  }

  // Create a rendered Markdown generator:
  if (markdownViewerTracker) {
    const renderedMarkdownGenerator = createRenderedMarkdownGenerator(
      markdownViewerTracker,
      toc,
      rendermime.sanitizer,
      translator,
      settings
    );
    registry.add(renderedMarkdownGenerator);
  }

  // Update the ToC when the active widget changes:
  if (labShell) {
    labShell.currentChanged.connect(onConnect);
  }

  return registry;

  /**
   * Callback invoked when the active widget changes.
   *
   * @private
   */
  function onConnect() {
    const widget = app.shell.currentWidget;
    if (!widget) {
      return;
    }
    const generator = registry.find(widget);
    if (!generator) {
      // If the previously used widget is still available, stick with it.
      // Otherwise, set the current ToC widget to null.
      if (toc.current && toc.current.widget.isDisposed) {
        toc.current = null;
      }
      return;
    }
    toc.current = { widget, generator };
  }
}

/**
 * Initialization data for the ToC extension.
 *
 * @private
 */
const extension: JupyterFrontEndPlugin<ITableOfContentsRegistry> = {
  id: `${PLUGIN_ID}:registry`,
  autoStart: true,
  description:
    'JupyterLab Table of Contents extension that includes code cells and markdown content (not only headings).',
  provides: ITableOfContentsRegistry,
  requires: [IDocumentManager, IRenderMimeRegistry, ITranslator],
  optional: [
    IEditorTracker,
    ILayoutRestorer,
    ILabShell,
    IMarkdownViewerTracker,
    INotebookTracker,
    ISettingRegistry
  ],
  activate: activateTOC
};

/**
 * Exports.
 */
export default extension;
