<?php

declare(strict_types=1);

namespace Rowbot\URL\State;

use Rowbot\URL\Component\Path;
use Rowbot\URL\ParserContext;
use Rowbot\URL\String\CodePoint;

/**
 * @see https://url.spec.whatwg.org/#path-state
 */
class PathState implements State
{
    /**
     * @see https://url.spec.whatwg.org/#double-dot-path-segment
     */
    private const DOUBLE_DOT_SEGMENT = [
        '..'     => '',
        '.%2e'   => '',
        '.%2E'   => '',
        '%2e.'   => '',
        '%2E.'   => '',
        '%2e%2e' => '',
        '%2E%2E' => '',
        '%2e%2E' => '',
        '%2E%2e' => '',
    ];

    /**
     * @see https://url.spec.whatwg.org/#single-dot-path-segment
     */
    private const SINGLE_DOT_SEGMENT = [
        '.'   => '',
        '%2e' => '',
        '%2E' => '',
    ];

    public function handle(ParserContext $context, string $codePoint): int
    {
        // 1. If one of the following is true:
        //      - c is the EOF code point or U+002F (/)
        //      - url is special and c is U+005C (\)
        //      - state override is not given and c is U+003F (?) or U+0023 (#)
        if (
            $codePoint === CodePoint::EOF
            || $codePoint === '/'
            || ($context->url->scheme->isSpecial() && $codePoint === '\\')
            || (!$context->isStateOverridden() && ($codePoint === '?' || $codePoint === '#'))
        ) {
            $urlIsSpecial = $context->url->scheme->isSpecial();

            // 1.1. If url is special and c is U+005C (\), validation error.
            if ($urlIsSpecial && $codePoint === '\\') {
                // Validation error.
            }

            $stringBuffer = (string) $context->buffer;

            // 1.2. If buffer is a double-dot path segment, then:
            if (isset(self::DOUBLE_DOT_SEGMENT[$stringBuffer])) {
                // 1.2.1. Shorten url’s path.
                $context->url->path->shorten($context->url->scheme);

                // 1.2.2. If neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string
                // to url’s path.
                if ($codePoint !== '/' && !($urlIsSpecial && $codePoint === '\\')) {
                    $context->url->path->push(new Path());
                }

            // 1.3. Otherwise, if buffer is a single-dot path segment and if neither c is U+002F (/), nor url is special
            // and c is U+005C (\), append the empty string to url’s path.
            } elseif (
                isset(self::SINGLE_DOT_SEGMENT[$stringBuffer])
                && $codePoint !== '/'
                && !($urlIsSpecial && $codePoint === '\\')
            ) {
                $context->url->path->push(new Path());

            // 1.4. Otherwise, if buffer is not a single-dot path segment, then:
            } elseif (!isset(self::SINGLE_DOT_SEGMENT[$stringBuffer])) {
                // 1.4.1. If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then
                // replace the second code point in buffer with U+003A (:).
                if (
                    $context->url->scheme->isFile()
                    && $context->url->path->isEmpty()
                    && $context->buffer->isWindowsDriveLetter()
                ) {
                    // This is a (platform-independent) Windows drive letter quirk.
                    $context->buffer->setCodePointAt(1, ':');
                }

                // 1.4.2. Append buffer to url’s path.
                $context->url->path->push($context->buffer->toPath());
            }

            // 1.5. Set buffer to the empty string.
            $context->buffer->clear();

            // 1.6. If c is U+003F (?), then set url’s query to the empty string and state to query state.
            if ($codePoint === '?') {
                $context->url->query = '';
                $context->state = new QueryState();

            // If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state.
            } elseif ($codePoint === '#') {
                $context->url->fragment = '';
                $context->state = new FragmentState();
            }

            return self::RETURN_OK;
        }

        // 2. Otherwise, run these steps:
        // 2.1. If c is not a URL code point and not U+0025 (%), validation error.
        if (!CodePoint::isUrlCodePoint($codePoint) && $codePoint !== '%') {
            // Validation error
        }

        // 2.2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error.
        if (
            $codePoint === '%'
            && !$context->input->substr($context->iter->key() + 1)->startsWithTwoAsciiHexDigits()
        ) {
            // Validation error
        }

        // 2.3. UTF-8 percent-encode c using the path percent-encode set and append the result to buffer.
        $context->buffer->append(CodePoint::utf8PercentEncode(
            $codePoint,
            CodePoint::PATH_PERCENT_ENCODE_SET
        ));

        return self::RETURN_OK;
    }
}
