odoo.define('payment.payment_form', function (require) {
"use strict";

var core = require('web.core');
var Dialog = require('web.Dialog');
var publicWidget = require('web.public.widget');

var _t = core._t;

publicWidget.registry.PaymentForm = publicWidget.Widget.extend({
    selector: '.o_payment_form',
    events: {
        'click #o_payment_form_pay': 'async payEvent',
        'click #o_payment_form_add_pm': 'addPmEvent',
        'click button[name="delete_pm"]': 'deletePmEvent',
        'click .o_payment_form_pay_icon_more': 'onClickMorePaymentIcon',
        'click .o_payment_acquirer_select': 'radioClickEvent',
    },

    /**
     * @override
     */
    start: function () {
        this.options = _.extend(this.$el.data(), this.options);
        this.updateNewPaymentDisplayStatus();
        $('[data-toggle="tooltip"]').tooltip();
        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {string} title
     * @param {string} message
     */
    displayError: function (title, message) {
        var $checkedRadio = this.$('input[type="radio"]:checked'),
            acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]);
        var $acquirerForm;
        if (this.isNewPaymentRadio($checkedRadio[0])) {
            $acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID);
        }
        else if (this.isFormPaymentRadio($checkedRadio[0])) {
            $acquirerForm = this.$('#o_payment_form_acq_' + acquirerID);
        }

        if ($checkedRadio.length === 0) {
            return new Dialog(null, {
                title: _t('Error: ') + _.str.escapeHTML(title),
                size: 'medium',
                $content: "<p>" + (_.str.escapeHTML(message) || "") + "</p>" ,
                buttons: [
                {text: _t('Ok'), close: true}]}).open();
        } else {
            // removed if exist error message
            this.$('#payment_error').remove();
            var messageResult = '<div class="alert alert-danger mb4" id="payment_error">';
            if (title != '') {
                messageResult = messageResult + '<b>' + _.str.escapeHTML(title) + ':</b></br>';
            }
            messageResult = messageResult + _.str.escapeHTML(message) + '</div>';
            $acquirerForm.append(messageResult);
        }
    },
    /**
     * @private
     * @param {DOMElement} element
     */
    getAcquirerIdFromRadio: function (element) {
        return $(element).data('acquirer-id');
    },
    /**
     * @private
     * @param {jQuery} $form
     */
    getFormData: function ($form) {
        var unindexed_array = $form.serializeArray();
        var indexed_array = {};

        $.map(unindexed_array, function (n, i) {
            indexed_array[n.name] = n.value;
        });
        return indexed_array;
    },
    /**
     * @private
     * @param {DOMElement} element
     */
    isFormPaymentRadio: function (element) {
        return $(element).data('form-payment') === 'True';
    },
    /**
     * @private
     * @param {DOMElement} element
     */
    isNewPaymentRadio: function (element) {
        return $(element).data('s2s-payment') === 'True';
    },
    /**
     * @private
     */
    updateNewPaymentDisplayStatus: function () {
        var checked_radio = this.$('input[type="radio"]:checked');
        // we hide all the acquirers form
        this.$('[id*="o_payment_add_token_acq_"]').addClass('d-none');
        this.$('[id*="o_payment_form_acq_"]').addClass('d-none');
        if (checked_radio.length !== 1) {
            return;
        }
        checked_radio = checked_radio[0];
        var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);

        // if we clicked on an add new payment radio, display its form
        if (this.isNewPaymentRadio(checked_radio)) {
            this.$('#o_payment_add_token_acq_' + acquirer_id).removeClass('d-none');
        }
        else if (this.isFormPaymentRadio(checked_radio)) {
            this.$('#o_payment_form_acq_' + acquirer_id).removeClass('d-none');
        }
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {Event} ev
     */
    payEvent: function (ev) {
        ev.preventDefault();
        var form = this.el;
        var checked_radio = this.$('input[type="radio"]:checked');
        var self = this;
        var button = ev.target;

        // first we check that the user has selected a payment method
        if (checked_radio.length === 1) {
            checked_radio = checked_radio[0];

            // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array
            var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);
            var acquirer_form = false;
            if (this.isNewPaymentRadio(checked_radio)) {
                acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id);
            } else {
                acquirer_form = this.$('#o_payment_form_acq_' + acquirer_id);
            }
            var inputs_form = $('input', acquirer_form);
            var ds = $('input[name="data_set"]', acquirer_form)[0];

            // if the user is adding a new payment
            if (this.isNewPaymentRadio(checked_radio)) {
                if (this.options.partnerId === undefined) {
                    console.warn('payment_form: unset partner_id when adding new token; things could go wrong');
                }
                var form_data = this.getFormData(inputs_form);
                var wrong_input = false;

                inputs_form.toArray().forEach(function (element) {
                    //skip the check of non visible inputs
                    if ($(element).attr('type') == 'hidden') {
                        return true;
                    }
                    $(element).closest('div.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
                    $(element).siblings( ".o_invalid_field" ).remove();
                    //force check of forms validity (useful for Firefox that refill forms automatically on f5)
                    $(element).trigger("focusout");
                    if (element.dataset.isRequired && element.value.length === 0) {
                            $(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
                            $(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>');
                            wrong_input = true;
                    }
                    else if ($(element).closest('div.form-group').hasClass('o_has_error')) {
                        wrong_input = true;
                        $(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>');
                    }
                });

                if (wrong_input) {
                    return;
                }

                $(button).attr('disabled', true);
                $(button).children('.fa-plus-circle').removeClass('fa-plus-circle');
                $(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i>&nbsp;</span>');

                var verify_validity = this.$el.find('input[name="verify_validity"]');

                if (verify_validity.length>0) {
                    form_data.verify_validity = verify_validity[0].value === "1";
                }

                // do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route'
                return this._rpc({
                    route: ds.dataset.createRoute,
                    params: form_data,
                }).then(function (data) {
                    // if the server has returned true
                    if (data.result) {
                        // and it need a 3DS authentication
                        if (data['3d_secure'] !== false) {
                            // then we display the 3DS page to the user
                            $("body").html(data['3d_secure']);
                        }
                        else {
                            checked_radio.value = data.id; // set the radio value to the new card id
                            form.submit();
                            return $.Deferred();
                        }
                    }
                    // if the server has returned false, we display an error
                    else {
                        if (data.error) {
                            self.displayError(
                                '',
                                data.error);
                        } else { // if the server doesn't provide an error message
                            self.displayError(
                                _t('Server Error'),
                                _t('e.g. Your credit card details are wrong. Please verify.'));
                        }
                    }
                    // here we remove the 'processing' icon from the 'add a new payment' button
                    $(button).attr('disabled', false);
                    $(button).children('.fa').addClass('fa-plus-circle');
                    $(button).find('span.o_loader').remove();
                }).fail(function (error, event) {
                    // if the rpc fails, pretty obvious
                    $(button).attr('disabled', false);
                    $(button).children('.fa').addClass('fa-plus-circle');
                    $(button).find('span.o_loader').remove();

                    self.displayError(
                        _t('Server Error'),
                        _t("We are not able to add your payment method at the moment.") +
                            error.data.message
                    );
                });
            }
            // if the user is going to pay with a form payment, then
            else if (this.isFormPaymentRadio(checked_radio)) {
                var $tx_url = this.$el.find('input[name="prepare_tx_url"]');
                // if there's a prepare tx url set
                if ($tx_url.length === 1) {
                    // if the user wants to save his credit card info
                    var form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked');
                    // then we call the route to prepare the transaction
                    return this._rpc({
                        route: $tx_url[0].value,
                        params: {
                            'acquirer_id': parseInt(acquirer_id),
                            'save_token': form_save_token,
                            'access_token': self.options.accessToken,
                            'success_url': self.options.successUrl,
                            'error_url': self.options.errorUrl,
                            'callback_method': self.options.callbackMethod,
                            'order_id': self.options.orderId,
                        },
                    }).then(function (result) {
                        if (result) {
                            // if the server sent us the html form, we create a form element
                            var newForm = document.createElement('form');
                            newForm.setAttribute("method", "post"); // set it to post
                            newForm.setAttribute("provider", checked_radio.dataset.provider);
                            newForm.hidden = true; // hide it
                            newForm.innerHTML = result; // put the html sent by the server inside the form
                            var action_url = $(newForm).find('input[name="data_set"]').data('actionUrl');
                            newForm.setAttribute("action", action_url); // set the action url
                            $(document.getElementsByTagName('body')[0]).append(newForm); // append the form to the body
                            $(newForm).find('input[data-remove-me]').remove(); // remove all the input that should be removed
                            if(action_url) {
                                newForm.submit(); // and finally submit the form
                                return $.Deferred();
                            }
                        }
                        else {
                            self.displayError(
                                _t('Server Error'),
                                _t("We are not able to redirect you to the payment form.")
                            );
                        }
                    }).fail(function (error, event) {
                        self.displayError(
                            _t('Server Error'),
                            _t("We are not able to redirect you to the payment form. ") +
                                error.data.message
                        );
                    });
                }
                else {
                    // we append the form to the body and send it.
                    this.displayError(
                        _t("Cannot set-up the payment"),
                        _t("We're unable to process your payment.")
                    );
                }
            }
            else {  // if the user is using an old payment then we just submit the form
                form.submit();
                return $.Deferred();
            }
        }
        else {
            this.displayError(
                _t('No payment method selected'),
                _t('Please select a payment method.')
            );
        }
    },
    /**
     * Called when clicking on the button to add a new payment method.
     *
     * @private
     * @param {Event} ev
     */
    addPmEvent: function (ev) {
        ev.stopPropagation();
        ev.preventDefault();
        var checked_radio = this.$('input[type="radio"]:checked');
        var self = this;
        var button = ev.target;

        // we check if the user has selected a 'add a new payment' option
        if (checked_radio.length === 1 && this.isNewPaymentRadio(checked_radio[0])) {
            // we retrieve which acquirer is used
            checked_radio = checked_radio[0];
            var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);
            var acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id);
            // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array
            var inputs_form = $('input', acquirer_form);
            var form_data = this.getFormData(inputs_form);
            var ds = $('input[name="data_set"]', acquirer_form)[0];
            var wrong_input = false;

            inputs_form.toArray().forEach(function (element) {
                //skip the check of non visible inputs
                if ($(element).attr('type') == 'hidden') {
                    return true;
                }
                $(element).closest('div.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
                $(element).siblings( ".o_invalid_field" ).remove();
                //force check of forms validity (useful for Firefox that refill forms automatically on f5)
                $(element).trigger("focusout");
                if (element.dataset.isRequired && element.value.length === 0) {
                        $(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
                        var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>';
                        $(element).closest('div.form-group').append(message);
                        wrong_input = true;
                }
                else if ($(element).closest('div.form-group').hasClass('o_has_error')) {
                    wrong_input = true;
                    var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>';
                    $(element).closest('div.form-group').append(message);
                }
            });

            if (wrong_input) {
                return;
            }
            // We add a 'processing' icon into the 'add a new payment' button
            $(button).attr('disabled', true);
            $(button).children('.fa-plus-circle').removeClass('fa-plus-circle');
            $(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i>&nbsp;</span>');

            // we force the check when adding a card trough here
            form_data.verify_validity = true;

            // do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route'
            this._rpc({
                route: ds.dataset.createRoute,
                params: form_data,
            }).then(function (data) {
                // if the server has returned true
                if (data.result) {
                    // and it need a 3DS authentication
                    if (data['3d_secure'] !== false) {
                        // then we display the 3DS page to the user
                        $("body").html(data['3d_secure']);
                    }
                    // if it doesn't require 3DS
                    else {
                        // we just go to the return_url or reload the page
                        if (form_data.return_url) {
                            window.location = form_data.return_url;
                        }
                        else {
                            window.location.reload();
                        }
                    }
                }
                // if the server has returned false, we display an error
                else {
                    if (data.error) {
                        self.displayError(
                            '',
                            data.error);
                    } else { // if the server doesn't provide an error message
                        self.displayError(
                            _t('Server Error'),
                            _t('e.g. Your credit card details are wrong. Please verify.'));
                    }
                }
                // here we remove the 'processing' icon from the 'add a new payment' button
                $(button).attr('disabled', false);
                $(button).children('.fa').addClass('fa-plus-circle');
                $(button).find('span.o_loader').remove();
            }).fail(function (error, event) {
                // if the rpc fails, pretty obvious
                $(button).attr('disabled', false);
                $(button).children('.fa').addClass('fa-plus-circle');
                $(button).find('span.o_loader').remove();

                self.displayError(
                    _t('Server error'),
                    _t("We are not able to add your payment method at the moment.</p>") +
                        error.data.message
                );
            });
        }
        else {
            this.displayError(
                _t('No payment method selected'),
                _t('Please select the option to add a new payment method.')
            );
        }
    },
    /**
     * Called when clicking on a button to delete a payment method.
     *
     * @private
     * @param {Event} ev
     */
    deletePmEvent: function (ev) {
        ev.stopPropagation();
        ev.preventDefault();
        var self = this;
        var pm_id = parseInt(ev.target.value);

        var tokenDelete = function () {
            self._rpc({
                model: 'payment.token',
                method: 'unlink',
                args: [pm_id],
            }).then(function (result) {
                if (result === true) {
                    ev.target.closest('div').remove();
                }
            }, function () {
                self.displayError(
                    _t('Server Error'),
                    _t("We are not able to delete your payment method at the moment.")
                );
            });
        };

        this._rpc({
            model: 'payment.token',
            method: 'get_linked_records',
            args: [pm_id],
        }).then(function (result) {
            if (result[pm_id].length > 0) {
                // if there's records linked to this payment method
                var content = '';
                result[pm_id].forEach(function (sub) {
                    content += '<p><a href="' + sub.url + '" title="' + sub.description + '">' + sub.name + '</a><p/>';
                });

                content = $('<div>').html(_t('<p>This card is currently linked to the following records:<p/>') + content);
                // Then we display the list of the records and ask the user if he really want to remove the payment method.
                new Dialog(self, {
                    title: _t('Warning!'),
                    size: 'medium',
                    $content: content,
                    buttons: [
                    {text: _t('Confirm Deletion'), classes: 'btn-primary', close: true, click: tokenDelete},
                    {text: _t('Cancel'), close: true}]
                }).open();
            }
            else {
                // if there's no records linked to this payment method, then we delete it
                tokenDelete();
            }
        }, function (err, event) {
            self.displayError(
                _t('Server Error'),
                _t("We are not able to delete your payment method at the moment.") + err.data.message
            );
        });
    },
    /**
     * Called when clicking on 'and more' to show more payment icon.
     *
     * @private
     * @param {Event} ev
     */
    onClickMorePaymentIcon: function (ev) {
        ev.preventDefault();
        ev.stopPropagation();
        var $listItems = $(ev.currentTarget).parents('ul').children('li');
        var $moreItem = $(ev.currentTarget).parents('li');
        $listItems.removeClass('d-none');
        $moreItem.addClass('d-none');
    },
    /**
     * Called when clicking on a radio button.
     *
     * @private
     * @param {Event} ev
     */
    radioClickEvent: function (ev) {
        // radio button checked when we click on entire zone(body) of the payment acquirer
        $(ev.currentTarget).find('input[type="radio"]').prop("checked", true);
        this.updateNewPaymentDisplayStatus();
    },
});
return publicWidget.registry.PaymentForm;
});
