import AJS from 'aui';
import $ from 'jquery';
import nav from 'bitbucket/util/navbuilder';
import transformer from 'bitbucket/internal/model-transformer';
import pageState from 'bitbucket/internal/model/page-state';
import ajax from 'bitbucket/internal/util/ajax';
import events from 'bitbucket/internal/util/events';
import SubmitSpinner from 'bitbucket/internal/widget/submit-spinner';
import getActionUrl from './get-action-url';
import setDialogButtonsDisabled from './set-dialog-buttons-disabled';
import updatePullRequest from './update-pull-request';

const DEFAULT_MERGE_TIMEOUT_SEC = 5 * 60;
let mergeDialog;

function initMergeDialog(props){
    ajax.rest({
        url: nav.rest()
            .currentRepo()
            .pullRequestSettings()
            .build(),
        type: 'GET'
    }).done(PullRequestJSON => {
        const pullRequest = pageState.getPullRequest();
        const filterStrategies = PullRequestJSON.mergeConfig.strategies.filter(strategy => {
            // filter out disabled strategies
            return strategy.enabled;
        }).filter(strategy => {
            // remove the default strategy
            return strategy.id !== PullRequestJSON.mergeConfig.defaultStrategy.id;
        });

        filterStrategies.unshift(PullRequestJSON.mergeConfig.defaultStrategy); // add default to beginning

        const options = {
            dialog: {
                person: pageState.getCurrentUser() && pageState.getCurrentUser().toJSON(),
                pullRequest: pullRequest.toJSON(),
                mergeStrategies: filterStrategies,
                defaultStrategy: PullRequestJSON.mergeConfig.defaultStrategy
            },
            ajax: {
                statusCode: {
                    '400': (xhr, textStatus, errorThrown, response, dominantError) => {
                        const $mergeDialogContent = mergeDialog.$el.find('.aui-dialog2-content');

                        if (response.errors) {
                            $mergeDialogContent.children('.aui-message').remove();
                            $mergeDialogContent.prepend(bitbucket.internal.feature.pullRequest.merge.errors({'errors': response.errors}));
                        }

                        return false;
                    },
                    '401': (xhr, textStatus, errorThrown, errors, dominantError) => {
                        mergeDialog.hide();
                        return Object.assign({}, dominantError, {
                            title: AJS.I18n.getText('bitbucket.web.pullrequest.merge.error.401.title'),
                            message: AJS.I18n.getText('bitbucket.web.pullrequest.merge.error.401.message'),
                            fallbackUrl: false,
                            shouldReload: true
                        });
                    },
                    '409': (xhr, textStatus, errorThrown, response, dominantError) => {
                        const firstError = response.errors && response.errors.length && response.errors[0];
                        const $mergeDialogContent = mergeDialog.$el.find('.aui-dialog2-content');

                        // if the PR is out of date, remove any existing error messages and hide the dialog
                        // and return so that the fallback error handling can show a dialog to reload the page
                        if (firstError.exceptionName === 'com.atlassian.bitbucket.pull.PullRequestOutOfDateException') {
                            $mergeDialogContent.find('.aui-message').remove();
                            mergeDialog.hide();
                            return;
                        }

                        if (firstError && (firstError.isConflicted || firstError.conflicted || (firstError.vetoes && firstError.vetoes.length))) {
                            events.trigger('bitbucket.internal.pull-request.cant.merge', null, pullRequest, firstError.conflicted || firstError.isConflicted, firstError.vetoes);
                            $mergeDialogContent.children('.aui-message').remove();
                            $mergeDialogContent.prepend(bitbucket.internal.feature.pullRequest.merge.errors({'errors': response.errors}));
                            if (mergeDialog.$el.attr('aria-hidden') !== 'false') {
                                events.trigger('bitbucket.internal.pull-request.show.cant.merge.help');
                            }
                            return false;
                        }
                    },
                    '*': () => {
                        mergeDialog.hide();
                    }
                },
                timeout: (props.mergeTimeout || DEFAULT_MERGE_TIMEOUT_SEC) * 1000
            },
            callback: StashPullRequestJSON => {
                updatePullRequest(transformer.pullRequest(StashPullRequestJSON), true, props);
                mergeDialog.hide();
            }
        };
        mergeDialog = createMergeDialog(options);
    });
}

