<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Serenity\CoreBundle\Shared\Twig\Extensions;

use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormView;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

/**
 * @phpstan-type CollectedFormError array{
 *   field: string,
 *   full_path: string,
 *   id: string,
 *   label: string,
 *   message: string,
 *   is_collection: bool
 * }
 */
class FormErrorsExtension extends AbstractExtension
{
    public function getFunctions(): array
    {
        return [
            new TwigFunction('form_collect_errors', [$this, 'collectErrors']),
            new TwigFunction('form_has_error', [$this, 'hasError']),
        ];
    }

    /**
     * Entry point called from Twig.
     *
     * @return list<CollectedFormError>
     */
    public function collectErrors(FormView $formView): array
    {
        return $this->recursiveCollect($formView);
    }

    /**
     * Check if a field is in error based on its full_path.
     *
     * @param list<CollectedFormError> $errors
     */
    public function hasError(string $fullPath, array $errors): bool
    {
        foreach ($errors as $err) {
            if ($err['full_path'] === $fullPath) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return list<CollectedFormError>
     */
    private function recursiveCollect(FormView $view, string $parentPath = ''): array
    {
        /** @var list<CollectedFormError> $errors */
        $errors = [];

        $name = (string) $view->vars['name'];
        $currentPath = '' !== $parentPath ? $parentPath.'.'.$name : $name;

        // Collect current field errors
        foreach ($view->vars['errors'] as $error) {
            /* @var FormError $error */
            $errors[] = [
                'field' => $name,
                'full_path' => $currentPath,
                'id' => (string) $view->vars['id'],
                'label' => (string) ($view->vars['label'] ?? $name),
                'message' => $error->getMessage(),
                'is_collection' => isset($view->vars['prototype']),
            ];
        }

        // Recurse children
        foreach ($view->children as $child) {
            $errors = array_merge(
                $errors,
                $this->recursiveCollect($child, $currentPath)
            );
        }

        return $errors;
    }
}
