<?php
/**
 * Core Functionality
 *
 * @package WPALLSTARS\FixPluginDoesNotExistNotices
 */

namespace WPALLSTARS\FixPluginDoesNotExistNotices;

/**
 * Core Class
 *
 * Handles the core functionality of finding and fixing invalid plugin references.
 */
class Core {

    /**
     * Stores a list of invalid plugins found in the active_plugins option.
     *
     * @var array|null
     */
    private $invalid_plugins = null;

    /**
     * Constructor
     */
    public function __construct() {
        // Add our plugin to the plugins list
        add_filter('all_plugins', array($this, 'add_missing_plugins_references'));

        // Add our action link to the plugins list
        add_filter('plugin_action_links', array($this, 'add_remove_reference_action'), 20, 4);

        // Handle the remove reference action
        add_action('admin_init', array($this, 'handle_remove_reference'));

        // Add admin notices for operation feedback
        add_action('admin_notices', array($this, 'admin_notices'));

        // Filter the plugin API to fix version display in plugin details popup
        add_filter('plugins_api', array($this, 'filter_plugin_details'), 10, 3);

        // Prevent WordPress from caching our plugin API responses
        add_filter('plugins_api_result', array($this, 'prevent_plugins_api_caching'), 10, 3);

        // Clear plugin API transients on plugin activation and when viewing plugins page
        add_action('admin_init', array($this, 'maybe_clear_plugin_api_cache'));
    }

    /**
     * Get a list of invalid plugins (plugins that are active but don't exist).
     *
     * @return array An array of plugin file paths that don't exist.
     */
    public function get_invalid_plugins() {
        // Return cached result if available
        if (is_array($this->invalid_plugins)) {
            return $this->invalid_plugins;
        }

        // Initialize empty array
        $invalid_plugins = array();

        // Handle multisite network admin context
        if (is_multisite() && is_network_admin()) {
            $active_plugins = get_site_option('active_sitewide_plugins', array());
            // Network active plugins are stored as key => timestamp
            $active_plugins = array_keys($active_plugins);
        } else {
            // Single site or non-network admin context
            $active_plugins = get_option('active_plugins', array());
        }

        // Check each active plugin
        foreach ($active_plugins as $plugin_file) {
            $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
            if (!file_exists($plugin_path)) {
                $invalid_plugins[] = $plugin_file;
            }
        }

        // Cache the result
        $this->invalid_plugins = $invalid_plugins;

        return $invalid_plugins;
    }

    /**
     * Check if the current page is the plugins page.
     *
     * @return bool True if on the plugins page, false otherwise.
     */
    public function is_plugins_page() {
        global $pagenow;
        return is_admin() && 'plugins.php' === $pagenow;
    }

    /**
     * Find and add invalid plugin references to the plugins list.
     *
     * Filters the list of plugins displayed on the plugins page to include
     * entries for active plugins whose files are missing.
     *
     * @param array $plugins An array of plugin data.
     * @return array The potentially modified array of plugin data.
     */
    public function add_missing_plugins_references($plugins) {
        // Only run on the plugins page
        if (!$this->is_plugins_page()) {
            return $plugins;
        }

        // Get active plugins that don't exist
        $invalid_plugins = $this->get_invalid_plugins();

        // Add each invalid plugin to the plugin list
        foreach ($invalid_plugins as $plugin_path) {
            if (!isset($plugins[$plugin_path])) {
                $plugin_name = basename($plugin_path);
                $plugin_slug = dirname($plugin_path);
                if ('.' === $plugin_slug) {
                    $plugin_slug = basename($plugin_path, '.php');
                }

                // Create a basic plugin data array
                $plugins[$plugin_path] = array(
                    'Name'        => $plugin_name . ' <span class="error">(File Missing)</span>',
                    /* translators: %s: Path to wp-content/plugins */
                    'Description' => sprintf(
                        __('This plugin is still marked as "Active" in your database — but its folder and files can\'t be found in %s. Click "Remove Notice" to permanently remove it from your active plugins list and eliminate the error notice.', 'wp-fix-plugin-does-not-exist-notices'),
                        '<code>/wp-content/plugins/</code>'
                    ),
                    'Version'     => FPDEN_VERSION, // Use our plugin version instead of 'N/A'
                    'Author'      => 'Marcus Quinn & WPALLSTARS',
                    'PluginURI'   => 'https://www.wpallstars.com',
                    'AuthorURI'   => 'https://www.wpallstars.com',
                    'Title'       => $plugin_name . ' (' . __('Missing', 'wp-fix-plugin-does-not-exist-notices') . ')',
                    'AuthorName'  => 'Marcus Quinn & WPALLSTARS',
                );

                // Add the data needed for the "View details" link
                $plugins[$plugin_path]['slug'] = $plugin_slug;
                $plugins[$plugin_path]['plugin'] = $plugin_path;
                $plugins[$plugin_path]['type'] = 'plugin';

                // Add Git Updater fields
                $plugins[$plugin_path]['GitHub Plugin URI'] = 'wpallstars/wp-fix-plugin-does-not-exist-notices';
                $plugins[$plugin_path]['GitHub Branch'] = 'main';
                $plugins[$plugin_path]['TextDomain'] = 'wp-fix-plugin-does-not-exist-notices';
            }
        }

        return $plugins;
    }

