'use strict';
import * as path from 'path';
import { commands, QuickPickOptions, TextDocumentShowOptions, Uri, window } from 'vscode';
import {
    Commands,
    CopyMessageToClipboardCommandArgs,
    CopyShaToClipboardCommandArgs,
    DiffDirectoryCommandArgs,
    DiffWithPreviousCommandArgs,
    ShowQuickCommitDetailsCommandArgs,
    StashApplyCommandArgs,
    StashDeleteCommandArgs
} from '../commands';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import {
    GitFile,
    GitFileStatus,
    GitLog,
    GitLogCommit,
    GitStashCommit,
    GitUri,
    RemoteResource
} from '../git/gitService';
import { KeyCommand, KeyNoopCommand, Keys } from '../keyboard';
import { Arrays, Iterables, Strings } from '../system';
import {
    CommandQuickPickItem,
    getQuickPickIgnoreFocusOut,
    KeyCommandQuickPickItem,
    OpenFileCommandQuickPickItem,
    OpenFilesCommandQuickPickItem,
    QuickPickItem,
    ShowCommitInResultsQuickPickItem
} from './commonQuickPicks';
import { OpenRemotesCommandQuickPickItem } from './remotesQuickPick';

export class CommitWithFileStatusQuickPickItem extends OpenFileCommandQuickPickItem {
    readonly status: GitFileStatus;

    readonly commit: GitLogCommit;

    constructor(commit: GitLogCommit, file: GitFile) {
        const octicon = GitFile.getStatusOcticon(file.status);
        const description = GitFile.getFormattedDirectory(file, true);

        super(GitUri.toRevisionUri(commit.sha, file, commit.repoPath), {
            label: `${Strings.pad(octicon, 4, 2)} ${path.basename(file.fileName)}`,
            description: description
        });

        this.commit = commit.toFileCommit(file);
        this.status = file.status;
    }

    get sha(): string {
        return this.commit.sha;
    }

    onDidPressKey(key: Keys): Promise<{} | undefined> {
        if (this.commit.previousSha === undefined) return super.onDidPressKey(key);

        return commands.executeCommand(Commands.DiffWithPrevious, this.commit.toGitUri(), {
            commit: this.commit,
            showOptions: {
                preserveFocus: true,
                preview: false
            } as TextDocumentShowOptions
        } as DiffWithPreviousCommandArgs) as Promise<{} | undefined>;
    }
}

export class OpenCommitFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem {
    constructor(commit: GitLogCommit, versioned: boolean = false, item?: QuickPickItem) {
        const repoPath = commit.repoPath;
        const uris = Arrays.filterMap(commit.files, f => GitUri.fromFile(f, repoPath));

        super(
            uris,
            item || {
                label: `$(file-symlink-file) Open Files`,
                description: ''
                // detail: `Opens all of the changed file in the working tree`
            }
        );
    }
}

export class OpenCommitFileRevisionsCommandQuickPickItem extends OpenFilesCommandQuickPickItem {
    constructor(commit: GitLogCommit, item?: QuickPickItem) {
        const uris = Arrays.filterMap(commit.files, f =>
            GitUri.toRevisionUri(f.status === 'D' ? commit.previousFileSha : commit.sha, f, commit.repoPath)
        );

        super(
            uris,
            item || {
                label: `$(file-symlink-file) Open Revisions`,
                description: `${Strings.pad(GlyphChars.Dash, 2, 3)} in ${GlyphChars.Space}$(git-commit) ${
                    commit.shortSha
                }`
                // detail: `Opens all of the changed files in $(git-commit) ${commit.shortSha}`
            }
        );
    }
}

