<?php
/**
 * Database handler for Maple Carts.
 *
 * @package MapleCarts
 */

defined( 'ABSPATH' ) || exit;

/**
 * Database class.
 */
class Maple_Carts_DB {

    /**
     * Create database tables on activation.
     */
    public static function activate() {
        self::create_tables();
        self::seed_default_emails();
        self::set_default_options();
        
        // Schedule cron events.
        if ( ! wp_next_scheduled( 'maple_carts_send_emails' ) ) {
            wp_schedule_event( time(), 'every_five_minutes', 'maple_carts_send_emails' );
        }
        if ( ! wp_next_scheduled( 'maple_carts_cleanup' ) ) {
            wp_schedule_event( time(), 'daily', 'maple_carts_cleanup' );
        }

        // Add custom cron interval.
        add_filter( 'cron_schedules', [ __CLASS__, 'add_cron_interval' ] );

        update_option( 'maple_carts_version', MAPLE_CARTS_VERSION );
    }

    /**
     * Add custom cron interval.
     *
     * @param array $schedules Existing schedules.
     * @return array
     */
    public static function add_cron_interval( $schedules ) {
        $schedules['every_five_minutes'] = [
            'interval' => 300,
            'display'  => __( 'Every Five Minutes', 'maple-carts' ),
        ];
        return $schedules;
    }

    /**
     * Create database tables.
     */
    public static function create_tables() {
        global $wpdb;

        $charset_collate = $wpdb->get_charset_collate();
        $carts_table     = $wpdb->prefix . MAPLE_CARTS_TABLE;
        $emails_table    = $wpdb->prefix . MAPLE_CARTS_EMAILS_TABLE;
        $log_table       = $wpdb->prefix . MAPLE_CARTS_EMAIL_LOG_TABLE;

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        // Abandoned carts table.
        $sql = "CREATE TABLE {$carts_table} (
            id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            session_id VARCHAR(60) NOT NULL,
            email VARCHAR(200) DEFAULT NULL,
            cart_contents LONGTEXT,
            cart_total DECIMAL(10,2) DEFAULT 0,
            items_count INT DEFAULT 0,
            currency VARCHAR(10) DEFAULT 'USD',
            customer_name VARCHAR(200) DEFAULT NULL,
            customer_phone VARCHAR(50) DEFAULT NULL,
            billing_data LONGTEXT,
            status ENUM('active','abandoned','recovered','lost','converted') DEFAULT 'active',
            abandonment_type ENUM('browsing','checkout','payment') DEFAULT NULL,
            recovery_url VARCHAR(255) DEFAULT NULL,
            coupon_code VARCHAR(50) DEFAULT NULL,
            recovery_source VARCHAR(50) DEFAULT NULL,
            unsubscribed TINYINT(1) DEFAULT 0,
            ip_address VARCHAR(45) DEFAULT NULL,
            user_agent TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            first_seen_at DATETIME DEFAULT NULL,
            last_tracked_at DATETIME DEFAULT NULL,
            abandoned_at DATETIME DEFAULT NULL,
            recovered_at DATETIME DEFAULT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY session_id (session_id),
            KEY email (email),
            KEY status (status),
            KEY created_at (created_at),
            KEY updated_at (updated_at),
            KEY last_tracked_at (last_tracked_at),
            KEY status_tracked (status, last_tracked_at)
        ) {$charset_collate};";
        dbDelta( $sql );