    /**
     * Add the Remove Notice action link to invalid plugins.
     *
     * Filters the action links displayed for each plugin on the plugins page.
     * Adds a "Remove Notice" link for plugins identified as missing.
     *
     * @param array  $actions     An array of plugin action links.
     * @param string $plugin_file Path to the plugin file relative to the plugins directory.
     * @param array  $plugin_data An array of plugin data.
     * @param string $context     The plugin context (e.g., 'all', 'active', 'inactive').
     * @return array The potentially modified array of plugin action links.
     */
    public function add_remove_reference_action($actions, $plugin_file, $plugin_data, $context) {
        // Only run on the plugins page
        if (!$this->is_plugins_page()) {
            return $actions;
        }

        // Get our list of invalid plugins
        $invalid_plugins = $this->get_invalid_plugins();

        // Check if this plugin file is in our list of invalid plugins
        if (in_array($plugin_file, $invalid_plugins, true)) {
            // Clear existing actions like "Activate", "Deactivate", "Edit"
            $actions = array();

            // Add our custom action
            $nonce      = wp_create_nonce('remove_plugin_reference_' . $plugin_file);
            $remove_url = admin_url('plugins.php?action=remove_reference&plugin=' . urlencode($plugin_file) . '&_wpnonce=' . $nonce);
            /* translators: %s: Plugin file path */
            $aria_label                 = sprintf(__('Remove reference to missing plugin %s', 'wp-fix-plugin-does-not-exist-notices'), esc_attr($plugin_file));
            $actions['remove_reference'] = '<a href="' . esc_url($remove_url) . '" class="delete" aria-label="' . $aria_label . '">' . esc_html__('Remove Notice', 'wp-fix-plugin-does-not-exist-notices') . '</a>';
        }

        return $actions;
    }

    /**
     * Handle the remove reference action triggered by the link.
     *
     * Checks for the correct action, verifies nonce and permissions,
     * calls the removal function, and redirects back to the plugins page.
     *
     * @return void
     */
    public function handle_remove_reference() {
        // Check if our specific action is being performed
        if (!isset($_GET['action']) || 'remove_reference' !== $_GET['action'] || !isset($_GET['plugin'])) {
            return;
        }

        // Verify user permissions
        if (!current_user_can('activate_plugins')) {
            wp_die(esc_html__('You do not have sufficient permissions to perform this action.', 'wp-fix-plugin-does-not-exist-notices'));
        }

        // Sanitize and get the plugin file path
        $plugin_file = isset($_GET['plugin']) ? sanitize_text_field(wp_unslash($_GET['plugin'])) : '';
        if (empty($plugin_file)) {
            wp_die(esc_html__('Invalid plugin specified.', 'wp-fix-plugin-does-not-exist-notices'));
        }

        // Verify nonce for security
        check_admin_referer('remove_plugin_reference_' . $plugin_file);

        // Attempt to remove the plugin reference
        $success = $this->remove_plugin_reference($plugin_file);

        // Prepare redirect URL with feedback query args
        $redirect_url = admin_url('plugins.php');
        $redirect_url = add_query_arg($success ? 'reference_removed' : 'reference_removal_failed', '1', $redirect_url);

        // Redirect and exit
        wp_safe_redirect($redirect_url);
        exit;
    }

