<?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\CoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity]
#[ORM\Index(columns: ['offer_id', 'end'])]
#[ORM\Index(columns: ['offer_id', 'begin'])]
class OfferDate
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer', options: ['unsigned' => true])]
    private ?int $id = null;

    #[ORM\Column(type: UuidType::NAME, unique: true)]
    private Uuid $uuid;

    /**
     * The two only mutable `datetime` columns left in the bundle: they are bound by
     * the admin date range form, whose DateTimeType hands over a `\DateTime`. The
     * property type keeps the mapping and the form in step — everything else moved
     * to `datetime_immutable`.
     */
    #[ORM\Column(type: 'datetime', nullable: true)]
    #[Groups('api:offer:anon')]
    #[Context(normalizationContext: [DateTimeNormalizer::TIMEZONE_KEY => 'Europe/Berlin'])]
    private ?\DateTime $begin = null;

    #[ORM\Column(type: 'datetime', nullable: true)]
    #[Groups('api:offer:anon')]
    #[Context(normalizationContext: [DateTimeNormalizer::TIMEZONE_KEY => 'Europe/Berlin'])]
    private ?\DateTime $end = null;

    #[ORM\Column(type: 'string', length: 255, nullable: true)]
    private ?string $recurrence = null;

    public function __construct(
        #[ORM\ManyToOne(targetEntity: OfferInterface::class, inversedBy: 'dates')]
        #[ORM\JoinColumn(name: 'offer_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
        private OfferInterface $offer
    ) {
        $this->uuid = Uuid::v7();
    }

    public function getUuid(): Uuid
    {
        return $this->uuid;
    }

    public function getOffer(): OfferInterface
    {
        return $this->offer;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getBegin(): ?\DateTime
    {
        return $this->begin;
    }

    public function getEnd(): ?\DateTime
    {
        return $this->end;
    }

    public function setBegin(?\DateTime $begin): void
    {
        $this->begin = $begin;
    }

    public function setEnd(?\DateTime $end): void
    {
        $this->end = $end;
    }

    public function getRecurrence(): ?string
    {
        return $this->recurrence;
    }

    public function setRecurrence(?string $recurrence): void
    {
        $this->recurrence = $recurrence;
    }

    /**
     * Expand the recurrence into concrete occurrences.
     *
     * Recurr keeps each occurrence's wall-clock time, so pass the view timezone
     * to keep a weekly 17:00 series at 17:00 even after the switch to winter
     * time. When $timezone is null the stored instants are kept as-is (no DST
     * re-anchoring) — enough for instant comparisons, but subject to the
     * 17:00 → 16:00 drift once rendered in a zone with DST.
     *
     * @return array<array{begin: \DateTimeInterface, end: \DateTimeInterface}>
     */
    public function getOccurrences(?\DateTimeZone $timezone = null): array
    {
        if (null === $this->begin || null === $this->end) {
            return [];
        }

        if (null === $this->recurrence) {
            return [['begin' => $this->begin, 'end' => $this->end]];
        }

        $begin = clone $this->begin;
        $end = clone $this->end;
        if (null !== $timezone) {
            $begin->setTimezone($timezone);
            $end->setTimezone($timezone);
        }

        $rule = new \Recurr\Rule($this->recurrence, $begin);
        $transformer = new \Recurr\Transformer\ArrayTransformer();

        $duration = $begin->diff($end);
        $occurrences = [];

        /** @var \Recurr\Occurrence $occurrence */
        foreach ($transformer->transform($rule) as $occurrence) {
            $start = $occurrence->getStart();
            $end = (clone $start)->add($duration);

            $occurrences[] = [
                'begin' => $start,
                'end' => $end,
            ];
        }

        return $occurrences;
    }

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

        if (str_contains($this->recurrence, 'FREQ=DAILY')) {
            return 'jeden Tag';
        }

        if (str_contains($this->recurrence, 'FREQ=WEEKLY')) {
            $parts = explode('BYDAY=', $this->recurrence);
            if (\count($parts) > 1) {
                $after = $parts[1];
                $endParts = explode(';', $after);
                $codes = explode(',', $endParts[0]);

                if (1 === \count($codes)) {
                    $weekdayNames = [
                        'MO' => 'Montag',
                        'TU' => 'Dienstag',
                        'WE' => 'Mittwoch',
                        'TH' => 'Donnerstag',
                        'FR' => 'Freitag',
                        'SA' => 'Samstag',
                        'SU' => 'Sonntag',
                    ];

                    return 'jeden '.($weekdayNames[$codes[0]] ?? $codes[0]);
                }
            }

            return 'wöchentlich';
        }

        return null;
    }

    public function toHumanReadable(): string
    {
        if (!$this->begin instanceof \DateTime || !$this->end instanceof \DateTime) {
            return '';
        }

        $begin = $this->begin->setTimezone(new \DateTimeZone('Europe/Berlin'));
        $end = $this->end->setTimezone(new \DateTimeZone('Europe/Berlin'));

        if ($begin->format('Y-m-d') === $end->format('Y-m-d')) {
            return \sprintf('%s - %s', $begin->format('d.m.Y H:i'), $end->format('H:i'));
        }

        return \sprintf('%s - %s', $begin->format('d.m. H:i'), $end->format('d.m.Y H:i'));
    }
}
