import * as assert from 'assert';
import * as vscode from 'vscode';

import SuggestedFix from '../../../utils/diagnostics/SuggestedFix';
import SuggestedFixCollection from '../../../utils/diagnostics/SuggestedFixCollection';

const uri1 = vscode.Uri.file('/file/1');
const uri2 = vscode.Uri.file('/file/2');

const mockDocument1 = ({
    uri: uri1
} as unknown) as vscode.TextDocument;

const mockDocument2 = ({
    uri: uri2
} as unknown) as vscode.TextDocument;

const range1 = new vscode.Range(
    new vscode.Position(1, 2),
    new vscode.Position(3, 4)
);
const range2 = new vscode.Range(
    new vscode.Position(5, 6),
    new vscode.Position(7, 8)
);

const diagnostic1 = new vscode.Diagnostic(range1, 'First diagnostic');
const diagnostic2 = new vscode.Diagnostic(range2, 'Second diagnostic');

// This is a mutable object so return a fresh instance every time
function suggestion1(): SuggestedFix {
    return new SuggestedFix(
        'Replace me!',
        new vscode.Location(uri1, range1),
        'With this!'
    );
}

describe('SuggestedFixCollection', () => {
    it('should add a suggestion then return it as a code action', () => {
        const suggestedFixes = new SuggestedFixCollection();
        suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);

        // Specify the document and range that exactly matches
        const codeActions = suggestedFixes.provideCodeActions(
            mockDocument1,
            range1
        );

        assert.strictEqual(codeActions.length, 1);
        const [codeAction] = codeActions;
        assert.strictEqual(codeAction.title, suggestion1().title);

        const { diagnostics } = codeAction;
        if (!diagnostics) {
            return assert.fail('Diagnostics unexpectedly missing');
        }

        assert.strictEqual(diagnostics.length, 1);
        assert.strictEqual(diagnostics[0], diagnostic1);
    });

    it('should not return code actions for different ranges', () => {
        const suggestedFixes = new SuggestedFixCollection();
        suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);

        const codeActions = suggestedFixes.provideCodeActions(
            mockDocument1,
            range2
        );

        assert(!codeActions || codeActions.length === 0);
    });

    it('should not return code actions for different documents', () => {
        const suggestedFixes = new SuggestedFixCollection();
        suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);

        const codeActions = suggestedFixes.provideCodeActions(
            mockDocument2,
            range1
        );

        assert(!codeActions || codeActions.length === 0);
    });

    it('should not return code actions that have been cleared', () => {
        const suggestedFixes = new SuggestedFixCollection();
        suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
        suggestedFixes.clear();

        const codeActions = suggestedFixes.provideCodeActions(
            mockDocument1,
            range1
        );

        assert(!codeActions || codeActions.length === 0);
    });

    it('should merge identical suggestions together', () => {
        const suggestedFixes = new SuggestedFixCollection();

        // Add the same suggestion for two diagnostics
        suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
        suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic2);

        const codeActions = suggestedFixes.provideCodeActions(
            mockDocument1,
            range1
        );

        assert.strictEqual(codeActions.length, 1);
        const [codeAction] = codeActions;
        const { diagnostics } = codeAction;

        if (!diagnostics) {
            return assert.fail('Diagnostics unexpectedly missing');
        }

        // We should be associated with both diagnostics
        assert.strictEqual(diagnostics.length, 2);
        assert.strictEqual(diagnostics[0], diagnostic1);
        assert.strictEqual(diagnostics[1], diagnostic2);
    });
});