<?php

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Twig\Components\Depot;

use CpCreation\VitiCore\Depot\Model\Category;
use CpCreation\VitiCore\Depot\Model\Depot;
use CpCreation\VitiCore\Depot\Repository\CategoryRepository;
use CpCreation\VitiCore\Depot\Repository\DepotRepository;
use CpCreation\VitiCore\Media\Model\Image;
use Ramsey\Uuid\Uuid;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\Metadata\UrlMapping;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent('depot', template: '@components/depot/Locator.html.twig')]
final class Locator
{
    use ComponentToolsTrait;
    use DefaultActionTrait;

    #[LiveProp]
    public string $locatorId;

    #[LiveProp(writable: true, url: new UrlMapping(as: 'category'))]
    public ?string $selectedCategoryId = null;

    #[LiveProp(writable: true, url: new UrlMapping(as: 'country'))]
    public ?string $selectedCountry = null;

    #[LiveProp(writable: true)]
    public ?string $selectedDepotId = null;

    #[LiveProp(writable: true)]
    public bool $sidebarOpen = false;

    public function __construct(
        private readonly DepotRepository $depotRepository,
        private readonly CategoryRepository $categoryRepository,
    ) {
    }

    public function mount(
        ?string $selectedCategoryId = null,
        ?string $selectedCountry = null,
        ?string $selectedDepotId = null,
        bool $sidebarOpen = false,
    ): void {
        $this->locatorId = 'depot-locator-'.Uuid::uuid4()->toString();
        $this->selectedCategoryId = $selectedCategoryId;
        $this->selectedCountry = $selectedCountry;
        $this->selectedDepotId = $selectedDepotId;
        $this->sidebarOpen = $sidebarOpen;
    }

    /**
     * @return array<int, Category>
     */
    public function getCategories(): array
    {
        return $this->categoryRepository->findCategories();
    }

    /**
     * @return array<int, Depot>
     */
    public function getDepots(): array
    {
        $depots = $this->getCategoryFilteredDepots();

        if (!$this->selectedCountry) {
            return $depots;
        }

        return array_values(array_filter(
            $depots,
            fn (Depot $depot): bool => $depot->getCountry() === $this->selectedCountry,
        ));
    }

    /**
     * @return array<int, array{code: string, label: string}>
     */
    public function getAvailableCountries(): array
    {
        $countries = [];

        foreach ($this->getCategoryFilteredDepots() as $depot) {
            $countryCode = $depot->getCountry();

            if (!$countryCode) {
                continue;
            }

            $countries[$countryCode] = $depot->getCountryName() ?: $countryCode;
        }

        asort($countries);

        return array_map(
            static fn (string $label, string $code): array => [
                'code' => $code,
                'label' => $label,
            ],
            $countries,
            array_keys($countries),
        );
    }

    public function getSelectedDepot(): ?Depot
    {
        foreach ($this->getDepots() as $depot) {
            if ($depot->getId()->toString() === $this->selectedDepotId) {
                return $depot;
            }
        }

        return null;
    }

    public function getResultsTitle(): ?string
    {
        if ($this->selectedCategoryId) {
            foreach ($this->getCategories() as $category) {
                if ($category->getId()->toString() === $this->selectedCategoryId) {
                    return $category->getName();
                }
            }
        }

        $selectedDepot = $this->getSelectedDepot();
        if ($selectedDepot?->getCategory()) {
            return $selectedDepot->getCategory()->getName();
        }

        return null;
    }

    public function getSelectedCountryLabel(): ?string
    {
        if (!$this->selectedCountry) {
            return null;
        }

        foreach ($this->getAvailableCountries() as $country) {
            if ($country['code'] === $this->selectedCountry) {
                return $country['label'];
            }
        }

        return null;
    }

    /**
     * @return array<int, Depot>
     */
    public function getListedDepots(): array
    {
        $selectedDepot = $this->getSelectedDepot();

        if (!$selectedDepot instanceof Depot) {
            return $this->getDepots();
        }

        return array_values(array_filter(
            $this->getDepots(),
            fn (Depot $depot): bool => $depot->getId()->toString() !== $selectedDepot->getId()->toString(),
        ));
    }

    /**
     * @return array{
     *     locatorId: string,
     *     markers: array<int, array<string, mixed>>,
     *     selectedDepotId: ?string,
     *     fitBounds: bool,
     *     focusOnSelect: bool
     * }
     */
    public function getMapPayload(bool $fitBounds = true, bool $focusOnSelect = false): array
    {
        return [
            'locatorId' => $this->locatorId,
            'markers' => array_map(
                fn (Depot $depot): array => $this->serializeDepot($depot),
                $this->getDepots(),
            ),
            'selectedDepotId' => $this->selectedDepotId,
            'fitBounds' => $fitBounds,
            'focusOnSelect' => $focusOnSelect,
        ];
    }

