<?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\Export\Offer\ICal;

use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
use Eluceo\iCal\Domain\Entity\Calendar;
use Eluceo\iCal\Domain\Entity\Event;
use Eluceo\iCal\Domain\ValueObject\DateTime;
use Eluceo\iCal\Domain\ValueObject\Location;
use Eluceo\iCal\Domain\ValueObject\TimeSpan;
use Eluceo\iCal\Presentation\Component;
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
use Ferienpass\CoreBundle\Entity\Offer\Base;
use Ferienpass\CoreBundle\Entity\OfferDate;
use Ferienpass\CoreBundle\Export\Offer\OffersExportInterface;

final readonly class ICalExport implements OffersExportInterface
{
    public function __construct(private VirtualFilesystemInterface $exportStorage)
    {
    }

    public static function getName(): string
    {
        return 'default.ics';
    }

    public function generate(iterable $offers, ?string $destination = null, bool $stream = false): mixed
    {
        $content = (string) $this->createICal($offers);

        if ($stream) {
            $stream = fopen('php://temp', 'r+');
            fwrite($stream, $content);
            rewind($stream);

            return $stream;
        }

        $destination ??= \sprintf('%s.ics', uniqid('ics-', true));
        $path = 'ics/'.$destination;

        $this->exportStorage->write($path, $content);

        return $path;
    }

    private function createICal(iterable $offers): Component
    {
        $calendar = new Calendar($this->eventsGenerator($offers));

        return new CalendarFactory()->createCalendar($calendar);
    }

    /**
     * @param iterable<int, Base>
     */
    private function eventsGenerator(iterable $offers): \Generator
    {
        // The "view timezone"
        $tz = new \DateTimeZone('Europe/Berlin');

        foreach ($offers as $offer) {
            /** @var OfferDate $date */
            foreach ($offer->getDates() as $date) {
                $begin = $date->getBegin() ?? $date->getOffer()->getEdition()?->getHoliday()?->getPeriodBegin();
                $end = $date->getEnd() ?? $date->getOffer()->getEdition()?->getHoliday()?->getPeriodEnd();

                if (null === $date->getBegin() || null === $date->getEnd()) {
                    continue;
                }

                yield new Event()
                    ->setSummary($offer->getName())
                    ->setDescription((string) $offer->getDescription())
                    ->setLocation(new Location((string) $offer->getMeetingPoint()))
                    ->setOccurrence(new TimeSpan(
                        // Setting the timezone will not change the underlying point-in-time.
                        // But showing "UTC" to the user might be confusing.
                        new DateTime($begin->setTimezone($tz), true),
                        new DateTime($end->setTimezone($tz), true)
                    ))
                ;
            }
        }
    }
}
