<?php

// If this file is called directly, abort.
defined('ABSPATH') || die('No direct access!');

class DRFS
{
    private static $file_system;

    private static function wp_file_system()
    {
        if (!isset(self::$file_system)) {
            include_once(ABSPATH . 'wp-admin/includes/file.php');
            WP_Filesystem();
            global $wp_filesystem;
            self::$file_system = $wp_filesystem;
        }
        return self::$file_system;
    }

    public static function read($path)
    {
        // Check if the file exists to prevent any unnecessary warnings.
        if (!file_exists($path)) {
            return false;
        }

        if (!is_readable($path)) {
            return false;
        }

        // Try using the standard file_get_contents.
        $data = @file_get_contents($path);

        // If the above call failed, try with the WordPress file system.
        if ($data === false) {
            if (!function_exists('WP_Filesystem')) {
                require_once(ABSPATH . 'wp-admin/includes/file.php');
                WP_Filesystem();
            }
            $data = self::wp_file_system()->get_contents($path);
        }

        // If both attempts failed
        if ($data === false) {
            return false;
        }

        return $data;
    }

    public static function delete($target, $exceptions = [], $identifier = '')
    {
        $realPath = realpath($target);
        if ($realPath === false) {
            return false;
        }

        $target = $realPath;

        if (is_dir($target)) {
            $identifier = $identifier ?: '{,.}[!.,!..]*';  // Includes all files, ensuring hidden ones are not missed
            $allFiles = glob(trailingslashit($target) . $identifier, GLOB_MARK | GLOB_NOSORT | GLOB_BRACE);

            foreach ($allFiles as $file) {
                $fileName = basename($file);
                if ($fileName == '.' || $fileName == '..') continue;  // Skips '.' and '..'

                if (!in_array($fileName, $exceptions)) {
                    if (is_dir($file)) {
                        if (!self::delete($file, $exceptions)) {
                            continue;
                        }
                    } else {
                        if (!self::wp_file_system()->delete($file)) {
                            continue;
                        }
                    }
                }
            }
        }

        // After attempting to clear the directory, check if it's empty and if it is, delete it
        if (is_dir($target) && self::is_dir_empty($target)) {
            if (!self::wp_file_system()->delete($target, true)) {  // Delete the directory itself
                error_log("Failed to delete the now-empty directory: {$target}");
                return false;
            }
        }

        // Handle case where the target itself is a file and not an exception
        if (is_file($target) && !in_array(basename($target), $exceptions)) {
            if (!self::wp_file_system()->delete($target)) {
                error_log("Failed to delete file: {$target}");
                return false;
            }
        }

        return true;
    }

    public static function is_dir_empty($dir)
    {
        if (!is_dir($dir)) {
            return false; // Return false if the provided path is not a directory
        }

        $files = @scandir($dir); // The @ operator suppresses any potential errors from scandir()

        if ($files === false) {
            return false; // Return false if scandir() fails to read the directory
        }

        // '.' and '..' are the only entries in an empty directory
        return count($files) === 2;
    }