    /**
     * Remove a plugin reference from the active plugins list in the database.
     *
     * Handles both single site and multisite network activated plugins.
     *
     * @param string $plugin_file The plugin file path to remove.
     * @return bool True on success, false on failure or if the plugin wasn't found.
     */
    public function remove_plugin_reference($plugin_file) {
        $success = false;

        // Ensure plugin file path is provided
        if (empty($plugin_file)) {
            return false;
        }

        // Handle multisite network admin context
        if (is_multisite() && is_network_admin()) {
            $active_plugins = get_site_option('active_sitewide_plugins', array());
            // Network active plugins are stored as key => timestamp
            if (isset($active_plugins[$plugin_file])) {
                unset($active_plugins[$plugin_file]);
                $success = update_site_option('active_sitewide_plugins', $active_plugins);
            }
        } else { // Handle single site or non-network admin context
            $active_plugins = get_option('active_plugins', array());
            // Single site active plugins are stored as an indexed array
            $key = array_search($plugin_file, $active_plugins, true); // Use strict comparison
            if (false !== $key) {
                unset($active_plugins[$key]);
                // Re-index the array numerically
                $active_plugins = array_values($active_plugins);
                $success        = update_option('active_plugins', $active_plugins);
            }
        }

        return $success;
    }

    /**
     * Display admin notices on the plugins page.
     *
     * Shows feedback messages after attempting to remove a reference.
     * The main informational notice is handled by JavaScript to position it
     * directly below the WordPress error message.
     *
     * @return void
     */
    public function admin_notices() {
        // Only run on the plugins page
        if (!$this->is_plugins_page()) {
            return;
        }

        // Check for feedback messages from the remove action
        if (isset($_GET['reference_removed']) && '1' === $_GET['reference_removed']) {
            ?>
            <div class="notice notice-success is-dismissible">
                <p><?php esc_html_e('Plugin reference removed successfully.', 'wp-fix-plugin-does-not-exist-notices'); ?></p>
            </div>
            <?php
        }

        if (isset($_GET['reference_removal_failed']) && '1' === $_GET['reference_removal_failed']) {
            ?>
            <div class="notice notice-error is-dismissible">
                <p><?php esc_html_e('Failed to remove plugin reference. The plugin may already have been removed, or there was a database issue.', 'wp-fix-plugin-does-not-exist-notices'); ?></p>
            </div>
            <?php
        }
    }

