(function ($) {
    "use strict";

    var prefix                    = 'gpls-wmfw-watermark-image-for-wordpress';
    var prefixUnderscored         = 'gpls_wmfw_watermark_image_for_wordpress';
    var localize_vars             = window[ prefixUnderscored + '_localize_vars' ];
    var classesPrefix             = 'gpls-wmfw';
    var imageWatermark            = null;
    var previewImage              = {};
    var windowDimension           = {};
    var waterSpecsChangeTimeout   = null;
    var initCurrentWatermarksFlag = false;
    var watermarksCount           = 0;
    var watermarksCounter         = 0;

    $.fn.resized = function (callback, timeout) {
        $(this).resize(function () {
            var $this = $(this);
            if ($this.data('resizeTimeout')) {
                clearTimeout($this.data('resizeTimeout'));
            }
            $this.data('resizeTimeout', setTimeout(callback, timeout));
        });
    };

    $(document).on('ready', function () {

        windowDimension['width'] = window.outerWidth;
        windowDimension['height'] = window.outerHeight;

        var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
        var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
          return new bootstrap.Tooltip(tooltipTriggerEl)
        })

        var collapseElementList = [].slice.call(document.querySelectorAll('.accordion-collapse'))
        var collapseList = collapseElementList.map(function (collapseEl) {
            return new bootstrap.Collapse(collapseEl, { toggle: false })
        })

        // Init the Media Gallery to select the preview image.
        initMediaGallery();

        imageWatermark = new ImageWatermark();
    });


    function initMediaGallery() {
        var mediaUploader;
        $( '.' + classesPrefix + '-open-gallery-btn' ).on( 'click', function (e) {
            e.preventDefault();

            var context = $(this).data('context');
            var options = {
                title: context === 'select-watermark' ? localize_vars.labels.select_watermark : localize_vars.labels.select_image,
                library: {
                    orderby: 'date',
                    query: true,
                    post_mime_type: [ 'image/*' ],
                    [ prefix + '-context-modal' ]: context
                },
                button: {
                    text: context === 'select-watermark' ? localize_vars.labels.select_watermark : localize_vars.labels.select_image
                },
                multiple: false
            }

            mediaUploader = wp.media( options );
            mediaUploader.open();

            mediaUploader.on( 'select', () => {
                var attachments = mediaUploader.state().get('selection').toJSON();
                // console.log( 'select attachments: ', attachments );
                if ( context === 'select-preview-image' ) {
                    previewImage = attachments[0];
                    // console.log( 'preview image : ', previewImage );
                    // 1) Show Add Watermark Buttons section.
                    $('.select-watermark-btn-section').removeClass('d-none');
                    // 2) Set the Preview Image ID.
                    $('.selected-preview-img-id').val( previewImage.id );
                    // 4) Initialize the target Image Box.
                    imageWatermark.initImage();
                    $('.select-watermark-btn-section').removeClass('d-none');
                    $('.preview-result').addClass('d-none');
                    $('.save-section').collapse('hide');
                } else if ( context === 'select-watermark' ) {

                    watermarksCount++;
                    watermarksCounter++;
                    // 2) Add the image Watermark to the GIF Box.
                    imageWatermark.addWatermark( 'image', attachments[0], true );
                }
            });
        });

    }

    /**
     * GIF WaterMark Class.
     */
    class ImageWatermark {

        constructor() {
            this.canvas                 = document.createElement('canvas');
            this.activePlaceholder      = null;
            this.editsElement           = $('.preview-selected-wrapper .edits');
            this.watermarks             = {};
            this.watermarksElements     = {};
            this.lastTextDegree         = 0;
            this.applyWatermarksOptions = {
                createBackup: true
            };
            this.spotsMapping           = {
                'tl': { left: 0, top: 0 },
                'tm': { left: 1, top: 0 },
                'tr': { left: 2, top: 0 },
                'ml': { left: 0, top: 1 },
                'mm': { left: 1, top: 1 },
                'mr': { left: 2, top: 1 },
                'bl': { left: 0, top: 2 },
                'bm': { left: 1, top: 2 },
                'br': { left: 2, top: 2 }
            };

            this.events();
        }

        events() {
            // Show - Hide Watermark placeholder on hover.
            $('.edits').on('click', '.edits-close', (e) => {
                $('.preview-selected-wrapper .edits').addClass('d-none');
            });
            $(document).on('mouseenter', '.actions', (e) => {
                var $this = $(e.target);
                $this.removeClass('d-none');
            });

            // Activate Watemark accordion on click.
            $(document).on('click', '.watermark-placeholder', (e) => {
                var $this                = $(e.target);
                var watermarkPlaceholder = $this.hasClass('.watermark-placeholder') ? $this : $this.closest('.watermark-placeholder');
                var watermarkID          = watermarkPlaceholder.attr('item');
                var targetPlaceholder    = $('.watermark-placeholder[item="' + watermarkID + '"]' );

                this.activateWatermarkSpecsItem(watermarkID);
                targetPlaceholder.css( 'zIndex', 200 );
                targetPlaceholder.siblings( '.watermark-placeholder' ).css( 'zIndex', 2 );
            });

            // Activate Watermar accordion when click on accordion head.
            $(document).on( 'click', '.accordion-button', (e) => {
                var $this             = $(e.target);
                var watermarkID       = $this.data('id');
                var targetPlaceholder = $('.watermark-placeholder[item="' + watermarkID + '"]' );
                targetPlaceholder.css( 'zIndex', 200 );
                targetPlaceholder.siblings( '.watermark-placeholder' ).css( 'zIndex', 2 );
            });

            $(document).on('mouseenter', '#selected-preview-container', (e) => {
                $('.watermark-placeholder').find('.actions').removeClass('d-none');
            });

            $(document).on('mouseleave', '#selected-preview-container', (e) => {
                $('.watermark-placeholder').find('.actions').addClass('d-none');
            });

            // Prevent Newline in Text Watermark.
            $(document).on('keypress', '.watermark-text-textarea', (e) => {
                if (e.which === 13) {
                    e.preventDefault();
                }
            });

            // Update width of Text Watermark on typing.
            $(document).on('keyup', '.watermark-text-textarea', (e) => {
                var watermarkTextarea = $(e.target);
                var placeholderElement = watermarkTextarea.parents('.watermark-placeholder-text');
                var watermarkID = placeholderElement.attr('item');
                var watermarkInfoObj = this.watermarks[watermarkID];
                var watermarkSpecsBox = $('#' + watermarkID + '_specs');
                watermarkSpecsBox.find('.edit-title').val(watermarkTextarea.text());
                watermarkInfoObj = this.updateWatermarkTextOffsets(watermarkInfoObj);
                this.repeatWatermarkPlaceholder(watermarkInfoObj);
            });

            // Rotate Watermark.
            $(document).on('mousedown', '.watermark-placeholder-rotate-handle', (e) => {
                var $this = $(e.target);
                var watermarkElement = $this.parents('.watermark-placeholder');
                watermarkElement.rotatable('instance').startRotate(e);
            });

            // Click Edit Button on Watermark.
            $(document).on('click', '.actions .action-edit', (e) => {
                var watermarkPlaceholder = $(e.target).closest('.watermark-placeholder');
                var watermarkID = watermarkPlaceholder.attr('item');
                this.activateWatermarkSpecsItem(watermarkID);
            });

            // Make watermark active on click on Watermarks Specs header.
            $(document).on('click', '.watermark-specs-header', (e) => {
                var watermarkID = $(e.target).data('id');
                var watermarkPlaceohlder = $('.watermark-placeholder[item="' + watermarkID + '"]');
                if (watermarkPlaceohlder.length) {
                    watermarkPlaceohlder.find('.actions').removeClass('d-none');
                    watermarkPlaceohlder.siblings().find('.actions').addClass('d-none');
                }
            });

            // Remove Watermark.
            $(document).on( 'click', '.accordion-item .action-remove', (e) => {
                if ( confirm( localize_vars.labels.remove_watermark ) ) {
                    var $this         = $(e.target);
                    var accordionItem = $this.closest('.accordion-item')
                    var watermarkID   = accordionItem.data('id');
                    watermarksCount--;
                    this.removeWatermark( watermarkID );
                }

            });
            $(document).on( 'click', '.actions .action-remove', (e) => {
                if ( confirm( localize_vars.labels.remove_watermark ) ) {
                    var $this              = $(e.target);
                    var placeholderElement = $this.parents('.watermark-placeholder')
                    var watermarkID        = placeholderElement.attr('item');
                    watermarksCount--;
                    this.removeWatermark( watermarkID );
                }
            });

            // Making changes in Watermark Styles inputs.
            $(document).on( 'input change', '.watermark-specs .edit', (e, isPypassRepeat ) => {
                if ( isPypassRepeat !== prefix + '-pypass-repeat' ) {
                    clearTimeout( waterSpecsChangeTimeout );
                }
                waterSpecsChangeTimeout = setTimeout( () => {

                    var $this              = $(e.target);
                    var styleType          = $this.data('type');
                    var styleValue         = $this.val();
                    var watermarkID        = $this.closest('.accordion-item').data('id');
                    var watermarkInfoObj   = this.watermarks[ watermarkID ];
                    var placeholderElement = $('.watermark-placeholder[item="' + watermarkID + '"]');
                    var watermarkSpecsItem = $('#' + watermarkID + '_specs' );
                    var styles             = watermarkInfoObj.styles;
                    var position           = {}
                    // console.log( 'style Type: ' + styleType, styleValue );
                    if ( styleType === 'opacity' || styleType === 'degree' ) {
                        if ( isNaN( styleValue ) ) {return;}
                        if ( watermarkInfoObj.type === 'text' ) {
                            if ( styleValue > 90 && styleValue < 270 ) {
                                styleValue          = styleValue < this.lastTextDegree ? 90 : 270
                                this.lastTextDegree = styleValue;
                                watermarkSpecsItem.find('.edit-degree').val( styleValue ).trigger('change');
                                return;
                            }
                            this.lastTextDegree = styleValue;
                        }
                        if ( styleType === 'degree' && styleValue < 0 ) {
                            styles[ styleType ] = 360 - Math.abs( styleValue );
                        } else {
                            styles[ styleType ] = styleValue;
                        }
                    } else if ( styleType === 'position-type' ) {
                        var positionTypeWrapper = watermarkSpecsItem.find('.' + styleValue + '-position');
                        positionTypeWrapper.collapse('show');
                        positionTypeWrapper.siblings('.position-type-input').collapse('hide');
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            position: {
                                positionType: styleValue
                            }
                        });
                    } else if ( styleType === 'position-left-percent' ) {
                        if ( isNaN( styleValue ) ) {return;}
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            position: {
                                leftPercent: styleValue
                            }
                        });
                    } else if ( styleType === 'position-top-percent' ) {
                        if ( isNaN( styleValue ) ) {return;}
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            position: {
                                topPercent: styleValue
                            }
                        });
                    } else if ( styleType === 'position-spot' ) {
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            position: {
                                positionSpot: styleValue
                            }
                        });
                    } else if ( styleType === 'position-left' ) {
                        if ( isNaN( styleValue ) ) {return;}
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            position: {
                                left: styleValue,
                            }
                        });
                    } else if ( styleType === 'position-top' ) {
                        if ( isNaN( styleValue ) ) {return;}
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            position: {
                                top: styleValue,
                            }
                        });
                    } else if ( styleType === 'position-center-offset' ) {
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            position: {
                                centerOffset: $this.is(':checked'),
                            }
                        });
                    } else if ( styleType === 'width' ) {
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            width: styleValue
                        });
                    } else if ( styleType === 'height' ) {
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, {
                            height: styleValue
                        });
                    } else {
                        styles.font[ styleType ] = styleValue;
                    }

                    if( styleType === 'title' ) {
                        watermarkInfoObj = this.updateWatermark( watermarkInfoObj, { text: styleValue } );
                        placeholderElement.find('.watermark-text-textarea').text( styleValue );
                    }

                    // 1) Update the Placeholder CSS with new specs data.
                    this.updateWatermarkPlaceholderStyles( placeholderElement, styles, watermarkInfoObj );

                    // 2) update the watermark Object with updated styles.
                    watermarkInfoObj = this.updateWatermarkStyles( watermarkInfoObj, styles );

                    // 3) Update the text watermark offset data.
                    if ( watermarkInfoObj.type === 'text' ) {
                        watermarkInfoObj = this.updateWatermarkTextOffsets( watermarkInfoObj );
                    }

                    // 4) Re Update placeholder CSS after styles update.
                    this.updateWatermarkPlaceholderStyles( placeholderElement, styles, watermarkInfoObj );

                    // 5) Draw Repeated Watermarks Placeholder.
                    this.repeatWatermarkPlaceholder( watermarkInfoObj );

                }, 200 );
            });


            // Watermarks count change event.
            $(document).on(prefixUnderscored + '_preview_watermark_save_toggle', (e, count) => {
                if (count) {
                    $('.preview-watermark-preview-btn').removeClass('disabled').prop('disabled', false);
                    $('.preview-watermark-preview-btn').removeClass('d-none');
                } else {
                    $('.preview-watermark-preview-btn').addClass('disabled').prop('disabled', true);
                    $('.preview-watermark-preview-btn').addClass('d-none');
                }
            });

            // Add Text Watermark.
            $(document).on('click', '.' + classesPrefix + '-add-text-watermark', (e) => {
                e.preventDefault();
                watermarksCount++;
                watermarksCounter++;
                imageWatermark.addWatermark('text', {}, true);
            });

            // Typing in Text watermark.
            $(document).on('input', '.watermark-text-textarea', (e) => {
                if (e.keyCode === 13) {
                    e.preventDefault();
                    return;
                }
                var textInputElement = $(e.target);
                var placeholderElement = textInputElement.parents('.watermark-placeholder-text');
                var watermarkID = placeholderElement.attr('item');
                var watermarkInfoObj = this.watermarks[watermarkID];
                var text = textInputElement.text();
                this.updateWatermark(watermarkInfoObj, {
                    text: text
                });
                this.updateWatermarkStyles(watermarkInfoObj, watermarkInfoObj.styles);
            });

            // Preview Watermarks Template.
            $(document).on('click', '.preview-watermark-preview-btn', (e) => {
                // var watermarkText = $('.watermark-text-textarea');
                this.previewWatermarks(e);
            });

            // = Position Repeats = //

            $(document).on('change', '.edit-position-repeat-status', (e) => {
                var $this = $(e.target);
                var repeatStatus = $this.is(':checked');
                var watermarkID = $this.data('watermarkid');
                var watermarkSpecsItem = $('#' + watermarkID + '_specs');
                var watermarkInfoObj = this.watermarks[watermarkID];
                watermarkSpecsItem.find('.repeat-axis-wrapper').collapse(repeatStatus ? 'show' : 'hide');
                watermarkInfoObj.isRepeat = repeatStatus;
                this.directUpdateWatermark(watermarkInfoObj);
                this.repeatWatermarkPlaceholder(watermarkInfoObj);
            });

            $(document).on('change', '.repeat-axis-select', (e) => {
                var $this = $(e.target);
                var positionAxis = $this.val();
                var watermarkID = $this.data('watermarkid');
                var watermarkInfoObj = this.watermarks[watermarkID];
                watermarkInfoObj.repeatAxis = positionAxis;
                this.directUpdateWatermark(watermarkInfoObj);
                this.repeatWatermarkPlaceholder(watermarkInfoObj);
            });

            $(document).on('change', '.repeat-axis-offset-input', (e) => {
                var $this = $(e.target);
                var positionAxis = $this.val();
                var watermarkID = $this.data('watermarkid');
                var axisType = $this.data('axistype');
                var watermarkInfoObj = this.watermarks[watermarkID];
                if (axisType === 'x') {
                    watermarkInfoObj.repeatXAxisOffset = positionAxis;
                } else {
                    watermarkInfoObj.repeatYAxisOffset = positionAxis;
                }
                this.directUpdateWatermark(watermarkInfoObj);
                this.repeatWatermarkPlaceholder(watermarkInfoObj);
            });

            // === Auto Apply Settings === //

            // Toggle Posts Auto Apply collapse.
            $(document).on('change', '.auto-apply-status', (e) => {
                var $this = $(e.target);
                var checked = $this.is(':checked');
                $('.apply-context .apply-type').collapse(checked ? 'show' : 'hide');
            });

            $(document).on('change', '.auto-apply-context-posts', (e) => {
                var $this = $(e.target);
                var checked = $this.is(':checked');
                $('#auto-apply-context-filter-posts').collapse(checked ? 'show' : 'hide');
            });

            $(document).on('change', '.context-posts-cpt', (e) => {
                var $this = $(e.target);
                var value = $this.val();
                var checked = $this.is(':checked');
                $('.context-by-cpt-filter-' + value).collapse(checked ? 'show' : 'hide');
            });

            // == Sections Toggles == //.

            // On Toggle "Original" in OVerwrite Select Option.
            $('#apply-watermarks-image-sizes-option-original').on('change', (e) => {
                var originalCheckBox = $(e.target)
                var applyTypeValue = $('.apply-watermarks-type:checked').val();
                if (2 == applyTypeValue) {
                    if (originalCheckBox.is(':checked')) {
                        $('.create-backup-option').collapse('show');
                    } else {
                        $('.create-backup-option').collapse('hide');
                    }
                }
            });

            // Toggle All Image Sizes Checkboxes.
            $('.apply-watermarks-image-sizes-options-all').on('change', (e) => {
                var $this = $(e.target);
                var value = $this.is(':checked');
                $('.apply-watermarks-image-sizes-option').prop('checked', value).trigger('change');
            });

            $('.apply-watermarks-image-sizes-option').on('change', (e) => {
                var imageSizes = [];
                var sizesOptions = $('.apply-watermarks-image-sizes-option');
                $.each(sizesOptions, (index, element) => {
                    var $this = $(element);
                    var value = $this.val();
                    if ($this.is(':checked')) {
                        imageSizes.push(value);
                    }
                });
                this.applyWatermarksOptions['imageSizes'] = imageSizes;
                if (!imageSizes.length) {
                    $('.step-4').collapse('hide');
                } else {
                    $('.step-4').collapse('show');
                }
            });

            // Toggle Apply Watermarks Template Overwrite Options.
            $(document).on('change', '.apply-watermarks-type', (e) => {
                var $this = $(e.target);
                var value = $this.val();
                var originalCheckBox = $('#apply-watermarks-image-sizes-option-original');
                this.applyWatermarksOptions['applyTemplateType'] = value;
                if (2 == value) {
                    if (originalCheckBox.is(':checked')) {
                        $('.create-backup-option').collapse('show');
                    }
                    $('.original-image-size-option').collapse('show');
                    $('.image-sizes-options-overwrite').collapse('show');
                    $('.image-sizes-options-new').collapse('hide');
                } else {
                    $('.create-backup-option').collapse('hide');
                    $('.original-image-size-option').collapse('hide');
                    $('.image-sizes-options-overwrite').collapse('hide');
                    $('.image-sizes-options-new').collapse('show');
                }
                $('.apply-watermarks-apply-subsizes-options').collapse('show');
            });

            // Toggle Backup Button.
            $('.apply-watermarks-create-backup-option').on('change', (e) => {
                var $this = $(e.target);
                var value = $this.is(':checked');
                this.applyWatermarksOptions['createBackup'] = value;
            });


            $(document).on('click', '.apply-watermarks-submit-btn', (e) => {
                e.preventDefault();
                this.applyWatermarks();
            });

            $(window).on('resize', (e) => {
                $('.preview-result').addClass('d-none');
            });
            $(window).resized( (e) => {
                var previewSelected = $('.selected-preview');
                previewImage.width  = Math.floor( previewSelected.width() );
                previewImage.height = Math.floor( previewSelected.height() );
                // Redraw the watermarks.
                this.initCurrentWatermarks( true );
            }, 300 );
        }

        initCurrentWatermarks( onLoad = true, isRefresh = false ) {
            // Init Watermarks Objects.
            if ( onLoad ) {
                if ( localize_vars?.current_watermarks?.watermarks ) {
                    var watermarks    = localize_vars['current_watermarks']['watermarks'];
                    watermarksCount   = Object.keys( watermarks ).length;
                    watermarksCounter = Object.keys( watermarks ).length;
                }
            } else {
                var watermarks = this.watermarks;
            }
            if ( watermarks ) {
                initCurrentWatermarksFlag = true;

                $.each( watermarks, (key, value) => {
                    if ( isRefresh ) {
                        this.removeWatermark( value['id'], true );
                    }
                    // 3) Draw watermarks.
                    this.addWatermark( value['type'], value, false, value );
                    this.initWatermarkSpecs( value['id'] );

                    var placeholderElement = $('.watermark-placeholder[item="' + value['id'] + '"]' );
                    // 4) Setup the styles.
                    this.updateWatermarkPlaceholderStyles( placeholderElement, value['styles'], value );
                    if ( value['type'] === 'text' ) {
                        placeholderElement.find('.watermark-text-textarea').text( value['text'] );
                        this.updateWatermarkTextOffsets( value );
                    }

                    this.updateWatermarkStyles( value['id'], value['styles'] );
                    this.updateWatermarkSpecs( value['id'], value );
                    this.repeatWatermarkPlaceholder( value );
                });
                initCurrentWatermarksFlag = false;
            }
            // console.log( 'Current Watermarks: ', this.watermarks );
        }

        applyWatermarks() {
            var previewBtn = $('.preview-watermark-preview-btn');
            var submitBtn = $('.apply-watermarks-submit-btn');
            previewBtn.attr('disabled', true);
            previewBtn.next('.spinner').addClass('visible');
            submitBtn.attr('disabled', true);
            submitBtn.next('.spinner').addClass('visible');
            $('.img-icon-box-container').addClass('d-none');

            var watermarks = Object.values(this.watermarks);
            var data = {
                action: localize_vars.singleApplyWatermarksAction,
                nonce: localize_vars['nonce'],
                preview_img: previewImage,
                watermarks: watermarks,
                options: this.applyWatermarksOptions
            };

            $.ajax({
                method: 'POST',
                url: localize_vars.ajaxUrl,
                dataType: 'json',
                data: data,
                success: (resp) => {
                    if (resp['data'] && resp['data']['msg']) {
                        showToast(resp['data']['msg'], 'bg-' + resp['data']['status']);
                    }
                    if (resp['data']['status'] && resp['success']) {
                        this.displayImgIcon(resp['data']['display']);
                    }
                },
                error: (err) => {
                    if (err['data'] && err['data']['msg']) {
                        showToast(err['data']['msg'], 'bg-danger');
                    }
                },
                complete: () => {
                    previewBtn.attr('disabled', false);
                    previewBtn.next('.spinner').removeClass('visible');
                    submitBtn.attr('disabled', false);
                    submitBtn.next('.spinner').removeClass('visible');
                }
            });
        }

        displayImgIcon(html) {
            var gifDisplayElement = $('.img-icon-box-container');
            gifDisplayElement.html(html);
            gifDisplayElement.removeClass('d-none');
        }

        calculatePosition( watermarkInfoObj, type = '' ) {
            if ( ! type.length ) {
                type = watermarkInfoObj.positionType;
            }
            var positionMapping = this.spotsMapping[ watermarkInfoObj['positionSpot'] ];
            var squareWidth     = Math.round( previewImage.width / 3 );
            var squareHeight    = Math.round( previewImage.height / 3 );

            if ( type === 'pixel' ) {
                var position = {
                    left: Math.round( positionMapping.left * squareWidth ) + parseInt( watermarkInfoObj['absLeft'] ),
                    top: Math.round( positionMapping.top * squareHeight ) + parseInt( watermarkInfoObj['absTop'] )
                }
            } else if ( type === 'percent' ) {
                var position = {
                    left: Math.round( positionMapping.left * squareWidth ) + parseInt( squareWidth * parseFloat( watermarkInfoObj['leftPercent'] / 100 ) ),
                    top: Math.round( positionMapping.top * squareHeight ) + parseInt( squareHeight * parseFloat( watermarkInfoObj['topPercent'] / 100 ) )
                }
            }
            if ( watermarkInfoObj['centerOffset'] ) {
                position.left -= Math.round( watermarkInfoObj.width / 2 );
                position.top  -= Math.round( watermarkInfoObj.height / 2 );
            }

            return position;
        }

        updateCurrentWatermarksPosition() {
            $.each(this.watermarks, (key, value) => {
                var position = ('pixel' === value['positionType'] ? {
                    absLeft: value['absLeft'],
                    absTop: value['absTop']
                } : {
                    topPercent: value['topPercent'],
                    leftPercent: value['leftPercent']
                });
                var placeholderElement = $('.watermark-placeholder[item="' + value['id'] + '"]');
                this.updateWatermarkPlaceholderStyles(placeholderElement, value['styles'], value);
            });
        }

        activateWatermarkSpecsItem( watermarkID ) {
            let watermarkspecsBtn = $('.accordion-button[data-id="' + watermarkID + '"]');
            if ( watermarkspecsBtn && watermarkspecsBtn.hasClass( 'collapsed' ) ) {
                watermarkspecsBtn.trigger('click');
            }
        }

        insertWatermarkToList(watermarkInfoObj) {
            // 1) Insert the Watermark Specs.
            var CurrentWatermarksList = $('#' + prefix + '-added-watermarks-list');
            if (!CurrentWatermarksList.length) {
                return;
            }
            CurrentWatermarksList.find('.watermarks-list-accordion').append(this.watermarksAccordionItemHTML(watermarkInfoObj, watermarksCount ) );
            var watermarkSpecsPlaceholder = $('.watermark-specs-placeholder').clone().attr('id', 'watermark-specs-item-' + watermarkInfoObj['id']).removeClass('watermark-specs-placeholder').addClass('watermark-specs').removeClass('d-none');
            $('#' + watermarkInfoObj['id'] + '_specs').find('.accordion-body').html(watermarkSpecsPlaceholder);
            this.initWatermarkSpecs(watermarkInfoObj['id']);
            var watermarkElement = $('.watermark-placeholder[item="' + watermarkInfoObj.id + '"]');
            watermarkElement.find('.watermark-placeholder-wrapper').trigger('click');

        }

        watermarksAccordionItemHTML(watermarkInfoObj, watermarkNum) {
            var watermarkID   = watermarkInfoObj.id;
            var watermarkType = watermarkInfoObj.type;
            var $html         =
            `<div class="accordion-item" data-id="` + watermarkID + `" >
            <h4 class="accordion-header watermark-specs-header" id="` + watermarkID + `_header" data-index="` + ( watermarkNum - 1 ) + `" data-id="` + watermarkID + `">
                <div class="header-wrapper d-flex flex-row align-items-center" style="height: 60px;">
                    <button class="accordion-button collapsed" type="button" data-id="` + watermarkID + `" data-bs-toggle="collapse" data-bs-target="#` + watermarkID + `_specs" aria-expanded="true" aria-controls="` + watermarkID + `_specs" >
                        Watermark ` + ( watermarksCounter ) + ` [` + this.watermarks[ watermarkID ]['type'] + `]
                    </button>
                    <span class="dashicons dashicons-dismiss action action-remove mx-2 bg-white" style="color:#F00;" type="button" data-id="` + watermarkID + `"></span>
                </div>
            <input type="hidden" name="watermarks[` + watermarkID + `][type]" value="` + watermarkType + `" />
            <input type="hidden" name="watermarks[` + watermarkID + `][id]" value="` + watermarkID + `" />
            <input type="hidden" name="watermarks[` + watermarkID + `][width]" value="` + watermarkInfoObj.width + `" />
            <input type="hidden" name="watermarks[` + watermarkID + `][height]" value="` + watermarkInfoObj.height + `" />
            ${ ( watermarkType === 'image' ) ?
                `<input type="hidden" name="watermarks[` + watermarkID + `][url]" value="` + watermarkInfoObj.url + `" />
                <input type="hidden" name="watermarks[` + watermarkID + `][imgID]" value="` + watermarkInfoObj.imgID + `" />`
            : `` }
            </h4>
            <div id="` + watermarkID + `_specs" class="accordion-collapse collapse" data-id="` + watermarkID + `" aria-labelledby="` + watermarkID + `_header" data-bs-parent="#` + classesPrefix + `-watermarks-list-accordion" >
                <div class="accordion-body">
                </div>
            </div>
            </div>`;
            return $html;
        }

        initWatermarkSpecs( watermarkID ) {
            // 1) Get the Watermark Specs element and watermark Info.
            var watermarkSpecsItem = $( '#watermark-specs-item-' + watermarkID );
            if ( ! watermarkSpecsItem.length ) {
                return;
            }
            var watermarkInfoObj = this.watermarks[ watermarkID ];
            // 2) Check the watermark type.
            if ( watermarkInfoObj['type'] === 'text' ) {
                watermarkSpecsItem.find('.edit.edit-title').val( watermarkInfoObj.text ).attr( 'name', 'watermarks[' + watermarkID + '][title]' );
                watermarkSpecsItem.find('.edit.edit-color').val( watermarkInfoObj.styles.font.color ).attr( 'name', 'watermarks[' + watermarkID + '][color]' );
                watermarkSpecsItem.find('.edit.edit-font-size').val( watermarkInfoObj.styles.font.fontSize ).attr( 'name', 'watermarks[' + watermarkID + '][fontsize]' );
                watermarkSpecsItem.find('.edit.edit-font-family').val( watermarkInfoObj.styles.font.fontFamily ).attr( 'name', 'watermarks[' + watermarkID + '][fontfamily]' );
                watermarkSpecsItem.find('.edit-position-botleft').val( watermarkInfoObj['botLeft'] ).attr( 'name', 'watermarks[' + watermarkID + '][botLeft]' );
                watermarkSpecsItem.find('.edit-position-bottop').val( watermarkInfoObj['botTop'] ).attr( 'name', 'watermarks[' + watermarkID + '][botTop]' );
                watermarkSpecsItem.find('.edit-position-exactwidth').val( watermarkInfoObj['exactWidth'] ).attr( 'name', 'watermarks[' + watermarkID + '][exactWidth]' );
                watermarkSpecsItem.find('.edit-position-baselineoffset').val( watermarkInfoObj['baselineOffset'] ).attr( 'name', 'watermarks[' + watermarkID + '][baselineOffset]' );
            } else if ( watermarkInfoObj['type'] === 'image' ) {
                watermarkSpecsItem.find('.edit-text').remove();
            }
            // Width - Height
            if ( watermarkInfoObj['type'] === 'text' ) {
                watermarkSpecsItem.find('.watermark-dimension-wrapper').addClass('d-none');
            }
            watermarkSpecsItem.find('.edit.edit-width').val( watermarkInfoObj.width ).attr( 'name', 'watermarks[' + watermarkID + '][width]' );
            watermarkSpecsItem.find('.edit.edit-height').val( watermarkInfoObj.height ).attr( 'name', 'watermarks[' + watermarkID + '][height]' );

            // opacity.
            var editOpacity = watermarkSpecsItem.find('.edit-opacity');
            editOpacity.val( watermarkInfoObj['styles']['opacity'] ).attr( 'name', 'watermarks[' + watermarkID + '][opacity]' );
            editOpacity.trigger('change');

            // Update Position.
            watermarkSpecsItem.find( '.' + classesPrefix + '-watermark-position-spot-' + watermarkInfoObj['positionSpot'] ).prop( 'checked', true );
            $.each( watermarkSpecsItem.find('.edit-position-spot'), ( index, element ) => {
                var $this = $(element);
                $this.attr('name', 'watermarks[' + watermarkID + '][positionSpot]');
            });
            watermarkSpecsItem.find('.edit-position-type').attr( 'name', 'watermarks[' + watermarkID + '][positionType]' );
            watermarkSpecsItem.find('.edit-position-repeat-status').attr( 'name', 'watermarks[' + watermarkID + '][isRepeat]' ).attr( 'data-watermarkid', watermarkID );
            watermarkSpecsItem.find('.repeat-axis-select').attr( 'name', 'watermarks[' + watermarkID + '][repeatAxis]' ).attr( 'data-watermarkid', watermarkID );
            watermarkSpecsItem.find('.repeat-x-axis-offset-input').val( watermarkInfoObj['repeatXAxisOffset'] ).attr( 'name', 'watermarks[' + watermarkID + '][repeatXAxisOffset]' ).attr( 'data-watermarkid', watermarkID );
            watermarkSpecsItem.find('.repeat-y-axis-offset-input').val( watermarkInfoObj['repeatYAxisOffset'] ).attr( 'name', 'watermarks[' + watermarkID + '][repeatYAxisOffset]' ).attr( 'data-watermarkid', watermarkID );
            watermarkSpecsItem.find('.position-type-' + watermarkInfoObj['positionType'] ).prop( 'checked', true );
            watermarkSpecsItem.find('.edit-position-center-offset').prop( 'checked', watermarkID['centerOffset'] );
            watermarkSpecsItem.find('.edit-position-left').val( watermarkInfoObj['absLeft'] ).attr( 'name', 'watermarks[' + watermarkID + '][absLeft]' );
            watermarkSpecsItem.find('.edit-position-top').val( watermarkInfoObj['absTop'] ).attr( 'name', 'watermarks[' + watermarkID + '][absTop]' );
            watermarkSpecsItem.find('.edit-position-left-percent').val( watermarkInfoObj['leftPercent'] ).attr( 'name', 'watermarks[' + watermarkID + '][leftPercent]' );
            watermarkSpecsItem.find('.edit-position-top-percent').val( watermarkInfoObj['topPercent'] ).attr( 'name', 'watermarks[' + watermarkID + '][topPercent]' );

            // Rotation.
            var editRotation = watermarkSpecsItem.find('.edit-degree');
            editRotation.val( watermarkInfoObj.styles.degree ).attr( 'name', 'watermarks[' + watermarkID + '][degree]' );

            // setup tooltips.
            watermarkSpecsItem.find('[data-bs-toggle="tooltip"]').tooltip();
        }

        fillInWatermarkEditsList(watermarkInfoObj) {
            if (watermarkInfoObj.type === 'text') {
                this.editsElement.find('.edit-title').val(watermarkInfoObj.text);
                this.editsElement.find('.edit-color').val(watermarkInfoObj.styles.font.color);
                this.editsElement.find('.edit-font-size').val(watermarkInfoObj.styles.font.fontSize);
                this.editsElement.find('.edit-font-family').val(watermarkInfoObj.styles.font.fontFamily);
            }
            this.editsElement.find('.edit-opacity').val(watermarkInfoObj.styles.opacity).trigger('change');
        }

        unique_id() {
            return 'watermark_' + Math.round(Math.random() * Math.round(Math.random() * Date.now()));
        }

        randomPosition(min, max) {
            return Math.round(Math.random() * (max - min + 1)) + min
        }

        /**
         * Initialize Text Watermark.
         * @returns TextInfo
         */
         initWaterMarkText( textObj = null ) {
            var that     = this;
            if ( textObj ) {
                var textInfo = textObj;
            } else {
                var placeholderText = localize_vars.labels.watermark + ' ' + ( watermarksCounter );
                var TextInfo        = function() {
                    this.id                = that.unique_id();
                    this.type              = 'text';
                    this.width             = 50;
                    this.height            = 30;
                    this.text              = placeholderText;
                    this.centerOffset      = false;
                    this.isRepeat          = false;
                    this.repeatAxis        = '';
                    this.repeatXAxisOffset = 200;
                    this.repeatYAxisOffset = 200;
                    this.positionSpot      = 'mm';
                    this.positionType      = 'pixel';
                    this.absLeft           = Math.round( 50 + ( 50 * Object.keys( that.watermarks ).length ) );
                    this.absTop            = Math.round( 50 + ( 50 * Object.keys( that.watermarks ).length ) );
                    this.leftPercent       = 0;
                    this.topPercent        = 0;
                    this.baselineOffset    = 5;
                    this.exactWidth        = 50;
                    this.botLeft           = 0;
                    this.botTop            = 0;
                    this.styles            = {
                        font: {
                            color: '#000000',
                            fontSize: 16,
                            fontFamily: 'verdana',
                            opacity: 1
                        },
                        opacity: 1,
                        degree: 0
                    }
                }

                var textInfo = new TextInfo();
            }
            return textInfo;
        }

        initWaterMarkImg( img, imgObj = null ) {
            var that       = this;
            if ( imgObj ) {
                var imginfo = imgObj;
            } else {
                let useExactDim = true;
                if ( img.width > previewImage['width'] || img.height > previewImage['height'] ) {
                    useExactDim = false;
                }
                var ImageInfo  = function() {
                    this.id                = that.unique_id();
                    this.type              = 'image';
                    this.width             = useExactDim ? img.width : 150;
                    this.sourceWidth       = img.width;
                    this.height            = useExactDim ? img.height : 150;
                    this.sourceHeight      = img.height;
                    this.imgID             = img.id;
                    this.centerOffset      = false;
                    this.isRepeat          = false;
                    this.repeatAxis        = '';
                    this.repeatXAxisOffset = 200;
                    this.repeatYAxisOffset = 200;
                    this.positionSpot      = 'mm';
                    this.positionType      = 'pixel';
                    this.absLeft           = Math.round( 50 + ( 50 * Object.keys( that.watermarks ).length ) );
                    this.absTop            = Math.round( 50 + ( 50 * Object.keys( that.watermarks ).length ) );
                    this.leftPercent       = 0;
                    this.topPercent        = 0;
                    this.url               = img.url;
                    this.styles            = {
                        opacity: 1,
                        degree: 0
                    }
                }
                var imginfo = new ImageInfo();
            }
            return imginfo;
        }


        updateWatermarkPlaceholderStyles( placeholderElement, styles, watermarkInfoObj ) {
            var type = watermarkInfoObj['type'];
            if ( type === 'image' ) {
                var watermarkTarget = placeholderElement.find('.img-placeholder img');
            } else if ( type === 'text' ) {
                var watermarkTarget = placeholderElement.find('.watermark-text-textarea');
            } else {
                return;
            }
            if ( watermarkTarget.length ) {
                for ( var key in styles.font ) {
                    if ( key === 'fontSize' ) {
                        watermarkTarget.css( key, styles.font[ key ] + 'px' );
                    } else {
                        watermarkTarget.css( key, styles.font[ key ] );
                    }
                }

                if ( styles.opacity ) {
                    watermarkTarget.css( 'opacity', styles.opacity );
                }

                watermarkTarget.width( watermarkInfoObj.width );
                watermarkTarget.height( watermarkInfoObj.height );

                // handle change in Rotation.
                if ( styles.degree ) {
                    this.rotateWatermarkElement( placeholderElement, styles.degree, type, watermarkInfoObj );
                }
                var pos = this.calculatePosition( watermarkInfoObj );
                placeholderElement.css( { left: pos.left + 'px', top: pos.top + 'px' } );
            }
        }

        rotateWatermarkElement( watermarkElement, degree, type, watermarkInfoObj ) {
            var transforms = 'rotate(' + degree + 'deg)';
            if ( ! watermarkInfoObj.centerOffset ) {
                watermarkElement.css('transform-origin', '0px 0px' );
                watermarkElement.css('-ms-transform-origin', '0px 0px' );
                watermarkElement.css('-webkit-transform-origin', '0px 0px' );
            } else {
                watermarkElement.css('transform-origin', 'center center' );
                watermarkElement.css('-ms-transform-origin', 'center center' );
                watermarkElement.css('-webkit-transform-origin', 'center center' );
            }
            watermarkElement.css('transform', transforms );
            watermarkElement.css('-moz-transform', transforms );
            watermarkElement.css('-webkit-transform', transforms );
            watermarkElement.css('-o-transform', transforms );

        }

        /**
         * Add Watermark to the preview image.
         * @param {object} img Selected Image Object.
         */
        addWatermark( type = 'image', img = {}, addSpecsBox = false, readyObj = null, scrollTo = true ) {
            if ( type === 'image' ) {
                // 1) initialize image Info Object.
                var imageInfoObj = this.initWaterMarkImg( img, readyObj );

                // 1) Store the Watermark.
                this.watermarks[ imageInfoObj['id'] ] = imageInfoObj;

                // 2) Trigger toggle Save Button event.
                $(document).trigger( prefixUnderscored + '_preview_watermark_save_toggle', Object.keys( this.watermarks ).length );

                // 3) Draw the watermark.
                this.drawWatermark( imageInfoObj, type, addSpecsBox, scrollTo );
            } else if ( type === 'text' ) {
               // 1) initialize text info object.
               var textInfoObj = this.initWaterMarkText( readyObj );

               // 2) store the watermark.
               this.watermarks[ textInfoObj['id'] ] = textInfoObj;

                // 3) Trigger toggle Save Button event.
                $(document).trigger( prefixUnderscored + '_preview_watermark_save_toggle', Object.keys( this.watermarks ).length );

                // 4) Draw the watermark.
                this.drawWatermark( textInfoObj, type, addSpecsBox, scrollTo );
            }

            $('.preview-watermark-preview-btn').removeClass('disabled').prop( 'disabled', false );
        }

        /**
         * Draw the Waterframe.
         * @param {ImageInfo|TextInfo} watermarkInfoObj
         */
         drawWatermark( watermarkInfoObj, type = 'image', addSpecsBox = false, scrollTo ) {
            // 1) Draw the watermark placeholder on top of the preview image.
            watermarkInfoObj = this.drawWatermarkPlaceholder( watermarkInfoObj, type, addSpecsBox );
            if ( type === 'text' && addSpecsBox ) {
                watermarkInfoObj = this.updateWatermarkTextOffsets( watermarkInfoObj );
            }

            // 2) insert the Watermark to Current Watermarks List.
            if ( addSpecsBox ) {
                this.insertWatermarkToList( watermarkInfoObj );
                // Activate the specs accordion tab.
                this.openWatermarkSpecsAccordion( watermarkInfoObj.id );
            }

            // Scroll to watermark.
            if ( scrollTo ) {
                $('html, body').animate({ scrollTop: $('.watermark-placeholder[item="' + watermarkInfoObj.id + '"]').offset().top - 300 }, 500);
            }
        }

        openWatermarkSpecsAccordion( watermarkID ) {
            let watermarkspecsBtn = $('.accordion-button[data-id="' + watermarkID + '"]');
            if ( watermarkspecsBtn && watermarkspecsBtn.hasClass( 'collapsed' ) ) {
                watermarkspecsBtn.trigger('click');
            }
        }

        resetWatermarkPlaceholder( watermarkInfoObj ) {
            this.removeWatermark( watermarkInfoObj.id, true );
            this.drawWatermarkPlaceholder( watermarkInfoObj, watermarkInfoObj.type, false );
        }

        /**
         * Draw the watermark placholder on top of the preview image.
         * @param {ImageInfo|TextInfo} watermarkInfoObj   watermark Info Object.
         */
        drawWatermarkPlaceholder( watermarkInfoObj, type, addSpecsBox ) {
            // 1) Clone the watermark placeholder.
            var watermarkElement = $('.watermark-' + type + '-placeholder-none').clone().removeClass('watermark-' + type + '-placeholder-none').removeClass('d-none').addClass('watermark-placeholder watermark-placeholder-' + type );
            // 2) set the item ID based on the watermark's.
            watermarkElement.attr('item', watermarkInfoObj.id );

            // 3) Append the watermark placeholder to the preview Image.
            var previewSelected = $('.selected-preview');
            previewSelected.after( watermarkElement );

            // 4) Set the Watermark Styles.
            this.setWatermarkStyles( watermarkInfoObj, watermarkElement );

            // 5) Insert the Watermark element.
            if ( ! this.watermarksElements[ watermarkInfoObj.id ] ) {
                this.watermarksElements[ watermarkInfoObj.id ] = {};
            }
            this.watermarksElements[ watermarkInfoObj.id ] = watermarkElement;

            var rotatableOptions = {
                containment: '.preview-selected-wrapper',
                wheelRotate: false,
                snap: true,
                step: 5.0,
                rotate: ( e, ui ) => {
                    var watermarkElement           = $(e.target);
                    var watermarkID                = watermarkElement.attr('item');
                    var degree                     = Math.round( ui.angle.current * 180 / Math.PI );
                    var watermarkInfoObj           = this.watermarks[ watermarkID ];
                    var watermarkType              = watermarkInfoObj.type;
                    watermarkInfoObj.styles.degree = degree;

                    if ( watermarkType === 'text' ) {
                        if ( degree < -90 ) {
                            degree           = -90;
                            ui.angle.current = ( Math.PI * degree / 180 );
                        } else if ( degree > 90 ) {
                            degree           = 90;
                            ui.angle.current = ( Math.PI * degree / 180 );
                        }
                    }
                    if ( degree == -0 ) {
                        degree = 0;
                    }
                    this.updateWatermarkSpecs( watermarkID, { degree: degree } );
                },
                stop: ( e, ui ) => {
                    var watermarkElement           = $(e.target);
                    var watermarkID                = watermarkElement.attr('item');
                    var degree                     = Math.round( ui.angle.current * 180 / Math.PI );
                    var watermarkInfoObj           = this.watermarks[ watermarkID ];
                    watermarkInfoObj.styles.degree = degree;
                    if ( watermarkInfoObj.type === 'text' ) {
                        watermarkInfoObj = this.updateWatermarkTextOffsets( watermarkInfoObj );
                    }
                    if ( degree == -0 ) {
                        degree = 0;
                    }
                    this.updateWatermarkSpecs( watermarkID, { degree: degree } );
                    watermarkInfoObj = this.updateWatermark( watermarkInfoObj, { degree, degree } );
                }
            }

            watermarkElement.rotatable( rotatableOptions );

            if ( type === 'text' && addSpecsBox ) {
                watermarkElement.find('.watermark-text-textarea').text( watermarkInfoObj.text );
            }

            return watermarkInfoObj;
        }

        updateWatermarkSpecs( watermarkID, specs ) {
            var watermarkSpecsBox = $( '#watermark-specs-item-' + watermarkID );
            if ( specs.width ) {
                watermarkSpecsBox.find('.edit-width').val( specs.width );
            }
            if ( specs.height !== undefined ) {
                watermarkSpecsBox.find('.edit-height').val( specs.height );
            }
            if ( specs.positionType !== undefined ) {
                watermarkSpecsBox.find( '#' + classesPrefix + '-watermark-position-type-' + specs.positionType ).prop( 'checked', true );
            }
            if ( specs.left !== undefined || specs.absLeft !== undefined ) {
                watermarkSpecsBox.find('.edit-position-left').val( specs.left || specs.absLeft )
            }
            if ( specs.top !== undefined|| specs.absTop !== undefined ) {
                watermarkSpecsBox.find('.edit-position-top').val( specs.top || specs.absTop )
            }
            if ( specs.leftPercent !== undefined ) {
                watermarkSpecsBox.find('.edit-position-left-percent').val( specs.leftPercent )
            }
            if ( specs.topPercent !== undefined ) {
                watermarkSpecsBox.find('.edit-position-top-percent').val( specs.topPercent )
            }
            if ( specs.positionSpot !== undefined ) {
                watermarkSpecsBox.find( '.' + classesPrefix + '-watermark-position-spot-' + specs.positionSpot ).prop( 'checked', true );
            }
            if ( specs.botLeft !== undefined ) {
                watermarkSpecsBox.find('.edit-position-botleft').val( specs.botLeft );
            }
            if ( specs.botTop !== undefined ) {
                watermarkSpecsBox.find('.edit-position-bottop').val( specs.botTop );
            }
            if ( specs.exactWidth !== undefined ) {
                watermarkSpecsBox.find('.edit-position-exactwidth').val( specs.exactWidth );
            }
            if ( specs.baselineOffset !== undefined ) {
                watermarkSpecsBox.find('.edit-position-baselineoffset').val( specs.baselineOffset );
            }
            if ( specs.degree !== undefined ) {
                watermarkSpecsBox.find('.edit-degree').val( ( specs.degree < 0 ) ? ( 360 - Math.abs( specs.degree ) ) : specs.degree ).trigger('change')
            }
        }

        setWatermarkStyles( watermarkInfoObj, watermarkElement ) {
            if ( watermarkInfoObj.type === 'image' ) {
                watermarkElement.find('.img-placeholder img').attr( 'src', watermarkInfoObj.url );
            }

            var watermarkTextarea = watermarkElement.find('.watermark-text-textarea');
            var lineHeight        = watermarkTextarea.css('line-height');

            if ( watermarkInfoObj.type === 'text' ) {
                watermarkElement.css({
                    'font-family': watermarkInfoObj.styles.font.fontFamily,
                    'font-size': watermarkInfoObj.styles.font.fontSize,
                    'width': 'auto !important',
                    'height': 'auto !important'
                });
                watermarkTextarea.css({
                    'color': watermarkInfoObj.styles.font.color
                });
            } else if ( watermarkInfoObj.type === 'image' ) {
                watermarkElement.find('img').css({
                    'width': watermarkInfoObj.width,
                    'height': watermarkInfoObj.height
                });
            }

            // 4) Set the offset.
            watermarkElement.position({
                my: "left top",
                at: "left top",
                of: "#selected-preview-container",
                collision: "fit"
            });
            var pos = this.calculatePosition( watermarkInfoObj );
            watermarkElement.css({
                left: pos.left,
                top: pos.top
            });
        }

        repeatWatermarkPlaceholder( watermarkInfoObj ) {
            // console.log( '==== repeat watermarks Placeholder ====', watermarkInfoObj );
            if ( ! watermarkInfoObj.isRepeat || ! watermarkInfoObj.repeatAxis.length ) {
                // 1) Clear previous repeats
                this.clearRepeatedPlaceholder( watermarkInfoObj.id );
                return;
            }
            // console.log( '=== preview Image ===', previewImage );
            var previewWidth  = previewImage.width;
            var previewHeight = previewImage.height;
            var XaxisOffset   = parseInt( watermarkInfoObj.repeatXAxisOffset );
            var YaxisOffset   = parseInt( watermarkInfoObj.repeatYAxisOffset );
            var pos           = this.calculatePosition( watermarkInfoObj );

            // 1) Clear previous repeats
            this.clearRepeatedPlaceholder( watermarkInfoObj.id );
            // 2) Start drawing the repeats based on the axis.
            var x = pos.left;
            var y = pos.top;
            if ( watermarkInfoObj.repeatAxis === 'x' ) {
                if ( XaxisOffset <= 0 ) {
                    return;
                }
                x += XaxisOffset;
                while ( x < previewWidth ) {
                    this.drawRepeatedPlaceholder( watermarkInfoObj, x, y );
                    x += XaxisOffset;
                }
            } else if ( watermarkInfoObj.repeatAxis === 'y' ) {
                if ( YaxisOffset <= 0 ) {
                    return;
                }
                y += YaxisOffset;
                while ( y < previewHeight ) {
                    this.drawRepeatedPlaceholder( watermarkInfoObj, x, y );
                    y += YaxisOffset;
                }
            } else if ( watermarkInfoObj.repeatAxis === 'diagonal' ) {
                if ( XaxisOffset > 0 && YaxisOffset > 0 ) {
                    while( x < previewWidth && y < previewHeight ) {
                        x += XaxisOffset;
                        y += YaxisOffset;
                        this.drawRepeatedPlaceholder( watermarkInfoObj, x, y );
                    }
                }
            } else if ( watermarkInfoObj.repeatAxis === 'both' ) {
                if ( XaxisOffset > 0 ) {
                    x += XaxisOffset;
                    while ( x < previewWidth ) {
                        this.drawRepeatedPlaceholder( watermarkInfoObj, x, y );
                        x += XaxisOffset;
                    }
                    x  = pos.left;
                }
                if ( YaxisOffset > 0 ) {
                    y += YaxisOffset;
                    while ( y < previewHeight ) {
                        this.drawRepeatedPlaceholder( watermarkInfoObj, x, y );
                        y += YaxisOffset;
                    }
                }

            } else if ( watermarkInfoObj.repeatAxis === 'full' ) {
                x += XaxisOffset;
                if ( YaxisOffset <= 0 || XaxisOffset <= 0 ) {
                    return;
                }
                while ( y < previewHeight ) {
                    if ( YaxisOffset > 0 ) {
                        while ( x < previewWidth ) {
                            this.drawRepeatedPlaceholder( watermarkInfoObj, x, y );
                            x += XaxisOffset;
                        }
                        x  = pos.left;
                    }
                    y += YaxisOffset;
                }
            }
        }

        /**
         * Draw repeated Watermark Placeholder.
         *
         */
        drawRepeatedPlaceholder( watermarkInfoObj, left, top ) {
            var watermarkElement = $('.watermark-placeholder[item="' + watermarkInfoObj.id + '"]');
            var placeholder      = watermarkElement.find('.watermark-placeholder-wrapper');
            var repeatedCloned   = placeholder.clone();

            repeatedCloned.addClass('position-absolute w-auto repeat-clone repeat-clone-' + watermarkInfoObj.id )
            repeatedCloned.find('.watermark-text-textarea').css( { 'background':'transparent', 'width': placeholder.find('.watermark-text-textarea').width() });

            if ( watermarkInfoObj.type === 'text' ) {
                repeatedCloned.find('.watermark-text-textarea').attr('contenteditable', false);
            }
            repeatedCloned.css({
                width: watermarkElement.width(),
                height: watermarkElement.height(),
                left: left,
                top: top
            });
            if ( watermarkInfoObj.styles.degree ) {
                this.rotateWatermarkElement( repeatedCloned, watermarkInfoObj.styles.degree, watermarkInfoObj.type, watermarkInfoObj );
            }
            $('#selected-preview-container .repeated-clones-wrapper').append( repeatedCloned );
        }

        clearRepeatedPlaceholder(watermarkID) {
            $('.repeat-clone-' + watermarkID).remove();
        }

        updateWatermarkTextOffsets( watermarkInfoObj ) {
            var width                = Math.round( parseFloat( $('[item="' + watermarkInfoObj.id + '"]' ).find('.watermark-text-textarea').css('width') ) );
            var height               = Math.round( parseFloat( $('[item="' + watermarkInfoObj.id + '"]' ).find('.watermark-text-textarea').css('height') ) );
            watermarkInfoObj.width   = width;
            watermarkInfoObj.height  = height;
            watermarkInfoObj.botLeft = Math.round( watermarkInfoObj.absLeft ) - Math.round( ( height ) * Math.sin( watermarkInfoObj.styles.degree * Math.PI / 180 ) );
            watermarkInfoObj.botTop  = Math.round( watermarkInfoObj.absTop ) + Math.round( ( height ) * Math.cos( watermarkInfoObj.styles.degree * Math.PI / 180 ) );
            this.directUpdateWatermark( watermarkInfoObj );
            this.updateWatermarkSpecs( watermarkInfoObj.id, { width: width, height: height, botTop: Math.round( watermarkInfoObj.botTop ), botLeft: Math.round( watermarkInfoObj.botLeft ) } );

            return watermarkInfoObj;
        }

        removeWatermark( watermarkID, isRefresh = false ) {
            // 1) Remove the watermark record.
            delete this.watermarks[ watermarkID ];
            delete this.watermarksElements[ watermarkID ];
            // 2) Trigger toggle Save Button event.
            $(document).trigger( prefixUnderscored + '_preview_watermark_save_toggle', Object.keys( this.watermarks ).length );
            // 3) Remove placeholder.
            var placeholderElement = $('.watermark-placeholder[item="' + watermarkID + '"]');
            placeholderElement.remove();
            // 4) Remove repeated clones.
            $('.repeat-clone-' + watermarkID ).remove();
            if ( ! isRefresh ) {
                // 5) Remove Watermark Specs Box.
                $('#' + watermarkID + '_header' ).remove();
                $('#' + watermarkID + '_specs' ).remove();
                // 6) Reset Watermark Spces Accordion.
                var accordionHeaders = $('.watermark-specs-header');
            }
        }

        directUpdateWatermark(watermarkInfoObj) {
            var watermarkID = watermarkInfoObj.id;
            this.watermarks[watermarkID] = watermarkInfoObj;
        }

        updateWatermark( watermarkInfoObj, updates ) {
            if ( updates.position ) {
                if ( updates.position.positionType !== undefined ) {
                    watermarkInfoObj.positionType  = updates.position.positionType;
                }
                if ( updates.position.top !== undefined ) {
                    watermarkInfoObj.absTop  = Math.round( updates.position.top );
                }
                if ( updates.position.left !== undefined ) {
                    watermarkInfoObj.absLeft = Math.round( updates.position.left );
                }
                if ( updates.position.positionSpot !== undefined ) {
                    watermarkInfoObj.positionSpot = updates.position.positionSpot;
                }
                if ( updates.position.topPercent !== undefined ) {
                    watermarkInfoObj.topPercent  = updates.position.topPercent;
                }
                if ( updates.position.leftPercent !== undefined ) {
                    watermarkInfoObj.leftPercent = updates.position.leftPercent;
                }
                if ( updates.position.centerOffset !== undefined ) {
                    watermarkInfoObj.centerOffset = updates.position.centerOffset;
                }
            }

            if ( updates.size ) {
                if ( updates.size.width !== undefined ) {
                    watermarkInfoObj.width  = updates.size.width;
                }
                if ( updates.size.height !== undefined ) {
                    watermarkInfoObj.height = updates.size.height;
                }
            }

            if ( updates.text ) {
                watermarkInfoObj.text = updates.text;
            }

            if ( updates[ prefixUnderscored + '_updates_additional' ] ) {
                watermarkInfoObj = Object.assign( watermarkInfoObj, updates[ prefixUnderscored + '_updates_additional' ] );
            }

            if ( updates.degree ) {
                watermarkInfoObj.styles.degree = updates.degree;
            }

            if ( updates.width !== undefined ) {
                watermarkInfoObj.width = updates.width;
            }
            if ( updates.height !== undefined ) {
                watermarkInfoObj.height = updates.height;
            }

            this.watermarks[ watermarkInfoObj.id ] = watermarkInfoObj;
            return watermarkInfoObj;
        }

        updateWatermarkStyles( watermarkInfoObj, styles ) {
            if ( initCurrentWatermarksFlag ) {
                return watermarkInfoObj;
            }
            if ( watermarkInfoObj.type === 'text' ) {
                var textHeights                 = this.getTextHeight( styles.font['fontFamily'], styles.font['fontSize'], this.watermarksElements[ watermarkInfoObj.id ].text() );
                var textWidth                   = this.getTextWidth( styles.font['fontFamily'], styles.font['fontSize'], this.watermarksElements[ watermarkInfoObj.id ].text() );
                watermarkInfoObj.baselineOffset = textHeights.descent;
                watermarkInfoObj.exactWidth     = textWidth;
                this.updateWatermarkSpecs( watermarkInfoObj['id'], { exactWidth: textWidth, baselineOffset: textHeights.descent } );
            }

            watermarkInfoObj.styles                = $.extend( true, watermarkInfoObj.styles, styles );
            this.watermarks[ watermarkInfoObj.id ] = watermarkInfoObj;
            return watermarkInfoObj;
        }

        displayWatermark(watermarkInfoObj) {
            var imgItem = $('.watermark-item');
            var src = watermarkInfoObj['url'];
            imgItem.find('img').attr('src', src);
            imgItem.removeClass('d-none');
        }

        initImage() {
            $('.selected-preview').attr('src', previewImage.url);
            $('.selected-preview').on('load', () => {
                this.createPreviewOverlayers();
            });
        }

        createPreviewOverlayers() {
            var overlayer = `<div class="preview-overlayer" width="` + previewImage['width'] + `" height="` + previewImage['height'] + `"></div>`;
            var previewSelected = $('.selected-preview');
            previewSelected.after(overlayer);

            previewImage.width  = Math.floor( previewSelected.width() );
            previewImage.height = Math.floor( previewSelected.height() );
        }

        /**
         * Preview Watermarks.
         */
        previewWatermarks(e) {
            e.preventDefault();
            $('.save-section').collapse('hide');
            $('.preview-result').addClass('d-none');
            $('.img-media-icon-box').remove();
            var createBtn = $(e.target);
            this.loading(createBtn, true);
            var watermarks = Object.values(this.watermarks);
            var data = {
                action: localize_vars.previewWatermarkstemplateAction,
                nonce: localize_vars['nonce'],
                preview_img: previewImage,
                watermarks: watermarks
            };
            $.ajax({
                method: 'POST',
                url: localize_vars['ajaxUrl'],
                dataType: 'json',
                data: data,
                success: (resp) => {
                    if (resp['data'] && resp['data']['msg']) {
                        showToast(resp['data']['msg'], 'bg-' + resp['data']['status']);
                    }
                    if (resp['success']) {
                        this.displayPreview(resp['data']['result']);
                        // Show Save Section.
                        $('.save-section').collapse('show');
                    }
                },
                error: (err) => {
                    // console.log( 'error preview watermarks', err );
                    if ( err?.data?.msg ) {
                        showToast(err['data']['msg'], 'bg-danger');
                    }
                },
                complete: () => {
                    this.loading(createBtn, false);
                }
            });
        }

        displayPreview(gifURL) {
            var gifResultElement = $('.preview-result');
            gifResultElement.find('.preview-img').attr('src', gifURL);
            gifResultElement.removeClass('d-none');
        }

        displayGifIcon(html) {
            var gifDisplayElement = $('.gif-icon-box-container');
            gifDisplayElement.html(html);
            gifDisplayElement.removeClass('d-none');
        }

        loading(btn, starting = true) {
            // All Buttons.
            if (starting) {
                btn.addClass('disabled').prop('disabled', true);
                btn.siblings('.spinner').addClass('is-active');
            } else {
                btn.removeClass('disabled').prop('disabled', false);
                btn.siblings('.spinner').removeClass('is-active disabled');
            }
        }

        rgba2hex(rgba) {
            return `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`
        }

        getTextHeight(fontFamily, fontSize, text) {
            var text = $('<span>' + $.trim(text) + '</span>').css({
                fontFamily: fontFamily,
                fontSize: fontSize + 'px'
            });
            var block = $('<div style="display: inline-block; width: 1px; height: 0px;"></div>');

            var div = $('<div></div>');
            div.append(text, block);

            var body = $('body');
            body.append(div);

            try {

                var result = {};

                block.css({
                    verticalAlign: 'baseline'
                });
                result.ascent = block.offset().top - text.offset().top;

                block.css({
                    verticalAlign: 'bottom'
                });
                result.height = block.offset().top - text.offset().top;

                result.descent = result.height - result.ascent;

            } finally {
                div.remove();
            }

            return result;
        }

        getTextWidth(fontFamily, fontSize, text) {
            var context = this.canvas.getContext("2d");
            context.font = Math.round(parseInt(fontSize)) + 'px ' + fontFamily;
            var metrics = context.measureText($.trim(text));
            return Math.round(Math.abs(metrics.actualBoundingBoxRight) - Math.abs(metrics.actualBoundingBoxLeft));
        }

    }

    function showToast(toastMsg, bgColor = 'bg-primary', is_multiple = false) {
        var toast = $('.' + localize_vars['classes_prefix'] + '-msgs-toast');
        toast.removeClass((index, className) => {
            return (className.match(/(^|\s)bg-\S+/g) || []).join(' ');
        });
        var html = '';
        if (!is_multiple) {
            html = '<p>' + toastMsg + '</p>';
        } else {
            html = '<ul class="toast-notice-list">'
            toastMsg.forEach((msg) => {
                html += '<li class="notice-item"><p>' + msg + '</p></li>';
            });
            html += '</ul>';
        }
        toast.addClass(bgColor).find('.toast-body').html(html);
        toast.toast('show')
    }

})(jQuery);