export class CommitQuickPick {
    static async show(
        commit: GitLogCommit,
        uri: Uri,
        goBackCommand?: CommandQuickPickItem,
        currentCommand?: CommandQuickPickItem,
        repoLog?: GitLog
    ): Promise<CommitWithFileStatusQuickPickItem | CommandQuickPickItem | undefined> {
        await commit.resolvePreviousFileSha();

        const items: (CommitWithFileStatusQuickPickItem | CommandQuickPickItem)[] = commit.files.map(
            fs => new CommitWithFileStatusQuickPickItem(commit, fs)
        );

        const stash = commit.isStash;

        let index = 0;

        if (stash) {
            items.splice(
                index++,
                0,
                new CommandQuickPickItem(
                    {
                        label: `$(git-pull-request) Apply Stashed Changes`,
                        description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.getShortMessage()}`
                    },
                    Commands.StashApply,
                    [
                        {
                            confirm: true,
                            deleteAfter: false,
                            stashItem: commit as GitStashCommit,
                            goBackCommand: currentCommand
                        } as StashApplyCommandArgs
                    ]
                )
            );

            items.splice(
                index++,
                0,
                new CommandQuickPickItem(
                    {
                        label: `$(x) Delete Stashed Changes`,
                        description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.getShortMessage()}`
                    },
                    Commands.StashDelete,
                    [
                        {
                            confirm: true,
                            stashItem: commit as GitStashCommit,
                            goBackCommand: currentCommand
                        } as StashDeleteCommandArgs
                    ]
                )
            );

            items.splice(index++, 0, new ShowCommitInResultsQuickPickItem(commit));
        }
        else {
            items.splice(index++, 0, new ShowCommitInResultsQuickPickItem(commit));

            const remotes = await Container.git.getRemotes(commit.repoPath);
            if (remotes.length) {
                items.splice(
                    index++,
                    0,
                    new OpenRemotesCommandQuickPickItem(
                        remotes,
                        {
                            type: 'commit',
                            sha: commit.sha
                        } as RemoteResource,
                        currentCommand
                    )
                );
            }
        }

        items.splice(index++, 0, new OpenCommitFilesCommandQuickPickItem(commit));
        items.splice(index++, 0, new OpenCommitFileRevisionsCommandQuickPickItem(commit));

