<?php

declare(strict_types=1);

namespace SiteFort\Includes;

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

/**
 * Resolve WordPress request paths across subdirectory installs and custom
 * content roots.
 */
class SitePathResolver {

    public static function get_home_filesystem_path(): string {
        if ( self::is_flywheel() ) {
            $document_root = self::server_value( 'DOCUMENT_ROOT' );
            if ( $document_root !== '' ) {
                return trailingslashit( wp_normalize_path( $document_root ) );
            }
        }

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

        if ( function_exists( 'get_home_path' ) ) {
            $home_path = get_home_path();
            if ( is_string( $home_path ) && trim( $home_path ) !== '' ) {
                return trailingslashit( wp_normalize_path( $home_path ) );
            }
        }

        return trailingslashit( wp_normalize_path( ABSPATH ) );
    }

    public static function get_user_ini_filename(): string {
        $filename = ini_get( 'user_ini.filename' );
        if ( ! is_string( $filename ) ) {
            return '';
        }

        $filename = trim( $filename );
        $filename = trim( $filename, "\"'" );

        if ( $filename === '' || $filename === '.' || $filename === '..' ) {
            return '';
        }

        if ( strpos( $filename, "\0" ) !== false ) {
            return '';
        }

        if ( preg_match( '/[\x00-\x1F\x7F]/', $filename ) === 1 ) {
            return '';
        }

        if ( preg_match( '#^(?:\./|/)+([A-Za-z0-9._-]+)$#', $filename, $matches ) === 1 ) {
            $filename = (string) $matches[1];
        }

        if ( preg_match( '#[\\\\/]#', $filename ) === 1 ) {
            return '';
        }

        // Keep the filename to a single config-safe token because it is reused
        // in generated Apache and Nginx directives as well as public-path joins.
        if ( preg_match( '/^[A-Za-z0-9._-]+$/', $filename ) !== 1 ) {
            return '';
        }

        return $filename;
    }

    private static function is_flywheel(): bool {
        return strpos( self::server_value( 'SERVER_SOFTWARE' ), 'Flywheel/' ) === 0;
    }

    private static function server_value( string $key ): string {
        if ( ! isset( $_SERVER[ $key ] ) || is_array( $_SERVER[ $key ] ) ) {
            return '';
        }

        $value = sanitize_text_field( wp_unslash( (string) $_SERVER[ $key ] ) );
        $value = is_string( $value ) ? trim( $value ) : '';

        if ( $value === '' || strpos( $value, "\0" ) !== false ) {
            return '';
        }

        return $value;
    }

    /**
     * Normalize a request path and optionally preserve a trailing slash.
     */
    public static function normalize_request_path( string $path, bool $trailing_slash = false ): string {
        $normalized = '/' . ltrim( trim( $path ), '/' );
        $normalized = preg_replace( '#/+#', '/', $normalized ) ?? $normalized;

        if ( $trailing_slash ) {
            return $normalized === '/' ? '/' : trailingslashit( $normalized );
        }

        return $normalized === '/' ? '/' : untrailingslashit( $normalized );
    }

    /**
     * Join two request-path fragments into a normalized absolute path.
     */
    public static function join_request_path( string $base_path, string $relative_path ): string {
        $base                    = self::normalize_request_path( $base_path, true );
        $preserve_trailing_slash = $relative_path === '' || substr( $relative_path, -1 ) === '/';

        return self::normalize_request_path( $base . ltrim( $relative_path, '/' ), $preserve_trailing_slash );
    }

    /**
     * Parse a URL into a normalized request path.
     */
    public static function get_public_path_from_url( string $url, bool $trailing_slash = false ): ?string {
        $path = wp_parse_url( $url, PHP_URL_PATH );
        if ( ! is_string( $path ) ) {
            return null;
        }

        return self::normalize_request_path( $path, $trailing_slash );
    }

