<?php

namespace MintyPHP\Module\Bookmarks\Support;

final class BookmarkUrlNormalizer
{
    public static function canonicalizeRelative(string $url): string
    {
        $url = trim($url);
        if ($url === '' || str_contains($url, "\x00")) {
            return '';
        }

        // Strip fragments: they are client-side only and should not
        // differentiate bookmarks during deduplication.
        $hashPos = strpos($url, '#');
        if ($hashPos !== false) {
            $url = substr($url, 0, $hashPos);
        }

        if ($url === '') {
            return '';
        }

        if (preg_match('#^[a-z][a-z0-9+.-]*:#i', $url) || str_starts_with($url, '//')) {
            return '';
        }

        $parts = parse_url($url);
        if ($parts === false) {
            return '';
        }

        $path = self::normalizePath((string) ($parts['path'] ?? ''));
        $query = self::normalizeQuery((string) ($parts['query'] ?? ''));

        if ($path === '' && $query === '') {
            return '';
        }

        if ($query === '') {
            return $path;
        }

        return $path !== '' ? $path . '?' . $query : '?' . $query;
    }

    public static function canonicalizeRequestUri(string $requestUri, string $localeBase): string
    {
        $requestPath = (string) (parse_url($requestUri, PHP_URL_PATH) ?? '');
        $requestQuery = (string) (parse_url($requestUri, PHP_URL_QUERY) ?? '');

        $basePath = (string) (parse_url($localeBase, PHP_URL_PATH) ?? '');
        $basePath = '/' . trim($basePath, '/');

        if ($basePath !== '/' && ($requestPath === $basePath || str_starts_with($requestPath, $basePath . '/'))) {
            $requestPath = substr($requestPath, strlen($basePath));
        }

        $relative = ltrim($requestPath, '/');
        if ($requestQuery !== '') {
            $relative .= '?' . $requestQuery;
        }

        return self::canonicalizeRelative($relative);
    }

    private static function normalizePath(string $path): string
    {
        $path = ltrim(trim($path), '/');
        if ($path === '') {
            return '';
        }

        $collapsed = preg_replace('#/+#', '/', $path);
        if ($collapsed === null) {
            return '';
        }

        $segments = explode('/', $collapsed);
        $normalized = [];
        $depth = 0;

        foreach ($segments as $segment) {
            $segment = trim($segment);
            if ($segment === '' || $segment === '.') {
                continue;
            }

            if ($segment === '..') {
                if ($depth <= 0) {
                    return '';
                }
                array_pop($normalized);
                $depth--;
                continue;
            }

            $normalized[] = $segment;
            $depth++;
        }

        return implode('/', $normalized);
    }

    private static function normalizeQuery(string $query): string
    {
        $query = trim($query);
        if ($query === '') {
            return '';
        }

        $parsed = [];
        foreach (explode('&', $query) as $pair) {
            if ($pair === '') {
                continue;
            }

            $separatorPos = strpos($pair, '=');
            if ($separatorPos === false) {
                $rawKey = $pair;
                $rawValue = '';
            } else {
                $rawKey = substr($pair, 0, $separatorPos);
                $rawValue = substr($pair, $separatorPos + 1);
            }

            $key = rawurldecode(str_replace('+', ' ', $rawKey));
            $value = rawurldecode(str_replace('+', ' ', $rawValue));

            if ($key === '') {
                continue;
            }

            $parsed[$key][] = $value;
        }

        if ($parsed === []) {
            return '';
        }

        ksort($parsed, SORT_STRING);

        $normalizedPairs = [];
        foreach ($parsed as $key => $values) {
            sort($values, SORT_STRING);
            foreach ($values as $value) {
                $normalizedPairs[] = rawurlencode($key) . '=' . rawurlencode((string) $value);
            }
        }

        return implode('&', $normalizedPairs);
    }
}