// Creates a AUI Dialog2 dialog, separate from the legacy actionDialog which uses ConfirmDialog (AUI Dialog 1)
function createMergeDialog(options) {
    const mergeDialog = AJS.dialog2(bitbucket.internal.feature.pullRequest.merge.dialog(options.dialog));
    // we manually add the dialog to the body so that it's on the DOM and available for the branch deletion plugin
    // to disable the checkbox
    $('body').append(mergeDialog.$el);

    let mergeXhr;
    let promiseDecorator;
    let mergeStrategy;

    $(document).on('click', '#merge-strategy-opts aui-item-link', e => {
        const $target = $(e.currentTarget);
        mergeStrategy = $target.data('strategy');
        mergeDialog.$el.find('#merge-strategy-button').text($target.find('.strategy-name').text());
        e.preventDefault();
    });

    mergeDialog.$el.find('.confirm-button').on('click', evt => {

        const spinner = new SubmitSpinner($('#merge-strategy-button') || evt.target, 'before');

        setDialogButtonsDisabled(mergeDialog, true);
        spinner.show();

        mergeXhr = ajax.rest(Object.assign({
            url: getActionUrl('merge'),
            type: 'POST',
            data: {
                message: mergeDialog.$el.find('#commit-message').val(),
                strategyId: mergeStrategy
            }
        }, options.ajax));

        let mergePromise = mergeXhr;

        mergeXhr.always(() => {
            mergeXhr = null; // null it out so that merge can't be cancelled below
            spinner.hide();
            setDialogButtonsDisabled(mergeDialog, false);
        });

        // HACK - we don't want to expose plugin points for the promise yet
        // we hard code a link to the branch deletion, if it's available
        if (!promiseDecorator) {
            try {
                // assigning require to a variable - otherwise Babel will move the import to
                // the top level require, removing the if !promiseDecorator condition. If the
                // branchDeletion module is not available, this merge-dialog module will fail
                var hideRequireFromBabel = require;
                promiseDecorator = hideRequireFromBabel('pullRequest/branchDeletion').getMergePromiseDecorator;
            } catch (err) {
                // ignore
            }
        }

        if (promiseDecorator) {
            mergePromise = promiseDecorator(mergePromise, () => mergeDialog.hide()) || mergePromise;
        }

        mergePromise.done(function (StashPullRequestJSON) {
            events.trigger('bitbucket.internal.feature.pullRequest.merged', null, {
                user: pageState.getCurrentUser().toJSON(),
                pullRequest: StashPullRequestJSON
            });
            options.callback(StashPullRequestJSON);
        });
    });

    mergeDialog.$el.find('.cancel-button').on('click', () => {
        if (mergeXhr) {
            mergeXhr.abort();
            mergeXhr = null;
        }

        collapseCommitMessage(mergeDialog);
        mergeDialog.hide();
    });

    mergeDialog.$el.find('.commit-message').on('focus', expandCommitMessage);

    mergeDialog.$el.find('.cancel-commit-message-link').click( e => {
        e.preventDefault();
        collapseCommitMessage(mergeDialog);
    });

    mergeDialog.on('hide', () => {
        collapseCommitMessage(mergeDialog);
    });

    mergeDialog.$el.on('click', '.view-merge-veto-details-button', () => {
        mergeDialog.hide();
        events.trigger('bitbucket.internal.pull-request.show.cant.merge.help');
    });

    return mergeDialog;
}

function onMergeClick() {
    mergeDialog.show();

    // Dialog2 automatically focuses the first focus-able element in the dialog, eg. the textarea, which
    // causes it to expand when the dialog is shown, so we re-add the .collapsed class to the form and
    // re-focus the Merge button instead.
    // Dialog2 docs mention a 'data-aui-focus-selector ' attribute to control what element receives focus
    // when the dialog is shown, but it appears to be ignored and overriden.
    // https://ecosystem.atlassian.net/browse/AUI-3299 to either re-implement it, or fix the docs
    collapseCommitMessage(mergeDialog);
    mergeDialog.$el.find('.confirm-button').focus();
}

function expandCommitMessage() {
    $(this).closest('.commit-message-form').removeClass('collapsed');
}

function collapseCommitMessage(mergedialog) {
    mergedialog.$el.find('.commit-message').val('');
    mergedialog.$el.find('.commit-message-form').addClass('collapsed');
}

export default {
    onMergeClick,
    initMergeDialog
}
