<?php
/**
 * Bounded summaries of Bricks element trees for the model.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Elements;

/**
 * Never dumps a full page tree. Summarizes by section with counts.
 */
final class TreeSummarizer
{
    public const DEFAULT_DEPTH = 2;

    public const MAX_NODES = 80;

    /**
     * @param array<int, array<string, mixed>> $elements
     * @return array{
     *   nodes: array<int, array<string, mixed>>,
     *   totals: array{elements: int, sections: int, roots: int},
     *   truncated: bool
     * }
     */
    public function summarize(array $elements, int $depth = self::DEFAULT_DEPTH, string $expand = ''): array
    {
        $tree  = ElementTree::fromList($elements);
        $depth = max(0, $depth);
        $used  = 0;

        $roots = [];
        foreach ($tree->toList() as $element) {
            $parent = $element['parent'] ?? 0;
            if ($parent === 0 || $parent === '0' || $parent === '' || $parent === null) {
                $roots[] = $element;
            }
        }

        $nodes     = [];
        $truncated = false;
        foreach ($roots as $root) {
            $node = $this->summarizeNode($tree, $root, $depth, $expand, 0, $used, $truncated);
            if ($node !== null) {
                $nodes[] = $node;
            }
        }

        $sections = 0;
        foreach ($tree->toList() as $element) {
            if ((string) ($element['name'] ?? '') === 'section') {
                ++$sections;
            }
        }

        return [
            'nodes'     => $nodes,
            'totals'    => [
                'elements' => count($tree->toList()),
                'sections' => $sections,
                'roots'    => count($roots),
            ],
            'truncated' => $truncated,
        ];
    }

    /**
     * @param array<string, mixed> $element
     * @return array<string, mixed>|null
     */
    private function summarizeNode(
        ElementTree $tree,
        array $element,
        int $maxDepth,
        string $expand,
        int $level,
        int &$used,
        bool &$truncated
    ): ?array {
        if ($used >= self::MAX_NODES) {
            $truncated = true;

            return null;
        }

        ++$used;

        $id       = (string) ($element['id'] ?? '');
        $children = is_array($element['children'] ?? null) ? $element['children'] : [];
        $settings = is_array($element['settings'] ?? null) ? $element['settings'] : [];
        $classes  = $settings['_cssGlobalClasses'] ?? [];
        $classCount = is_array($classes) ? count($classes) : 0;

        $node = [
            'id'          => $id,
            'name'        => (string) ($element['name'] ?? ''),
            'child_count' => count($children),
            'classes'     => $classCount,
        ];

        $label = $this->labelFromSettings($settings);
        if ($label !== '') {
            $node['label'] = $label;
        }

        $expandHere = $expand !== '' && ($id === $expand || in_array($expand, $tree->descendantIds($id), true));
        $canDescend = $children !== [] && ($level < $maxDepth || $expandHere);

        if (!$canDescend) {
            if ($children !== [] && $level >= $maxDepth && !$expandHere) {
                $node['truncated'] = true;
                $truncated         = true;
            }

            return $node;
        }

        $childNodes = [];
        foreach ($children as $childId) {
            $child = $tree->findById((string) $childId);
            if ($child === null) {
                continue;
            }

            $childNode = $this->summarizeNode(
                $tree,
                $child,
                $maxDepth,
                $expand,
                $level + 1,
                $used,
                $truncated
            );
            if ($childNode !== null) {
                $childNodes[] = $childNode;
            }
        }

        if ($childNodes !== []) {
            $node['children'] = $childNodes;
        }

        return $node;
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function labelFromSettings(array $settings): string
    {
        foreach (['text', 'title', 'label', '_hidden'] as $key) {
            $value = $settings[$key] ?? null;
            if (is_string($value) && $value !== '') {
                $plain = trim(wp_strip_all_tags($value));
                if ($plain !== '') {
                    return function_exists('mb_substr')
                        ? mb_substr($plain, 0, 80)
                        : substr($plain, 0, 80);
                }
            }
        }

        return '';
    }
}
