<?php
/**
 * Plugin Name: Fast Fuzzy Search
 * Plugin URI: https://redoxbird.com/product/fast-fuzzy-search
 * Description: A blazing fast and lightweight search engine plugin for your WordPress site.
 * Version: 1.2.0
 * Author: RedOxbird
 * Author URI: https://redoxbird.com
 * License: GPL v3
 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
 * Text Domain: fast-fuzzy-search
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

register_activation_hook(__FILE__, function() {
    if (!function_exists('deactivate_plugins')) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }

    $free_plugin = 'fast-fuzzy-search/fast-fuzzy-search.php';

    // If this plugin file is NOT the free plugin, deactivate the free plugin to prevent conflicts.
    if (plugin_basename(__FILE__) !== $free_plugin && is_plugin_active($free_plugin)) {
        deactivate_plugins($free_plugin);
    }
});

// Load Composer autoloader (if available)
$autoload_path = plugin_dir_path(__FILE__) . 'vendor/autoload.php';
if ( file_exists( $autoload_path ) ) {
    require_once $autoload_path;
}

// Load Freemius integration.
require_once plugin_dir_path(__FILE__) . 'includes/class-freemius-loader.php';

require_once plugin_dir_path(__FILE__) . 'includes/settings-framework.php';

if (!defined('FAST_FUZZY_SEARCH_VERSION')) {
    define('FAST_FUZZY_SEARCH_VERSION', '1.2.0');
}
if (!defined('FAST_FUZZY_SEARCH_JOIN_SYMBOL')) {
    define('FAST_FUZZY_SEARCH_JOIN_SYMBOL', ' / ');
}
if (!defined('FAST_FUZZY_SEARCH_CACHE_DIR')) {
    define('FAST_FUZZY_SEARCH_CACHE_DIR',  WP_CONTENT_DIR . '/cache/fast-fuzzy-search/');
}
if (!defined('FAST_FUZZY_SEARCH_DIR_URL')) {
    define('FAST_FUZZY_SEARCH_DIR_URL', plugin_dir_url(__FILE__));
}
if (!defined('FAST_FUZZY_SEARCH_PLUGIN_FILE')) {
    define('FAST_FUZZY_SEARCH_PLUGIN_FILE', __FILE__ );
}
if (!defined('FAST_FUZZY_SEARCH_PLUGIN_DIR')) {
    define('FAST_FUZZY_SEARCH_PLUGIN_DIR', plugin_dir_path( __FILE__ ));
}

/**
 * Enqueue necessary scripts.
 */

add_action( 'wp_enqueue_scripts', function() {
    // Scripts
    wp_enqueue_script('alpine-focus', plugin_dir_url( __FILE__ ) . 'js/alpine-focus.min.js', array(), "3.14.8", false);
    wp_enqueue_script('alpine-collapse', plugin_dir_url( __FILE__ ) . 'js/alpine-collapse.min.js', array(), "3.14.8", false);
    wp_enqueue_script('alpine-intersect', plugin_dir_url( __FILE__ ) . 'js/alpine-intersect.min.js', array(), "3.14.8", false);
    wp_enqueue_script('alpine-persist', plugin_dir_url( __FILE__ ) . 'js/alpine-persist.min.js', array(), "3.14.8", false);
    wp_enqueue_script('alpine-csp', plugin_dir_url( __FILE__ ) . 'js/alpine-csp.min.js', array(), "3.14.8", false);
    wp_enqueue_script('uFuzzy', plugin_dir_url( __FILE__ ) . 'js/uFuzzy.iife.min.js', array(), '1.0.18', false);

    wp_enqueue_script('fast-fuzzy-search-main', plugin_dir_url( __FILE__ ) . 'js/main.js', array('jquery'), fast_fuzzy_search_get_version(), true);

    // Base styles — always loaded (block layout, position variants, BEM base)
    wp_enqueue_style(
        'fast-fuzzy-search-base',
        plugin_dir_url( __FILE__ ) . 'css/style.css',
        array(),
        fast_fuzzy_search_get_version()
    );

    // Theme styles — load only the active theme file
    $options   = get_option('fast_fuzzy_search_options', fast_fuzzy_search_get_default_options());
    $aesthetic = !empty($options['aesthetic']) ? sanitize_key($options['aesthetic']) : 'minimal-light';
    $is_pro    = function_exists('fast_fuzzy_search_is_pro') && fast_fuzzy_search_is_pro();

    $free_themes = array('minimal-light', 'minimal-dark');
    $pro_themes  = array('material-light', 'material-dark', 'glassmorphic', 'brutal', 'custom', 'iris', 'crisp', 'terracotta', 'editorial');
    $all_themes  = array_merge($free_themes, $pro_themes);

    if (!in_array($aesthetic, $all_themes, true)) {
        $aesthetic = 'minimal-light';
    }

    if (in_array($aesthetic, $pro_themes, true) && !$is_pro) {
        $aesthetic = 'minimal-light';
    }

    $css_url = plugin_dir_url( __FILE__ ) . 'css/';
    $version = fast_fuzzy_search_get_version();

    // Custom aesthetic — load generated file from uploads, or fall back to base + inline
    if ($aesthetic === 'custom') {
        $custom_url = get_option('fast_fuzzy_search_custom_css_url', '');
        $custom_dir = wp_upload_dir();
        $custom_path = $custom_dir['basedir'] . '/fast-fuzzy-search/custom.css';

        if ($custom_url && file_exists($custom_path)) {
            $custom_ver = (string) filemtime($custom_path);
            wp_enqueue_style(
                'fast-fuzzy-search-theme',
                $custom_url,
                array('fast-fuzzy-search-base'),
                $custom_ver !== '' ? $custom_ver : $version
            );
        } else {
            // Fallback: load base theme + inline custom CSS
            $base_theme = !empty($options['custom_base_theme']) ? sanitize_key($options['custom_base_theme']) : 'minimal-light';
            $base_theme = in_array($base_theme, $all_themes, true) ? $base_theme : 'minimal-light';
            wp_enqueue_style(
                'fast-fuzzy-search-theme',
                $css_url . $base_theme . '.css',
                array('fast-fuzzy-search-base'),
                $version
            );
            $fallback_css = !empty($options['custom_css']) ? trim((string) $options['custom_css']) : '';
            if ($fallback_css !== '') {
                wp_add_inline_style('fast-fuzzy-search-theme', $fallback_css);
            }
        }
    } else {
        wp_enqueue_style(
            'fast-fuzzy-search-theme',
            $css_url . $aesthetic . '.css',
            array('fast-fuzzy-search-base'),
            $version
        );
    }

    // 1. Custom CSS inline (for non-custom aesthetics that still have custom CSS)
    if ($aesthetic !== 'custom') {
        $custom_css = !empty($options['custom_css']) ? trim((string) $options['custom_css']) : '';
        if ($custom_css !== '') {
            wp_add_inline_style('fast-fuzzy-search-theme', $custom_css);
        }
    }

    // 2. Theme-file auto-discovery — wp-content/themes/{active}/fast-fuzzy-search/custom.css
    $theme_custom_path = get_stylesheet_directory() . '/fast-fuzzy-search/custom.css';
    if (file_exists($theme_custom_path)) {
        $theme_custom_uri = get_stylesheet_directory_uri() . '/fast-fuzzy-search/custom.css';
        $theme_custom_ver = (string) filemtime($theme_custom_path);
        wp_enqueue_style(
            'fast-fuzzy-search-theme-custom',
            $theme_custom_uri,
            array('fast-fuzzy-search-theme'),
            $theme_custom_ver !== '' ? $theme_custom_ver : $version
        );
    }

    wp_localize_script('fast-fuzzy-search-main', 'FastFuzzySearch', array(
        '_wpnonce' => wp_create_nonce('fast_fuzzy_search_ajax'),
        'homeURL' => esc_url(home_url()),
        'ajax_url' => admin_url('admin-ajax.php'),
        'ajax_action' => 'fast_fuzzy_search_get_posts_list',
        'rest_url' => esc_url_raw(rest_url('fast-fuzzy-search/v1/search')),
        'options' => $options,
    ));
});

add_action('admin_enqueue_scripts', function() {
    $settings_page_slug = 'fast_fuzzy_search_options';
    $screen = get_current_screen();

    $is_settings_page = isset( $screen->id ) && $screen->id === 'toplevel_page_' . $settings_page_slug;
    $is_docs_page     = isset( $screen->id ) && $screen->id === 'fast-fuzzy-search_page_fast_fuzzy_search_docs';

    // Both pages use the WPDS admin styles (ffs-page, ffs-plugin-header, etc.)
    if ( $is_settings_page || $is_docs_page ) {
        wp_enqueue_style(
            'fast-fuzzy-search-admin',
            plugin_dir_url( __FILE__ ) . 'css/admin.css',
            [],
            fast_fuzzy_search_get_version()
        );
    }

    // Docs page also needs its own docs stylesheet
    if ( $is_docs_page ) {
        wp_enqueue_style(
            'fast-fuzzy-search-docs',
            plugin_dir_url( __FILE__ ) . 'docs/style.css',
            array(),
            fast_fuzzy_search_get_version()
        );
    }

    // Only the settings page needs Alpine + admin.js
    if ( $is_settings_page ) {
        wp_enqueue_script('alpine', plugin_dir_url( __FILE__ ) . 'js/alpine.min.js', array(), "3.14.8", false);
        wp_enqueue_script('fast-fuzzy-search-admin', plugin_dir_url( __FILE__ ) . 'js/admin.js', array('jquery'), fast_fuzzy_search_get_version(), true);
        wp_localize_script('fast-fuzzy-search-admin', 'FastFuzzySearch', array(
            '_wpnonce'    => wp_create_nonce('fast_fuzzy_search_ajax'),
            'homeURL'     => esc_url(home_url()),
            'ajax_url'    => admin_url('admin-ajax.php'),
            'ajax_action' => 'fast_fuzzy_search_get_posts_list',
        ));
    }
});

add_action('admin_notices', function() {
    // Check if both the free and pro plugin files exist (installed)
    $free_plugin_path = dirname( FAST_FUZZY_SEARCH_PLUGIN_DIR ) . '/fast-fuzzy-search/fast-fuzzy-search.php'; 
    $pro_plugin_path  = dirname( FAST_FUZZY_SEARCH_PLUGIN_DIR ) . '/fast-fuzzy-search-pro/fast-fuzzy-search.php';
  

    if (file_exists($free_plugin_path) && file_exists($pro_plugin_path)) { ?>
      <div class="notice notice-warning">
        <p><strong>Warning:</strong> Both the <strong>Fast Fuzzy Search</strong> and <strong>Fast Fuzzy Search Pro</strong> are installed. Please activate only one version to avoid conflicts.</p>
      </div>
    <?php
  }
});