        items.splice(
            index++,
            0,
            new CommandQuickPickItem(
                {
                    label: `$(git-compare) Open Directory Compare with Previous Revision`,
                    description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.previousFileShortSha} ${
                        GlyphChars.Space
                    } $(git-compare) ${GlyphChars.Space} $(git-commit) ${commit.shortSha}`
                },
                Commands.DiffDirectory,
                [
                    commit.uri,
                    {
                        ref1: commit.previousFileSha,
                        ref2: commit.sha
                    } as DiffDirectoryCommandArgs
                ]
            )
        );

        items.splice(
            index++,
            0,
            new CommandQuickPickItem(
                {
                    label: `$(git-compare) Open Directory Compare with Working Tree`,
                    description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.shortSha} ${
                        GlyphChars.Space
                    } $(git-compare) ${GlyphChars.Space} $(file-directory) Working Tree`
                },
                Commands.DiffDirectory,
                [
                    uri,
                    {
                        ref1: commit.sha
                    } as DiffDirectoryCommandArgs
                ]
            )
        );

        if (!stash) {
            items.splice(
                index++,
                0,
                new CommandQuickPickItem(
                    {
                        label: `$(clippy) Copy Commit ID to Clipboard`,
                        description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.shortSha}`
                    },
                    Commands.CopyShaToClipboard,
                    [
                        uri,
                        {
                            sha: commit.sha
                        } as CopyShaToClipboardCommandArgs
                    ]
                )
            );
        }

        items.splice(
            index++,
            0,
            new CommandQuickPickItem(
                {
                    label: `$(clippy) Copy Commit Message to Clipboard`,
                    description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.getShortMessage()}`
                },
                Commands.CopyMessageToClipboard,
                [
                    uri,
                    {
                        message: commit.message,
                        sha: commit.sha
                    } as CopyMessageToClipboardCommandArgs
                ]
            )
        );

        items.splice(
            index++,
            0,
            new CommandQuickPickItem(
                {
                    label: `Changed Files`,
                    description: commit.getFormattedDiffStatus()
                },
                Commands.ShowQuickCommitDetails,
                [
                    uri,
                    {
                        commit,
                        repoLog,
                        sha: commit.sha,
                        goBackCommand
                    } as ShowQuickCommitDetailsCommandArgs
                ]
            )
        );

        if (goBackCommand) {
            items.splice(0, 0, goBackCommand);
        }

        let previousCommand: KeyCommand | (() => Promise<KeyCommand>) | undefined = undefined;
        let nextCommand: KeyCommand | (() => Promise<KeyCommand>) | undefined = undefined;
        if (!stash) {
            // If we have the full history, we are good
            if (repoLog !== undefined && !repoLog.truncated && repoLog.sha === undefined) {
                previousCommand =
                    commit.previousSha === undefined
                        ? undefined
                        : new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [
                              commit.previousUri,
                              {
                                  repoLog,
                                  sha: commit.previousSha,
                                  goBackCommand
                              } as ShowQuickCommitDetailsCommandArgs
                          ]);

                nextCommand =
                    commit.nextSha === undefined
                        ? undefined
                        : new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [
                              commit.nextUri,
                              {
                                  repoLog,
                                  sha: commit.nextSha,
                                  goBackCommand
                              } as ShowQuickCommitDetailsCommandArgs
                          ]);
            }
            else {
                previousCommand = async () => {
                    let log = repoLog;
                    let c = log && log.commits.get(commit.sha);

                    // If we can't find the commit or the previous commit isn't available (since it isn't trustworthy)
                    if (c === undefined || c.previousSha === undefined) {
                        log = await Container.git.getLog(commit.repoPath, {
                            maxCount: Container.config.advanced.maxListItems,
                            ref: commit.sha
                        });
                        c = log && log.commits.get(commit.sha);

                        if (c) {
                            // Copy over next info, since it is trustworthy at this point
                            c.nextSha = commit.nextSha;
                        }
                    }

                    if (c === undefined || c.previousSha === undefined) return KeyNoopCommand;

                    return new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [
                        c.previousUri,
                        {
                            repoLog: log,
                            sha: c.previousSha,
                            goBackCommand
                        } as ShowQuickCommitDetailsCommandArgs
                    ]);
                };

                nextCommand = async () => {
                    let log = repoLog;
                    let c = log && log.commits.get(commit.sha);

                    // If we can't find the commit or the next commit isn't available (since it isn't trustworthy)
                    if (c === undefined || c.nextSha === undefined) {
                        log = undefined;
                        c = undefined;

                        // Try to find the next commit
                        const nextLog = await Container.git.getLog(commit.repoPath, {
                            maxCount: 1,
                            reverse: true,
                            ref: commit.sha
                        });
                        const next = nextLog && Iterables.first(nextLog.commits.values());
                        if (next !== undefined && next.sha !== commit.sha) {
                            c = commit;
                            c.nextSha = next.sha;
                        }
                    }

                    if (c === undefined || c.nextSha === undefined) return KeyNoopCommand;

                    return new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [
                        c.nextUri,
                        {
                            repoLog: log,
                            sha: c.nextSha,
                            goBackCommand
                        } as ShowQuickCommitDetailsCommandArgs
                    ]);
                };
            }
        }

        const scope = await Container.keyboard.beginScope({
            left: goBackCommand,
            ',': previousCommand,
            '.': nextCommand
        });

        const pick = await window.showQuickPick(items, {
            matchOnDescription: true,
            matchOnDetail: true,
            placeHolder: `${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${
                commit.author ? `${commit.author}, ` : ''
            }${commit.formattedDate} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getShortMessage()}`,
            ignoreFocusOut: getQuickPickIgnoreFocusOut(),
            onDidSelectItem: (item: QuickPickItem) => {
                void scope.setKeyCommand('right', item);
                if (typeof item.onDidSelect === 'function') {
                    item.onDidSelect();
                }
            }
        } as QuickPickOptions);

        await scope.dispose();

        return pick;
    }
}