        // Email templates table.
        $sql = "CREATE TABLE {$emails_table} (
            id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            name VARCHAR(200) NOT NULL,
            subject VARCHAR(500) NOT NULL,
            body LONGTEXT NOT NULL,
            delay_value INT(11) DEFAULT 1,
            delay_unit ENUM('minutes','hours','days') DEFAULT 'hours',
            is_active TINYINT(1) DEFAULT 0,
            include_coupon TINYINT(1) DEFAULT 0,
            coupon_type ENUM('percent','fixed_cart') DEFAULT 'percent',
            coupon_amount DECIMAL(10,2) DEFAULT 10,
            coupon_expires_days INT(11) DEFAULT 7,
            sort_order INT(11) DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY is_active (is_active),
            KEY sort_order (sort_order)
        ) {$charset_collate};";
        dbDelta( $sql );

        // Email log table.
        $sql = "CREATE TABLE {$log_table} (
            id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            cart_id BIGINT(20) UNSIGNED NOT NULL,
            email_template_id BIGINT(20) UNSIGNED NOT NULL,
            scheduled_at DATETIME NOT NULL,
            sent_at DATETIME DEFAULT NULL,
            status ENUM('pending','sent','failed','cancelled') DEFAULT 'pending',
            coupon_code VARCHAR(50) DEFAULT NULL,
            error_message TEXT,
            PRIMARY KEY (id),
            KEY cart_id (cart_id),
            KEY email_template_id (email_template_id),
            KEY status (status),
            KEY scheduled_at (scheduled_at)
        ) {$charset_collate};";
        dbDelta( $sql );
    }

    /**
     * Set default options.
     */
    public static function set_default_options() {
        $defaults = [
            'enabled'           => 'yes',
            'cart_cutoff_time'  => 15, // Minutes before cart is considered abandoned.
            'delete_after_days' => 90, // Days to keep abandoned cart data.
            'email_mode'        => 'builtin', // 'builtin' or 'mailjet'.
            'mailjet_api_key'   => '',
            'mailjet_secret_key'=> '',
            'from_name'         => get_bloginfo( 'name' ),
            'from_email'        => get_option( 'admin_email' ),
            'reply_to'          => get_option( 'admin_email' ),
            'notify_admin'      => 'yes',
            'admin_email'       => get_option( 'admin_email' ),
            'require_consent'   => 'no',
            'consent_text'      => __( 'I agree to receive cart reminder emails if I don\'t complete my purchase.', 'maple-carts' ),
            'show_delete_link'  => 'no',
            'exclude_roles'     => [],
            'gdpr_notice'       => '',
        ];

        $existing = get_option( 'maple_carts_settings', [] );
        $merged   = array_merge( $defaults, $existing );
        update_option( 'maple_carts_settings', $merged );
    }

    /**
     * Seed default email templates.
     */
    public static function seed_default_emails() {
        global $wpdb;

        $table = $wpdb->prefix . MAPLE_CARTS_EMAILS_TABLE;
        $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );

        if ( $count > 0 ) {
            return;
        }

        $templates = [
            [
                'name'           => __( 'First Reminder', 'maple-carts' ),
                'subject'        => __( '{{customer_name}}, you left something behind!', 'maple-carts' ),
                'body'           => self::get_default_email_body( 1 ),
                'delay_value'    => 1,
                'delay_unit'     => 'hours',
                'is_active'      => 0,
                'include_coupon' => 0,
                'sort_order'     => 1,
            ],
            [
                'name'           => __( 'Second Reminder', 'maple-carts' ),
                'subject'        => __( 'Your cart is waiting, {{customer_name}}', 'maple-carts' ),
                'body'           => self::get_default_email_body( 2 ),
                'delay_value'    => 24,
                'delay_unit'     => 'hours',
                'is_active'      => 0,
                'include_coupon' => 0,
                'sort_order'     => 2,
            ],
            [
                'name'           => __( 'Final Reminder with Discount', 'maple-carts' ),
                'subject'        => __( 'Last chance! {{coupon_amount}} off your cart', 'maple-carts' ),
                'body'           => self::get_default_email_body( 3 ),
                'delay_value'    => 3,
                'delay_unit'     => 'days',
                'is_active'      => 0,
                'include_coupon' => 1,
                'coupon_type'    => 'percent',
                'coupon_amount'  => 10,
                'sort_order'     => 3,
            ],
        ];

        foreach ( $templates as $template ) {
            $wpdb->insert( $table, $template );
        }
    }

    /**
     * Get default email body.
     *
     * @param int $template_num Template number.
     * @return string
     */
    private static function get_default_email_body( $template_num ) {
        $bodies = [
            1 => '<p>Hi {{customer_name}},</p>
<p>We noticed you left some items in your cart at {{site_name}}. No worries — your cart is saved and ready when you are!</p>
<p>{{cart_contents}}</p>
<p><a href="{{recovery_url}}" style="background-color:#0073aa;color:#ffffff;padding:12px 24px;text-decoration:none;border-radius:4px;display:inline-block;">Complete Your Order</a></p>
<p>If you have any questions, just reply to this email.</p>
<p>Thanks,<br>{{site_name}}</p>
<p style="font-size:12px;color:#666;">{{unsubscribe_link}}{{delete_data_link}}</p>',

            2 => '<p>Hi {{customer_name}},</p>
<p>Just a friendly reminder that your cart at {{site_name}} is still waiting for you.</p>
<p>{{cart_contents}}</p>
<p><strong>Cart Total: {{cart_total}}</strong></p>
<p><a href="{{recovery_url}}" style="background-color:#0073aa;color:#ffffff;padding:12px 24px;text-decoration:none;border-radius:4px;display:inline-block;">Return to Your Cart</a></p>
<p>Best,<br>{{site_name}}</p>
<p style="font-size:12px;color:#666;">{{unsubscribe_link}}{{delete_data_link}}</p>',

            3 => '<p>Hi {{customer_name}},</p>
<p>This is your last reminder about the items in your cart. To help you complete your purchase, we\'re offering you an exclusive discount!</p>
<p>{{cart_contents}}</p>
<p><strong>Use code <span style="background:#f0f0f0;padding:4px 8px;font-family:monospace;">{{coupon_code}}</span> for {{coupon_amount}} off!</strong></p>
<p><a href="{{recovery_url}}" style="background-color:#0073aa;color:#ffffff;padding:12px 24px;text-decoration:none;border-radius:4px;display:inline-block;">Claim Your Discount</a></p>
<p>This offer expires in {{coupon_expires}} days.</p>
<p>Thanks,<br>{{site_name}}</p>
<p style="font-size:12px;color:#666;">{{unsubscribe_link}}{{delete_data_link}}</p>',
        ];

        return $bodies[ $template_num ] ?? $bodies[1];
    }

    /**
     * Get a cart by session ID.
     *
     * @param string $session_id Session ID.
     * @return object|null
     */
    public static function get_cart_by_session( $session_id ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_TABLE;
        return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE session_id = %s", $session_id ) );
    }

    /**
     * Get a cart by email.
     *
     * @param string $email Email address.
     * @param array  $statuses Statuses to filter by.
     * @return object|null
     */
    public static function get_cart_by_email( $email, $statuses = [ 'active', 'abandoned' ] ) {
        global $wpdb;
        $table        = $wpdb->prefix . MAPLE_CARTS_TABLE;
        $placeholders = implode( ',', array_fill( 0, count( $statuses ), '%s' ) );
        $params       = array_merge( [ $email ], $statuses );
        
        return $wpdb->get_row( 
            $wpdb->prepare( 
                "SELECT * FROM {$table} WHERE email = %s AND status IN ({$placeholders}) ORDER BY created_at DESC LIMIT 1",
                $params
            )
        );
    }

    /**
     * Insert or update a cart.
     *
     * @param array $data Cart data.
     * @return int|false Cart ID or false on failure.
     */
    public static function save_cart( $data ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_TABLE;

        // Always update last_tracked_at when saving.
        $data['last_tracked_at'] = current_time( 'mysql' );

        $existing = null;
        if ( ! empty( $data['session_id'] ) ) {
            $existing = self::get_cart_by_session( $data['session_id'] );
        }

        if ( $existing ) {
            $wpdb->update( $table, $data, [ 'id' => $existing->id ] );
            $cart_id = $existing->id;
        } else {
            // Set first_seen_at for new carts.
            $data['first_seen_at'] = current_time( 'mysql' );
            $wpdb->insert( $table, $data );
            $cart_id = $wpdb->insert_id;
        }

        // Fire webhook for integrations (Facebook CAPI, etc.).
        if ( $cart_id ) {
            $cart = self::get_cart_by_id( $cart_id );
            if ( $cart ) {
                /**
                 * Fires when a cart is created or updated.
                 * Useful for Facebook CAPI, retargeting pixels, etc.
                 *
                 * @param object $cart Cart object.
                 */
                do_action( 'maple_carts_cart_updated', $cart );
            }
        }

        return $cart_id;
    }

    /**
     * Update cart status.
     *
     * @param int    $cart_id Cart ID.
     * @param string $status  New status.
     * @param array  $extra   Extra data to update.
     */
    public static function update_cart_status( $cart_id, $status, $extra = [] ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_TABLE;

        $data = array_merge( [ 'status' => $status ], $extra );

        if ( 'abandoned' === $status ) {
            $data['abandoned_at'] = current_time( 'mysql' );
        } elseif ( 'recovered' === $status ) {
            $data['recovered_at'] = current_time( 'mysql' );
        }

        $wpdb->update( $table, $data, [ 'id' => $cart_id ] );

        // Fire hooks for integrations (Mailjet, etc.).
        $cart = self::get_cart_by_id( $cart_id );
        if ( $cart ) {
            if ( 'abandoned' === $status ) {
                /**
                 * Fires when a cart is marked as abandoned.
                 *
                 * @param int    $cart_id Cart ID.
                 * @param object $cart    Cart object.
                 */
                do_action( 'maple_carts_cart_abandoned', $cart_id, $cart );
            } elseif ( 'recovered' === $status ) {
                /**
                 * Fires when a cart is recovered.
                 *
                 * @param int    $cart_id Cart ID.
                 * @param object $cart    Cart object.
                 */
                do_action( 'maple_carts_cart_recovered', $cart_id, $cart );
            } elseif ( 'converted' === $status ) {
                /**
                 * Fires when a cart is converted (order placed).
                 *
                 * @param int    $cart_id Cart ID.
                 * @param object $cart    Cart object.
                 */
                do_action( 'maple_carts_cart_converted', $cart_id, $cart );
            }
        }
    }

    /**
     * Get cart by ID.
     *
     * @param int $cart_id Cart ID.
     * @return object|null
     */
    public static function get_cart_by_id( $cart_id ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_TABLE;
        
        return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $cart_id ) );
    }

    /**
     * Get carts by status.
     *
     * @param string $status Status.
     * @param int    $limit  Limit.
     * @param int    $offset Offset.
     * @return array
     */
    public static function get_carts_by_status( $status, $limit = 50, $offset = 0 ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_TABLE;

        return $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM {$table} WHERE status = %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
                $status,
                $limit,
                $offset
            )
        );
    }

    /**
     * Count carts by status.
     *
     * @param string $status Status.
     * @return int
     */
    public static function count_carts_by_status( $status ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_TABLE;

        return (int) $wpdb->get_var(
            $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE status = %s", $status )
        );
    }

    /**
     * Get all email templates.
     *
     * @param bool $active_only Only active templates.
     * @return array
     */
    public static function get_email_templates( $active_only = false ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_EMAILS_TABLE;

        $where = $active_only ? 'WHERE is_active = 1' : '';

        return $wpdb->get_results( "SELECT * FROM {$table} {$where} ORDER BY sort_order ASC" );
    }

    /**
     * Get email template by ID.
     *
     * @param int $id Template ID.
     * @return object|null
     */
    public static function get_email_template( $id ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_EMAILS_TABLE;

        return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ) );
    }

    /**
     * Save email template.
     *
     * @param array $data Template data.
     * @return int|false Template ID or false.
     */
    public static function save_email_template( $data ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_EMAILS_TABLE;

        if ( ! empty( $data['id'] ) ) {
            $id = $data['id'];
            unset( $data['id'] );
            $wpdb->update( $table, $data, [ 'id' => $id ] );
            return $id;
        } else {
            $wpdb->insert( $table, $data );
            return $wpdb->insert_id;
        }
    }

    /**
     * Delete email template.
     *
     * @param int $id Template ID.
     * @return bool
     */
    public static function delete_email_template( $id ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_EMAILS_TABLE;

        return $wpdb->delete( $table, [ 'id' => $id ] ) !== false;
    }

    /**
     * Schedule emails for a cart.
     *
     * @param int $cart_id Cart ID.
     */
    public static function schedule_emails( $cart_id ) {
        global $wpdb;

        $log_table = $wpdb->prefix . MAPLE_CARTS_EMAIL_LOG_TABLE;
        $cart      = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}" . MAPLE_CARTS_TABLE . " WHERE id = %d", $cart_id ) );
        
        if ( ! $cart || $cart->unsubscribed ) {
            return;
        }

        $templates = self::get_email_templates( true );
        if ( empty( $templates ) ) {
            return;
        }

        $base_time = $cart->abandoned_at ? $cart->abandoned_at : current_time( 'mysql' );

        // Get all existing scheduled emails for this cart in one query.
        $existing_template_ids = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT email_template_id FROM {$log_table} WHERE cart_id = %d",
                $cart_id
            )
        );
        $existing_set = array_flip( $existing_template_ids );

        foreach ( $templates as $template ) {
            // Skip if already scheduled.
            if ( isset( $existing_set[ $template->id ] ) ) {
                continue;
            }

            // Calculate scheduled time.
            $delay_seconds = self::get_delay_seconds( $template->delay_value, $template->delay_unit );
            $scheduled_at  = gmdate( 'Y-m-d H:i:s', strtotime( $base_time ) + $delay_seconds );

            $wpdb->insert(
                $log_table,
                [
                    'cart_id'           => $cart_id,
                    'email_template_id' => $template->id,
                    'scheduled_at'      => $scheduled_at,
                    'status'            => 'pending',
                ]
            );
        }
    }

    /**
     * Get delay in seconds.
     *
     * @param int    $value Delay value.
     * @param string $unit  Delay unit.
     * @return int
     */
    private static function get_delay_seconds( $value, $unit ) {
        switch ( $unit ) {
            case 'minutes':
                return $value * 60;
            case 'hours':
                return $value * 3600;
            case 'days':
                return $value * 86400;
            default:
                return $value * 3600;
        }
    }

    /**
     * Get pending emails to send.
     *
     * @return array
     */
    public static function get_pending_emails() {
        global $wpdb;

        $log_table   = $wpdb->prefix . MAPLE_CARTS_EMAIL_LOG_TABLE;
        $carts_table = $wpdb->prefix . MAPLE_CARTS_TABLE;
        $email_table = $wpdb->prefix . MAPLE_CARTS_EMAILS_TABLE;
        $now         = current_time( 'mysql' );

        return $wpdb->get_results(
            $wpdb->prepare(
                "SELECT l.*, c.email, c.customer_name, c.cart_contents, c.cart_total, c.currency, 
                        c.session_id, c.coupon_code as cart_coupon, c.billing_data,
                        e.subject, e.body, e.include_coupon, e.coupon_type, e.coupon_amount, e.coupon_expires_days
                 FROM {$log_table} l
                 JOIN {$carts_table} c ON l.cart_id = c.id
                 JOIN {$email_table} e ON l.email_template_id = e.id
                 WHERE l.status = 'pending' 
                   AND l.scheduled_at <= %s
                   AND c.status = 'abandoned'
                   AND c.unsubscribed = 0
                 ORDER BY l.scheduled_at ASC
                 LIMIT 50",
                $now
            )
        );
    }

    /**
     * Update email log status.
     *
     * @param int    $id     Log ID.
     * @param string $status Status.
     * @param array  $extra  Extra data.
     */
    public static function update_email_log( $id, $status, $extra = [] ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_EMAIL_LOG_TABLE;

        $data = array_merge( [ 'status' => $status ], $extra );

        if ( 'sent' === $status ) {
            $data['sent_at'] = current_time( 'mysql' );
        }

        $wpdb->update( $table, $data, [ 'id' => $id ] );
    }

    /**
     * Cancel pending emails for a cart.
     *
     * @param int $cart_id Cart ID.
     */
    public static function cancel_pending_emails( $cart_id ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_EMAIL_LOG_TABLE;

        $wpdb->update(
            $table,
            [ 'status' => 'cancelled' ],
            [ 'cart_id' => $cart_id, 'status' => 'pending' ]
        );
    }

    /**
     * Get cart statistics.
     *
     * @param string $period Period (7days, 30days, all).
     * @return array
     */
    public static function get_stats( $period = '30days' ) {
        global $wpdb;
        $table = $wpdb->prefix . MAPLE_CARTS_TABLE;

        $date_filter = '';
        if ( '7days' === $period ) {
            $date_filter = "AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)";
        } elseif ( '30days' === $period ) {
            $date_filter = "AND created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
        }

        $stats = [
            'abandoned'         => 0,
            'recovered'         => 0,
            'lost'              => 0,
            'abandoned_value'   => 0,
            'recovered_value'   => 0,
            'recovery_rate'     => 0,
        ];

        $results = $wpdb->get_results(
            "SELECT status, COUNT(*) as count, SUM(cart_total) as total 
             FROM {$table} 
             WHERE 1=1 {$date_filter}
             GROUP BY status"
        );

        foreach ( $results as $row ) {
            if ( 'abandoned' === $row->status ) {
                $stats['abandoned']       = (int) $row->count;
                $stats['abandoned_value'] = (float) $row->total;
            } elseif ( 'recovered' === $row->status ) {
                $stats['recovered']       = (int) $row->count;
                $stats['recovered_value'] = (float) $row->total;
            } elseif ( 'lost' === $row->status ) {
                $stats['lost'] = (int) $row->count;
            }
        }

        $total_actionable = $stats['abandoned'] + $stats['recovered'] + $stats['lost'];
        if ( $total_actionable > 0 ) {
            $stats['recovery_rate'] = round( ( $stats['recovered'] / $total_actionable ) * 100, 1 );
        }

        return $stats;
    }

    /**
     * Cleanup old data.
     * Runs daily, processes in batches to prevent memory issues.
     */
    public static function cleanup_old_data() {
        global $wpdb;

        // Prevent overlapping runs.
        if ( get_transient( 'maple_carts_cleanup_running' ) ) {
            return;
        }
        set_transient( 'maple_carts_cleanup_running', 1, 3600 ); // 1 hour lock.

        $days        = (int) Maple_Carts::get_option( 'delete_after_days', 90 );
        $carts_table = $wpdb->prefix . MAPLE_CARTS_TABLE;
        $log_table   = $wpdb->prefix . MAPLE_CARTS_EMAIL_LOG_TABLE;
        $batch_size  = 500;

        // Delete old carts in batches.
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$carts_table} WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY) LIMIT %d",
                $days,
                $batch_size
            )
        );

        // Delete orphaned email logs in batches.
        $wpdb->query(
            "DELETE l FROM {$log_table} l 
             LEFT JOIN {$carts_table} c ON l.cart_id = c.id 
             WHERE c.id IS NULL
             LIMIT {$batch_size}"
        );

        // Delete expired coupons created by this plugin (batched).
        $expired_coupons = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT ID FROM {$wpdb->posts} 
                 WHERE post_type = 'shop_coupon' 
                 AND ID IN (
                     SELECT post_id FROM {$wpdb->postmeta} 
                     WHERE meta_key = '_maple_carts_coupon' AND meta_value = '1'
                 )
                 AND ID IN (
                     SELECT post_id FROM {$wpdb->postmeta} 
                     WHERE meta_key = 'date_expires' AND meta_value < UNIX_TIMESTAMP()
                 )
                 LIMIT %d",
                $batch_size
            )
        );

        foreach ( $expired_coupons as $coupon_id ) {
            wp_delete_post( $coupon_id, true );
        }

        // Delete unused coupons older than 30 days (never used, cart may have converted another way).
        $unused_old_coupons = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT p.ID FROM {$wpdb->posts} p
                 INNER JOIN {$wpdb->postmeta} pm_maple ON p.ID = pm_maple.post_id 
                     AND pm_maple.meta_key = '_maple_carts_coupon' AND pm_maple.meta_value = '1'
                 INNER JOIN {$wpdb->postmeta} pm_created ON p.ID = pm_created.post_id 
                     AND pm_created.meta_key = '_maple_carts_created_at' AND pm_created.meta_value < %d
                 INNER JOIN {$wpdb->postmeta} pm_usage ON p.ID = pm_usage.post_id 
                     AND pm_usage.meta_key = 'usage_count' AND pm_usage.meta_value = '0'
                 WHERE p.post_type = 'shop_coupon'
                 LIMIT %d",
                strtotime( '-30 days' ),
                $batch_size
            )
        );

        foreach ( $unused_old_coupons as $coupon_id ) {
            wp_delete_post( $coupon_id, true );
        }

        delete_transient( 'maple_carts_cleanup_running' );
    }

    /**
     * Delete a cart.
     *
     * @param int $cart_id Cart ID.
     * @return bool
     */
    public static function delete_cart( $cart_id ) {
        global $wpdb;

        // Delete email logs first.
        $wpdb->delete( $wpdb->prefix . MAPLE_CARTS_EMAIL_LOG_TABLE, [ 'cart_id' => $cart_id ] );
        
        // Delete cart.
        return $wpdb->delete( $wpdb->prefix . MAPLE_CARTS_TABLE, [ 'id' => $cart_id ] ) !== false;
    }
}

// Register cron interval early.
add_filter( 'cron_schedules', [ 'Maple_Carts_DB', 'add_cron_interval' ] );