    /**
     * Get the public request path to the WordPress install root.
     */
    public static function get_site_root_path(): string {
        return self::get_public_path_from_url( site_url( '/' ), true ) ?? '/';
    }

    public static function get_home_root_path(): string {
        return self::get_public_path_from_url( home_url( '/' ), true ) ?? '/';
    }

    /**
     * Resolve the public request path for a site-root file.
     */
    public static function get_site_root_file_path( string $filename ): string {
        return self::join_request_path( self::get_site_root_path(), $filename );
    }

    public static function get_home_root_file_path( string $filename ): string {
        return self::join_request_path( self::get_home_root_path(), $filename );
    }

    /**
     * Resolve the public request path for an admin-relative path.
     */
    public static function get_admin_public_path( string $relative = '' ): ?string {
        $admin_path = self::get_public_path_from_url( admin_url( '/' ), true );
        if ( $admin_path === null ) {
            return null;
        }

        return self::join_request_path( $admin_path, $relative );
    }

    /**
     * Resolve the public request path for a wp-includes-relative path.
     */
    public static function get_includes_public_path( string $relative = '' ): ?string {
        $includes_path = self::get_public_path_from_url( includes_url( '/' ), true );
        if ( $includes_path === null ) {
            return null;
        }

        return self::join_request_path( $includes_path, $relative );
    }

    /**
     * Resolve the public request path for the content root.
     */
    public static function get_content_public_path( string $relative = '' ): ?string {
        $content_path = self::get_public_path_from_url( content_url( '/' ), true );
        if ( $content_path === null ) {
            return null;
        }

        return self::join_request_path( $content_path, $relative );
    }

    /**
     * Resolve the public request path for a directory that may need PHP blocking.
     */
    public static function get_public_directory_path( string $dir ): ?string {
        switch ( $dir ) {
            case 'uploads':
                $url = (string) ( wp_upload_dir()['baseurl'] ?? '' );
                break;
            case 'plugins':
                $url = plugins_url( '/' );
                break;
            case 'mu-plugins':
                if ( defined( 'WPMU_PLUGIN_URL' ) && is_string( WPMU_PLUGIN_URL ) && WPMU_PLUGIN_URL !== '' ) {
                    $url = trailingslashit( WPMU_PLUGIN_URL );
                    break;
                }

                return self::get_content_public_path( 'mu-plugins/' );
            case 'themes':
                $url = trailingslashit( get_theme_root_uri() );
                break;
            default:
                return null;
        }

        return self::get_public_path_from_url( $url, true );
    }

    /**
     * Convert a site-local filesystem path to its public request path.
     */
    public static function get_public_path_from_local_path( string $path ): ?string {
        $path = trim( $path );
        if ( $path === '' ) {
            return null;
        }

        $normalized_path    = wp_normalize_path( $path );
        $normalized_abspath = trailingslashit( wp_normalize_path( ABSPATH ) );
        $normalized_content = trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) );

        if ( str_starts_with( $normalized_path, $normalized_abspath ) ) {
            $relative = ltrim( substr( $normalized_path, strlen( $normalized_abspath ) ), '/' );
            return self::join_request_path( self::get_site_root_path(), $relative );
        }

        if ( str_starts_with( $normalized_path, $normalized_content ) ) {
            $content_root = self::get_content_public_path( '' );
            if ( $content_root === null ) {
                return null;
            }

            $relative = ltrim( substr( $normalized_path, strlen( $normalized_content ) ), '/' );
            return self::join_request_path( $content_root, $relative );
        }

        return null;
    }

    /**
     * Strip the site-root prefix from a public request path.
     */
    public static function get_site_relative_path( string $path ): string {
        $normalized = self::normalize_request_path( $path, false );
        $site_root  = self::get_site_root_path();

        if ( $site_root !== '/' && str_starts_with( $normalized, $site_root ) ) {
            return '/' . ltrim( substr( $normalized, strlen( $site_root ) ), '/' );
        }

        return $normalized;
    }
}
