<?php
/**
 * Uninstall cleanup for Calliope Social Publisher.
 *
 * The plugin preserves data by default. Cleanup only runs when the site owner
 * enables the uninstall setting or defines CALLIOPE_SP_CLEANUP_ON_DELETE.
 * All cleanup steps are defensive so plugin deletion is never blocked by a
 * stale option, transient, upload directory, or host filesystem restriction.
 *
 * @package Calliope_Social_Publisher
 */

if (!defined('WP_UNINSTALL_PLUGIN')) {
    exit;
}

if (!function_exists('calliope_sp_uninstall_should_cleanup')) {
    /**
     * Decide whether plugin-owned data should be removed on uninstall.
     *
     * @return bool
     */
    function calliope_sp_uninstall_should_cleanup() {
        $enabled = (bool) get_option('calliope_sp_cleanup_on_delete', false);

        if (defined('CALLIOPE_SP_CLEANUP_ON_DELETE')) {
            $enabled = (bool) CALLIOPE_SP_CLEANUP_ON_DELETE;
        }

        /**
         * Filter whether Calliope Social Publisher should remove its data on uninstall.
         *
         * @param bool $enabled Current cleanup decision.
         */
        return (bool) apply_filters('calliope_sp_cleanup_on_delete', $enabled);
    }
}

if (!function_exists('calliope_sp_uninstall_delete_options')) {
    /**
     * Delete a list of current-site options safely.
     *
     * @param string[] $option_names Option names.
     * @return void
     */
    function calliope_sp_uninstall_delete_options(array $option_names) {
        foreach ($option_names as $option_name) {
            $option_name = sanitize_key($option_name);
            if ($option_name === '') {
                continue;
            }

            delete_option($option_name);
        }
    }
}

if (!function_exists('calliope_sp_uninstall_delete_transient_patterns')) {
    /**
     * Delete known transient keys without using wildcard direct database queries.
     *
     * WordPress does not expose a safe wildcard transient deletion API. During
     * uninstall, avoid custom SQL so Plugin Check does not report unescaped
     * table or column identifiers. OAuth state transients that include random
     * suffixes can expire naturally.
     *
     * @param string[] $patterns Transient keys or legacy wildcard patterns.
     * @return void
     */
    function calliope_sp_uninstall_delete_transient_patterns(array $patterns) {
        foreach ($patterns as $pattern) {
            $transient_name = str_replace('%', '', (string) $pattern);
            $transient_name = sanitize_key($transient_name);

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

            delete_transient($transient_name);
        }
    }
}

