<?php

namespace Plainwork\Http;

class Request
{
    private string $requestMethod;
    private array $postParams = [];
    private array $queryParams = [];
    private array $routeParams = [];
    private array $headers = [];
    private $body = null;
    private $parsedBody = [];
    private array $attributes = [];
    private string $path = '';
    private ?string $routePattern = null;
    private string $prefix = '';
    private string $requestedPath;
    private string $levelPrefix = '';

    public function __construct($prefix = '')
    {
        $this->prefix = $prefix;
    }

    public function setLevelPrefix(string $v): self
    {
        $this->levelPrefix = $v;

        return $this;
    }

    public function getBody()
    {
        return $this->body;
    }

    public function withBody($v): self
    {
        $ret = clone $this;
        $ret->body = $v;

        return $ret;
    }

    public function getParsedBody(): array
    {
        return $this->parsedBody;
    }

    public function withParsedBody(array $data): self
    {
        $ret = clone $this;
        $ret->parsedBody = $data;

        return $ret;
    }

    public function getParamName(string $name): string
    {
        return $this->prefix . $name;
    }

    public function isDelete()
    {
        return ('DELETE' === $this->getRequestMethod()) ? true : false;
    }

    public function isPut()
    {
        return ('PUT' === $this->getRequestMethod()) ? true : false;
    }

    public function isPatch()
    {
        return ('PATCH' === $this->getRequestMethod()) ? true : false;
    }

    public function isPost()
    {
        return ('POST' === $this->getRequestMethod()) ? true : false;
    }

    public function isGet()
    {
        return ('GET' === $this->getRequestMethod()) ? true : false;
    }

    public function isHead()
    {
        return ('HEAD' === $this->getRequestMethod()) ? true : false;
    }

    public function getRequestMethod(): string
    {
        return $this->requestMethod;
        $ret = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $ret = strtoupper($ret);

        return $ret;
    }

    public function getPath(): string
    {
        return $this->path;
    }

    public function getRequestedPath(): string
    {
        return $this->requestedPath;
    }

    public function getRoutePattern(): ?string
    {
        return $this->routePattern;
    }

    public function getPostParam($k)
    {
        return $this->postParams[$k] ?? null;
    }

    public function withPostParam(string $k, $v): self
    {
        $ret = clone $this;

        if (null === $v) {
            unset($ret->postParams[$k]);
        } else {
            $ret->postParams[$k] = $v;
        }

        return $ret;
    }

    public function getPostParams()
    {
        return $this->postParams;
    }

    public function withPostParams(array $params): self
    {
        $ret = clone $this;
        $ret->postParams = $params;

        return $ret;
    }

    public function getRouteParam($k)
    {
        return $this->routeParams[$k] ?? null;
    }

    public function getHeader(string $name)
    {
        return $this->headers[$this->prepareHeaderName($name)] ?? null;
    }

    public function withHeader(string $name, $value): self
    {
        $ret = clone $this;
        $ret->headers[$this->prepareHeaderName($name)] = $value;

        return $ret;
    }

    public function withHeaders(array $vs): self
    {
        $ret = clone $this;

        $ret->headers = [];
        foreach ($vs as $name => $value) {
            while (is_array($value)) {
                $value = current($value);
            }
            $ret->headers[$this->prepareHeaderName($name)] = $value;
        }

        return $ret;
    }

    private function prepareHeaderName(string $name): string
    {
        $ret = $name;
        $ret = strtolower($ret);
        $ret = str_replace(' ', '-', $ret);
        $ret = str_replace('_', '-', $ret);

        return $ret;
    }

    public function queryParam(string $k, bool $includeFromUpper = true)
    {
        return $this->getQueryParam($k, $includeFromUpper);
    }

    public function getQueryParam(string $k, bool $includeFromUpper = true)
    {
        if (array_key_exists($k, $this->routeParams)) {
            return $this->routeParams[$k];
        }

        if (array_key_exists($k, $this->queryParams)) {
            return $this->queryParams[$k];
        }

    // level prefix is on? check upper level params
        $len = strlen($this->levelPrefix);
        if ($len > 0 && $includeFromUpper) {
            $candidates = array_filter(array_keys($this->queryParams), fn($e) => $this->levelPrefix . $k === substr($e, -(strlen($k) + $len)));
            if (!$candidates) {
                return null;
            }

            usort($candidates, fn($a, $b) => strlen($a) - strlen($b));
            $k2 = current($candidates);

            return $this->getQueryParam($k2);
        }

        return null;
    }

    public function getParam($k)
    {
        return $this->getAttribute($k) ?? $this->getQueryParam($k);
    }

    public function withAttribute($attrName, $attrValue): self
    {
        $ret = clone $this;
        $ret->attributes[$attrName] = $attrValue;

        return $ret;
    }

    public function getAttribute($attrName)
    {
        return $this->attributes[$attrName] ?? null;
    }

    public function getQueryParams(): array
    {
        return $this->queryParams;
    }

    public function withRequestMethod(string $method): self
    {
        $ret = clone $this;
        $ret->requestMethod = strtoupper($method);

        return $ret;
    }

    public function withPath(string $v): self
    {
        $ret = clone $this;
        $ret->path = $v;

        return $ret;
    }

    public function withRequestedPath(string $v): self
    {
        $ret = clone $this;
        $ret->requestedPath = $v;

        return $ret;
    }

    public function withRoutePattern(?string $v): self
    {
        $ret = clone $this;
        $ret->routePattern = $v;

        return $ret;
    }

    public function withRouteParam(string $k, $v): self
    {
        $ret = clone $this;
        $ret->routeParams[$k] = $v;

        return $ret;
    }

    public function withRouteParams(array $params): self
    {
        $ret = clone $this;
        $ret->routeParams = $params;

        return $ret;
    }

    public function withQueryParam(string $k, $v): self
    {
        $params = $this->getQueryParams();
        if (null === $v) {
            unset($params[$k]);
        } else {
            $params[$k] = $v;
        }

        return $this->withQueryParams($params);
    }

    public function withQueryParams(array $params): self
    {
        $ret = clone $this;
        $ret->queryParams = $params;

        return $ret;
    }

    public function resolvePath(string $path): string
    {
        $pathArray = strlen($path) ? explode('/', $path) : [];
        $currentPath = $this->getPath();
        $currentPathArray = strlen($currentPath) ? explode('/', $currentPath) : [];

        $copyCurrentPathArray = $currentPathArray;
        $ret = [];
        while ($pathArray) {
            $p = array_shift($pathArray);
            if ('.' === $p) {
                $ret = $currentPathArray;
            } elseif ('..' === $p) {
                array_pop($copyCurrentPathArray);
                $ret = $copyCurrentPathArray;
            } else {
                $ret[] = $p;
            }
        }

        $ret = join('/', $ret);

        return $ret;
    }

    // public function toString(): string
    // {
        // $ret = $this->getRoute();
        // $params = $this->getQueryParams();
        // if ($params) {
            // $paramsString = http_build_query($params);
            // if ($paramsString) {
                // $ret .= $paramsString;
            // }
        // }

        // return $ret;
    // }
}
