<?php
// Security: Prevent direct access to plugin files.
defined( 'ABSPATH' ) || exit;

class IOTC_Scanner {

    /**
     * Minimum free bytes per table to count as reclaimable overhead.
     * InnoDB always keeps a small amount of pre-allocated free space per
     * table (a few MB) that no amount of OPTIMIZE/ANALYZE can remove; below
     * this floor it's normal engine behavior, not real fragmentation.
     */
    const OVERHEAD_THRESHOLD_BYTES = 2097152; // 2 MB

    /**
     * List of core WordPress tables to exclude by default.
     */
    private static $core_tables = array(
        'posts', 'postmeta', 'options', 'users', 'usermeta', 'term_taxonomy',
        'terms', 'term_relationships', 'termmeta', 'comments', 'commentmeta', 'links'
    );

    /**
     * Retrieve the slugs of all installed plugins (both active and inactive).
     * Returns an array of slugs.
     */
    public static function get_installed_plugins_slugs() {
        if ( ! function_exists( 'get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        
        $plugins = get_plugins();
        $slugs   = array();

        foreach ( $plugins as $plugin_file => $plugin_data ) {
            $dirname = dirname( $plugin_file );
            $slug = ( '.' !== $dirname ) ? $dirname : basename( $plugin_file, '.php' );
            $slugs[] = $slug;
        }

        return array_unique( $slugs );
    }

    /**
     * Retrieve known prefixes from Cloud API (with local fallback).
     */
    private static function get_known_prefixes() {
        // Removed Cloud API call to comply with WP.org guidelines (phoning home)
        // Fallback dictionary is used directly instead.

        if ( empty( $prefixes ) || ! is_array( $prefixes ) ) {
            // Fallback dictionary
            $prefixes = array(
                'actionscheduler_' => array('slugs' => array('woocommerce', 'wpforms-lite', 'wpforms', 'wp-mail-smtp', 'mailpoet', 'all-in-one-seo-pack', 'seo-by-rank-math', 'jetpack', 'action-scheduler'), 'name' => 'Action Scheduler'),
                'e_'               => array('slugs' => array('elementor', 'elementor-pro'), 'name' => 'Elementor'),
                'wpr_'             => array('slugs' => array('wp-rocket'), 'name' => 'WP Rocket'),
                'wpforms_'         => array('slugs' => array('wpforms-lite', 'wpforms'), 'name' => 'WPForms'),
                'wpmailsmtp_'      => array('slugs' => array('wp-mail-smtp'), 'name' => 'WP Mail SMTP'),
                'woocommerce_'     => array('slugs' => array('woocommerce'), 'name' => 'WooCommerce'),
                'wc_'              => array('slugs' => array('woocommerce'), 'name' => 'WooCommerce'),
                'yoast_'           => array('slugs' => array('wordpress-seo', 'wordpress-seo-premium'), 'name' => 'Yoast SEO'),
                'rank_math_'       => array('slugs' => array('seo-by-rank-math'), 'name' => 'Rank Math'),
                'smush_'           => array('slugs' => array('wp-smushit', 'wp-smush-pro'), 'name' => 'Smush'),
                'wf'               => array('slugs' => array('wordfence'), 'name' => 'Wordfence'),
                'itsec_'           => array('slugs' => array('better-wp-security'), 'name' => 'iThemes Security'),
                'gf_'              => array('slugs' => array('gravityforms'), 'name' => 'Gravity Forms'),
                'rg_'              => array('slugs' => array('gravityforms'), 'name' => 'Gravity Forms'),
                'frm_'             => array('slugs' => array('formidable'), 'name' => 'Formidable Forms'),
                'nf3_'             => array('slugs' => array('ninja-forms'), 'name' => 'Ninja Forms'),
                'revslider_'       => array('slugs' => array('revslider'), 'name' => 'Slider Revolution'),
                'cmplz_'           => array('slugs' => array('complianz-gdpr', 'complianz-gdpr-premium'), 'name' => 'Complianz'),
                'eg_'              => array('slugs' => array('essential-grid'), 'name' => 'Essential Grid'),
                'pmxe_'            => array('slugs' => array('wp-all-export', 'wp-all-export-pro'), 'name' => 'WP All Export'),
                'pmxi_'            => array('slugs' => array('wp-all-import', 'wp-all-import-pro'), 'name' => 'WP All Import'),
                'xyz_ips_'         => array('slugs' => array('insert-php-code-snippet'), 'name' => 'Insert PHP Code Snippet'),
                'icl_'             => array('slugs' => array('sitepress-multilingual-cms', 'polylang'), 'name' => 'WPML / Polylang'),
                'trp_'             => array('slugs' => array('translatepress-multilingual'), 'name' => 'TranslatePress'),
            );
        }

        return $prefixes;
    }

    /**
     * Check whether a table's base name matches a known prefix.
     * 'wf' is only 2 chars and would false-positive on unrelated custom
     * tables; Wordfence's own tables are camelCase (wfHits, wfConfig, ...),
     * so require that shape specifically for that prefix.
     */
    private static function prefix_matches( $base_name, $prefix_key ) {
        if ( strpos( $base_name, $prefix_key ) !== 0 ) {
            return false;
        }

        if ( 'wf' === $prefix_key ) {
            $next_char = substr( $base_name, strlen( $prefix_key ), 1 );
            return '' !== $next_char && ctype_upper( $next_char );
        }

        return true;
    }

    /**
     * Core logic: scan database and return an array of orphan tables.
     */
    public static function get_orphan_tables() {
        global $wpdb;
        $prefix = $wpdb->prefix;
        $slugs = self::get_installed_plugins_slugs();
        $ignored_tables = get_option( 'iotc_ignored_tables', array() );
        $orphan_tables = array();

        $tables_query = $wpdb->prepare( "SHOW TABLE STATUS LIKE %s", $wpdb->esc_like( $prefix ) . '%' );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $tables       = $wpdb->get_results( $tables_query );

        if ( ! $tables ) {
            return $orphan_tables;
        }

        $known_prefixes = self::get_known_prefixes();

        $generic_words = array( 'data', 'meta', 'logs', 'info', 'settings', 'options', 'cache', 'events', 'tasks' );

        foreach ( $tables as $table_obj ) {
            $table_name = isset( $table_obj->Name ) ? $table_obj->Name : current( (array) $table_obj );

            if ( strpos( $table_name, $prefix ) !== 0 ) {
                continue;
            }

            // Filter out trashed (Dry Run) tables
            if ( substr( $table_name, -11 ) === '_iotc_trash' ) {
                continue;
            }

            $base_name = substr( $table_name, strlen( $prefix ) );

            if ( in_array( $base_name, self::$core_tables, true ) ) {
                continue;
            }

            if ( in_array( $table_name, $ignored_tables, true ) ) {
                continue;
            }

            $is_orphan = true;
            $plugin_name_hint = '';

            // 1. Check known prefixes
            foreach ( $known_prefixes as $prefix_key => $data ) {
                if ( self::prefix_matches( $base_name, $prefix_key ) ) {
                    $plugin_name_hint = $data['name'];
                    foreach ( $data['slugs'] as $p_slug ) {
                        if ( $p_slug === '*' || in_array( $p_slug, $slugs, true ) ) {
                            $is_orphan = false;
                            break 2;
                        }
                    }
                }
            }

            // 2. Reverse Check
            if ( $is_orphan ) {
                foreach ( $slugs as $slug ) {
                    $slug_underscored = str_replace( '-', '_', $slug );
                    $slug_no_dashes   = str_replace( '-', '', $slug );
                    
                    if ( 
                        $base_name === $slug || 
                        $base_name === $slug_underscored ||
                        strpos( $base_name, $slug ) !== false || 
                        strpos( $base_name, $slug_underscored ) !== false ||
                        strpos( $base_name, $slug_no_dashes ) !== false
                    ) {
                        $is_orphan = false;
                        break;
                    }

                    $parts = explode( '_', $base_name );
                    foreach ( $parts as $part ) {
                        if ( strlen( $part ) > 3 && ! in_array( $part, $generic_words, true ) ) {
                            if ( strpos( $slug, $part ) !== false || strpos( $slug_no_dashes, $part ) !== false ) {
                                $is_orphan = false;
                                break 2;
                            }
                        }
                    }
                }
            }

            if ( $is_orphan ) {
                $size_bytes = $table_obj->Data_length + $table_obj->Index_length;
                $size_mb    = round( $size_bytes / 1048576, 2 );

                $last_modified = isset( $table_obj->Update_time ) && ! empty( $table_obj->Update_time ) ? $table_obj->Update_time : ( isset( $table_obj->Create_time ) ? $table_obj->Create_time : '' );
                if ( ! empty( $last_modified ) ) {
                    $last_modified_formatted = wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $last_modified ) );
                } else {
                    $last_modified_formatted = __( 'Unknown', 'intouch-orphan-table-cleaner' );
                }

                $status_text = __( 'Potentially Orphan', 'intouch-orphan-table-cleaner' );
                if ( ! empty( $plugin_name_hint ) ) {
                    /* translators: %s: Plugin name hint */
                    $status_text = sprintf( __( 'Potentially Orphan (Ex: %s)', 'intouch-orphan-table-cleaner' ), $plugin_name_hint );
                }

                $orphan_tables[] = array(
                    'name'          => $table_name,
                    'size_mb'       => $size_mb,
                    'last_modified' => $last_modified_formatted,
                    'status_text'   => $status_text
                );
            }
        }

        return $orphan_tables;
    }