if (!function_exists('calliope_sp_uninstall_delete_option_prefixes')) {
    /**
     * Delete plugin-owned dynamic options by exact prefix.
     *
     * @param string[] $prefixes Option prefixes.
     * @return void
     */
    function calliope_sp_uninstall_delete_option_prefixes(array $prefixes) {
        global $wpdb;

        foreach ($prefixes as $prefix) {
            $prefix = sanitize_key((string) $prefix);
            if ($prefix === '' || strpos($prefix, 'calliope_sp_') !== 0) {
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Uninstall cleanup must enumerate plugin-owned dynamic options by prefix.
            $option_names = $wpdb->get_col(
                $wpdb->prepare(
                    "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
                    $wpdb->esc_like($prefix) . '%'
                )
            );

            foreach ((array) $option_names as $option_name) {
                $option_name = is_string($option_name) ? $option_name : '';
                if ($option_name !== '' && strpos($option_name, $prefix) === 0) {
                    delete_option($option_name);
                }
            }
        }
    }
}

if (!function_exists('calliope_sp_uninstall_delete_transient_prefixes')) {
    /**
     * Delete plugin-owned dynamic transients by exact prefix.
     *
     * @param string[] $prefixes Transient prefixes, without WordPress transient markers.
     * @return void
     */
    function calliope_sp_uninstall_delete_transient_prefixes(array $prefixes) {
        global $wpdb;

        foreach ($prefixes as $prefix) {
            $prefix = sanitize_key((string) $prefix);
            if ($prefix === '' || strpos($prefix, 'calliope_sp_') !== 0) {
                continue;
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Uninstall cleanup must enumerate plugin-owned dynamic transients by prefix.
            $option_names = $wpdb->get_col(
                $wpdb->prepare(
                    "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
                    $wpdb->esc_like('_transient_' . $prefix) . '%',
                    $wpdb->esc_like('_transient_timeout_' . $prefix) . '%'
                )
            );

            foreach ((array) $option_names as $option_name) {
                $option_name = is_string($option_name) ? $option_name : '';
                if (strpos($option_name, '_transient_timeout_') === 0) {
                    delete_transient(substr($option_name, strlen('_transient_timeout_')));
                } elseif (strpos($option_name, '_transient_') === 0) {
                    delete_transient(substr($option_name, strlen('_transient_')));
                }
            }

        }
    }
}

if (!function_exists('calliope_sp_uninstall_get_filesystem')) {
    /**
     * Get an initialized WordPress filesystem instance for uninstall cleanup.
     *
     * @return WP_Filesystem_Base|null
     */
    function calliope_sp_uninstall_get_filesystem() {
        global $wp_filesystem;

        if (!function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        if (!WP_Filesystem()) {
            return null;
        }

        return $wp_filesystem;
    }
}

if (!function_exists('calliope_sp_uninstall_delete_upload_dir')) {
    /**
     * Recursively remove a plugin-owned directory under uploads.
     *
     * @param string $dir Directory path.
     * @return void
     */
    function calliope_sp_uninstall_delete_upload_dir($dir) {
        $dir = wp_normalize_path((string) $dir);
        if ($dir === '') {
            return;
        }

        $filesystem = calliope_sp_uninstall_get_filesystem();
        if (!$filesystem || !$filesystem->is_dir($dir)) {
            return;
        }

        $filesystem->delete($dir, true);
    }
}

if (!function_exists('calliope_sp_uninstall_cleanup_current_site')) {
    /**
     * Remove plugin-owned data for the current site only.
     *
     * Multisite uninstall switches blogs before calling this function so every
     * site uses its own option table, uploads directory, post meta, and history table.
     *
     * @return void
     */
    function calliope_sp_uninstall_cleanup_current_site() {
        global $wpdb;

    $calliope_sp_option_names = [
        'calliope_sp_cleanup_on_delete',
        'calliope_sp_history_retry_enabled',
        'calliope_sp_social_templates_enabled',
        'calliope_sp_social_templates',
        'calliope_sp_tracking_enabled',
        'calliope_sp_tracking_settings',
        'calliope_sp_short_links_enabled',
        'calliope_sp_short_links_db_version',
        'calliope_sp_short_links_db_upgrade_lock',
        'calliope_sp_short_links_rewrite_version',
        'calliope_sp_failure_alerts_enabled',
        'calliope_sp_failure_alert_email',
        'calliope_sp_connection_backup',
        'calliope_sp_blogger_access_token',
        'calliope_sp_blogger_blog_id',
        'calliope_sp_blogger_blogs_last',
        'calliope_sp_blogger_client_id',
        'calliope_sp_blogger_client_secret',
        'calliope_sp_blogger_connection_status',
        'calliope_sp_blogger_expires_at',
        'calliope_sp_blogger_granted_scopes',
        'calliope_sp_blogger_auth_method',
        'calliope_sp_blogger_refresh_token',
        'calliope_sp_blogger_validation',
        'calliope_sp_bsky_connection_status',
        'calliope_sp_bsky_handle',
        'calliope_sp_bsky_password',
        'calliope_sp_bsky_validation',
        'calliope_sp_debug_logs',
        'calliope_sp_fb_app_id',
        'calliope_sp_fb_app_secret',
        'calliope_sp_fb_business_id',
        'calliope_sp_fb_businesses_last',
        'calliope_sp_fb_config_id',
        'calliope_sp_fb_connection_status',
        'calliope_sp_fb_expires_at',
        'calliope_sp_fb_last_oauth_debug',
        'calliope_sp_fb_page_access_token',
        'calliope_sp_fb_page_id',
        'calliope_sp_fb_pages_last',
        'calliope_sp_fb_user_access_token',
        'calliope_sp_fb_granted_scopes',
        'calliope_sp_facebook_validation',
        'calliope_sp_gemini_api_key',
        'calliope_sp_gemini_model',
        'calliope_sp_generation_language',
        'calliope_sp_history_db_version',
        'calliope_sp_history_db_upgrade_lock',
        'calliope_sp_data_migration_version',

        // Legacy Flipboard options cleanup, kept for old installations.
        'calliope_sp_flipboard_enable_share_button',
        'calliope_sp_flipboard_feed_url',

        'calliope_sp_ig_access_token',
        'calliope_sp_ig_app_id',
        'calliope_sp_ig_app_secret',
        'calliope_sp_ig_connection_status',
        'calliope_sp_ig_expires_at',
        'calliope_sp_ig_page_id',
        'calliope_sp_ig_user_id',
        'calliope_sp_ig_username',
        'calliope_sp_instagram_validation',
        'calliope_sp_li_access_token',
        'calliope_sp_li_client_id',
        'calliope_sp_li_client_secret',
        'calliope_sp_li_connection_status',
        'calliope_sp_li_expires_at',
        'calliope_sp_li_refresh_token',
        'calliope_sp_li_refresh_expires_at',
        'calliope_sp_li_last_page_target',
        'calliope_sp_li_last_scope_list',
        'calliope_sp_li_last_target_kind',
        'calliope_sp_li_person_id',
        'calliope_sp_li_validation_status',
        'calliope_sp_li_validated_at',
        'calliope_sp_li_validated_token_fingerprint',
        'calliope_sp_li_validated_target_fingerprint',
        'calliope_sp_linkedin_validation',
        'calliope_sp_linkedin_oauth_transients',
        'calliope_sp_mstdn_client_id',
        'calliope_sp_mstdn_client_instance',
        'calliope_sp_mstdn_client_secret',
        'calliope_sp_mstdn_connection_status',
        'calliope_sp_mstdn_instance',
        'calliope_sp_mstdn_token',
        'calliope_sp_mstdn_validation',
        'calliope_sp_pinterest_app_id',
        'calliope_sp_pinterest_app_secret',
        'calliope_sp_pinterest_board_id',
        'calliope_sp_pinterest_boards_last',
        'calliope_sp_pinterest_connection_status',
        'calliope_sp_pinterest_env',
        'calliope_sp_pinterest_token_environment',
        'calliope_sp_pinterest_board_id_production',
        'calliope_sp_pinterest_board_id_sandbox',
        'calliope_sp_pinterest_boards_production',
        'calliope_sp_pinterest_boards_sandbox',
        'calliope_sp_pinterest_publish_access_denial',
        'calliope_sp_pinterest_refresh_token',
        'calliope_sp_pinterest_access_token_expires_at',
        'calliope_sp_pinterest_refresh_token_expires_at',
        'calliope_sp_pinterest_granted_scopes',
        'calliope_sp_pinterest_auth_method',
        'calliope_sp_pinterest_validation',
        'calliope_sp_pinterest_token',
        'calliope_sp_publish_queue_cleanup_version',
        'calliope_sp_threads_access_token',
        'calliope_sp_threads_requested_scopes',
        'calliope_sp_threads_client_id',
        'calliope_sp_threads_client_secret',
        'calliope_sp_threads_connection_status',
        'calliope_sp_threads_expires_at',
        'calliope_sp_threads_user_id',
        'calliope_sp_threads_validation',
        'calliope_sp_refresh_lock_pinterest',
        'calliope_sp_refresh_lock_blogger',
        'calliope_sp_refresh_lock_threads',
        'calliope_sp_refresh_lock_linkedin',
        'calliope_sp_refresh_lock_instagram',
        'calliope_sp_telegram_bot_token',
        'calliope_sp_telegram_chat_id',
        'calliope_sp_telegram_connection_status',
        'calliope_sp_telegram_message_thread_id',
        'calliope_sp_telegram_validation',
        'calliope_sp_tumblr_blog_identifier',
        'calliope_sp_tumblr_connection_status',
        'calliope_sp_tumblr_consumer_key',
        'calliope_sp_tumblr_consumer_secret',
        'calliope_sp_tumblr_token',
        'calliope_sp_tumblr_token_secret',
        'calliope_sp_tumblr_validation',
        // Legacy Facebook options should not survive a full cleanup.
        'sh_fb_app_id',
        'sh_fb_app_secret',
        'sh_fb_config_id',
        'sh_fb_page_access_token',
        'sh_fb_page_id',
        'sh_fb_pages_last',
        'sh_fb_user_access_token',
    ];

    calliope_sp_uninstall_delete_options($calliope_sp_option_names);

    calliope_sp_uninstall_delete_option_prefixes([
        'calliope_sp_job_execution_',
        'calliope_sp_manual_attempt_',
        'calliope_sp_network_attempt_',
        'calliope_sp_publish_execution_',
        'calliope_sp_publish_job_',
    ]);

    $calliope_sp_transient_names = [
        'calliope_sp_fb_oauth_state',
        'calliope_sp_restored_connections_notice',
    ];

    foreach ($calliope_sp_transient_names as $calliope_sp_transient_name) {
        delete_transient($calliope_sp_transient_name);
    }

    calliope_sp_uninstall_delete_transient_patterns([
        'calliope_sp_ai_rate_%',
        'calliope_sp_blogger_oauth_%',
        'calliope_sp_fb_oauth_%',
        'calliope_sp_instagram_oauth_%',
        'calliope_sp_linkedin_oauth_%',
        'calliope_sp_mastodon_oauth_%',
        'calliope_sp_meta_static_proxy_%',
        'calliope_sp_pinterest_oauth_%',
        'calliope_sp_public_media_%',
        'calliope_sp_threads_oauth_%',
        'calliope_sp_tumblr_oauth_%',
        'calliope_sp_tumblr_req_secret_%',
    ]);

    calliope_sp_uninstall_delete_transient_prefixes([
        'calliope_sp_ai_rate_',
        'calliope_sp_blogger_oauth_',
        'calliope_sp_fb_oauth_',
        'calliope_sp_instagram_oauth_',
        'calliope_sp_linkedin_oauth_',
        'calliope_sp_mastodon_oauth_',
        'calliope_sp_meta_static_proxy_',
        'calliope_sp_pinterest_oauth_',
        'calliope_sp_public_media_',
        'calliope_sp_threads_oauth_',
        'calliope_sp_tumblr_oauth_',
        'calliope_sp_tumblr_req_secret_',
        'calliope_sp_telegram_channels_',
    ]);

    $calliope_sp_post_meta_keys = [
        'calliope_sp_gb_enabled',
        'calliope_sp_gb_title',
        'calliope_sp_gb_text',
        'calliope_sp_gb_hashtags',
        'calliope_sp_gb_destinations',
        'calliope_sp_gb_posted',
        'calliope_sp_gb_last_log',
        '_calliope_sp_autopost_done',
        '_calliope_sp_autopost_pending',
        'calliope_spp_gb_timing_mode',
        'calliope_spp_gb_scheduled_local',
        '_calliope_spp_gb_schedule_uuid',
    ];

    foreach ($calliope_sp_post_meta_keys as $calliope_sp_post_meta_key) {
        delete_metadata('post', 0, $calliope_sp_post_meta_key, '', true);
    }

    $calliope_sp_uploads = wp_get_upload_dir();
    if (!empty($calliope_sp_uploads['basedir'])) {
        $calliope_sp_base_dir = wp_normalize_path((string) $calliope_sp_uploads['basedir']);
        $calliope_sp_uploads_plugin_dir = wp_normalize_path(trailingslashit($calliope_sp_base_dir) . 'calliope-social-publisher');
        $calliope_sp_uploads_meta_dir = wp_normalize_path(trailingslashit($calliope_sp_base_dir) . 'calliope-social-publisher-meta');

        if ($calliope_sp_base_dir !== '' && strpos($calliope_sp_uploads_plugin_dir, $calliope_sp_base_dir) === 0) {
            calliope_sp_uninstall_delete_upload_dir($calliope_sp_uploads_plugin_dir);
        }

        if ($calliope_sp_base_dir !== '' && strpos($calliope_sp_uploads_meta_dir, $calliope_sp_base_dir) === 0) {
            calliope_sp_uninstall_delete_upload_dir($calliope_sp_uploads_meta_dir);
        }
    }

    // Remove persistent publication history only when full plugin data cleanup is enabled.
    if (isset($wpdb) && $wpdb instanceof wpdb) {
        $calliope_sp_history_table = $wpdb->prefix . 'calliope_sp_publication_history';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- Explicit uninstall cleanup for the plugin-owned table.
        $wpdb->query($wpdb->prepare('DROP TABLE IF EXISTS %i', $calliope_sp_history_table));
        $calliope_sp_short_links_table = $wpdb->prefix . 'calliope_sp_short_links';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- Explicit uninstall cleanup for the plugin-owned table.
        $wpdb->query($wpdb->prepare('DROP TABLE IF EXISTS %i', $calliope_sp_short_links_table));
    }
    }
}

// Preserve data by default. On multisite, honor the cleanup preference of each site.
if (is_multisite()) {
    $calliope_sp_offset = 0;
    $calliope_sp_batch_size = 100;

    do {
        $calliope_sp_site_ids = get_sites([
            'fields'  => 'ids',
            'number'  => $calliope_sp_batch_size,
            'offset'  => $calliope_sp_offset,
            'orderby' => 'id',
            'order'   => 'ASC',
        ]);

        foreach ((array) $calliope_sp_site_ids as $calliope_sp_site_id) {
            switch_to_blog((int) $calliope_sp_site_id);
            try {
                if (calliope_sp_uninstall_should_cleanup()) {
                    calliope_sp_uninstall_cleanup_current_site();
                }
            } finally {
                restore_current_blog();
            }
        }

        $calliope_sp_count = is_array($calliope_sp_site_ids) ? count($calliope_sp_site_ids) : 0;
        $calliope_sp_offset += $calliope_sp_count;
    } while ($calliope_sp_count === $calliope_sp_batch_size);
} elseif (calliope_sp_uninstall_should_cleanup()) {
    calliope_sp_uninstall_cleanup_current_site();
}