    /**
     * Filter plugin details API response to provide custom data for our plugin.
     *
     * @param object|WP_Error $result The result object or WP_Error.
     * @param string $action The type of information being requested.
     * @param object $args Plugin API arguments.
     * @return object|WP_Error The result object or WP_Error.
     */
    public function filter_plugin_details($result, $action, $args) {
        // Only modify plugin_information requests
        if ('plugin_information' !== $action) {
            return $result;
        }

        // Check if we have a slug to work with
        if (empty($args->slug)) {
            return $result;
        }

        // Get our list of invalid plugins
        $invalid_plugins = $this->get_invalid_plugins();

        // Check if this is our plugin or a missing plugin
        $our_plugin = in_array($args->slug, array('wp-fix-plugin-does-not-exist-notices', 'fix-plugin-does-not-exist-notices'), true);
        $is_missing_plugin = $this->is_missing_plugin($args->slug, $invalid_plugins);

        // Only modify the result if this is our plugin or a missing plugin
        if ($our_plugin || $is_missing_plugin) {
            // Create a new result object
            $new_result = new \stdClass();

            // Set all the properties we need
            $new_result->name = $our_plugin ? 'Fix \'Plugin file does not exist\' Notices' : (isset($result->name) ? $result->name : $args->slug);
            $new_result->slug = $args->slug;
            $new_result->version = FPDEN_VERSION;
            $new_result->author = '<a href="https://www.wpallstars.com">Marcus Quinn & WPALLSTARS</a>';
            $new_result->author_profile = 'https://www.wpallstars.com';
            $new_result->requires = '5.0';
            $new_result->tested = '6.7.2'; // Updated to match readme.txt
            $new_result->requires_php = '7.0';
            $new_result->last_updated = date('Y-m-d H:i:s');

            // Add a cache buster timestamp
            $new_result->cache_buster = time();

            // Get full readme content for our plugin
            $readme_file = FPDEN_PLUGIN_DIR . 'readme.txt';
            $readme_content = '';
            $description = '';
            $changelog = '';
            $faq = '';
            $installation = '';
            $screenshots = '';

            if (file_exists($readme_file) && $our_plugin) {
                $readme_content = file_get_contents($readme_file);

                // Extract description
                if (preg_match('/== Description ==(.+?)(?:==|$)/s', $readme_content, $matches)) {
                    $description = trim($matches[1]);
                }

                // Extract changelog
                if (preg_match('/== Changelog ==(.+?)(?:==|$)/s', $readme_content, $matches)) {
                    $changelog = trim($matches[1]);
                }

                // Extract FAQ
                if (preg_match('/== Frequently Asked Questions ==(.+?)(?:==|$)/s', $readme_content, $matches)) {
                    $faq = trim($matches[1]);
                }

                // Extract installation
                if (preg_match('/== Installation ==(.+?)(?:==|$)/s', $readme_content, $matches)) {
                    $installation = trim($matches[1]);
                }

                // Extract screenshots
                if (preg_match('/== Screenshots ==(.+?)(?:==|$)/s', $readme_content, $matches)) {
                    $screenshots = trim($matches[1]);
                }
            } else {
                // Fallback content if readme.txt doesn't exist or for missing plugins
                $changelog = '<h2>' . FPDEN_VERSION . '</h2><ul><li>Fixed: Plugin details popup version display issue with Git Updater integration</li><li>Added: JavaScript-based solution to ensure correct version display in plugin details</li><li>Improved: Version consistency across all plugin views</li><li>Enhanced: Cache busting for plugin information API</li></ul>';
            }

            // Set description based on whether this is our plugin or a missing plugin
            if ($our_plugin) {
                $description = !empty($description) ? wpautop($description) : 'Adds missing plugins to your plugins list with a "Remove Notice" action link, allowing you to safely clean up invalid plugin references.';
            } else {
                $description = sprintf(
                    __('This plugin is still marked as "Active" in your database — but its folder and files can\'t be found in %s. Use the "Remove Notice" link on the plugins page to permanently remove it from your active plugins list and eliminate the error notice.', 'wp-fix-plugin-does-not-exist-notices'),
                    '<code>/wp-content/plugins/</code>'
                );
            }

            // Prepare sections
            $new_result->sections = array(
                'description' => $description,
                'changelog' => !empty($changelog) ? wpautop($changelog) : $changelog,
                'faq' => !empty($faq) ? wpautop($faq) : '<h3>Is it safe to remove plugin references?</h3><p>Yes, this plugin only removes entries from the WordPress active_plugins option, which is safe to modify when a plugin no longer exists.</p>',
            );

            // Add installation section if available
            if (!empty($installation)) {
                $new_result->sections['installation'] = wpautop($installation);
            }

            // Add screenshots section if available
            if (!empty($screenshots)) {
                $new_result->sections['screenshots'] = wpautop($screenshots);
            }

            // Add contributors information
            $new_result->contributors = array(
                'marcusquinn' => array(
                    'profile' => 'https://profiles.wordpress.org/marcusquinn/',
                    'avatar' => 'https://secure.gravatar.com/avatar/',
                    'display_name' => 'Marcus Quinn'
                ),
                'wpallstars' => array(
                    'profile' => 'https://profiles.wordpress.org/wpallstars/',
                    'avatar' => 'https://secure.gravatar.com/avatar/',
                    'display_name' => 'WPALLSTARS'
                )
            );

            // Add a random number and timestamp to force cache refresh
            $new_result->download_link = 'https://www.wpallstars.com/plugins/wp-fix-plugin-does-not-exist-notices.zip?v=' . FPDEN_VERSION . '&cb=' . mt_rand(1000000, 9999999) . '&t=' . time();

            // Add active installations count
            $new_result->active_installs = 1000;

            // Add rating information
            $new_result->rating = 100;
            $new_result->num_ratings = 5;
            $new_result->ratings = array(
                5 => 5,
                4 => 0,
                3 => 0,
                2 => 0,
                1 => 0
            );

            // Add homepage and download link
            $new_result->homepage = 'https://www.wpallstars.com';

            // Set no caching
            $new_result->cache_time = 0;

            // Return our completely new result object
            return $new_result;
        }

        return $result;
    }

