<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\AdminBundle\Form;

use Symfony\Component\DependencyInjection\Attribute\Exclude;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatableInterface;

#[Exclude]
class FormScreen
{
    private ?array $violatedProperties = null;
    private ?array $violatedSections = null;
    private ?array $warningMessagesBySection = null;
    private array $extraSections = [];
    private ?array $sectionOrder = null;

    public function __construct(private readonly FormInterface $form, private readonly ValidatorInterface $validator, private readonly ?object $formData)
    {
    }

    public function withExtraSections(array $extraSections): self
    {
        $this->extraSections = $extraSections;

        return $this;
    }

    public function withSectionOrder(array $sectionOrder): self
    {
        $this->sectionOrder = $sectionOrder;

        return $this;
    }

    public function withFormData(object $formData): self
    {
        return $this;
    }

    public function sections(): array
    {
        $sections = array_values(array_filter(array_unique(
            array_map(static fn (FormInterface $formElement) => $formElement->getConfig()->getOption('section'), $this->form->all())
        )));

        $all = array_merge($sections, $this->extraSections);

        if ($this->sectionOrder) {
            usort($all, function ($a, $b) {
                $posA = array_search($a, $this->sectionOrder, true);
                $posB = array_search($b, $this->sectionOrder, true);

                if (false === $posA && false === $posB) {
                    return 0;
                }
                if (false === $posA) {
                    return 1;
                }
                if (false === $posB) {
                    return -1;
                }

                return $posA <=> $posB;
            });
        }

        return $all;
    }

    /**
     * @return array<string, array<string>>
     */
    public function violatedProperties(): array
    {
        if (null !== $this->violatedProperties) {
            return $this->violatedProperties;
        }

        $violatedProperties = [];
        foreach (['warning', 'error'] as $level) {
            $invalid = $this->validator->validate($this->formData, groups: $level);
            $violatedProperties[$level] = array_map(static fn (ConstraintViolationInterface $violation): string => $violation->getPropertyPath(), iterator_to_array($invalid->getIterator()));
        }

        return $this->violatedProperties = $violatedProperties;
    }

    /**
     * @return array<string, array<string>>
     */
    public function violatedSections(): array
    {
        if (null !== $this->violatedSections) {
            return $this->violatedSections;
        }

        $violatedSections = array_map(fn (array $violatedProperties) => array_unique(array_map(
            fn (string $property): ?string => $this->form->has($property) ? $this->form->get($property)->getConfig()->getOption('section') : null,
            $violatedProperties
        )), $this->violatedProperties());

        return $this->violatedSections = $violatedSections;
    }

    /**
     * @return list<string>
     */
    public function warningMessages(string $section): array
    {
        if (null === $this->warningMessagesBySection) {
            $bySection = [];
            $violations = $this->validator->validate($this->formData, groups: 'warning');
            foreach ($violations as $violation) {
                $property = $violation->getPropertyPath();
                $targetSection = $this->form->has($property) ? $this->form->get($property)->getConfig()->getOption('section') : null;
                if (null === $targetSection) {
                    continue;
                }
                $bySection[$targetSection] ??= [];
                $bySection[$targetSection][] = $violation->getMessage();
            }

            // de-duplicate messages
            foreach ($bySection as $sec => $messages) {
                $bySection[$sec] = array_values(array_unique($messages));
            }

            $this->warningMessagesBySection = $bySection;
        }

        return $this->warningMessagesBySection[$section] ?? [];
    }

    public function hasWarning(string $group): bool
    {
        return \in_array($group, $this->violatedSections()['warning'] ?? [], true);
    }

    public function hasError(string $group): bool
    {
        return \in_array($group, $this->violatedSections()['error'] ?? [], true);
    }

    public function sectionLabel(string $section): TranslatableInterface
    {
        return new TranslatableMessage(
            str_replace(['label.', '%name%'], ['legend.', $section.'.0'], $this->form->getConfig()->getOption('label_format')),
            domain: 'admin'
        );
    }

    public function sectionDescription(string $section): TranslatableInterface
    {
        return new TranslatableMessage(
            str_replace(['label.', '%name%'], ['legend.', $section.'.1'], $this->form->getConfig()->getOption('label_format')),
            domain: 'admin'
        );
    }
}
