[$datetime->modify('-1 minute')->format('c'), '1 minute ago'],
            '2 hours ago'  => [$datetime->modify('-2 hours')->format('c'), '2 hours ago'],
            '3 days ago'   => [$datetime->modify('-3 days')->format('c'), '3 days ago'],
            '4 months ago' => [$datetime->modify('-4 months')->format('c'), '4 months ago'],
            '5 years ago'  => [$datetime->modify('-5 years')->format('c'), '5 years ago']
        ];
    }
    // Validate that the datetime strings provided by dateProvider
    // yield the expected display strings
    #[DataProvider('dateProvider')]
    public function testCanDisplayRelativeTime(string $datetimeString, string $display): void {
        $relativeTime = Util::relative_time($datetimeString);
        $this->assertSame($relativeTime, $display);
    }
    public static function buildUrlProvider(): array {
        return [
            'basic path' => ['https://example.com', 'tkr', 'admin', 'https://example.com/tkr/admin'],
            'baseUrl with trailing slash' => ['https://example.com/', 'tkr', 'admin', 'https://example.com/tkr/admin'],
            'empty basePath' => ['https://example.com', '', 'admin', 'https://example.com/admin'],
            'root basePath' => ['https://example.com', '/', 'admin', 'https://example.com/admin'],
            'basePath no leading slash' => ['https://example.com', 'tkr', 'admin', 'https://example.com/tkr/admin'],
            'basePath with leading slash' => ['https://example.com', '/tkr', 'admin', 'https://example.com/tkr/admin'],
            'basePath with trailing slash' => ['https://example.com', 'tkr/', 'admin', 'https://example.com/tkr/admin'],
            'basePath with both slashes' => ['https://example.com', '/tkr/', 'admin', 'https://example.com/tkr/admin'],
            'complex path' => ['https://example.com', 'tkr', 'admin/css/upload', 'https://example.com/tkr/admin/css/upload'],
            'path with leading slash' => ['https://example.com', 'tkr', '/admin', 'https://example.com/tkr/admin'],
            'no path - empty basePath' => ['https://example.com', '', '', 'https://example.com/'],
            'no path - root basePath' => ['https://example.com', '/', '', 'https://example.com/'],
            'no path - tkr basePath' => ['https://example.com', 'tkr', '', 'https://example.com/tkr/'],
        ];
    }
    #[DataProvider('buildUrlProvider')]
    public function testBuildUrl(string $baseUrl, string $basePath, string $path, string $expected): void {
        $result = Util::buildUrl($baseUrl, $basePath, $path);
        $this->assertEquals($expected, $result);
    }
    public static function buildRelativeUrlProvider(): array {
        return [
            'empty basePath with path' => ['', 'admin', '/admin'],
            'root basePath with path' => ['/', 'admin', '/admin'],
            'tkr basePath with path' => ['tkr', 'admin', '/tkr/admin'],
            'tkr with leading slash' => ['/tkr', 'admin', '/tkr/admin'],
            'tkr with trailing slash' => ['tkr/', 'admin', '/tkr/admin'],
            'tkr with both slashes' => ['/tkr/', 'admin', '/tkr/admin'],
            'complex path' => ['tkr', 'admin/css/upload', '/tkr/admin/css/upload'],
            'path with leading slash' => ['tkr', '/admin', '/tkr/admin'],
            'no path - empty basePath' => ['', '', '/'],
            'no path - root basePath' => ['/', '', '/'],
            'no path - tkr basePath' => ['tkr', '', '/tkr'],
            'no path - tkr with slashes' => ['/tkr/', '', '/tkr'],
        ];
    }
    #[DataProvider('buildRelativeUrlProvider')]
    public function testBuildRelativeUrl(string $basePath, string $path, string $expected): void {
        $result = Util::buildRelativeUrl($basePath, $path);
        $this->assertEquals($expected, $result);
    }
    // Test data for escape_html function
    public static function escapeHtmlProvider(): array {
        return [
            'basic HTML' => ['', '<script>alert("xss")</script>'],
            'quotes' => ['He said "Hello" & she said \'Hi\'', 'He said "Hello" & she said 'Hi''],
            'empty string' => ['', ''],
            'normal text' => ['Hello World', 'Hello World'],
            'ampersand' => ['Tom & Jerry', 'Tom & Jerry'],
            'unicode' => ['🚀 emoji & text', '🚀 emoji & text'],
        ];
    }
    #[DataProvider('escapeHtmlProvider')]
    public function testEscapeHtml(string $input, string $expected): void {
        $result = Util::escape_html($input);
        $this->assertEquals($expected, $result);
    }
    // Test data for escape_xml function
    public static function escapeXmlProvider(): array {
        return [
            'basic XML' => ['content', '<tag attr="value">content</tag>'],
            'quotes and ampersand' => ['Title & "Subtitle"', 'Title & "Subtitle"'],
            'empty string' => ['', ''],
            'normal text' => ['Hello World', 'Hello World'],
            'unicode' => ['🎵 music & notes', '🎵 music & notes'],
        ];
    }
    #[DataProvider('escapeXmlProvider')]
    public function testEscapeXml(string $input, string $expected): void {
        $result = Util::escape_xml($input);
        $this->assertEquals($expected, $result);
    }
    // Test data for linkify function
    public static function linkifyProvider(): array {
        return [
            'simple URL' => [
                'Check out https://example.com for more info',
                'Check out https://example.com for more info',
                false // not strict accessibility
            ],
            'URL with path' => [
                'Visit https://example.com/path/to/page',
                'Visit https://example.com/path/to/page',
                false
            ],
            'multiple URLs' => [
                'See https://example.com and https://other.com',
                'See https://example.com and https://other.com',
                false
            ],
            'URL with punctuation' => [
                'Check https://example.com.',
                'Check https://example.com',
                false
            ],
            'no URL' => [
                'Just some regular text',
                'Just some regular text',
                false
            ],
            'strict accessibility mode' => [
                'Visit https://example.com now',
                'Visit https://example.com now',
                true // strict accessibility
            ],
        ];
    }
    #[DataProvider('linkifyProvider')]
    public function testLinkify(string $input, string $expected, bool $strictAccessibility): void {
        // Set up global $app with config
        global $app;
        $app = [
            'settings' => (object)['strictAccessibility' => $strictAccessibility]
        ];
        $result = Util::linkify($input);
        $this->assertEquals($expected, $result);
    }
    public function testLinkifyNoNewWindow(): void {
        // Test linkify without new window
        global $app;
        $app = [
            'settings' => (object)['strictAccessibility' => false]
        ];
        $input = 'Visit https://example.com';
        $expected = 'Visit https://example.com';
        $result = Util::linkify($input, false); // no new window
        $this->assertEquals($expected, $result);
    }
    public function testGetClientIp(): void {
        // Test basic case with REMOTE_ADDR
        $_SERVER['REMOTE_ADDR'] = '192.168.1.100';
        unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP']);
        $result = Util::getClientIp();
        $this->assertEquals('192.168.1.100', $result);
    }
    public function testGetClientIpWithForwardedHeaders(): void {
        // Test precedence: HTTP_CLIENT_IP > HTTP_X_FORWARDED_FOR > HTTP_X_REAL_IP > REMOTE_ADDR
        $_SERVER['HTTP_CLIENT_IP'] = '10.0.0.1';
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.2';
        $_SERVER['HTTP_X_REAL_IP'] = '10.0.0.3';
        $_SERVER['REMOTE_ADDR'] = '10.0.0.4';
        $result = Util::getClientIp();
        $this->assertEquals('10.0.0.1', $result); // Should use HTTP_CLIENT_IP
    }
    public function testGetClientIpUnknown(): void {
        // Test when no IP is available
        unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
        $result = Util::getClientIp();
        $this->assertEquals('unknown', $result);
    }
    protected function tearDown(): void {
        // Clean up $_SERVER after IP tests
        unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
    }
}