// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { Message, MessageLoop } from '@lumino/messaging';

import { Widget } from '@lumino/widgets';

import { simulate } from 'simulate-event';

import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';

import { CodeMirrorEditor } from '@jupyterlab/codemirror';

import { framePromise } from '@jupyterlab/testutils';

class LogEditor extends CodeMirrorEditor {
  methods: string[] = [];
  events: string[] = [];

  handleEvent(event: Event): void {
    super.handleEvent(event);
    this.events.push(event.type);
  }

  refresh(): void {
    super.refresh();
    this.methods.push('refresh');
  }

  setSize(dims: CodeEditor.IDimension | null): void {
    super.setSize(dims);
    this.methods.push('setSize');
  }
}

class LogWidget extends CodeEditorWrapper {
  methods: string[] = [];

  protected onActivateRequest(msg: Message): void {
    super.onActivateRequest(msg);
    this.methods.push('onActivateRequest');
  }

  protected onAfterAttach(msg: Message): void {
    super.onAfterAttach(msg);
    this.methods.push('onAfterAttach');
  }

  protected onBeforeDetach(msg: Message): void {
    super.onBeforeDetach(msg);
    this.methods.push('onBeforeDetach');
  }

  protected onAfterShow(msg: Message): void {
    super.onAfterShow(msg);
    this.methods.push('onAfterShow');
  }

  protected onResize(msg: Widget.ResizeMessage): void {
    super.onResize(msg);
    this.methods.push('onResize');
  }
}

describe('CodeEditorWrapper', () => {
  let widget: LogWidget;
  const editorFactory = (options: CodeEditor.IOptions) => {
    options.uuid = 'foo';
    return new LogEditor(options);
  };

  beforeEach(() => {
    const model = new CodeEditor.Model();
    widget = new LogWidget({ factory: editorFactory, model });
  });

  afterEach(() => {
    widget.dispose();
  });

  describe('#constructor()', () => {
    it('should be a CodeEditorWrapper', () => {
      expect(widget).toBeInstanceOf(CodeEditorWrapper);
    });

    it('should add a focus listener', () => {
      widget.node.tabIndex = -1;
      simulate(widget.node, 'focus');
      const editor = widget.editor as LogEditor;
      expect(editor.events).toEqual(expect.arrayContaining(['focus']));
    });
  });

  describe('#editor', () => {
    it('should be a a code editor', () => {
      expect(widget.editor.getOption('lineNumbers')).toBe(false);
    });
  });

  describe('#dispose()', () => {
    it('should dispose of the resources used by the widget', () => {
      expect(widget.isDisposed).toBe(false);
      widget.dispose();
      expect(widget.isDisposed).toBe(true);
      widget.dispose();
      expect(widget.isDisposed).toBe(true);
    });

    it('should remove the focus listener', () => {
      const editor = widget.editor as LogEditor;
      expect(editor.isDisposed).toBe(false);
      widget.dispose();
      expect(editor.isDisposed).toBe(true);

      widget.node.tabIndex = -1;
      simulate(widget.node, 'focus');
      expect(editor.events).toEqual(expect.not.arrayContaining(['focus']));
    });
  });

  describe('#handleEvent()', () => {
    describe('focus', () => {
      it('should be a no-op if the editor was not resized', () => {
        Widget.attach(widget, document.body);
        const editor = widget.editor as LogEditor;
        editor.methods = [];
        simulate(editor.editor.getInputField(), 'focus');
        expect(editor.methods).toEqual([]);
      });

      it('should refresh if editor was resized', () => {
        Widget.attach(widget, document.body);
        const editor = widget.editor as LogEditor;
        MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
        editor.methods = [];
        simulate(editor.editor.getInputField(), 'focus');
        expect(editor.methods).toEqual(['refresh']);
      });
    });
  });

  describe('#onActivateRequest()', () => {
    it('should focus the editor', () => {
      Widget.attach(widget, document.body);
      MessageLoop.sendMessage(widget, Widget.Msg.ActivateRequest);
      expect(widget.methods).toEqual(
        expect.arrayContaining(['onActivateRequest'])
      );
      expect(widget.editor.hasFocus()).toBe(true);
    });
  });

  describe('#onAfterAttach()', () => {
    it('should refresh the editor', async () => {
      Widget.attach(widget, document.body);
      const editor = widget.editor as LogEditor;
      await framePromise();
      expect(editor.methods).toEqual(expect.arrayContaining(['refresh']));
    });
  });

  describe('#onAfterShow()', () => {
    it('should refresh the editor', async () => {
      widget.hide();
      Widget.attach(widget, document.body);
      const editor = widget.editor as LogEditor;
      expect(editor.methods).toEqual(expect.not.arrayContaining(['refresh']));
      widget.show();
      expect(widget.methods).toEqual(expect.arrayContaining(['onAfterShow']));
      await framePromise();
      expect(editor.methods).toEqual(expect.arrayContaining(['refresh']));
    });
  });

  describe('#onResize()', () => {
    it('should set the size of the editor', () => {
      const msg = new Widget.ResizeMessage(10, 10);
      const editor = widget.editor as LogEditor;
      MessageLoop.sendMessage(widget, msg);
      expect(editor.methods).toEqual(expect.arrayContaining(['setSize']));
    });

    it('should refresh the editor', () => {
      const editor = widget.editor as LogEditor;
      Widget.attach(widget, document.body);
      editor.focus();
      editor.methods = [];
      MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
      expect(editor.methods).toEqual(expect.arrayContaining(['refresh']));
    });

    it('should defer the refresh until focused', () => {
      const editor = widget.editor as LogEditor;
      Widget.attach(widget, document.body);
      editor.methods = [];
      MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
      expect(editor.methods).toEqual([]);
      simulate(editor.editor.getInputField(), 'focus');
      expect(editor.methods).toEqual(['refresh']);
    });
  });
});
