<?php

namespace Rize;

use Rize\UriTemplate\Parser;

/**
 * URI Template.
 */
class UriTemplate
{
    protected Parser $parser;
    protected array $parsed = [];

    public function __construct(protected string $base_uri = '', protected array $params = [], ?Parser $parser = null)
    {
        $this->parser = $parser ?: $this->createNodeParser();
    }

    /**
     * Expands URI Template.
     *
     * @param mixed $params
     */
    public function expand(string $uri, $params = []): string
    {
        $params += $this->params;
        $uri     = $this->base_uri . $uri;
        $result  = [];

        // quick check
        if (!str_contains($uri, '{')) {
            return $uri;
        }

        $parser = $this->parser;
        $nodes  = $parser->parse($uri);

        foreach ($nodes as $node) {
            $result[] = $node->expand($parser, $params);
        }

        return implode('', $result);
    }

    /**
     * Extracts variables from URI.
     *
     * @return null|array params or null if not match and $strict is true
     */
    public function extract(string $template, string $uri, bool $strict = false): ?array
    {
        $params = [];
        $nodes  = $this->parser->parse($template);

        // PHP 8.1.0RC4-dev still throws deprecation warning for `strlen`.
        // $uri    = (string) $uri;

        foreach ($nodes as $node) {
            // if strict is given, and there's no remaining uri just return null
            if ($strict && (string) $uri === '') {
                return null;
            }

            // URI will be truncated from the start when a match is found
            $match = $node->match($this->parser, $uri, $params, $strict);

            if ($match === null) {
                return null;
            }

            [$uri, $params] = $match;
        }

        // if there's remaining $uri, matching is failed
        if ($strict && (string) $uri !== '') {
            return null;
        }

        return $params;
    }

    public function getParser(): Parser
    {
        return $this->parser;
    }

    protected function createNodeParser(): Parser
    {
        static $parser;

        if ($parser) {
            return $parser;
        }

        return $parser = new Parser();
    }
}