    /**
     * Retrieve tables that have been trashed (simulated deletion).
     */
    public static function get_trashed_tables() {
        global $wpdb;
        $prefix = $wpdb->prefix;
        $trashed_tables = array();

        $tables_query = $wpdb->prepare( "SHOW TABLE STATUS LIKE %s", $wpdb->esc_like( $prefix ) . '%' );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $tables       = $wpdb->get_results( $tables_query );

        if ( ! $tables ) {
            return $trashed_tables;
        }

        foreach ( $tables as $table_obj ) {
            $table_name = isset( $table_obj->Name ) ? $table_obj->Name : current( (array) $table_obj );

            if ( strpos( $table_name, $prefix ) !== 0 ) {
                continue;
            }

            if ( substr( $table_name, -11 ) === '_iotc_trash' ) {
                $size_bytes = $table_obj->Data_length + $table_obj->Index_length;
                $size_mb    = round( $size_bytes / 1048576, 2 );

                $last_modified = isset( $table_obj->Update_time ) && ! empty( $table_obj->Update_time ) ? $table_obj->Update_time : ( isset( $table_obj->Create_time ) ? $table_obj->Create_time : '' );
                if ( ! empty( $last_modified ) ) {
                    $last_modified_formatted = wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $last_modified ) );
                } else {
                    $last_modified_formatted = __( 'Unknown', 'intouch-orphan-table-cleaner' );
                }

                $original_name = substr( $table_name, 0, -11 );

                $trashed_tables[] = array(
                    'name'          => $table_name,
                    'original_name' => $original_name,
                    'size_mb'       => $size_mb,
                    'last_modified' => $last_modified_formatted,
                    'status_text'   => __( 'Trashed (Dry Run)', 'intouch-orphan-table-cleaner' )
                );
            }
        }

        return $trashed_tables;
    }

    /**
     * Retrieve database overhead (Data_free).
     * Returns total overhead in bytes, MB, and an array of table names that need optimization.
     */
    public static function get_database_overhead() {
        global $wpdb;
        $prefix = $wpdb->prefix;
        $result = array(
            'overhead_bytes' => 0,
            'overhead_mb'    => 0,
            'tables'         => array()
        );

        $tables_query = $wpdb->prepare( "SHOW TABLE STATUS LIKE %s", $wpdb->esc_like( $prefix ) . '%' );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $tables       = $wpdb->get_results( $tables_query );

        if ( ! $tables ) {
            return $result;
        }

        foreach ( $tables as $table_obj ) {
            $table_name = isset( $table_obj->Name ) ? $table_obj->Name : current( (array) $table_obj );
            $data_free  = isset( $table_obj->Data_free ) ? (int) $table_obj->Data_free : 0;

            if ( strpos( $table_name, $prefix ) !== 0 ) {
                continue;
            }

            if ( $data_free > self::OVERHEAD_THRESHOLD_BYTES ) {
                $result['overhead_bytes'] += $data_free;
                $result['tables'][] = $table_name;
            }
        }

        if ( $result['overhead_bytes'] > 0 ) {
            $result['overhead_mb'] = round( $result['overhead_bytes'] / 1048576, 2 );
        }

        return $result;
    }
}
