import * as path from 'path';
import { generateLinkReferences } from '../../src/janitor';
import { bootstrap } from '../../src/bootstrap';
import { createConfigFromFolders } from '../../src/config';
import { Services, Note } from '../../src';
import { FileDataStore } from '../../src/services/datastore';
import { Logger } from '../../src/utils/log';
import { URI } from '../../src/common/uri';
import { FoamWorkspace } from '../../src/model/workspace';
import { getBasename } from '../../src/utils';

Logger.setLevel('error');

describe('generateLinkReferences', () => {
  let _workspace: FoamWorkspace;
  const findBySlug = (slug: string): Note => {
    return _workspace.list().find(res => getBasename(res.uri) === slug) as Note;
  };

  beforeAll(async () => {
    const config = createConfigFromFolders([
      URI.file(path.join(__dirname, '..', '__scaffold__')),
    ]);
    const services: Services = {
      dataStore: new FileDataStore(config),
    };
    _workspace = await bootstrap(config, services).then(foam => foam.workspace);
  });

  it('initialised test graph correctly', () => {
    expect(_workspace.list().length).toEqual(6);
  });

  it('should add link references to a file that does not have them', () => {
    const note = findBySlug('index');
    const expected = {
      newText: textForNote(
        note,
        `
[//begin]: # "Autogenerated link references for markdown compatibility"
[first-document]: first-document "First Document"
[second-document]: second-document "Second Document"
[file-without-title]: file-without-title "file-without-title"
[//end]: # "Autogenerated link references"`
      ),
      range: {
        start: pointForNote(note, {
          line: 10,
          column: 1,
          offset: 140,
        }),
        end: pointForNote(note, {
          line: 10,
          column: 1,
          offset: 140,
        }),
      },
    };

    const actual = generateLinkReferences(note, _workspace, false);

    expect(actual!.range.start).toEqual(expected.range.start);
    expect(actual!.range.end).toEqual(expected.range.end);
    expect(actual!.newText).toEqual(expected.newText);
  });

  it('should remove link definitions from a file that has them, if no links are present', () => {
    const note = findBySlug('second-document');

    const expected = {
      newText: '',
      range: {
        start: pointForNote(note, {
          line: 7,
          column: 1,
          offset: 105,
        }),
        end: pointForNote(note, {
          line: 9,
          column: 43,
          offset: 269,
        }),
      },
    };

    const actual = generateLinkReferences(note, _workspace, false);

    expect(actual!.range.start).toEqual(expected.range.start);
    expect(actual!.range.end).toEqual(expected.range.end);
    expect(actual!.newText).toEqual(expected.newText);
  });

  it('should update link definitions if they are present but changed', () => {
    const note = findBySlug('first-document');

    const expected = {
      newText: textForNote(
        note,
        `[//begin]: # "Autogenerated link references for markdown compatibility"
[file-without-title]: file-without-title "file-without-title"
[//end]: # "Autogenerated link references"`
      ),
      range: {
        start: pointForNote(note, {
          line: 9,
          column: 1,
          offset: 145,
        }),
        end: pointForNote(note, {
          line: 11,
          column: 43,
          offset: 312,
        }),
      },
    };

    const actual = generateLinkReferences(note, _workspace, false);

    expect(actual!.range.start).toEqual(expected.range.start);
    expect(actual!.range.end).toEqual(expected.range.end);
    expect(actual!.newText).toEqual(expected.newText);
  });

  it('should not cause any changes if link reference definitions were up to date', () => {
    const note = findBySlug('third-document');

    const expected = null;

    const actual = generateLinkReferences(note, _workspace, false);

    expect(actual).toEqual(expected);
  });
});

/**
 * Will adjust a text line separator to match
 * what is used by the note
 * Necessary when running tests on windows
 *
 * @param note the note we are adjusting for
 * @param text starting text, using a \n line separator
 */
function textForNote(note: Note, text: string): string {
  return text.split('\n').join(note.source.eol);
}

/**
 * Will adjust a point to take into account the EOL length
 * of the note
 * Necessary when running tests on windows
 *
 * @param note the note we are adjusting for
 * @param pos starting position
 */
function pointForNote(
  note: Note,
  pos: { line: number; column: number; offset: number }
) {
  const rows = pos.line - 1;
  return {
    ...pos,
    offset: pos.offset - rows + rows * note.source.eol.length,
  };
}
