1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the league/commonmark package.
7 *
8 * (c) Colin O'Dell <colinodell@gmail.com>
9 *
10 * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
11 *  - (c) Atlassian Pty Ltd
12 *
13 * For the full copyright and license information, please view the LICENSE
14 * file that was distributed with this source code.
15 */
16
17namespace League\CommonMark\Delimiter\Processor;
18
19use League\CommonMark\Delimiter\DelimiterInterface;
20use League\CommonMark\Exception\InvalidArgumentException;
21use League\CommonMark\Node\Inline\AbstractStringContainer;
22
23/**
24 * An implementation of DelimiterProcessorInterface that dispatches all calls to two or more other DelimiterProcessors
25 * depending on the length of the delimiter run. All child DelimiterProcessors must have different minimum
26 * lengths. A given delimiter run is dispatched to the child with the largest acceptable minimum length. If no
27 * child is applicable, the one with the largest minimum length is chosen.
28 *
29 * @internal
30 */
31final class StaggeredDelimiterProcessor implements DelimiterProcessorInterface
32{
33    /** @psalm-readonly */
34    private string $delimiterChar;
35
36    /** @psalm-readonly-allow-private-mutation */
37    private int $minLength = 0;
38
39    /**
40     * @var array<int, DelimiterProcessorInterface>|DelimiterProcessorInterface[]
41     *
42     * @psalm-readonly-allow-private-mutation
43     */
44    private array $processors = []; // keyed by minLength in reverse order
45
46    public function __construct(string $char, DelimiterProcessorInterface $processor)
47    {
48        $this->delimiterChar = $char;
49        $this->add($processor);
50    }
51
52    public function getOpeningCharacter(): string
53    {
54        return $this->delimiterChar;
55    }
56
57    public function getClosingCharacter(): string
58    {
59        return $this->delimiterChar;
60    }
61
62    public function getMinLength(): int
63    {
64        return $this->minLength;
65    }
66
67    /**
68     * Adds the given processor to this staggered delimiter processor
69     *
70     * @throws InvalidArgumentException if attempting to add another processors for the same character and minimum length
71     */
72    public function add(DelimiterProcessorInterface $processor): void
73    {
74        $len = $processor->getMinLength();
75
76        if (isset($this->processors[$len])) {
77            throw new InvalidArgumentException(\sprintf('Cannot add two delimiter processors for char "%s" and minimum length %d', $this->delimiterChar, $len));
78        }
79
80        $this->processors[$len] = $processor;
81        \krsort($this->processors);
82
83        $this->minLength = \min($this->minLength, $len);
84    }
85
86    public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int
87    {
88        return $this->findProcessor($opener->getLength())->getDelimiterUse($opener, $closer);
89    }
90
91    public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void
92    {
93        $this->findProcessor($delimiterUse)->process($opener, $closer, $delimiterUse);
94    }
95
96    private function findProcessor(int $len): DelimiterProcessorInterface
97    {
98        // Find the "longest" processor which can handle this length
99        foreach ($this->processors as $processor) {
100            if ($processor->getMinLength() <= $len) {
101                return $processor;
102            }
103        }
104
105        // Just use the first one in our list
106        $first = \reset($this->processors);
107        \assert($first instanceof DelimiterProcessorInterface);
108
109        return $first;
110    }
111}
112