    #[LiveAction]
    public function resetCategory(): void
    {
        $this->selectedCategoryId = null;
        $this->selectedCountry = null;
        $this->selectedDepotId = null;
        $this->sidebarOpen = false;

        $this->syncMap(true);
    }

    #[LiveAction]
    public function filterByCategory(#[LiveArg('categoryId')] ?Category $category = null): void
    {
        $this->selectedCategoryId = $category?->getId()->toString();
        $this->selectedCountry = null;
        $this->selectedDepotId = null;
        $this->sidebarOpen = false;

        $this->syncMap(true);
    }

    #[LiveAction]
    public function resetCountry(): void
    {
        $this->selectedCountry = null;
        $this->selectedDepotId = null;
        $this->sidebarOpen = false;

        $this->syncMap(true);
    }

    #[LiveAction]
    public function filterByCountry(#[LiveArg('country')] ?string $country = null): void
    {
        $this->selectedCountry = $country ?: null;
        $this->selectedDepotId = null;
        $this->sidebarOpen = false;

        $this->syncMap(true);
    }

    #[LiveAction]
    public function selectDepot(#[LiveArg('depotId')] ?Depot $depot = null): void
    {
        if (!$depot instanceof Depot || !$this->isDepotVisible($depot)) {
            return;
        }

        $this->selectedDepotId = $depot->getId()->toString();
        $this->sidebarOpen = true;

        $this->syncMap(false, false);
    }

    #[LiveAction]
    public function toggleSidebar(): void
    {
        $this->sidebarOpen = !$this->sidebarOpen;

        $this->syncMap(false, false);
    }

    #[LiveAction]
    public function closeSidebar(): void
    {
        $this->sidebarOpen = false;

        $this->syncMap(false, false);
    }

    #[LiveAction]
    public function clearSelection(): void
    {
        $this->selectedDepotId = null;
        $this->sidebarOpen = false;

        $this->syncMap(false, false);
    }

    private function syncMap(bool $fitBounds, bool $focusOnSelect = false): void
    {
        $this->dispatchBrowserEvent('depot-locator:map-sync', $this->getMapPayload($fitBounds, $focusOnSelect));
    }

    /**
     * @return array<int, Depot>
     */
    private function getCategoryFilteredDepots(): array
    {
        $depots = $this->depotRepository->findAllOrderByCountryOrderByCategoryOrderPosition();

        if (!$this->selectedCategoryId) {
            return $depots;
        }

        return array_values(array_filter(
            $depots,
            fn (Depot $depot): bool => $depot->getCategory()?->getId()->toString() === $this->selectedCategoryId,
        ));
    }

    private function isDepotVisible(Depot $depot): bool
    {
        foreach ($this->getDepots() as $availableDepot) {
            if ($availableDepot->getId()->toString() === $depot->getId()->toString()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return array<string, mixed>
     */
    private function serializeDepot(Depot $depot): array
    {
        $category = $depot->getCategory();
        $description = trim((string) $depot->getDescription());

        return [
            'id' => $depot->getId()->toString(),
            'title' => $depot->getName() ?: $depot->getCity(),
            'name' => $depot->getName(),
            'description' => $description !== '' ? $description : null,
            'address' => $depot->getAddress(),
            'postal' => $depot->getPostal(),
            'city' => $depot->getCity(),
            'country' => $depot->getCountryName() ?: $depot->getCountry(),
            'lat' => $depot->getLat() !== null ? (float) $depot->getLat() : null,
            'lng' => $depot->getLng() !== null ? (float) $depot->getLng() : null,
            'phone' => $depot->getPhone(),
            'email' => $depot->getEmail(),
            'website' => $depot->getWebsite(),
            'category' => $this->serializeCategory($category),
        ];
    }

    /**
     * @return array<string, mixed>|null
     */
    private function serializeCategory(?Category $category): ?array
    {
        if (!$category instanceof Category) {
            return null;
        }

        $serializedCategory = [
            'id' => $category->getId()->toString(),
            'name' => $category->getName(),
            'color' => method_exists($category, 'getColor') ? $category->getColor() : null,
            'icon' => null,
            'iconHtml' => null,
        ];

        $icon = method_exists($category, 'getIcon') ? $category->getIcon() : null;
        if (\is_string($icon) && '' !== trim($icon)) {
            $serializedCategory['icon'] = $icon;
        }

        $image = method_exists($category, 'getImage') ? $category->getImage() : null;
        if (!$image instanceof Image && $icon instanceof Image) {
            $image = $icon;
        }

        if ($image instanceof Image && $image->getPath()) {
            $alt = htmlspecialchars((string) ($image->getTitle() ?: $category->getName()), \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
            $src = htmlspecialchars('/upload/image/'.$image->getPath(), \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');

            $serializedCategory['iconHtml'] = sprintf(
                '<img src="%s" alt="%s" style="display:block; width:18px; height:18px; object-fit:contain;">',
                $src,
                $alt,
            );
        }

        return $serializedCategory;
    }
}
