<?php
/**
 * Plugin Name:       Spin Wheel
 * Plugin URI:        https://bdthemes.com/plugin/spin-wheel/
 * Description:       Engage your visitors with an interactive spinning wheel that offers coupons and other rewards. Increase user engagement and boost conversions with this fun and rewarding experience.
 * Version:           2.3.0
 * Requires at least: 6.7
 * Requires PHP:      7.4
 * Author:            bdthemes
 * Author URI:        https://bdthemes.com
 * License:           GPL-2.0-or-later
 * License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain:       spin-wheel
 * Domain Path:       /languages
 */

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

// Define plugin constants
define('SPIN_WHEEL_VERSION', '2.3.0');
define('SPIN_WHEEL__FILE__', __FILE__);
define('SPIN_WHEEL_PATH', plugin_dir_path(__FILE__));
define('SPIN_WHEEL_URL', plugin_dir_url(__FILE__));
define('SPIN_WHEEL_BASENAME', plugin_basename(__FILE__));
define('SPIN_WHEEL_ASSETS', SPIN_WHEEL_URL . 'assets/');
define( 'SPIN_WHEEL_ASSETS_URL_ADMIN', SPIN_WHEEL_URL . 'admin/' );
/** Option: post ID of the wheel auto-embedded site-wide (0 = none). */
define( 'SWP_OPTION_AUTO_EMBED_WHEEL_ID', 'swp_auto_embed_wheel_id' );

/**
 * Main Spin Wheel Class
 */
final class Spin_Wheel {
    
    /**
     * Plugin instance
     *
     * @var Spin_Wheel
     */
    private static $instance = null;
    
