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 * For the full copyright and license information, please view the LICENSE
11 * file that was distributed with this source code.
12 */
13
14namespace League\CommonMark\Extension\TableOfContents\Normalizer;
15
16use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
17use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
18use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
19
20final class RelativeNormalizerStrategy implements NormalizerStrategyInterface
21{
22    /** @psalm-readonly */
23    private TableOfContents $toc;
24
25    /**
26     * @var array<int, ListItem>
27     *
28     * @psalm-readonly-allow-private-mutation
29     */
30    private array $listItemStack = [];
31
32    public function __construct(TableOfContents $toc)
33    {
34        $this->toc = $toc;
35    }
36
37    public function addItem(int $level, ListItem $listItemToAdd): void
38    {
39        $previousLevel = \array_key_last($this->listItemStack);
40
41        // Pop the stack if we're too deep
42        while ($previousLevel !== null && $level < $previousLevel) {
43            \array_pop($this->listItemStack);
44            $previousLevel = \array_key_last($this->listItemStack);
45        }
46
47        $lastListItem = \end($this->listItemStack);
48
49        // Need to go one level deeper? Add that level
50        if ($lastListItem !== false && $level > $previousLevel) {
51            $targetListBlock = new ListBlock($lastListItem->getListData());
52            $targetListBlock->setStartLine($listItemToAdd->getStartLine());
53            $targetListBlock->setEndLine($listItemToAdd->getEndLine());
54            $lastListItem->appendChild($targetListBlock);
55        // Otherwise we're at the right level
56        // If there's no stack we're adding this item directly to the TOC element
57        } elseif ($lastListItem === false) {
58            $targetListBlock = $this->toc;
59        // Otherwise add it to the last list item
60        } else {
61            $targetListBlock = $lastListItem->parent();
62        }
63
64        $targetListBlock->appendChild($listItemToAdd);
65        $this->listItemStack[$level] = $listItemToAdd;
66    }
67}
68