    public static function size($directory)
    {
        $bytesTotal = 0;
        $path = realpath($directory);
        if ($path !== false && $path != '' && file_exists($path)) {
            foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS)) as $object) {
                $bytesTotal += $object->getSize();
            }
        }

        return $bytesTotal;
    }

    public static function write($path, $data, $flags = 0)
    {
        $dir = dirname($path);

        // Check if the directory does not exist
        if (!file_exists($dir)) {
            // Attempt to create the directory
            if (!self::mkdir($dir)) {
                return false;
            }
        }
        if (!is_writable($dir)) {
            // If the directory exists but is not writable, handle the error
            return false;
        }

        if (@file_put_contents($path, $data, $flags) !== false) {
            return true;
        }

        return self::wp_file_system()->put_contents($path, $data, $flags) !== false;
    }

    public static function copy($s1, $s2)
    {
        $dir = pathinfo($s2, PATHINFO_DIRNAME);

        if (!file_exists($dir)) {
            self::mkdir($dir);
        }

        if (!@copy($s1, $s2)) {
            echo "copy failed \n";
        }
    }

    public static function mkdir($path, $chmod = 0755)
    {
        if (!$path) {
            return false;
        }

        // Try using native PHP function first
        if (@mkdir($path, $chmod, true)) {
            return true;
        }
        // If native PHP function fails, fallback to WordPress methods
        else {
            if (wp_mkdir_p($path) || (function_exists('self::wp_file_system') && self::wp_file_system()->mkdir($path, $chmod))) {
                return true;
            }
        }

        return false;
    }

    public static function file_serach($file, $searchfor)
    {
        $contents = self::read($file);
        $pattern = preg_quote($searchfor, '/');
        $pattern = "/^.*$pattern.*\$/m";

        if (preg_match($pattern, $contents, $match)) {
            return true;
        } else {
            return false;
        }
    }

    public static function wp_config_filepath()
    {
        $config_file = ABSPATH . 'wp-config.php';
        $config_file_alt = dirname(ABSPATH) . '/wp-config.php';

        if (file_exists($config_file)) {
            return wp_normalize_path($config_file);
        } elseif (file_exists($config_file_alt)) {
            return wp_normalize_path($config_file_alt);
        }

        return false;
    }

    public static function htaccess_filepath()
    {
        $htaccess_file = ABSPATH . '.htaccess';
        $htaccess_file_alt = dirname(ABSPATH) . '/.htaccess';

        if (file_exists($htaccess_file)) {
            return wp_normalize_path($htaccess_file);
        } elseif (file_exists($htaccess_file_alt)) {
            return wp_normalize_path($htaccess_file_alt);
        }

        $wpconfig = self::wp_config_filepath();

        return is_string($wpconfig) ? str_replace('wp-config.php', '.htaccess', $wpconfig) : false;
    }

    public static function nginx_filepath()
    {
        $nginx_file = ABSPATH . 'nginx.conf';
        $nginx_file_alt = dirname(ABSPATH) . '/nginx.conf';

        if (file_exists($nginx_file)) {
            return wp_normalize_path($nginx_file);
        } elseif (file_exists($nginx_file_alt)) {
            return wp_normalize_path($nginx_file_alt);
        }

        return false;
    }

    public static function get_valid_hosts()
    {
        return wp_list_pluck(
            self::get_paths_urls(),
            'host'
        );
    }

    public static function get_paths_urls()
    {
        $regex = '#^(https?)?:?//(|www\.)#i';

        // We add https:// back for parse_url() to prevent it from failing
        $site_url  = preg_replace($regex, '', site_url());
        $site_host = parse_url('https://' . $site_url, PHP_URL_HOST);

        $content_url  = preg_replace($regex, '', content_url());
        $content_host = parse_url('https://' . $content_url, PHP_URL_HOST);

        /**
         * This array will be processed in order it's defined to find the matching host and URL.
         */
        $hosts = [

            // First priority to use content_host and content_url()
            'content' => [
                'url'  => $content_url,
                'path' => WP_CONTENT_DIR,
                'host' => $content_host
            ],

            // Fallback to using site URL with ABSPATH
            'site' => [
                'url'  => $site_url,
                'path' => ABSPATH,
                'host' => $site_host
            ],
        ];

        return $hosts;
    }

    public static function local_to_url($path)
    {
        $url = str_replace(
            wp_normalize_path(untrailingslashit(ABSPATH)),
            site_url(),
            wp_normalize_path($path)
        );
        return esc_url_raw($url);
    }

    public static function url_to_local($url)
    {
        $url = trim($url);

        // Not a URL, just return the path.
        if (substr($url, 0, 4) !== 'http' && substr($url, 0, 2) !== '//') {
            return $url;
        }

        $url = explode('?', trim($url));
        $url = trim($url[0]);

        // We're not working with encoded URLs.
        if (strpos($url, '%') !== false) {
            $url = urldecode($url);
        }

        // Add https:// for parse_url() or it fails.
        $url_no_proto = preg_replace('#^(https?)?:?//(|www\.)#i', '', $url);
        $url_host     = parse_url('https://' . $url_no_proto, PHP_URL_HOST);

        // Not a known host / URL.
        if (!in_array($url_host, self::get_valid_hosts())) {
            return false;
        }

        /**
         * Go through each known path url map and stop at first matched.
         */
        $valid_urls   = self::get_paths_urls();
        $url_dirname  = dirname($url_no_proto);
        $matched      = [];

        foreach ($valid_urls as $path_url) {

            if (strpos($url_dirname, untrailingslashit($path_url['url'])) !== false) {
                $matched = $path_url;
                break;
            }
        }

        // We have a matched path.
        if (!empty($matched['path'])) {
            $path = wp_normalize_path(
                $matched['path'] . str_replace($matched['url'], '', $url_dirname)
            );

            $file_dir = trailingslashit($path) . wp_basename($url_no_proto);

            if (file_exists($file_dir) && is_file($file_dir) && is_readable($file_dir)) {
                return $file_dir;
            } elseif (is_dir($file_dir)) {
                return $file_dir;
            }
        }

        return false;
    }

    public static function getServerSoftware(): ?string
    {
        return $_SERVER['SERVER_SOFTWARE'] ?? null;
    }

    public static function is_iis(): bool
    {
        return stristr(self::getServerSoftware(), 'IIS') !== false;
    }

    public static function is_nginx(): bool
    {
        return stristr(self::getServerSoftware(), 'nginx') !== false;
    }

    public static function is_openlitespeed(): bool
    {
        // Check if the LSWS_EDITION server variable is set and contains 'OpenLiteSpeed'
        return isset($_SERVER['LSWS_EDITION']) && stristr($_SERVER['LSWS_EDITION'], 'OpenLiteSpeed') !== false;
    }

    public static function is_litespeed(): bool
    {
        return stristr(self::getServerSoftware(), 'LiteSpeed') !== false;
    }

    public static function is_apache(): bool
    {
        return stristr(self::getServerSoftware(), 'Apache') !== false;
    }

    public static function is_htaccess(): bool
    {
        static $supportsHtaccess = null;

        if ($supportsHtaccess === null) {
            $webserver = self::get_webserver_name();
            $supportsHtaccess = ($webserver === 'LiteSpeed' || $webserver === 'Apache');
        }

        return $supportsHtaccess;
    }

    public static function get_webserver_name(): string
    {
        $servers = [
            'OpenLiteSpeed' => [self::class, 'is_openlitespeed'],
            'LiteSpeed' => [self::class, 'is_litespeed'],
            'Nginx' => [self::class, 'is_nginx'],
            'IIS' => [self::class, 'is_iis'],
            'Apache' => [self::class, 'is_apache']
        ];

        foreach ($servers as $name => $callback) {
            if ($callback()) {
                return $name;
            }
        }

        return 'Unknown';
    }
}