    /**
     * Get plugin instance
     *
     * @return Spin_Wheel
     */
    public static function instance() {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Constructor
     */
    private function __construct() {
        $this->init_hooks();
    }
    
    /**
     * Initialize hooks
     */
    private function init_hooks() {
        add_action('plugins_loaded', [$this, 'init']);
        register_activation_hook(__FILE__, [$this, 'activate']);
        register_deactivation_hook(__FILE__, [$this, 'deactivate']);
        
        // Add cron hook for auto cleanup
        // Note: the swp_daily_cleanup handler (auto-cleanup of old entries) is
        // provided by the Spin Wheel Pro add-on. The daily event is still
        // scheduled here; without Pro it is a harmless no-op.
    }
    
    /**
     * Initialize plugin
     */
    public function init() {
        // Translations for plugins hosted on WordPress.org are loaded
        // automatically since WordPress 4.6; no manual load required.

        // Load required files
        $this->load_files();
        
        // Initialize components
        $this->init_components();
    }
    
    /**
     * Load required files
     */
    private function load_files() {
        // Core
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-post-type.php';
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-database.php';
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-ajax.php';
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-shortcode.php';
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-blocks.php';
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-email.php';
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-pro-helper.php';

        // Admin
        if (is_admin()) {
            require_once SPIN_WHEEL_PATH . 'admin/class-swp-admin.php';
            require_once SPIN_WHEEL_PATH . 'admin/class-swp-admin-entries.php';
            require_once SPIN_WHEEL_PATH . 'admin/class-swp-metaboxes.php';
        }
        
        // Frontend
        require_once SPIN_WHEEL_PATH . 'public/class-swp-public.php';
    }
    
    /**
     * Initialize components
     */
    private function init_components() {
        // Core components
        SWP_Post_Type::instance();
        SWP_Database::instance();
        SWP_Ajax::instance();
        SWP_Shortcode::instance();
        SWP_Blocks::instance();
        SWP_Email::instance();
        
        // Admin components
        if (is_admin()) {
            SWP_Admin::instance();
            SWP_Admin_Entries::instance();
            SWP_Metaboxes::instance();
        }
        
        // Frontend components
        if (!is_admin()) {
            SWP_Public::instance();
        }
    }
    
    /**
     * Activate plugin
     */
    public function activate() {
        // Create database tables
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-database.php';
        SWP_Database::create_tables();
        
        // Register post type
        require_once SPIN_WHEEL_PATH . 'includes/class-swp-post-type.php';
        SWP_Post_Type::register();
        
        // Schedule daily cleanup cron job if not already scheduled
        if (!wp_next_scheduled('swp_daily_cleanup')) {
            wp_schedule_event(time(), 'daily', 'swp_daily_cleanup');
        }
        
        // Flush rewrite rules
        flush_rewrite_rules();
    }
    
    /**
     * Deactivate plugin
     */
    public function deactivate() {
        // Clear scheduled cleanup cron job
        $timestamp = wp_next_scheduled('swp_daily_cleanup');
        if ($timestamp) {
            wp_unschedule_event($timestamp, 'swp_daily_cleanup');
        }
        
        // Flush rewrite rules
        flush_rewrite_rules();
    }
    
}

// The global helper functions and "swp" hooks below use the plugin's established
// prefix, which is part of its data schema and Pro add-on API; kept for compatibility.
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals

/**
 * Initialize plugin
 */
function spin_wheel() {
    return Spin_Wheel::instance();
}

/**
 * Get wheel settings with global defaults applied
 * 
 * @param int $wheel_id Wheel ID
 * @param string $meta_key Meta key to retrieve
 * @return array Merged settings with global defaults
 */
function swp_get_wheel_data_with_defaults($wheel_id, $meta_key = '_swp_settings') {
    $global_settings = get_option('swp_settings', []);
    $wheel_data = get_post_meta($wheel_id, $meta_key, true);
    
    if (!is_array($wheel_data)) {
        $wheel_data = [];
    }
    
    // Apply global defaults based on meta key
    if ($meta_key === '_swp_settings') {
        $defaults = [
            'one_entry_per_user' => $global_settings['one_entry_per_user'] ?? 1,
            'enable_confetti' => $global_settings['enable_confetti'] ?? 1,
            'enable_animations' => $global_settings['enable_animations'] ?? 1,
        ];
        return array_merge($defaults, $wheel_data);
    }
    
    if ($meta_key === '_swp_styles') {
        $defaults = [
            'width' => $global_settings['default_width'] ?? 460,
            'height' => $global_settings['default_height'] ?? 460,
            'button_text' => $global_settings['default_button_text'] ?? 'SPIN',
            'button_color' => '#667eea',
        ];
        return array_merge($defaults, $wheel_data);
    }
    
    return $wheel_data;
}

/**
 * Check if Pro plugin is active
 * 
 * @return bool
 */
function spin_wheel_is_pro_activated() {
    return apply_filters('swp_is_pro_active', false);
}

/**
 * Default and coerce per-wheel form settings (visibility, required flags, terms).
 *
 * @param array $form_settings Raw meta.
 * @return array Normalized settings.
 */
function swp_normalize_form_settings($form_settings) {
    if (!is_array($form_settings)) {
        $form_settings = [];
    }
    $defaults = [
        'show_name'             => 1,
        'show_email'            => 1,
        'show_phone'            => 1,
        'require_name'          => 1,
        'require_email'         => 1,
        'require_phone'         => 0,
        'enable_terms_checkbox' => 0,
        'enable_utm_capture'    => 0,
        'terms_label'           => __('I agree to the terms and conditions.', 'spin-wheel'),
    ];
    $out = array_merge($defaults, $form_settings);
    $toggle_keys = ['show_name', 'show_email', 'show_phone', 'require_name', 'require_email', 'require_phone', 'enable_terms_checkbox', 'enable_utm_capture'];
    foreach ($toggle_keys as $key) {
        if (!array_key_exists($key, $form_settings) || $form_settings[$key] === null) {
            $out[$key] = $defaults[$key];
        } else {
            $out[$key] = !empty($form_settings[$key]) ? 1 : 0;
        }
    }
    if (!isset($out['terms_label']) || $out['terms_label'] === '') {
        $out['terms_label'] = $defaults['terms_label'];
    }
    return $out;
}

/**
 * Decode spin_data JSON from a DB row.
 *
 * @param object $entry Row from swp_entries.
 * @return array<string, mixed>
 */
function swp_entry_spin_data_array($entry) {
    if (!is_object($entry) || !isset($entry->spin_data) || $entry->spin_data === null || $entry->spin_data === '') {
        return [];
    }
    if (is_array($entry->spin_data)) {
        return $entry->spin_data;
    }
    $decoded = json_decode((string) $entry->spin_data, true);
    return is_array($decoded) ? $decoded : [];
}

/**
 * UTM / campaign params stored under spin_data.utm.
 *
 * @param object $entry DB row.
 * @return array<string, string>
 */
function swp_entry_get_utm_params($entry) {
    $spin = swp_entry_spin_data_array($entry);
    if (empty($spin['utm']) || !is_array($spin['utm'])) {
        return [];
    }
    $out = [];
    foreach ($spin['utm'] as $k => $v) {
        if (!is_string($k) || $k === '') {
            continue;
        }
        if (is_array($v) || is_object($v)) {
            continue;
        }
        $out[$k] = (string) $v;
    }
    return $out;
}

/**
 * Admin table cell: multi-line HTML summary of UTM params.
 *
 * @param object $entry DB row.
 * @return string Safe HTML (empty if none).
 */
function swp_entry_format_utm_admin_cell($entry) {
    $utm = swp_entry_get_utm_params($entry);
    if ($utm === []) {
        return '';
    }
    $parts = [];
    foreach ($utm as $k => $v) {
        $parts[] = esc_html($k) . ': ' . esc_html($v);
    }
    return implode('<br>', $parts);
}

/**
 * Single cell for CSV export (semicolon-separated key=value).
 *
 * @param object $entry DB row.
 * @return string
 */
function swp_entry_format_utm_csv($entry) {
    $utm = swp_entry_get_utm_params($entry);
    if ($utm === []) {
        return '';
    }
    $parts = [];
    foreach ($utm as $k => $v) {
        $parts[] = $k . '=' . $v;
    }
    return implode('; ', $parts);
}

/**
 * Recursively sanitize a value from user input.
 *
 * Sanitizes every string leaf with sanitize_text_field() while preserving the
 * array structure and non-string scalars (int/float/bool/null). Used to clean
 * nested settings/styles arrays before persisting them to post meta.
 *
 * @param mixed $data Raw value (already unslashed by the caller).
 * @return mixed
 */
function swp_sanitize_recursive($data) {
    if (is_array($data)) {
        $out = [];
        foreach ($data as $key => $value) {
            $out[sanitize_text_field((string) $key)] = swp_sanitize_recursive($value);
        }
        return $out;
    }
    if (is_string($data)) {
        return sanitize_text_field($data);
    }
    // Preserve int/float/bool/null.
    return $data;
}

/**
 * Neutralize spreadsheet formula injection in a CSV cell.
 *
 * A leading =, +, -, @, tab or carriage return causes Excel/LibreOffice/Sheets to
 * evaluate the cell as a formula. Prefix such values with a single quote so they are
 * treated as literal text. Applied to every user-supplied field before fputcsv().
 *
 * @param mixed $value Cell value.
 * @return string
 */
function swp_csv_safe_cell($value) {
    $value = (string) $value;
    if ($value !== '' && in_array($value[0], ['=', '+', '-', '@', "\t", "\r"], true)) {
        return "'" . $value;
    }
    return $value;
}

/**
 * Deterministic placeholder email when the email field is hidden or optional and empty.
 *
 * @param int    $wheel_id Wheel post ID.
 * @param string $ip_address Client IP.
 * @return string
 */
function swp_get_anonymous_guest_email($wheel_id, $ip_address) {
    $salt = apply_filters('swp_guest_email_salt', 'spin-wheel');
    $hash = substr(hash('sha256', (string) $wheel_id . '|' . (string) $ip_address . '|' . $salt), 0, 20);
    return 'guest+' . $hash . '@example.invalid';
}

/**
 * Wheel editor field label: text, optional help tooltip (?), optional Pro badge (same pattern as "Maximum Participants").
 *
 * @param string $for_id    Input id for the `for` attribute, or '' to omit.
 * @param string $label     Translated label text.
 * @param string $tooltip   Translated tooltip plain text, or '' to hide the help icon.
 * @param bool   $pro_badge Whether to print the Pro badge after the tooltip.
 */
function swp_admin_field_label($for_id, $label, $tooltip = '', $pro_badge = false) {
    if ($for_id !== '' && $for_id !== null) {
        printf('<label class="swp-label" for="%s">', esc_attr($for_id));
    } else {
        echo '<label class="swp-label">';
    }
    echo '<span class="swp-label-text">' . esc_html($label) . '</span>';
    if ($tooltip !== '') {
        echo '<span class="swp-tooltip-wrapper">';
        echo '<span class="swp-tooltip-icon" aria-hidden="true">?</span>';
        echo '<span class="swp-tooltip-text">' . esc_html($tooltip) . '</span>';
        echo '</span>';
    }
    // The $pro_badge flag is retained for signature compatibility; the free
    // plugin no longer renders upsell badges.
    unset($pro_badge);
    echo '</label>';
}

// Start the plugin
spin_wheel();

// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals
