<?php

declare(strict_types=1);

namespace App\Framework\Http\Url;

use InvalidArgumentException;
use Uri\WhatWg\Url as NativeWhatwgUrl;

/**
 * WHATWG URL Standard implementation
 *
 * Wraps native PHP 8.5+ Uri\WhatWg\Url for browser-compatible URL handling.
 *
 * Use Cases:
 * - Browser redirects
 * - Deep links and universal links
 * - HTML form actions
 * - Client-side JavaScript compatibility
 *
 * Characteristics:
 * - Living Standard (matches modern browsers)
 * - Automatic percent-encoding
 * - URL normalization
 * - Special scheme handling (http, https, file, etc.)
 */
final readonly class WhatwgUrl implements Url
{
    /**
     * @param NativeWhatwgUrl $url Native PHP WHATWG URL instance
     */
    private function __construct(
        private NativeWhatwgUrl $url
    ) {
    }

    /**
     * Parse WHATWG URL from string
     *
     * @param string $input URL string to parse
     * @param Url|null $base Optional base URL for relative resolution
     * @return self New WHATWG URL instance
     * @throws InvalidArgumentException If URL is invalid
     */
    public static function parse(string $input, ?Url $base = null): self
    {
        try {
            if ($base instanceof self) {
                // WHATWG URL resolution with base
                $url = $base->url->resolve($input);
            } else {
                $url = new NativeWhatwgUrl($input);
            }

            return new self($url);
        } catch (\Throwable $e) {
            throw new InvalidArgumentException(
                "Failed to parse WHATWG URL: {$input}",
                previous: $e
            );
        }
    }

    public function getSpec(): UrlSpec
    {
        return UrlSpec::WHATWG;
    }

    // Component Getters (WHATWG methods)

    public function getScheme(): string
    {
        return $this->url->getScheme() ?? '';
    }

    public function getHost(): string
    {
        // Prefer Unicode host for display
        return $this->url->getUnicodeHost() ?? '';
    }

    public function getPort(): ?int
    {
        return $this->url->getPort();
    }

    public function getPath(): string
    {
        return $this->url->getPath() ?? '';
    }

    public function getQuery(): string
    {
        return $this->url->getQuery() ?? '';
    }

    public function getFragment(): string
    {
        return $this->url->getFragment() ?? '';
    }

    public function getUserInfo(): string
    {
        $user = $this->url->getUsername() ?? '';
        $pass = $this->url->getPassword() ?? '';

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

        return $pass !== '' ? "{$user}:{$pass}" : $user;
    }

    // Immutable Withers (delegate to native withers)

    public function withScheme(string $scheme): self
    {
        return new self($this->url->withScheme($scheme));
    }

    public function withHost(string $host): self
    {
        return new self($this->url->withHost($host));
    }

    public function withPort(?int $port): self
    {
        return new self($this->url->withPort($port));
    }

    public function withPath(string $path): self
    {
        return new self($this->url->withPath($path));
    }

    public function withQuery(string $query): self
    {
        return new self($this->url->withQuery($query !== '' ? $query : null));
    }

    public function withFragment(string $fragment): self
    {
        return new self($this->url->withFragment($fragment !== '' ? $fragment : null));
    }

    public function withUserInfo(string $user, ?string $password = null): self
    {
        $withUser = $this->url->withUsername($user);

        return new self($withUser->withPassword($password ?? ''));
    }

    // Serialization

    public function toString(): string
    {
        return $this->url->toUnicodeString();
    }

    public function toAsciiString(): string
    {
        // WHATWG URLs with Punycode encoding
        return $this->url->toAsciiString();
    }

    // Utilities

    public function resolve(string $relative): self
    {
        $resolved = $this->url->resolve($relative);

        return new self($resolved);
    }

    public function equals(Url $other, bool $includeFragment = false): bool
    {
        if (! $other instanceof self) {
            return false;
        }

        if ($includeFragment) {
            return $this->url->equals($other->url);
        }

        // Compare without fragments
        $thisWithoutFragment = $this->url->withFragment(null);
        $otherWithoutFragment = $other->url->withFragment(null);

        return $thisWithoutFragment->equals($otherWithoutFragment);
    }

    public function getNativeUrl(): NativeWhatwgUrl
    {
        return $this->url;
    }

    /**
     * String representation (allows string casting)
     */
    public function __toString(): string
    {
        return $this->url->toUnicodeString();
    }
}
