import { languages } from 'monaco-editor';
import { setDiagnosticsOptions as yamlDiagnosticsOptions } from 'monaco-yaml';
import {
  isTextFile,
  registerLanguages,
  registerSchema,
  trimPathComponents,
  trimTrailingWhitespace,
  getPathParents,
  getPathParent,
  readFileAsDataURL,
  addNumericSuffix,
} from '~/ide/utils';

describe('WebIDE utils', () => {
  describe('isTextFile', () => {
    it.each`
      mimeType        | name        | type        | result
      ${'image/png'}  | ${'my.png'} | ${'binary'} | ${false}
      ${'IMAGE/PNG'}  | ${'my.png'} | ${'binary'} | ${false}
      ${'text/plain'} | ${'my.txt'} | ${'text'}   | ${true}
      ${'TEXT/PLAIN'} | ${'my.txt'} | ${'text'}   | ${true}
    `('returns $result for known $type types', ({ mimeType, name, result }) => {
      expect(isTextFile({ content: 'file content', mimeType, name })).toBe(result);
    });

    it.each`
      content                                   | mimeType                      | name
      ${'{"éêė":"value"}'}                      | ${'application/json'}         | ${'my.json'}
      ${'{"éêė":"value"}'}                      | ${'application/json'}         | ${'.tsconfig'}
      ${'SELECT "éêė" from tablename'}          | ${'application/sql'}          | ${'my.sql'}
      ${'{"éêė":"value"}'}                      | ${'application/json'}         | ${'MY.JSON'}
      ${'SELECT "éêė" from tablename'}          | ${'application/sql'}          | ${'MY.SQL'}
      ${'var code = "something"'}               | ${'application/javascript'}   | ${'Gruntfile'}
      ${'MAINTAINER Александр "a21283@me.com"'} | ${'application/octet-stream'} | ${'dockerfile'}
    `(
      'returns true for file extensions that Monaco supports syntax highlighting for',
      ({ content, mimeType, name }) => {
        expect(isTextFile({ content, mimeType, name })).toBe(true);
      },
    );

    it('returns false if filename is same as the expected extension', () => {
      expect(
        isTextFile({
          name: 'sql',
          content: 'SELECT "éêė" from tablename',
          mimeType: 'application/sql',
        }),
      ).toBe(false);
    });

    it('returns true for ASCII only content for unknown types', () => {
      expect(
        isTextFile({
          name: 'hello.mytype',
          content: 'plain text',
          mimeType: 'application/x-new-type',
        }),
      ).toBe(true);
    });

    it('returns false for non-ASCII content for unknown types', () => {
      expect(
        isTextFile({
          name: 'my.random',
          content: '{"éêė":"value"}',
          mimeType: 'application/octet-stream',
        }),
      ).toBe(false);
    });

    it.each`
      name            | result
      ${'myfile.txt'} | ${true}
      ${'Dockerfile'} | ${true}
      ${'img.png'}    | ${false}
      ${'abc.js'}     | ${true}
      ${'abc.random'} | ${false}
      ${'image.jpeg'} | ${false}
    `('returns $result for $filename when no content or mimeType is passed', ({ name, result }) => {
      expect(isTextFile({ name })).toBe(result);
    });

    it('returns true if content is empty string but false if content is not passed', () => {
      expect(isTextFile({ name: 'abc.dat' })).toBe(false);
      expect(isTextFile({ name: 'abc.dat', content: '' })).toBe(true);
      expect(isTextFile({ name: 'abc.dat', content: '  ' })).toBe(true);
    });

    it('returns true if there is a `binary` property already set on the file object', () => {
      expect(isTextFile({ name: 'abc.txt', content: '' })).toBe(true);
      expect(isTextFile({ name: 'abc.txt', content: '', binary: true })).toBe(false);

      expect(isTextFile({ name: 'abc.tex', content: 'éêė' })).toBe(false);
      expect(isTextFile({ name: 'abc.tex', content: 'éêė', binary: false })).toBe(true);
    });
  });

  describe('trimPathComponents', () => {
    it.each`
      input                           | output
      ${'example path '}              | ${'example path'}
      ${'p/somefile '}                | ${'p/somefile'}
      ${'p /somefile '}               | ${'p/somefile'}
      ${'p/ somefile '}               | ${'p/somefile'}
      ${' p/somefile '}               | ${'p/somefile'}
      ${'p/somefile  .md'}            | ${'p/somefile  .md'}
      ${'path / to / some/file.doc '} | ${'path/to/some/file.doc'}
    `('trims all path components in path: "$input"', ({ input, output }) => {
      expect(trimPathComponents(input)).toEqual(output);
    });
  });

  describe('registerLanguages', () => {
    let langs;

    beforeEach(() => {
      langs = [
        {
          id: 'html',
          extensions: ['.html'],
          conf: { comments: { blockComment: ['<!--', '-->'] } },
          language: { tokenizer: {} },
        },
        {
          id: 'css',
          extensions: ['.css'],
          conf: { comments: { blockComment: ['/*', '*/'] } },
          language: { tokenizer: {} },
        },
        {
          id: 'js',
          extensions: ['.js'],
          conf: { comments: { blockComment: ['/*', '*/'] } },
          language: { tokenizer: {} },
        },
      ];

      jest.spyOn(languages, 'register').mockImplementation(() => {});
      jest.spyOn(languages, 'setMonarchTokensProvider').mockImplementation(() => {});
      jest.spyOn(languages, 'setLanguageConfiguration').mockImplementation(() => {});
    });

    it('registers all the passed languages with Monaco', () => {
      registerLanguages(...langs);

      expect(languages.register.mock.calls).toEqual([
        [
          {
            conf: { comments: { blockComment: ['/*', '*/'] } },
            extensions: ['.css'],
            id: 'css',
            language: { tokenizer: {} },
          },
        ],
        [
          {
            conf: { comments: { blockComment: ['/*', '*/'] } },
            extensions: ['.js'],
            id: 'js',
            language: { tokenizer: {} },
          },
        ],
        [
          {
            conf: { comments: { blockComment: ['<!--', '-->'] } },
            extensions: ['.html'],
            id: 'html',
            language: { tokenizer: {} },
          },
        ],
      ]);

      expect(languages.setMonarchTokensProvider.mock.calls).toEqual([
        ['css', { tokenizer: {} }],
        ['js', { tokenizer: {} }],
        ['html', { tokenizer: {} }],
      ]);

      expect(languages.setLanguageConfiguration.mock.calls).toEqual([
        ['css', { comments: { blockComment: ['/*', '*/'] } }],
        ['js', { comments: { blockComment: ['/*', '*/'] } }],
        ['html', { comments: { blockComment: ['<!--', '-->'] } }],
      ]);
    });
  });

  describe('registerSchema', () => {
    let schema;

    beforeEach(() => {
      schema = {
        uri: 'http://myserver/foo-schema.json',
        fileMatch: ['*'],
        schema: {
          id: 'http://myserver/foo-schema.json',
          type: 'object',
          properties: {
            p1: { enum: ['v1', 'v2'] },
            p2: { $ref: 'http://myserver/bar-schema.json' },
          },
        },
      };

      jest.spyOn(languages.json.jsonDefaults, 'setDiagnosticsOptions');
    });

    it('registers the given schemas with monaco for both json and yaml languages', () => {
      registerSchema(schema);

      expect(languages.json.jsonDefaults.setDiagnosticsOptions).toHaveBeenCalledWith(
        expect.objectContaining({ schemas: [schema] }),
      );
      expect(yamlDiagnosticsOptions).toHaveBeenCalledWith(
        expect.objectContaining({ schemas: [schema] }),
      );
    });
  });

  describe('trimTrailingWhitespace', () => {
    it.each`
      input                                                            | output
      ${'text     \n   more text   \n'}                                | ${'text\n   more text\n'}
      ${'text     \n   more text   \n\n   \n'}                         | ${'text\n   more text\n\n\n'}
      ${'text  \t\t   \n   more text   \n\t\ttext\n   \n\t\t'}         | ${'text\n   more text\n\t\ttext\n\n'}
      ${'text     \r\n   more text   \r\n'}                            | ${'text\r\n   more text\r\n'}
      ${'text     \r\n   more text   \r\n\r\n   \r\n'}                 | ${'text\r\n   more text\r\n\r\n\r\n'}
      ${'text  \t\t   \r\n   more text   \r\n\t\ttext\r\n   \r\n\t\t'} | ${'text\r\n   more text\r\n\t\ttext\r\n\r\n'}
    `("trims trailing whitespace in each line of file's contents: $input", ({ input, output }) => {
      expect(trimTrailingWhitespace(input)).toBe(output);
    });
  });

  describe('getPathParents', () => {
    it.each`
      path                                  | parents
      ${'foo/bar/baz/index.md'}             | ${['foo/bar/baz', 'foo/bar', 'foo']}
      ${'foo/bar/baz'}                      | ${['foo/bar', 'foo']}
      ${'index.md'}                         | ${[]}
      ${'path with/spaces to/something.md'} | ${['path with/spaces to', 'path with']}
    `('gets all parent directory names for path: $path', ({ path, parents }) => {
      expect(getPathParents(path)).toEqual(parents);
    });

    it.each`
      path                      | depth | parents
      ${'foo/bar/baz/index.md'} | ${0}  | ${[]}
      ${'foo/bar/baz/index.md'} | ${1}  | ${['foo/bar/baz']}
      ${'foo/bar/baz/index.md'} | ${2}  | ${['foo/bar/baz', 'foo/bar']}
      ${'foo/bar/baz/index.md'} | ${3}  | ${['foo/bar/baz', 'foo/bar', 'foo']}
      ${'foo/bar/baz/index.md'} | ${4}  | ${['foo/bar/baz', 'foo/bar', 'foo']}
    `('gets only the immediate $depth parents if when depth=$depth', ({ path, depth, parents }) => {
      expect(getPathParents(path, depth)).toEqual(parents);
    });
  });

  describe('getPathParent', () => {
    it.each`
      path                                  | parents
      ${'foo/bar/baz/index.md'}             | ${'foo/bar/baz'}
      ${'foo/bar/baz'}                      | ${'foo/bar'}
      ${'index.md'}                         | ${undefined}
      ${'path with/spaces to/something.md'} | ${'path with/spaces to'}
    `('gets the immediate parent for path: $path', ({ path, parents }) => {
      expect(getPathParent(path)).toEqual(parents);
    });
  });

  describe('readFileAsDataURL', () => {
    it('reads a file and returns its output as a data url', () => {
      const file = new File(['foo'], 'foo.png', { type: 'image/png' });

      return readFileAsDataURL(file).then((contents) => {
        expect(contents).toBe('data:image/png;base64,Zm9v');
      });
    });
  });

  /*
   *  hello-2425 -> hello-2425
   *  hello.md -> hello-1.md
   *  hello_2.md -> hello_3.md
   *  hello_ -> hello_1
   *  main-patch-22432 -> main-patch-22433
   *  patch_332 -> patch_333
   */

  describe('addNumericSuffix', () => {
    it.each`
      input                 | output
      ${'hello'}            | ${'hello-1'}
      ${'hello2'}           | ${'hello-3'}
      ${'hello.md'}         | ${'hello-1.md'}
      ${'hello_2.md'}       | ${'hello_3.md'}
      ${'hello_'}           | ${'hello_1'}
      ${'main-patch-22432'} | ${'main-patch-22433'}
      ${'patch_332'}        | ${'patch_333'}
    `('adds a numeric suffix to a given filename/branch name: $input', ({ input, output }) => {
      expect(addNumericSuffix(input)).toBe(output);
    });

    it.each`
      input                 | output
      ${'hello'}            | ${'hello-39135'}
      ${'hello2'}           | ${'hello-39135'}
      ${'hello.md'}         | ${'hello-39135.md'}
      ${'hello_2.md'}       | ${'hello_39135.md'}
      ${'hello_'}           | ${'hello_39135'}
      ${'main-patch-22432'} | ${'main-patch-39135'}
      ${'patch_332'}        | ${'patch_39135'}
    `('adds a random suffix if randomize=true is passed for name: $input', ({ input, output }) => {
      jest.spyOn(Math, 'random').mockReturnValue(0.391352525);

      expect(addNumericSuffix(input, true)).toBe(output);
    });
  });
});