class DragonizerPerformanceMonitor
{
    private float $startTime;
    private int $startMemory;
    private array $checkpoints = [];
    private ?array $cpuUsageStart = null;

    public function start(): self
    {
        $this->startTime = microtime(true);
        $this->startMemory = memory_get_usage(true);
        $this->cpuUsageStart = $this->getCpuUsage();
        $this->checkpoints = [];

        return $this;
    }

    public function checkpoint(string $label, array $additionalInfo = []): self
    {
        $this->checkpoints[$label] = [
            'time' => microtime(true),
            'memory' => memory_get_usage(true),
            'cpu' => $this->getCpuUsage(),
            'peak_memory' => memory_get_peak_usage(true),
            'additional_info' => $additionalInfo
        ];

        return $this;
    }

    private function getCpuUsage(): ?array
    {
        if (!function_exists('getrusage')) {
            return null;
        }

        $usage = getrusage();
        return [
            'user' => $usage['ru_utime.tv_sec'] + ($usage['ru_utime.tv_usec'] / 1000000),
            'system' => $usage['ru_stime.tv_sec'] + ($usage['ru_stime.tv_usec'] / 1000000)
        ];
    }

    public function getMetrics(): array
    {
        $endTime = microtime(true);
        $endMemory = memory_get_usage(true);
        $endCpu = $this->getCpuUsage();
        $peakMemory = memory_get_peak_usage(true);

        $metrics = [
            'overview' => [
                'total_execution_time' => round(($endTime - $this->startTime) * 1000, 4) . ' ms',
                'total_memory_usage' => $this->formatBytes($endMemory - $this->startMemory),
                'peak_memory_usage' => $this->formatBytes($peakMemory),
            ]
        ];

        if ($endCpu && $this->cpuUsageStart) {
            $metrics['cpu_usage'] = [
                'user' => round($endCpu['user'] - $this->cpuUsageStart['user'], 4) . 's',
                'system' => round($endCpu['system'] - $this->cpuUsageStart['system'], 4) . 's'
            ];
        }

        if (!empty($this->checkpoints)) {
            $metrics['checkpoints'] = $this->processCheckpoints();
        }

        return $metrics;
    }

    private function processCheckpoints(): array
    {
        $processed = [];
        $previousTime = $this->startTime;
        $previousMemory = $this->startMemory;

        foreach ($this->checkpoints as $label => $data) {
            $processed[$label] = [
                'time_from_start' => round(($data['time'] - $this->startTime) * 1000, 4) . ' ms',
                'time_from_previous' => round(($data['time'] - $previousTime) * 1000, 4) . ' ms',
                'memory_usage' => $this->formatBytes($data['memory']),
                'memory_change' => $this->formatBytes($data['memory'] - $previousMemory),
                'additional_info' => $data['additional_info']
            ];

            if ($data['cpu'] && $this->cpuUsageStart) {
                $processed[$label]['cpu'] = [
                    'user' => round($data['cpu']['user'] - $this->cpuUsageStart['user'], 4) . 's',
                    'system' => round($data['cpu']['system'] - $this->cpuUsageStart['system'], 4) . 's'
                ];
            }

            $previousTime = $data['time'];
            $previousMemory = $data['memory'];
        }

        return $processed;
    }

    private function formatBytes(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= (1 << (10 * $pow));

        return round($bytes, 2) . ' ' . $units[$pow];
    }
}
