import Component from '@ember/component';
import {computed} from '@ember/object';
import {isBlank} from '@ember/utils';
import {reads} from '@ember/object/computed';
import {task, timeout} from 'ember-concurrency';

/**
 * Task Button works exactly like Spin button, but with one major difference:
 *
 * Instead of passing a "submitting" parameter (which is bound to the parent object),
 * you pass an ember-concurrency task. All of the "submitting" behavior is handled automatically.
 *
 * As another bonus, there's no need to handle canceling the promises when something
 * like a controller changes. Because the only task running is handled through this
 * component, all running promises will automatically be cancelled when this
 * component is removed from the DOM
 */
const GhTaskButton = Component.extend({
    tagName: 'button',
    classNameBindings: [
        'isRunning:appear-disabled',
        'isIdleClass',
        'isRunningClass',
        'isSuccessClass',
        'isFailureClass'
    ],
    attributeBindings: ['disabled', 'form', 'type', 'tabindex'],

    task: null,
    taskArgs: undefined,
    disabled: false,
    defaultClick: false,
    buttonText: 'Save',
    idleClass: '',
    runningClass: '',
    showSuccess: true, // set to false if you want the spinner to show until a transition occurs
    successText: 'Saved',
    successClass: 'gh-btn-green',
    failureText: 'Retry',
    failureClass: 'gh-btn-red',

    // Allowed actions
    action: () => {},

    runningText: reads('buttonText'),

    // hasRun is needed so that a newly rendered button does not show the last
    // state of the associated task
    hasRun: computed('task.performCount', function () {
        return this.get('task.performCount') > this._initialPerformCount;
    }),

    isIdleClass: computed('isIdle', function () {
        return this.isIdle ? this.idleClass : '';
    }),

    isRunning: computed('task.last.isRunning', 'hasRun', 'showSuccess', function () {
        let isRunning = this.get('task.last.isRunning');

        if (this.hasRun && this.get('task.last.value') && !this.showSuccess) {
            isRunning = true;
        }

        return isRunning;
    }),

    isRunningClass: computed('isRunning', function () {
        return this.isRunning ? (this.runningClass || this.idleClass) : '';
    }),

    isSuccess: computed('hasRun', 'isRunning', 'task.last.value', function () {
        if (!this.hasRun || this.isRunning || !this.showSuccess) {
            return false;
        }

        let value = this.get('task.last.value');
        return !isBlank(value) && value !== false;
    }),

    isSuccessClass: computed('isSuccess', function () {
        return this.isSuccess ? this.successClass : '';
    }),

    isFailure: computed('hasRun', 'isRunning', 'isSuccess', 'task.last.error', function () {
        if (!this.hasRun || this.isRunning || this.isSuccess) {
            return false;
        }

        return this.get('task.last.error') !== undefined;
    }),

    isFailureClass: computed('isFailure', function () {
        return this.isFailure ? this.failureClass : '';
    }),

    isIdle: computed('isRunning', 'isSuccess', 'isFailure', function () {
        return !this.isRunning && !this.isSuccess && !this.isFailure;
    }),

    init() {
        this._super(...arguments);
        this._initialPerformCount = this.get('task.performCount');
    },

    click() {
        // let the default click bubble if defaultClick===true - useful when
        // you want to handle a form submit action rather than triggering a
        // task directly
        if (this.defaultClick) {
            if (!this.isRunning) {
                this._restartAnimation.perform();
            }
            return;
        }

        // do nothing if disabled externally
        if (this.disabled) {
            return false;
        }

        let task = this.task;
        let taskName = this.get('task.name');
        let lastTaskName = this.get('task.last.task.name');

        // task-buttons are never disabled whilst running so that clicks when a
        // taskGroup is running don't get dropped BUT that means we need to check
        // here to avoid spamming actions from multiple clicks
        if (this.isRunning && taskName === lastTaskName) {
            return;
        }

        this.action();
        task.perform(this.taskArgs);

        this._restartAnimation.perform();

        // prevent the click from bubbling and triggering form actions
        return false;
    },

    // mouseDown can be prevented, this is useful for situations where we want
    // to avoid on-blur events triggering before the button click
    mouseDown(event) {
        if (this.disableMouseDown) {
            event.preventDefault();
        }
    },

    // when local validation fails there's no transition from failed->running
    // so we want to restart the retry spinner animation to show something
    // has happened when the button is clicked
    _restartAnimation: task(function* () {
        let elem = this.element.querySelector('.retry-animated');
        if (elem) {
            elem.classList.remove('retry-animated');
            yield timeout(10);
            elem.classList.add('retry-animated');
        }
    })
});

export default GhTaskButton;
