<?php

declare(strict_types=1);

namespace App\Framework\Router\ValueObjects;

use InvalidArgumentException;

final readonly class RoutePath
{
    /** @var array<string|Placeholder|TypedPlaceholder> */
    private array $elements;

    private function __construct(string|Placeholder|TypedPlaceholder ...$elements)
    {
        if (empty($elements)) {
            throw new InvalidArgumentException('Route path cannot be empty');
        }

        $this->elements = array_values($elements);
        $this->validateElements();
    }

    /**
     * Create route path from path elements
     *
     * @param string|Placeholder|TypedPlaceholder ...$elements
     */
    public static function fromElements(string|Placeholder|TypedPlaceholder ...$elements): self
    {
        return new self(...$elements);
    }

    /**
     * Create route path from string (for backward compatibility)
     */
    public static function fromString(string $path): self
    {
        if (empty($path)) {
            throw new InvalidArgumentException('Route path cannot be empty');
        }

        // Remove leading slash
        $path = ltrim($path, '/');

        // Split by slash and convert to elements
        $segments = explode('/', $path);
        $elements = [];

        foreach ($segments as $segment) {
            if (empty($segment)) {
                continue;
            }

            // Check if it's a placeholder {param} or {param*}
            if (preg_match('/^\{(\w+)(\*)?}$/', $segment, $matches)) {
                $paramName = $matches[1];
                $isWildcard = isset($matches[2]) && $matches[2] === '*';

                $elements[] = $isWildcard
                    ? Placeholder::wildcard($paramName)
                    : Placeholder::fromString($paramName);
            } else {
                $elements[] = $segment;
            }
        }

        return new self(...$elements);
    }

    /**
     * Fluent builder pattern
     */
    public static function create(): RoutePathBuilder
    {
        return new RoutePathBuilder();
    }

    /**
     * Convert to string representation for router compilation
     */
    public function toString(): string
    {
        $segments = [];

        foreach ($this->elements as $element) {
            if ($element instanceof TypedPlaceholder) {
                $segments[] = $element->toString();
            } elseif ($element instanceof Placeholder) {
                $segments[] = $element->toString();
            } else {
                $segments[] = $element;
            }
        }

        return '/' . implode('/', $segments);
    }

    /**
     * Get all elements of this route path
     *
     * @return array<string|Placeholder>
     */
    public function getElements(): array
    {
        return $this->elements;
    }

    /**
     * Get all placeholders in this route path
     *
     * @return array<Placeholder|TypedPlaceholder>
     */
    public function getPlaceholders(): array
    {
        return array_filter(
            $this->elements,
            fn ($element) => $element instanceof Placeholder || $element instanceof TypedPlaceholder
        );
    }

    /**
     * Check if this route has any dynamic parameters
     */
    public function isDynamic(): bool
    {
        return ! empty($this->getPlaceholders());
    }

    /**
     * Check if this route is static (no parameters)
     */
    public function isStatic(): bool
    {
        return ! $this->isDynamic();
    }

    /**
     * Get parameter names for dynamic routes
     *
     * @return array<string>
     */
    public function getParameterNames(): array
    {
        $placeholders = $this->getPlaceholders();

        return array_map(
            fn (Placeholder|TypedPlaceholder $placeholder) => $placeholder->getName(),
            $placeholders
        );
    }

    /**
     * Convert to regex pattern for route matching
     */
    public function toRegex(): string
    {
        $segments = [];

        foreach ($this->elements as $element) {
            if ($element instanceof TypedPlaceholder) {
                $segments[] = $element->getPattern();
            } elseif ($element instanceof Placeholder) {
                $segments[] = $element->getPattern();
            } else {
                $segments[] = preg_quote($element, '~');
            }
        }

        return '~^/' . implode('/', $segments) . '$~';
    }

    /**
     * Add another element to create a new RoutePath
     */
    public function append(string|Placeholder $element): self
    {
        $newElements = [...$this->elements, $element];

        return new self(...$newElements);
    }

    /**
     * Prepend an element to create a new RoutePath
     */
    public function prepend(string|Placeholder $element): self
    {
        $newElements = [$element, ...$this->elements];

        return new self(...$newElements);
    }

    /**
     * Get the number of segments in this route
     */
    public function getSegmentCount(): int
    {
        return count($this->elements);
    }

    /**
     * Validate route elements
     */
    private function validateElements(): void
    {
        foreach ($this->elements as $element) {
            if ($element instanceof TypedPlaceholder || $element instanceof Placeholder) {
                continue;
            }

            if (! is_string($element)) {
                throw new InvalidArgumentException(
                    'Route elements must be strings, Placeholder, or TypedPlaceholder objects'
                );
            }

            if (empty($element)) {
                throw new InvalidArgumentException('Route segments cannot be empty');
            }

            // Check for invalid characters in static segments
            if (preg_match('/[{}]/', $element)) {
                throw new InvalidArgumentException(
                    "Invalid characters in route segment: {$element}"
                );
            }
        }
    }

    /**
     * Convert to string when used as string
     */
    public function __toString(): string
    {
        return $this->toString();
    }
}
