(function( $, api ){
    const dynamicCSS = {},
        googleFonts = {},
        tabletWidth = '@media(max-width: 768px)',
        mobileWidth = '@media(max-width: 610px)',
        googleFontsURL = 'https://fonts.googleapis.com/css2',
        previewControlIds = Object.keys( BlogBeeConfig )

    var preloaderTimeout = ''

    /**
     * Utility functions
     * 
     * @since 1.0.0
     * @var { object }
     */
    const Utils = {
        /**
         * Get control function
         * 
         * @since 1.0.0
         * @param { string }    type    control type
         * @return { string }   function to call from PreviewJs object
         */
        getControlFunction: function( type ) {
            const allControlTypes = {
                'box-shadow': 'boxShadow',
                'typography': 'typography',
                'bbe-number': 'number',
                'border': 'border',
                'dimension': 'dimension',
                'bbe-color': 'color',
                'radio-tab': 'toggleClass',
                'radio-image': 'toggleClass',
                'select': 'toggleClass',
                'bbe-select': 'toggleClass',
                'toggle-button': 'toggleButton',
                'bbe-text': 'text',
                'bbe-textarea': 'text',
            }
            return allControlTypes[ type ]
        },
        /**
         * Add style tag for dynamic css
         * 
         * @since 1.0.0
         * @param { css }   css     The css to append to style tag
         * @return { void }
         */
        generateStyleTag: function( id, css ) {
            dynamicCSS[ id ] = css
            let styleTag = $( '#blog-bee-dynamic-preview' );

            if( ! styleTag.length ) styleTag = $( '<style id="blog-bee-dynamic-preview"></style>' ).appendTo( 'head' );

            let allCSS = Object.values( dynamicCSS ).join( '\n' );
            styleTag.html( allCSS );
        },
        /**
         * Add link tag for dynamic google fonts
         * 
         * @since 1.0.0
         * @param { css }   css     The css to append to style tag
         * @return { void }
         */
        generateGoogleFontsLinkTag: function( id, font ) {
            const { font_family, font_weight } = font
            let family = font_family.value,
                { weight, style } = this.getFontWeightAndStyle( font_weight )

            // add only one font family for one control
            googleFonts[ id ] = {
                family,
                weight,
                style
            }

            let linkTag = $( '#blog-bee-google-fonts' ),
                href = this.getFullGoogleFontsLink()

            if( ! linkTag.length ) linkTag = $( '<link id="blog-bee-google-fonts" rel="stylesheet"></link>' ).appendTo( 'head' );
            linkTag.attr( 'href', href )
        },
        /**
         * Get font weight and style in an object
         * 
         * @since 1.0.0
         */
        getFontWeightAndStyle: function( fontWeight ) {
            let weight = "400", style = ''
            if( fontWeight === 'regular' ) {
                style = 'normal'
            } else if( fontWeight === 'italic' ) {
                style = 'italic'
            } else if( [ '100', '200', '300', '500', '600', '700', '800', '900' ].includes( fontWeight ) ) {
                weight = fontWeight
                style = 'normal'
            } else {
                let regex = fontWeight.match( /^(\d+)([a-z]+)$/i )
                weight = String( regex[ 1 ] ),
                style = regex[ 2 ]
            }

            return {
                weight,
                style
            }
        },
        /**
         * Get filtered fonts
         * 
         * @since 1.0.0
         * @returns { object }
         */
        filterGoogleFonts: function() {
            return Object.values( googleFonts ).reduce( ( _this, args ) => {
                let { family, weight, style } = args

                // if font family doesn't exist
                if( ! Object.hasOwn( _this, family ) ) {
                    _this[ family ] = {
                        weight: [ weight ],
                        style: [ style ],
                    }
                }
    
                // if font weight doesn't exist
                if( ! _this[ family ].weight.includes( weight ) ) {
                    _this[ family ].weight.push( weight )
                }
    
                // if font style doesn't exist
                if( ! _this[ family ].style.includes( style ) ) {
                    _this[ family ].style.push( style )
                }

                return _this
            }, {} )
        },
        /**
         * Get full google fonts link
         * 
         * @since 1.0.0
         * @returns { string }
         */
        getFullGoogleFontsLink: function() {
            let filteredFonts = this.filterGoogleFonts(),
                fontArgs = Object.entries( filteredFonts ).reduce( ( _this, font ) => {
                    let [ family, args ] = font,
                        { weight, style } = args,
                        joinedWeights = weight.join( ';' ),
                        includesItalic = style.includes( 'italic' ),
                        prefix = includesItalic ? 'ital,' : "",
                        weights = includesItalic ? `0,${ weight.join( ';0,' ) };1,${ weight.join( ';1,' ) }` : joinedWeights

                    return [
                        ..._this,
                        `family=${ family }:${ prefix }wght@${ weights }`
                    ]
                }, [] ).join( '&' )
            
            return `${ googleFontsURL }?${ fontArgs }&display=swap`
        },
        /**
         * Get color css
         * 
         * @since 1.0.0
         * @param { array } color   The color value saved in db
         * @param { string } property   The css property
         * @returns { css }
         */
        getColorCss: function( color, property ) {
            let { type, value } = color
            if( type === 'image' ) {
                let css = ''
                if( Object.hasOwn( color, 'image' ) ) {
                    let image = color.image
                    let backgroundImage = ''
                    if( Object.hasOwn( image, 'overlay' ) ) {
                        let overlay = image.overlay
                        if( overlay.includes( '--bbe-' ) ) {
                            overlay = `var(${ overlay })`
                        }
                        backgroundImage += `${ overlay }, `
                    }
                    if( Object.hasOwn( image, 'url' ) ) {
                        backgroundImage += `url(${ image.url })`
                    }
                    if( backgroundImage ) {
                        css += `background-image: ${ backgroundImage };`
                    }
                    if( Object.hasOwn( image, 'position' ) ) css += `background-position: ${ image.position };`
                    if( Object.hasOwn( image, 'repeat' ) ) css += `background-repeat: ${ image.repeat };`
                    if( Object.hasOwn( image, 'size' ) ) css += `background-size: ${ image.size };`
                }
                return css
            } else {
                let css = ''
                if( value.includes( '--bbe-' ) ) {
                    value = `var(${ value })`
                }
                if( type === 'gradient' ) {
                    css += `background-image: ${ value };`
                } else {
                    css += `background-image: initial;`
                }
                css += `
                    ${ property }: ${ value };
                    background-position: initial;
                    background-repeat: initial;
                    background-size: initial;
                `
                return css
            }
        },
        /**
         * Convert integer to word
         * 
         * @since 1.0.0
         * @param   { int } param   Integer to convert to word
         */
        convertToWord: function( param ) {
            switch( param ) {
                case 2:
                        return 'two'
                    break;
                case 3:
                        return 'three'
                    break;
                case 4:
                        return 'four'
                    break;
                case 5:
                        return 'five'
                    break;
                case 6:
                        return 'six'
                    break;
                case 7:
                        return 'seven'
                    break;
                case 8:
                        return 'eight'
                    break;
                case 9:
                        return 'nine'
                    break;
                case 10:
                        return 'ten'
                    break;
                default: 
                        return 'one'
                    break;
            }
        },
        /**
         * Toggle Class
         * 
         * @since 1.0.0
         * @param { Object } props
         * @param { String } props.selector Selector to target and reflect chenges in
         * @param { String } props.prefix   Prefix of class to change
         * @param { String|Int } props.value    Suffix to append after prefix class
         */
        toggleClass: function( props ) {
            const { selector, prefix, value } = props,
                self = this,
                sanitizedValue = Number.isInteger( value ) ? this.convertToWord( value ) : value

            $( selector ).removeClass( function( index, currentClass ) {
                let regex = new RegExp( "\\b" + prefix + "\\S*", 'g' )
                return currentClass.match( regex || [] )?.join( ' ' )
            } ).addClass( `${ prefix }${ sanitizedValue }` )
        },
        /**
         * Initialize preloader
         * 
         * @since 1.0.0
         */
        initPreloader() {
            clearTimeout( preloaderTimeout )
            let preloader = $( '.bbe-preloader' )
            preloader.removeClass( 'hidden' )
            preloaderTimeout = setTimeout( function() {
                preloader.addClass( 'hidden' )
            }, 3000 )
        }
    }
    
    /**
     * Collection of functions to use to update preview
     * 
     * @since 1.0.0
     * @var { object }
     */
    const PreviewJs = {
        /**
         * MARK: Border
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        border: function( id, value ) {
            const { color, style, width } = value,
                { top, right, bottom, left } = width,
                { selector, property = 'border' } = BlogBeeConfig[ id ]

            let colorCode = color;
            if( color.includes( '--bbe-' ) ) {
                colorCode = `var(${ color })`
            }

            let css = ''

            if( style === 'none' ) {
                css = `
                    ${ selector } {
                        ${ color ? `${ property }-color: unset;` : ''}
                        ${ style ? `${ property }-style: none;` : ''}
                        ${ width ? `${ property }-width: 0;` : ''}
                    }
                `
            } else {
                css = `
                    ${ selector } {
                        ${ color ? `${ property }-color: ${ colorCode };` : ''}
                        ${ style ? `${ property }-style: ${ style };` : ''}
                        ${ width ? `${ property }-width: ${ top }px ${ right }px ${ bottom }px ${ left }px;` : ''}
                    }
                `;
            }

            Utils.generateStyleTag( id, css )
        },
        
        /**
         * MARK: Number
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        number: function( id, value ) {
            const { selector, property, prefix } = BlogBeeConfig[ id ]

            if( prefix ) {
                this.responsiveToggleClass( id, value )
                return
            }

            let css = ''

            if( ( typeof value === 'object' ) && ( 'desktop' in value ) ) {
                const { desktop, tablet, mobile } = value
                css = `
                    ${ selector } {
                        ${ property }: ${ desktop };
                    }
                    ${ tabletWidth } { ${ selector } {
                        ${ property }: ${ tablet };
                    } }
                    ${ mobileWidth } { ${ selector } {
                        ${ property }: ${ mobile };
                    } }
                `
            } else {
                css = `
                    ${ selector } {
                        ${ property }: ${ value };
                    }
                `
            }

            Utils.generateStyleTag( id, css )
        },

        /**
         * MARK: Box shadow
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        boxShadow: function( id, value ) {
            const { selector, property = 'box-shadow' } = BlogBeeConfig[ id ],
                { enable, offsetx, offsety, inset, blur, spread, color } = value,
                type = inset ? 'inset' : ''

            let colorCode = color

            if( color.includes( '--bbe-' ) ) {
                colorCode = `var(${ color })`
            }

            let css = ''

            if( enable ) {
                css = `
                    ${ selector } {
                        ${ property }: ${ type } ${ offsetx }px ${ offsety }px ${ blur }px ${ spread }px ${ colorCode };
                    }
                `
            } else {
                css = `
                    ${ selector } {
                        ${ property }: none;
                    }
                `
            }
            
            Utils.generateStyleTag( id, css )
        },

        /**
         * MARK: Dimension
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        dimension: function( id, value ) {
            const { selector, property } = BlogBeeConfig[ id ]

            let css = ''

            if( ( typeof value === 'object' ) && ( 'desktop' in value ) ) {
                const { desktop, tablet, mobile } = value
                css = `
                    ${ selector } {
                        ${ property }: ${ desktop.top }px ${ desktop.right }px ${ desktop.bottom }px ${ desktop.left }px;
                    }
                    ${ tabletWidth } { ${ selector } {
                        ${ property }: ${ tablet.top }px ${ tablet.right }px ${ tablet.bottom }px ${ tablet.left }px;
                    } }
                    ${ mobileWidth } { ${ selector } {
                        ${ property }: ${ mobile.top }px ${ mobile.right }px ${ mobile.bottom }px ${ mobile.left }px;
                    } }
                `
            } else {
                css = `
                    ${ selector } {
                        ${ property }: ${ value.top }px ${ value.right }px ${ value.bottom }px ${ value.left }px;
                    }
                `
            }

            Utils.generateStyleTag( id, css )
        },

        /**
         * MARK: Typography
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        typography: function( id, value ) {
            const { selector } = BlogBeeConfig[ id ],
                { font_family, font_weight, font_size, line_height, letter_spacing, text_decoration, text_transform } = value,
                { weight, style } = Utils.getFontWeightAndStyle( font_weight )

            Utils.generateGoogleFontsLinkTag( id, {
                font_family,
                font_weight
            } )

            let css = `
                ${ selector } {
                    --bbe-site-font-family: '${ font_family.value }';
                    --bbe-site-font-weight: ${ weight };
                    --bbe-site-font-size: ${ font_size.desktop };
                    --bbe-site-font-lineheight: ${ line_height.desktop };
                    --bbe-site-font-letterspacing: ${ letter_spacing.desktop };
                    --bbe-site-font-decoration: ${ text_decoration };
                    --bbe-site-font-texttransform: ${ text_transform };
                    --bbe-site-font-style: ${ style };
                }
                ${ tabletWidth } { ${ selector } {
                    --bbe-site-font-size: ${ font_size.tablet };
                    --bbe-site-font-lineheight: ${ line_height.tablet };
                    --bbe-site-font-letterspacing: ${ letter_spacing.tablet };
                } }
                ${ mobileWidth } { ${ selector } {
                    --bbe-site-font-size: ${ font_size.mobile };
                    --bbe-site-font-lineheight: ${ line_height.mobile };
                    --bbe-site-font-letterspacing: ${ letter_spacing.mobile };
                } }
            `

            Utils.generateStyleTag( id, css )
        },

        /**
         * MARK: Color
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        color: function( id, value ) {
            const { selector, property } = BlogBeeConfig[ id ],
                isHoverType = Object.hasOwn( value, 'hover' )

            let color = value,
                hoverColor = '',
                hoverSelector = `${ selector }:hover`,
                hoverSelectorExists = false,
                hoverProperty = property

            if( isHoverType ) {
                let { initial, hover } = value
                color = initial
                hoverColor = hover
                hoverSelectorExists = true
            }

            if( Object.hasOwn( BlogBeeConfig[ id ], 'hover_selector' ) ) {
                hoverSelector = BlogBeeConfig[ id ].hover_selector
            }

            let css = `
                ${ selector } { ${ Utils.getColorCss( color, property ) } }
            `
            if( hoverSelectorExists && isHoverType ) {
                if( property.includes( '--bbe' ) ) {
                    hoverProperty = property + '-hover'
                }
                css += `
                    ${ hoverSelector } { ${ Utils.getColorCss( hoverColor, hoverProperty ) } }
                `
            }
            Utils.generateStyleTag( id, css )
        },

        /**
         * MARK: Toggle class
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        toggleClass: function( id, value ) {
            const { selector, prefix } = BlogBeeConfig[ id ]

            if( prefix ) {
                Utils.toggleClass( { selector, prefix, value } )
            } else {
                $( selector ).toggleClass( value )
            }
        },
        /**
         * MARK: Responsive Toggle class
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        responsiveToggleClass: function( id, value ) {
            const { selector, prefix } = BlogBeeConfig[ id ],
                tabletPrefix = `tablet-${ prefix }`,
                mobilePrefix = `mobile-${ prefix }`

            Utils.toggleClass( { selector, prefix, value: value[ 'desktop' ] } )
            Utils.toggleClass( { selector, prefix: tabletPrefix, value: value[ 'tablet' ] } )
            Utils.toggleClass( { selector, prefix: mobilePrefix, value: value[ 'mobile' ] } )
        },
        /**
         * MARK: Text
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        text: function( id, value ) {
            const { selector, parent, context } = BlogBeeConfig[ id ]

            if( $( selector ).length ) {
                $( selector ).text( value )
            } else {
                $( parent ).prepend( `<span class="label">${ value }</span>` )
            }

            if( value.length ) {
                $( selector ).show()
                $( selector ).parents( '.section-head' ).removeClass( 'empty' )
            } else {
                $( selector ).hide()
                $( selector ).parents( '.section-head' ).addClass( 'empty' )
            }
        },
        /**
         * MARK: Toggle Button
         * 
         * @since 1.0.0
         * @param { string }    id  updated value of the setting
         * @param { array }     value   updated value of the setting
         */
        toggleButton: function( id, value ) {
            const { selector, on = '', off = '', prefix = '' } = BlogBeeConfig[ id ]

            $( selector ).toggleClass( `${ prefix }${ off } ${ prefix }${ on }` )
        }
    }
    
    $( document ).ready( function(){
        for( const controlId of previewControlIds ) {
            api( controlId, function( setting ) {
                setting.bind( function( value ) {
                    const settingArgs = BlogBeeConfig[ controlId ],
                        { type } = settingArgs,
                        functionToCall = Utils.getControlFunction( type )
    
                    PreviewJs[ functionToCall ]( controlId, value )
                } )
            } )
        }
        
        api.preview.bind( 'bbe-section-expanded-id', function( sectionParams ) {

            if( ! sectionParams ) return
            
            const { id, section_to_open } = sectionParams
            
            if( ( id === 'header_builder_section' ) || ( ( section_to_open === 'header_builder_section' ) && ( id !== 'sticky_header_section' ) ) ) {
                $( 'html, body' ).animate( {
                    scrollTop: $( '#masthead' ).offset().top
                }, 800 )
            }
            if( ( id === 'footer_builder_section' ) || ( section_to_open === 'footer_builder_section' ) ) {
                $( 'html, body' ).animate( {
                    scrollTop: $( '#colophon' ).offset().top
                }, 800 )
            }
            if( ( id === 'sticky_header_section' ) || ( section_to_open === 'sticky_header_section' ) ) {
                $( 'html, body' ).animate( {
                    scrollTop: $( '#masthead' ).offset().top + 500
                }, 800 )
            }
        } )

        // Icon style
        const iconStyleControls = {
            'search_icon_style': '.bbe-search-icon',
            'social_icons_style': '.bbe-header .bbe-social-icons',
            'footer_social_icons_style': '.bbe-footer-social-icons',
            'off_canvas_icon_style': '.bbe-off-canvas .canvas-icon',
            'carousel_view_all_button_style': '.bbe-carousel .view-all',
            'category_collection_style': '#bbe-category-collection',
            'pagination_style': '.bbe-pagination',
            'post_buttons_style': '.entry-read-more',
            'post_button_style': '.bbe-post-button.entry-read-more',
            'error_button_style': '.bbe-error-404 .error-actions',
            'category_colors_style': 'body',
            'error_icon_style': 'body.error404 #primary .error-actions .error-button',
        }
        for( const controlId in iconStyleControls ) {
            api( controlId, function( setting ) {
                setting.bind( function( value ) {
                    const element = $( iconStyleControls[ controlId ] )
                    if( element.length ) {
                        if( 'category_colors_style' === controlId ) {
                            let classes = value.replaceAll( ' ', ' cat-' )
                            element.removeClass( 'cat-default cat-filled cat-outline cat-rounded cat-circle cat-square' )
                            element.addClass( `cat-${ classes }` )
                        } else {
                            element.removeClass( 'filled outline rounded circle square' ).addClass( value )
                        }
                    }
                } )
            } )
        }

        // Column controls
        const columns = {
            'archives_grid_number_of_column': 'body.home.blog .blog-bee-main-wrap, body.blog .blog-bee-main-wrap',
            'archives_list_number_of_column': 'body.home.blog .blog-bee-main-wrap, body.blog .blog-bee-main-wrap',
            'related_posts_number_of_columns': 'body.single .bbe-related-posts',
            'ticker_news_number_of_columns': '.bbe-ticker-news',
            'category_collection_number_of_columns': '.bbe-category-collection',
            'three_blocks_left_number_of_columns': '.bbe-three-blocks .block-item.left-block',
            'three_blocks_center_number_of_columns': '.bbe-three-blocks .block-item.center-block',
            'three_blocks_right_number_of_columns': '.bbe-three-blocks .block-item.right-block',
            'post_grid_number_of_column': '.bbe-post-grid',
            'post_list_number_of_column': '.bbe-post-list',
            'ymhm_number_of_column': '.bbe-you-may-have-missed',
        }
        for( const controlId in columns ) {
            api( controlId, function( setting ) {
                setting.bind( function( value ) {
                    let selector = columns[ controlId ]
                    if( typeof value === 'object' ) {
                        Utils.toggleClass( { selector, prefix: 'column--', value: value[ 'desktop' ] } )
                        Utils.toggleClass( { selector, prefix: 'tab-column--', value: value[ 'tablet' ] } )
                        Utils.toggleClass( { selector, prefix: 'mobile-column--', value: value[ 'mobile' ] } )
                    } else {
                        Utils.toggleClass( { selector, prefix: 'column--', value: value } )
                    }
                } )
            } )
        }

        // Solid preset colors
        api( 'solid_color_palette', function( setting ) {
            setting.bind( function( value ) {
                let { palettes, active } = value

                let preset = palettes[ active ]

                for( let i = 0; i < preset.length; i++ ) {
                    $( 'body' )[ 0 ].style.setProperty( `--bbe-solid-preset-${ i + 1 }`, preset[ i ] );
                }
            } )
        } )

        // gradient preset colors
        api( 'gradient_color_palette', function( setting ) {
            setting.bind( function( value ) {
                let { palettes, active } = value

                let preset = palettes[ active ]

                for( let i = 0; i < preset.length; i++ ) {
                    $( 'body' )[ 0 ].style.setProperty( `--bbe-gradient-preset-${ i + 1 }`, preset[ i ] );
                }
            } )
        } )

        // preloader style
        api( 'preloader_style', function( setting ) {
            setting.bind( function( value ) {
                Utils.toggleClass( { selector: '.bbe-preloader', prefix: 'style--', value } )
                Utils.initPreloader()
            } )
        } )

        // preloader color
        api( 'preloader_color', function( setting ) {
            setting.bind( function( value ) {
                if( ! Object.hasOwn( BlogBeeConfig, 'preloader_color' ) ) {
                    BlogBeeConfig[ 'preloader_color' ] = {
                        selector: '.bbe-preloader',
                        property: '--bbe-site-color'
                    }
                }
                PreviewJs.color( 'preloader_color', value )
                Utils.initPreloader()
            } )
        } )

        // preloader background
        api( 'preloader_background', function( setting ) {
            setting.bind( function( value ) {
                if( ! Object.hasOwn( BlogBeeConfig, 'preloader_background' ) ) {
                    BlogBeeConfig[ 'preloader_background' ] = {
                        selector: '.bbe-preloader',
                        property: '--bbe-site-bg-color'
                    }
                }
                PreviewJs.color( 'preloader_background', value )
                Utils.initPreloader()
            } )
        } )

    } )
})( jQuery, wp.customize )