add_action('init', function() {
    list($add_field, $add_section) = fast_fuzzy_search_register_settings_framework(
        fast_fuzzy_search_is_pro() ? __('Fast Fuzzy Search Pro', 'fast-fuzzy-search') : __('Fast Fuzzy Search', 'fast-fuzzy-search'),
        __('Fast Fuzzy Search', 'fast-fuzzy-search'),
        'fast_fuzzy_search_options'
    );

    $add_section([
        'id'          => 'display',
        'title'       => __('Display', 'fast-fuzzy-search'),
        'description' => __('Choose how the search bar looks on the front end.', 'fast-fuzzy-search'),
        'icon'        => 'admin-customizer',
        'order'       => 10,
    ]);

    $add_section([
        'id'          => 'behavior',
        'title'       => __('Behavior', 'fast-fuzzy-search'),
        'description' => __('Control how and where the search appears.', 'fast-fuzzy-search'),
        'icon'        => 'admin-settings',
        'order'       => 20,
    ]);

    $add_section([
        'id'          => 'index',
        'title'       => __('Search Index', 'fast-fuzzy-search'),
        'description' => __('Tune how content is indexed and cached.', 'fast-fuzzy-search'),
        'icon'        => 'database',
        'order'       => 30,
    ]);

    $add_field([
        'id'          => 'primary_color',
        'section'     => 'display',
        'label'       => __('Primary Color', 'fast-fuzzy-search'),
        'description' => __('Tint applied to the search trigger and result highlights.', 'fast-fuzzy-search'),
        'type'        => 'color_picker',
        'default'     => '#2271b1',
    ]);

    $add_field([
        'id'          => 'type',
        'section'     => 'display',
        'label'       => __('Style', 'fast-fuzzy-search'),
        'description' => __('Pick the visual style that best fits your site.', 'fast-fuzzy-search'),
        'type'        => 'image_radio',
        'options'     => [
            'input-field' => [
                'label' => __('Input Field', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/type-input-field.png',
                'pro'   => false,
            ],
            'icon-button' => [
                'label' => __('Icon Button', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/type-icon-button.png',
                'pro'   => false,
            ],
            'button' => [
                'label' => __('Button', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/type-button.png',
                'pro'   => false,
            ],
            'input-button' => [
                'label' => __('Input Button', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/type-input-field.png',
                'pro'   => false,
            ],
        ],
    ]);

    $add_field([
        'id'          => 'aesthetic',
        'section'     => 'display',
        'label'       => __('Aesthetic', 'fast-fuzzy-search'),
        'description' => __('Visual treatment for the search overlay.', 'fast-fuzzy-search'),
        'type'        => 'image_radio',
        'options'     => [
            'minimal-light' => [
                'label' => __('Minimal Light', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/minimal-light.png',
                'pro'   => false,
            ],
            'minimal-dark' => [
                'label' => __('Minimal Dark', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/minimal-dark.png',
                'pro'   => false,
            ],
            'material-light' => [
                'label' => __('Material Light', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/material-light.png',
                'pro'   => true,
            ],
            'material-dark' => [
                'label' => __('Material Dark', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/material-dark.png',
                'pro'   => true,
            ],
            'glassmorphic' => [
                'label' => __('Glassmorphic', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/glassmorphic.png',
                'pro'   => true,
            ],
            'brutal' => [
                'label' => __('Brutal', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/brutal.png',
                'pro'   => true,
            ],
            'crisp' => [
                'label' => __('Crisp', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/crisp.png',
                'pro'   => true,
            ],
            'custom' => [
                'label' => __('Custom', 'fast-fuzzy-search'),
                'image' => plugin_dir_url( __FILE__ ) . 'media/custom.png',
                'pro'   => true,
            ],
        ],
    ]);

    $add_field([
        'id'          => 'placeholder',
        'section'     => 'display',
        'label'       => __('Placeholder Text', 'fast-fuzzy-search'),
        'description' => __('Text shown inside the search input before the user types.', 'fast-fuzzy-search'),
        'type'        => 'text',
        'default'     => __('Search for something here...', 'fast-fuzzy-search'),
        'placeholder' => __('Search for something here...', 'fast-fuzzy-search'),
    ]);

    $add_field([
        'id'          => 'mode',
        'section'     => 'behavior',
        'label'       => __('Display Mode', 'fast-fuzzy-search'),
        'description' => __('Show the search automatically on every page, or place it manually with a shortcode.', 'fast-fuzzy-search'),
        'type'        => 'tabbed_radio',
        'options'     => [
            'auto' => [
                'label'    => __('Auto', 'fast-fuzzy-search'),
                'template' => 'template-parts/admin/mode-auto',
            ],
            'shortcode' => [
                'label'    => __('Shortcode', 'fast-fuzzy-search'),
                'template' => 'template-parts/admin/mode-shortcode',
            ],
        ],
    ]);

    $add_field([
        'id'          => 'position',
        'section'     => 'behavior',
        'label'       => __('Trigger Position', 'fast-fuzzy-search'),
        'description' => __('Where the floating search trigger appears on the screen.', 'fast-fuzzy-search'),
        'type'        => 'tabbed_radio',
        'options'     => [
            'bottom-left' => [
                'label'    => __('Bottom Left', 'fast-fuzzy-search'),
                'template' => '',
            ],
            'bottom-center' => [
                'label'    => __('Bottom Center', 'fast-fuzzy-search'),
                'template' => '',
            ],
            'bottom-right' => [
                'label'    => __('Bottom Right', 'fast-fuzzy-search'),
                'template' => '',
            ],
        ],
    ]);

    $add_field([
        'id'          => 'hide_on_scroll',
        'section'     => 'behavior',
        'label'       => __('Hide on Scroll', 'fast-fuzzy-search'),
        'description' => __('Collapse the search overlay as the visitor scrolls down the page.', 'fast-fuzzy-search'),
        'type'        => 'switch',
        'default'     => false,
    ]);

    $add_field([
        'id'          => 'hide_filters',
        'section'     => 'behavior',
        'label'       => __('Hide Filters', 'fast-fuzzy-search'),
        'description' => __('Remove the filter chips from the search results panel.', 'fast-fuzzy-search'),
        'type'        => 'switch',
        'default'     => false,
    ]);

    $add_field([
        'id'          => 'excluded_post_types',
        'section'     => 'index',
        'label'       => __('Exclude Content Types', 'fast-fuzzy-search'),
        'description' => __('Content types you select here will be skipped when building the search index.', 'fast-fuzzy-search'),
        'type'        => 'checkbox_group',
        'options'     => fast_fuzzy_search_get_searchable_post_types(),
    ]);

    $add_field([
        'id'          => 'cache_expiration_time',
        'section'     => 'index',
        'label'       => __('Cache Expiration', 'fast-fuzzy-search'),
        'description' => __('How long the search index is cached before being rebuilt.', 'fast-fuzzy-search'),
        'type'        => 'number',
        'default'     => HOUR_IN_SECONDS * 8,
        'unit'        => __('seconds', 'fast-fuzzy-search'),
        'min'         => 60,
        'max'         => DAY_IN_SECONDS * 7,
        'step'        => 60,
        'help'        => sprintf(
            /* translators: %s: default cache value in seconds */
            esc_html__('Defaults to %s (eight hours). Minimum one minute, maximum seven days.', 'fast-fuzzy-search'),
            HOUR_IN_SECONDS * 8
        ),
    ]);

    $settings = get_option('fast_fuzzy_search_options', []);
    $default_settings = fast_fuzzy_search_get_default_options();
    $updated_settings = array_merge($default_settings, $settings);

    // Update the option only if changes were made
    if ($updated_settings !== $settings) {
        update_option('fast_fuzzy_search_options', $updated_settings);
    }
}, 20);

add_action('admin_menu', function() {
    add_submenu_page(
        'fast_fuzzy_search_options',
        __('Docs', 'fast-fuzzy-search'),
        __('Docs', 'fast-fuzzy-search'),
        'manage_options',
        'fast_fuzzy_search_docs',
        'fast_fuzzy_search_render_docs_page',
        30
    );
}, 20);

if(!function_exists('fast_fuzzy_search_render_docs_page')) {
    function fast_fuzzy_search_render_docs_page() {
        $docs_file = FAST_FUZZY_SEARCH_PLUGIN_DIR . 'docs/index.html';

        if ( ! file_exists( $docs_file ) ) {
            echo '<div class="wrap"><h1>' . esc_html__( 'Docs', 'fast-fuzzy-search' ) . '</h1><p>' . esc_html__( 'Documentation file not found.', 'fast-fuzzy-search' ) . '</p></div>';
            return;
        }

        $html = file_get_contents( $docs_file );

        if ( false === $html ) {
            echo '<div class="wrap"><h1>' . esc_html__( 'Docs', 'fast-fuzzy-search' ) . '</h1><p>' . esc_html__( 'Could not load documentation file.', 'fast-fuzzy-search' ) . '</p></div>';
            return;
        }

        libxml_use_internal_errors( true );
        $dom = new DOMDocument();
        $dom->loadHTML( $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
        libxml_clear_errors();

        $xpath = new DOMXPath( $dom );

        $sidebar  = '';
        $content  = '';

        $sidebar_nodes = $xpath->query( '//section[contains(concat(" ",normalize-space(@class)," ")," wp-sync-docs-sidebar ")]' );
        if ( $sidebar_nodes && $sidebar_nodes->length > 0 ) {
            $sidebar = $dom->saveHTML( $sidebar_nodes->item(0) );
        }

        $content_nodes = $xpath->query( '//main[contains(concat(" ",normalize-space(@class)," ")," wp-sync-docs-main ")]' );
        if ( $content_nodes && $content_nodes->length > 0 ) {
            $content = $dom->saveHTML( $content_nodes->item(0) );
        }

        if ( empty( $sidebar ) && empty( $content ) ) {
            echo '<div class="wrap"><h1>' . esc_html__( 'Docs', 'fast-fuzzy-search' ) . '</h1><p>' . esc_html__( 'No documentation content found.', 'fast-fuzzy-search' ) . '</p></div>';
            return;
        }

        ?>
        <div class="wrap ffs-page">

            <header class="ffs-plugin-header">
                <div class="ffs-plugin-header__brand">
                    <span class="ffs-plugin-header__icon" aria-hidden="true">
                        <?php echo wp_kses( fast_fuzzy_search_svg( 'icon' ), fast_fuzzy_search_allowed_svg_tags() ); ?>
                    </span>
                    <div class="ffs-plugin-header__title">
                        <h1 class="ffs-plugin-header__name"><?php esc_html_e( 'Documentation', 'fast-fuzzy-search' ); ?></h1>
                        <p class="ffs-plugin-header__subtitle"><?php esc_html_e( 'Theme integration guide for Fast Fuzzy Search.', 'fast-fuzzy-search' ); ?></p>
                    </div>
                </div>
                <div class="ffs-plugin-header__actions">
                    <a class="ffs-plugin-header__action" href="<?php echo esc_url( admin_url( 'admin.php?page=fast_fuzzy_search_docs' ) ); ?>">
                        <span class="dashicons dashicons-book" aria-hidden="true"></span>
                        <?php esc_html_e( 'Documentation', 'fast-fuzzy-search' ); ?>
                    </a>
                    <a class="ffs-plugin-header__action" href="https://redoxbird.com/contact" target="_blank" rel="noopener">
                        <span class="dashicons dashicons-sos" aria-hidden="true"></span>
                        <?php esc_html_e( 'Support', 'fast-fuzzy-search' ); ?>
                    </a>
                    <?php if (!fast_fuzzy_search_is_pro()): ?>
                        <a class="ffs-plugin-header__action ffs-plugin-header__action--upgrade" href="<?php echo esc_url( function_exists( 'ffs_fs' ) ? ffs_fs()->get_upgrade_url() : 'https://redoxbird.com/product/fast-fuzzy-search/' ); ?>"<?php if ( ! function_exists( 'ffs_fs' ) ): ?> target="_blank" rel="noopener"<?php endif; ?>>
                            <span class="dashicons dashicons-star-filled" aria-hidden="true"></span>
                            <?php esc_html_e( 'Upgrade to Pro', 'fast-fuzzy-search' ); ?>
                        </a>
                    <?php else: ?>
                        <span class="ffs-plugin-header__status" aria-label="<?php esc_attr_e('Pro active', 'fast-fuzzy-search'); ?>">
                            <span class="ffs-plugin-header__status-dot ffs-plugin-header__status-dot--success" aria-hidden="true"></span>
                            <?php esc_html_e('Pro', 'fast-fuzzy-search'); ?>
                        </span>
                    <?php endif; ?>
                </div>
            </header>

            <?php fast_fuzzy_search_get_template_part( 'template-parts/admin/docs', null, array( 'sidebar' => $sidebar, 'content' => $content ) ); ?>
        </div>
        <?php
    }
}

if(!function_exists('fast_fuzzy_search_get_default_options')) {
    function fast_fuzzy_search_get_default_options() {
        return [
            'excluded_post_types' => [],
            'position' => 'bottom-center',
            'aesthetic' => 'minimal-light',
            'cache_expiration_time' => HOUR_IN_SECONDS * 8,
            'placeholder' => __('Search for something here...', 'fast-fuzzy-search'),
            'primary_color' => '#2271b1',
            'mode' => 'auto',
            'type' => 'icon-button',
            'hide_on_scroll' => false,
            'hide_filters' => false,
            'custom_css' => '',
        ];
    };
}

add_action('wp_enqueue_scripts', function() {
    $settings = get_option('fast_fuzzy_search_options', []);
    $shades = fast_fuzzy_search_generate_color_Shades($settings['primary_color']);

    // Generate CSS variables
    $hex = ltrim( $settings['primary_color'] ?? '#2271b1', '#' );
    if ( strlen( $hex ) === 6 && ctype_xdigit( $hex ) ) {
        $r = hexdec( substr( $hex, 0, 2 ) );
        $g = hexdec( substr( $hex, 2, 2 ) );
        $b = hexdec( substr( $hex, 4, 2 ) );
    } else {
        $r = 34; $g = 113; $b = 177; // fallback #2271b1
    }

    $css = ':root {' . PHP_EOL;
    $css .= "    --ffs-color-primary-rgb: $r, $g, $b;" . PHP_EOL;
    foreach ($shades as $level => $color) {
        $css .= "    --ffs-color-primary-$level: $color;" . PHP_EOL;
    }
    $css .= '}';

    // Add the CSS to the site
    wp_add_inline_style('wp-block-library', $css);
});

if(!function_exists('fast_fuzzy_search_script_loader_tag')) {
    function fast_fuzzy_search_script_loader_tag($tag, $handle, $src) {
        $defer_scripts = array('alpine', 'alpine-csp', 'alpine-focus', 'alpine-collapse', 'alpine-intersect', 'alpine-persist');

        if (!is_string($tag) || empty($tag) || !is_string($handle) || empty($handle)) {
            return $tag;
        }

        if (strpos($tag, ' src=') === false) {
            return $tag;
        }

        if (in_array($handle, $defer_scripts, true)) {
            $tag = str_replace('<script ', '<script defer ', $tag);
        }

        return $tag;
    }
}

add_filter("script_loader_tag", 'fast_fuzzy_search_script_loader_tag', 10, 3);

// ---------------------------------------------------------------------------
// AJAX handler — fetch a theme's CSS content for the custom aesthetic editor
// ---------------------------------------------------------------------------
add_action('wp_ajax_fast_fuzzy_search_get_theme_css', 'fast_fuzzy_search_get_theme_css_callback');

if (!function_exists('fast_fuzzy_search_get_theme_css_callback')) {
    function fast_fuzzy_search_get_theme_css_callback() {
        nocache_headers();
        check_ajax_referer('fast_fuzzy_search_ajax', 'security');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        $theme = isset($_POST['theme']) ? sanitize_key(wp_unslash($_POST['theme'])) : '';
        if (empty($theme)) {
            wp_send_json_error(['message' => 'No theme specified']);
        }

        $file = FAST_FUZZY_SEARCH_PLUGIN_DIR . 'css/' . $theme . '.css';
        if (!file_exists($file)) {
            wp_send_json_error(['message' => 'Theme file not found']);
        }

        $css = file_get_contents($file);
        wp_send_json_success(['css' => $css]);
    }
}

// ---------------------------------------------------------------------------
// REST API endpoint (cache-plugin friendly)
// ---------------------------------------------------------------------------
add_action('rest_api_init', function() {
    register_rest_route('fast-fuzzy-search/v1', '/search', array(
        'methods'             => 'POST',
        'callback'            => 'fast_fuzzy_search_rest_search_callback',
        'permission_callback' => '__return_true',
    ));
});

if(!function_exists('fast_fuzzy_search_rest_search_callback')) {
    function fast_fuzzy_search_rest_search_callback() {
        nocache_headers();
        $list = fast_fuzzy_search_build_haystack();
        return new WP_REST_Response($list, 200);
    }
}

if(!function_exists('fast_fuzzy_search_build_haystack')) {
    function fast_fuzzy_search_build_haystack() {
        $options = get_option('fast_fuzzy_search_options');

        $cacheExpirationTime = (isset($options['cache_expiration_time']) && is_numeric($options['cache_expiration_time']) && $options['cache_expiration_time'] > 0) ? (int) $options['cache_expiration_time'] : HOUR_IN_SECONDS * 8;
        
        // Try to fetch cached data first
        $list = fast_fuzzy_search_get_file_cache('public_posts_haystack', $cacheExpirationTime);

        if (!empty($list)) {
            return json_decode($list, true);
        }

        global $wpdb;

        $list = [
            "haystack" => [],
            "data" => []
        ];

        // Pagination setup to fetch posts in batches
        $posts_per_batch = 500;
        $paged = 1;
        $total_posts = 0;

        // Get all searchable post type slugs
        $post_types = array_keys(fast_fuzzy_search_get_searchable_post_types());

        // Remove excluded post types
        $excluded_post_types = !empty($options['excluded_post_types']) ? $options['excluded_post_types'] : [];
        $post_types = array_diff($post_types, $excluded_post_types);

        // Ensure only valid post types
        $post_types = array_filter($post_types, 'post_type_exists');

        do {
            $query_args = [
                'post_type'      => $post_types,
                'posts_per_page' => $posts_per_batch,
                'paged'          => $paged,
                'orderby'        => 'menu_order',
                'order'          => 'ASC',
                'post_status'    => 'publish',
                'fields'         => 'ids',
            ];

            $query = new WP_Query($query_args);

            if (!$query->have_posts()) {
                break;
            }

            while ($query->have_posts()) {
                $query->the_post();

                $post = get_post(get_the_ID());
                $post_type = get_post_type($post->ID);
                $post_type_labels = get_post_type_labels(get_post_type_object($post_type));

                $entry = [
                    'title'          => get_the_title(),
                    'path'           => get_the_permalink(),
                    'type'           => $post_type,
                    'type_label'     => $post_type_labels->singular_name,
                    'id'             => $post->ID,
                    'featured_image' => get_the_post_thumbnail_url($post->ID),
                    'price'          => false,
                ];

                $search_text = $post_type_labels->name . FAST_FUZZY_SEARCH_JOIN_SYMBOL . get_the_title();

                // WooCommerce Integration
                if (class_exists('WooCommerce') && $post_type === 'product') {
                    $product = wc_get_product($post->ID);
                
                    if ($product) {
                        $entry['price'] = wp_strip_all_tags(wc_price($product->get_price()));
                        $search_text .= FAST_FUZZY_SEARCH_JOIN_SYMBOL . $product->get_sku();
                        $product_id = $product->get_id();
                        
                        if (fast_fuzzy_search_is_pro()) {
                            $like_clause = $wpdb->esc_like('pa_') . '%';

                            $terms = $wpdb->get_col(
                                $wpdb->prepare(
                                    "
                                    SELECT DISTINCT t.name 
                                    FROM {$wpdb->terms} AS t
                                    INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
                                    INNER JOIN {$wpdb->term_relationships} AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
                                    WHERE tr.object_id = %d 
                                    AND (tt.taxonomy IN ('product_cat', 'product_tag') OR tt.taxonomy LIKE %s)
                                    ",
                                    $product_id,
                                    $like_clause
                                )
                            );
                    
                            if (!empty($terms)) {
                                $search_text .= FAST_FUZZY_SEARCH_JOIN_SYMBOL . implode(FAST_FUZZY_SEARCH_JOIN_SYMBOL, $terms);
                            }
                        }
                    }
                }        

                // Pro features: third-party integrations
                if (fast_fuzzy_search_is_pro()) {

                    if (class_exists('Easy_Digital_Downloads') && $post_type === 'download') {
                        $price_html = edd_price($post->ID, false);
                        $entry['price'] = wp_strip_all_tags($price_html);
                    
                        $terms = $wpdb->get_col(
                            $wpdb->prepare(
                                "
                                SELECT DISTINCT t.name 
                                FROM {$wpdb->terms} AS t
                                INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
                                INNER JOIN {$wpdb->term_relationships} AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
                                WHERE tr.object_id = %d 
                                AND tt.taxonomy IN ('download_category', 'download_tag')
                                ",
                                $post->ID
                            )
                        );
                    
                        if (!empty($terms)) {
                            $search_text .= FAST_FUZZY_SEARCH_JOIN_SYMBOL . implode(FAST_FUZZY_SEARCH_JOIN_SYMBOL, $terms);
                        }
                    }
                    
                    if (class_exists('Easy_Property_Listings') && $post_type === 'property') {
                        $price = epl_get_property_price($post->ID);
                        $entry['price'] = wp_strip_all_tags($price ? $price : __('Price on request', 'fast-fuzzy-search'));
                    
                        $terms = $wpdb->get_col(
                            $wpdb->prepare(
                                "
                                SELECT DISTINCT t.name 
                                FROM {$wpdb->terms} AS t
                                INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
                                INNER JOIN {$wpdb->term_relationships} AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
                                WHERE tr.object_id = %d 
                                AND tt.taxonomy IN ('property_type', 'property_feature', 'property_status', 'location', 'rental_bond')
                                ",
                                $post->ID
                            )
                        );
                    
                        $property_meta = [
                            get_post_meta($post->ID, 'property_address_suburb', true),
                            get_post_meta($post->ID, 'property_address_state', true),
                            get_post_meta($post->ID, 'property_address_postal_code', true),
                            get_post_meta($post->ID, 'property_bedrooms', true),
                            get_post_meta($post->ID, 'property_bathrooms', true),
                            get_post_meta($post->ID, 'property_garage', true),
                        ];
                    
                        $search_terms = array_merge($terms, array_filter($property_meta));
                        
                        if (!empty($search_terms)) {
                            $search_text .= FAST_FUZZY_SEARCH_JOIN_SYMBOL . implode(FAST_FUZZY_SEARCH_JOIN_SYMBOL, $search_terms);
                        }
                    }
                }

                $list['haystack'][] = $search_text;
                $list['data'][] = $entry;

                if ($post_type === 'docs' || $post_type === 'post') {
                    $content = get_the_content();
                    $headings = fast_fuzzy_search_extract_headings($content, ['h2']);

                    preg_match_all('/<h2>(.*?)<\/h2>/i', $content, $matches);
                    
                    if (!empty($headings)) {
                        foreach ($headings as $heading) {
                            $list['haystack'][] = $post_type_labels->name . FAST_FUZZY_SEARCH_JOIN_SYMBOL . get_the_title() . FAST_FUZZY_SEARCH_JOIN_SYMBOL . $heading['text'];
                            $list['data'][] = [
                                'title'      => $heading['text'],
                                'path'       => get_the_permalink() . '#' . $heading['id'],
                                'type'       => 'heading',
                                'type_label' => __('Heading', 'fast-fuzzy-search'),
                            ];
                        }
                    }
                }
            }

            $paged++;
            $total_posts += count($query->posts);

        } while ($total_posts < $query->found_posts);

        wp_reset_postdata();

        fast_fuzzy_search_set_file_cache('public_posts_haystack', $list);

        return $list;
    }
}

add_action('wp_ajax_fast_fuzzy_search_get_posts_list', 'fast_fuzzy_search_get_posts_list_callback');
add_action('wp_ajax_nopriv_fast_fuzzy_search_get_posts_list', 'fast_fuzzy_search_get_posts_list_callback');

if(!function_exists('fast_fuzzy_search_get_posts_list_callback')) {
    function fast_fuzzy_search_get_posts_list_callback() {
        nocache_headers();
        // Ensure the request came from a valid source
        check_ajax_referer('fast_fuzzy_search_ajax', 'security');
        $list = fast_fuzzy_search_build_haystack();
        wp_send_json($list);
    }
}

if(!function_exists('fast_fuzzy_search_get_searchable_post_types')) {
    function fast_fuzzy_search_get_searchable_post_types() {
        $post_types = get_post_types();
    
        // Filter post types and map them to their labels
        $post_types = array_filter($post_types, function ($post_type) {
            $args = get_post_type_object($post_type);
    
            // Ensure post type is publicly queryable and not excluded from search
            $is_searchable = isset($args->publicly_queryable) && $args->publicly_queryable &&
                             (isset($args->exclude_from_search) ? !$args->exclude_from_search : true);
    
            // Explicitly exclude 'attachment'
            $is_not_attachment = $post_type !== 'attachment';
    
            return $is_searchable && $is_not_attachment;
        });
    
        // Ensure 'post' and 'page' are always included
        $required_post_types = ['post', 'page'];
        foreach ($required_post_types as $required_post_type) {
            if (!isset($post_types[$required_post_type])) {
                $args = get_post_type_object($required_post_type);
                if ($args) {
                    $post_types[$required_post_type] = $args->label;
                }
            }
        }
    
        // Map remaining post types to their labels
        foreach ($post_types as $post_type => $value) {
            $args = get_post_type_object($post_type);
            $post_types[$post_type] = $args->label;
        }
    
        return $post_types;
    }
}

if(!function_exists('fast_fuzzy_search_extract_headings')) {
    /**
     * Extract headings from HTML content (handles attributes, nested tags, Gutenberg output).
     *
     * @param string $content HTML content
     * @param array  $levels  Heading tags to extract (e.g. ['h2','h3'])
     * @return array          Array of headings: [ ['text'=>..., 'id'=>..., 'level'=>..., 'raw'=>...], ... ]
     */
    function fast_fuzzy_search_extract_headings($content, $levels = ['h2']) {
        $headings = [];

        if (empty($content) || !is_string($content)) {
            return $headings;
        }

        foreach ((array) $levels as $level) {
            // Match <hX ...>inner HTML</hX> including attributes and multiline content
            $pattern = '/<' . preg_quote($level, '/') . '\b([^>]*)>([\s\S]*?)<\/' . preg_quote($level, '/') . '>/iu';

            if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
                foreach ($matches as $m) {
                    $attr_string = isset($m[1]) ? $m[1] : '';
                    $inner_html  = isset($m[2]) ? $m[2] : '';

                    // Prefer an id attribute if present
                    $id = null;
                    if (preg_match('/\bid=["\']([^"\']+)["\']/i', $attr_string, $id_match)) {
                        $id = $id_match[1];
                    }

                    // Normalize heading text: decode entities and strip tags
                    $heading_text = trim( wp_strip_all_tags( html_entity_decode( $inner_html, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) ) );

                    if ($heading_text === '') {
                        continue;
                    }

                    // Fallback id generated from heading text
                    if (empty($id)) {
                        $id = sanitize_title($heading_text);
                    }

                    $headings[] = [
                        'text'  => $heading_text,
                        'id'    => $id,
                        'level' => strtolower($level),
                        'raw'   => $inner_html,
                    ];
                }
            }
        }

        return $headings;
    }
}

add_action('wp_ajax_fast_fuzzy_search_delete_posts_search_index', 'fast_fuzzy_search_delete_posts_search_index_callback');

if (!function_exists('fast_fuzzy_search_delete_posts_search_index_callback')) {
    function fast_fuzzy_search_delete_posts_search_index_callback() {
        // Verify nonce
        if (!isset($_POST['security']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'fast_fuzzy_search_ajax')) {
            wp_send_json_error(['message' => 'Invalid nonce']);
        }

        fast_fuzzy_search_delete_file_cache('public_posts_haystack');

        wp_send_json_success();
    }
}


add_action('wp_body_open', 'fast_fuzzy_search_render_search_field');

if(!function_exists('fast_fuzzy_search_render_search_field')) {
    function fast_fuzzy_search_render_search_field() {
        $options = get_option('fast_fuzzy_search_options');
        $type = !empty($options['type']) ? $options['type'] : 'input-field';

        if(!empty($options) && !empty($options['mode']) && $options['mode'] === 'shortcode') {
            if($type !== 'input-field') {
                fast_fuzzy_search_get_template_part('template-parts/search-panel', null, ['is_inline' => true, 'options' => $options]);
            }
            
            return;
        }


        // Render the input field
        if (!is_admin()) {
            if($type !== 'input-field') {
                fast_fuzzy_search_get_template_part('template-parts/search-panel', null, ['is_inline' => true, 'options' => $options]);
            }
            
            fast_fuzzy_search_get_template_part('template-parts/'. $type);
        }
    };
};

if(!function_exists('fast_fuzzy_search_get_template_part')) {
    function fast_fuzzy_search_get_template_part($slug, $name = null, $args = []) {
        $templates = [];
        $name = (string) $name;
    
        if ('' !== $name) {
            $templates[] = "{$slug}-{$name}.php";
        }
    
        $templates[] = "{$slug}.php";
    
        $template = fast_fuzzy_search_locate_template($templates, false);
    
        if (!$template) {
            return;
        }
    
        include $template;
    }
}

if(!function_exists('fast_fuzzy_search_locate_template')) {
    function fast_fuzzy_search_locate_template($template_names, $load = false, $require_once = true, $args = array()) {
        $located = '';
    
        foreach ((array) $template_names as $template_name) {
            if (!$template_name) {
                continue;
            }
            if (file_exists(plugin_dir_path(__FILE__) . $template_name)) {
                $located = plugin_dir_path(__FILE__) . $template_name;
                break;
            }
            if (file_exists(get_stylesheet_directory() . '/' . $template_name)) {
                $located = get_stylesheet_directory() . '/' . $template_name;
                break;
            } elseif (file_exists(get_template_directory() . '/' . $template_name)) {
                $located = get_template_directory() . '/' . $template_name;
                break;
            }
        }
    
        if ($load && '' !== $located) {
            load_template($located, $require_once, $args);
        }
    
        return $located;
    }    
}

if(!function_exists('fast_fuzzy_search_get_version')) {
    function fast_fuzzy_search_get_version() {
        $version = FAST_FUZZY_SEARCH_VERSION;

        if (!function_exists('wp_get_environment_type')) {
            return $version;
        }

        switch (wp_get_environment_type()) {
            case 'local':
            case 'development':
                $version = time();
                break;
        }

        return $version;
    }
}

if(!function_exists('fast_fuzzy_search_svg')) {
    function fast_fuzzy_search_svg($filename, $class = "") {
        if (!$filename) {
            return;
        }

        $filename = basename($filename);
        $file_location = plugin_dir_path(__FILE__) . 'icons/' . $filename . '.svg';

        if (!file_exists($file_location)) {
            return;
        }

        $svg_content = file_get_contents($file_location);

        if (!empty($class)) {
            // Check if the SVG has an opening <svg> tag
            if (strpos($svg_content, '<svg') !== false) {
                // Add the class to the opening <svg> tag
                $svg_content = str_replace('<svg', '<svg class="' . esc_attr($class) . '"', $svg_content);
            } else {
                // If the <svg> tag is missing, wrap the content with it and add the class
                $svg_content = '<svg class="' . esc_attr($class) . '">' . $svg_content . '</svg>';
            }
        }

        return $svg_content;
    }
}

if(!function_exists('fast_fuzzy_search_to_title_case')) {
    function fast_fuzzy_search_to_title_case($string) {
        // Replace hyphens with spaces and capitalize each word
        $formattedString = ucwords(str_replace('-', ' ', $string));
        return $formattedString;
    }    
}

if (!function_exists('fast_fuzzy_search_generate_custom_css')) {
    function fast_fuzzy_search_generate_custom_css($options) {
        if (empty($options['aesthetic']) || $options['aesthetic'] !== 'custom') {
            return;
        }

        $custom_css = !empty($options['custom_css']) ? trim((string) $options['custom_css']) : '';
        $combined = $custom_css;

        $upload_dir = wp_upload_dir();
        $ffs_dir = $upload_dir['basedir'] . '/fast-fuzzy-search/';
        if (!file_exists($ffs_dir)) {
            wp_mkdir_p($ffs_dir);
        }

        $file_path = $ffs_dir . 'custom.css';

        if (!function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }
        global $wp_filesystem;
        WP_Filesystem();
        $wp_filesystem->put_contents($file_path, $combined, FS_CHMOD_FILE);

        update_option('fast_fuzzy_search_custom_css_url', $upload_dir['baseurl'] . '/fast-fuzzy-search/custom.css');
    }
}

if (!function_exists('fast_fuzzy_search_set_file_cache')) {
    function fast_fuzzy_search_set_file_cache($key, $data) {
        $cache_dir = FAST_FUZZY_SEARCH_CACHE_DIR;
        if (!is_dir($cache_dir)) {
            wp_mkdir_p($cache_dir);
        }
        $cache_file = $cache_dir . get_locale() . '-' . md5($key) . '.json';
        file_put_contents($cache_file, wp_json_encode($data), LOCK_EX);
    }
}

if (!function_exists('fast_fuzzy_search_get_file_cache')) {
    function fast_fuzzy_search_get_file_cache($key, $expiration = 3600) {
        $cache_file = FAST_FUZZY_SEARCH_CACHE_DIR . get_locale() . '-' . md5($key) . '.json';
        if (file_exists($cache_file) && (time() - filemtime($cache_file) < $expiration)) {
            return file_get_contents($cache_file);
        }
        if (file_exists($cache_file)) {
            unlink($cache_file);
        }
        return null;
    }
}

if (!function_exists('fast_fuzzy_search_delete_file_cache')) {
    function fast_fuzzy_search_delete_file_cache($key) {
        $cache_file = FAST_FUZZY_SEARCH_CACHE_DIR . get_locale() . '-' . md5($key) . '.json';
        if (file_exists($cache_file)) {
            unlink($cache_file);
        }
    }
}

if(!function_exists('fast_fuzzy_search_clear_cache_on_changes')) {
    function fast_fuzzy_search_clear_cache_on_changes($post_id) {
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
    
        if (!is_numeric($post_id) || $post_id <= 0) {
            return;
        }
    
        if (!function_exists('fast_fuzzy_search_delete_file_cache')) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                trigger_error('Function fast_fuzzy_search_delete_file_cache is not defined.', E_USER_WARNING);
            }
            return;
        }
    
        $post_type = get_post_type($post_id);
    
        if (!$post_type || !post_type_supports($post_type, 'public')) {
            return;
        }
    
        fast_fuzzy_search_delete_file_cache('public_posts_haystack');
    }
}

add_action('save_post', 'fast_fuzzy_search_clear_cache_on_changes', 10, 3);
add_action('trash_post', 'fast_fuzzy_search_clear_cache_on_changes', 10, 2);
add_action('untrash_post', 'fast_fuzzy_search_clear_cache_on_changes', 10, 2);
add_action('delete_post', 'fast_fuzzy_search_clear_cache_on_changes', 10, 2);

// Handle post status changes
add_action('transition_post_status', function($new_status, $old_status, $post) {
    // Verify the function exists
    if (!function_exists('fast_fuzzy_search_delete_file_cache')) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            trigger_error('Function fast_fuzzy_search_delete_file_cache is not defined.', E_USER_WARNING);
        }
        return;
    }

    // Ensure post is a public post type
    if (!post_type_supports($post->post_type, 'public')) {
        return;
    }

    // Perform the cache deletion
    fast_fuzzy_search_delete_file_cache('public_posts_haystack');
}, 10, 3);

// Handle permalink structure changes
add_action('update_option_permalink_structure', function() {
    // Verify the function exists
    if (!function_exists('fast_fuzzy_search_delete_file_cache')) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            trigger_error('Function fast_fuzzy_search_delete_file_cache is not defined.', E_USER_WARNING);
        }
        return;
    }

    // Perform the cache deletion
    fast_fuzzy_search_delete_file_cache('public_posts_haystack');
});


add_action('wp_ajax_fast_fuzzy_search_save_custom_options', 'fast_fuzzy_search_save_custom_options');

if (!function_exists('fast_fuzzy_search_save_custom_options')) {
    function fast_fuzzy_search_save_custom_options() {
        // Verify nonce
        if (!isset($_POST['security']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'fast_fuzzy_search_ajax')) {
            wp_send_json_error(['message' => 'Invalid nonce']);
        }

        // Validate user capability before processing any data
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        // Get and unslash options from POST data
        if (!isset($_POST['options'])) {
            wp_send_json_error(['message' => 'No options provided']);
        }

        $options_raw = wp_unslash($_POST['options']);  // Unslash the options JSON string

        // Decode the JSON options
        $options = json_decode($options_raw, true);

        if (!$options || !is_array($options)) {
            wp_send_json_error(['message' => 'Invalid options data']);
        }

        // Sanitize each field based on its expected type
        $sanitized_options = [];

        if (isset($options['post_types']) && is_array($options['post_types'])) {
            $sanitized_options['post_types'] = array_map('sanitize_key', $options['post_types']); // sanitize_key for slugs like 'post' or 'page'
        }

        if (isset($options['excluded_post_types']) && is_array($options['excluded_post_types'])) {
            $sanitized_options['excluded_post_types'] = array_map('sanitize_key', $options['excluded_post_types']); // sanitize_key for slugs like 'post' or 'page'
        }

        if (isset($options['position'])) {
            $sanitized_options['position'] = sanitize_text_field($options['position']);
        }

        if (isset($options['aesthetic'])) {
            $sanitized_options['aesthetic'] = sanitize_text_field($options['aesthetic']);
        }

        if (isset($options['cache_expiration_time'])) {
            $sanitized_options['cache_expiration_time'] = intval($options['cache_expiration_time']); // Ensure it's an integer
        }

        if (isset($options['placeholder'])) {
            $sanitized_options['placeholder'] = sanitize_text_field($options['placeholder']);
        }

        if (isset($options['primary_color'])) {
            $sanitized_options['primary_color'] = sanitize_hex_color($options['primary_color']); // Validate and sanitize hex color
        }

        if (isset($options['mode'])) {
            $sanitized_options['mode'] = sanitize_text_field($options['mode']);
        }

        if (isset($options['type'])) {
            $sanitized_options['type'] = sanitize_text_field($options['type']);
        }

        if (isset($options['hide_on_scroll'])) {
            $sanitized_options['hide_on_scroll'] = (bool) $options['hide_on_scroll']; // Cast to boolean
        }
        
        if (isset($options['hide_filters'])) {
            $sanitized_options['hide_filters'] = (bool) $options['hide_filters']; // Cast to boolean
        }

        // Merge sanitized options with existing to prevent data loss
        // (ensures fields not sent in the request keep their current values)
        $option_name = 'fast_fuzzy_search_options';
        $existing_options = get_option($option_name, []);
        $merged_options = array_merge($existing_options, $sanitized_options);
        if (update_option($option_name, $merged_options)) {
            wp_send_json_success(['message' => 'Options saved successfully']);
        } else {
            wp_send_json_error(['message' => 'Failed to save options']);
        }
    }
}

add_action('wp_ajax_fast_fuzzy_search_get_initial_options', 'fast_fuzzy_search_get_initial_options');

if(!function_exists('fast_fuzzy_search_get_initial_options')) {
    function fast_fuzzy_search_get_initial_options() {
        // Verify nonce
        if (!isset($_POST['security']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'fast_fuzzy_search_ajax')) {
            wp_send_json_error(['message' => 'Invalid nonce']);
        }

        // Check if the user has the correct capability to view the settings
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Permission Denied']);
        }

        // Retrieve all plugin settings from the options table
        $settings = get_option('fast_fuzzy_search_options', []);

        wp_send_json_success($settings);
    }
}

if(!function_exists('fast_fuzzy_search_convert_hr_to_bytes')) {
    function fast_fuzzy_search_convert_hr_to_bytes($hr_value) {
        // Unlimited memory
        if ((int) $hr_value === -1) {
            return PHP_INT_MAX;
        }

        $bytes = substr($hr_value, -1);
        $value = (int) $hr_value;
    
        switch (strtoupper($bytes)) {
            case 'G':
                return $value * 1024 * 1024 * 1024;
            case 'M':
                return $value * 1024 * 1024;
            case 'K':
                return $value * 1024;
            default:
                return $value;
        }
    }
}

if (!function_exists('fast_fuzzy_search_get_post_type_counts')) {
    function fast_fuzzy_search_get_post_type_counts() {
        global $wpdb;

        // Try to get the cached result
        $cache_key = 'post_type_counts';
        $counts = wp_cache_get($cache_key, 'fast_fuzzy_search');

        if ($counts === false) {
            // Cache miss, directly prepare and execute the query
            $result = $wpdb->get_results($wpdb->prepare("
                SELECT post_type, COUNT(*) AS count
                FROM {$wpdb->posts}
                WHERE post_status = %s
                GROUP BY post_type
            ", 'publish'));

            $counts = [];
            $total_count = 0;

            // Loop through each post type count
            if ($result) {
                foreach ($result as $row) {
                    $post_type = $row->post_type;
                    $count = (int) $row->count;
                    $counts[$post_type] = $count;
                    $total_count += $count;
                }
            }

            // Store the result in the cache for 1 hour
            wp_cache_set($cache_key, $counts, 'fast_fuzzy_search', HOUR_IN_SECONDS);
        } else {
            // If cached, calculate total count directly
            $total_count = array_sum($counts);
        }

        // Add the total count to the result array
        $counts['total_count'] = $total_count;

        return $counts;
    }
}


if (!function_exists('fast_fuzzy_search_get_recommended_limits')) {
    function fast_fuzzy_search_get_recommended_limits($posts_count) {
        $memory_limits = [
            [1, 1000, '256M'],
            [1000, 10000, '512M'],
            [10000, 20000, '1G'],
            [20000, 30000, '2G'],
            [30000, 40000, '3G'],
            [40000, 50000, '4G'],
            [50000, 60000, '5G'],
            [60000, 70000, '6G'],
            [70000, 80000, '7G'],
            [80000, 90000, '8G'],
            [90000, 100000, '9G'],
        ];

        $timeout_limits = [
            [1, 1000, 10],
            [1000, 10000, 30],
            [10000, 50000, 60],
            [50000, 100000, 180],
            [100000, 500000, 600],
        ];

        $get_limit = function($count, $ranges, $default = 'Unknown') {
            foreach ($ranges as [$min, $max, $value]) {
                if ($count >= $min && $count <= $max) {
                    return $value;
                }
            }
            return $default;
        };

        return [
            'memory_limit'       => $get_limit($posts_count, $memory_limits),
            'max_execution_time' => $get_limit($posts_count, $timeout_limits),
        ];
    }
}

if(!function_exists('fast_fuzzy_search_generate_color_Shades')) {
    function fast_fuzzy_search_generate_color_Shades($hexColor) {
        // Validate input
        if (!is_string($hexColor) || '' === $hexColor || '#' === $hexColor) {
            $hexColor = '#2271b1';
        }

        // Convert HEX to RGB
        $hexColor = ltrim($hexColor, '#');

        // Ensure it's a valid 6-character hex value
        if (strlen($hexColor) !== 6 || !ctype_xdigit($hexColor)) {
            $hexColor = '2271b1';
        }

        $r = hexdec(substr($hexColor, 0, 2));
        $g = hexdec(substr($hexColor, 2, 2));
        $b = hexdec(substr($hexColor, 4, 2));
    
        // Generate shades with larger variations
        $shades = [];
        foreach ([50, 100, 200, 300, 400, 500, 600, 700, 800, 900] as $level) {
            // Base factor: -1 at level 50, 0 at level 500, +0.889 at level 900.
            // Capped at ±0.95 so the extreme shades keep a faint tint of the
            // input color rather than going to pure white / pure black.
            $capped = max(-0.95, min(0.95, ($level - 500) / 450));
            $adjust = function ($color) use ($capped) {
                return round($capped > 0
                    ? $color * (1 - $capped)              // Darken (level > 500)
                    : $color + (255 - $color) * -$capped); // Lighten (level < 500)
            };
            $shades[$level] = sprintf("#%02x%02x%02x",
                max(0, min(255, $adjust($r))),
                max(0, min(255, $adjust($g))),
                max(0, min(255, $adjust($b)))
            );
        }
    
        return $shades;
    }
}

if(!function_exists('fast_fuzzy_search_is_pro')) {
    function fast_fuzzy_search_is_pro() {
        // Use Freemius SDK to determine premium status.
        // Falls back to false if Freemius is not connected.
        if ( function_exists( 'ffs_is_premium' ) ) {
            return ffs_is_premium();
        }

        return false;
    }
}


add_shortcode('fast_fuzzy_search', function($atts) {
    ob_start();
    // Parse attributes (optional)
    $options = get_option('fast_fuzzy_search_options');
    $merged_options = array_merge($options, $atts);
    $type = !empty($merged_options['type']) ? $merged_options['type'] : 'input-field';   

    fast_fuzzy_search_get_template_part('template-parts/'. $type, null, [
        'is_inline' => true,
        'options' => $merged_options,
    ]);

    return ob_get_clean();
});

if(!function_exists('fast_fuzzy_search_allowed_svg_tags')) {
    function fast_fuzzy_search_allowed_svg_tags() {
        return [
            'svg'    => ['xmlns' => true, 'width' => true, 'height' => true, 'viewbox' => true, 'fill' => true, 'class' => true, 'aria-hidden' => true, 'role' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true],
            'g'      => ['fill' => true, 'stroke' => true, 'stroke-width' => true],
            'path'   => ['d' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true],
            'circle' => ['cx' => true, 'cy' => true, 'r' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true],
            'rect'   => ['x' => true, 'y' => true, 'width' => true, 'height' => true, 'fill' => true, 'stroke' => true],
            'line'   => ['x1' => true, 'y1' => true, 'x2' => true, 'y2' => true, 'stroke' => true, 'stroke-width' => true],
            'polyline' => ['points' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true],
            'polygon'  => ['points' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true],
            'title'  => [], // Allows adding a title inside SVG
            'desc'   => [], // Allows adding a description inside SVG
            'use'    => ['href' => true, 'x' => true, 'y' => true, 'width' => true, 'height' => true],
            'animatetransform' => [
                'attributename' => true,
                'type'          => true,
                'from'          => true,
                'to'            => true,
                'dur'           => true,
                'repeatcount'   => true,
            ],
        ];
    }
}