    /**
     * Check if a slug matches one of our missing plugins.
     *
     * @param string $slug The plugin slug to check.
     * @param array $invalid_plugins List of invalid plugin paths.
     * @return bool True if the slug matches a missing plugin.
     */
    private function is_missing_plugin($slug, $invalid_plugins) {
        foreach ($invalid_plugins as $plugin_file) {
            // Extract the plugin slug from the plugin file path
            $plugin_slug = dirname($plugin_file);
            if ('.' === $plugin_slug) {
                $plugin_slug = basename($plugin_file, '.php');
            }

            if ($slug === $plugin_slug) {
                return true;
            }
        }
        return false;
    }

    /**
     * Prevent WordPress from caching our plugin API responses.
     *
     * @param object|WP_Error $result The result object or WP_Error.
     * @param string $action The type of information being requested.
     * @param object $args Plugin API arguments.
     * @return object|WP_Error The result object or WP_Error.
     */
    public function prevent_plugins_api_caching($result, $action, $args) {
        // Only modify plugin_information requests
        if ('plugin_information' !== $action) {
            return $result;
        }

        // Check if we have a slug to work with
        if (empty($args->slug)) {
            return $result;
        }

        // Get our list of invalid plugins
        $invalid_plugins = $this->get_invalid_plugins();

        // Check if the requested plugin is one of our missing plugins
        foreach ($invalid_plugins as $plugin_file) {
            // Extract the plugin slug from the plugin file path
            $plugin_slug = dirname($plugin_file);
            if ('.' === $plugin_slug) {
                $plugin_slug = basename($plugin_file, '.php');
            }

            // If this is one of our missing plugins, prevent caching
            if ($args->slug === $plugin_slug) {
                // Add a filter to prevent caching of this response
                add_filter('plugins_api_result_' . $args->slug, '__return_false');

                // Add a timestamp to force cache busting
                if (is_object($result)) {
                    $result->last_updated = current_time('mysql');
                    $result->cache_time = 0;
                }
            }
        }

        return $result;
    }

    /**
     * Clear plugin API cache when viewing the plugins page.
     *
     * @return void
     */
    public function maybe_clear_plugin_api_cache() {
        // Only run on the plugins page
        if (!$this->is_plugins_page()) {
            return;
        }

        // Get our list of invalid plugins
        $invalid_plugins = $this->get_invalid_plugins();

        // Clear transients for each invalid plugin
        foreach ($invalid_plugins as $plugin_file) {
            // Extract the plugin slug from the plugin file path
            $plugin_slug = dirname($plugin_file);
            if ('.' === $plugin_slug) {
                $plugin_slug = basename($plugin_file, '.php');
            }

            // Delete all possible transients for this plugin
            delete_transient('plugins_api_' . $plugin_slug);
            delete_site_transient('plugins_api_' . $plugin_slug);
            delete_transient('plugin_information_' . $plugin_slug);
            delete_site_transient('plugin_information_' . $plugin_slug);

            // Clear any other transients that might be caching plugin info
            $this->clear_all_plugin_transients();
        }

        // Also clear our own plugin's cache
        $this->clear_own_plugin_cache();
    }

    /**
     * Clear all plugin-related transients that might be caching information.
     *
     * @return void
     */
    private function clear_all_plugin_transients() {
        // Clear update cache
        delete_site_transient('update_plugins');
        delete_site_transient('update_themes');
        delete_site_transient('update_core');

        // Clear plugins API cache
        delete_site_transient('plugin_information');

        // Clear plugin update counts
        delete_transient('plugin_updates_count');
        delete_site_transient('plugin_updates_count');

        // Clear plugin slugs cache
        delete_transient('plugin_slugs');
        delete_site_transient('plugin_slugs');
    }

    /**
     * Clear cache specifically for our own plugin.
     *
     * @return void
     */
    private function clear_own_plugin_cache() {
        // Clear our own plugin's cache (both old and new slugs)
        $our_slugs = array('wp-fix-plugin-does-not-exist-notices', 'fix-plugin-does-not-exist-notices');

        foreach ($our_slugs as $slug) {
            delete_transient('plugins_api_' . $slug);
            delete_site_transient('plugins_api_' . $slug);
            delete_transient('plugin_information_' . $slug);
            delete_site_transient('plugin_information_' . $slug);
        }

        // Clear plugin update transients
        delete_site_transient('update_plugins');
        delete_site_transient('plugin_information');

        // Force refresh of plugin update information if function exists
        if (function_exists('wp_clean_plugins_cache')) {
            wp_clean_plugins_cache(true);
        }

        // Clear object cache if function exists
        if (function_exists('wp_cache_flush')) {
            wp_cache_flush();
        }
    }
}