<?php

namespace Planaday\Entities\Booking\Form;

use ArrayAccess;
use Countable;
use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Config\Course\General\GeneralSettings;
use Planaday\Config\Payment\PaymentSettings;
use Planaday\Entities\Booking\BookingController;
use Planaday\Entities\Booking\DTO\BookingCompany;
use Planaday\Entities\Booking\DTO\BookingForm;
use Planaday\Entities\Booking\DTO\BookingStudent;
use Planaday\Entities\Booking\Form\Company\CompanyFormFactory;
use Planaday\Entities\Booking\Form\Company\CompanySettings;
use Planaday\Entities\Booking\Form\DayParts\DayPartsFormFactory;
use Planaday\Entities\Booking\Form\Materials\MaterialFormFactory;
use Planaday\Entities\Booking\Form\Materials\MaterialSettings;
use Planaday\Entities\Booking\Form\Students\StudentFormFactory;
use Planaday\Entities\Booking\Form\Voucher\VoucherFormFactory;
use Planaday\Entities\Course\CourseController;
use Planaday\Entities\Course\DTO\ApiAttribute;
use Planaday\Entities\Course\DTO\ApiAttributeValue;
use Planaday\Entities\Course\DTO\DayPartDisplayTypes;
use Planaday\Entities\Course\Exception\CourseNotFoundException;
use Planaday\Platform\PlatformAbstraction\LocalizationController;
use Planaday\Utility\DataGrouping\Collection;
use Planaday\Utility\DataGrouping\Map;
use Planaday\Utility\DependencyInjection\Attributes\Scoped;
use Planaday\Utility\FormBuilder\Conditions\InputValueCondition;
use Planaday\Utility\FormBuilder\DataTypes\ArrayType;
use Planaday\Utility\FormBuilder\DataTypes\BoolType;
use Planaday\Utility\FormBuilder\DataTypes\StringType;
use Planaday\Utility\FormBuilder\InputFields\BoolInput;
use Planaday\Utility\FormBuilder\InputFields\MultiSelectInput;
use Planaday\Utility\FormBuilder\InputFields\RadioInput;
use Planaday\Utility\FormBuilder\LayoutItems\Header;
use Planaday\Utility\FormBuilder\LayoutItems\Html;
use Planaday\Utility\FormBuilder\Structures\BasicGroup;
use Planaday\Utility\FormBuilder\Structures\RepeatingGroup;
use Planaday\Utility\FormBuilder\Validators\Required;
use Traversable;

#[Scoped]
class BookingFormFactory {
	public function __construct(
		protected BookingFormSettings $settings,
		protected MaterialSettings $materialSettings,
		protected CompanySettings $companySettings,
		protected LocalizationController $localizationController,
		protected CompanyFormFactory $companyFormFactory,
		protected StudentFormFactory $studentsFormFactory,
		protected MaterialFormFactory $materialFormFactory,
		protected DayPartsFormFactory $dayPartsFormFactory,
		protected CourseController $courseController,
		protected BookingController $bookingController,
		protected VoucherFormFactory $voucherFormFactory,
		protected GeneralSettings $generalSettings,
		protected PaymentSettings $paymentSettings
	) {}

	/**
	 * @param BookingForm $bookingForm
	 *
	 * @return BasicGroup
	 * @throws InvalidResponseException
	 * @throws CourseNotFoundException
	 */
	public function getFormForObject(BookingForm $bookingForm): BasicGroup{
		$t = function (string $text) {
			return $this->localizationController->translate($text);
		};
		$course = $this->courseController->getCourse($bookingForm->getCourseId(), true);

		$outputGroup = (new BasicGroup())
			->addContents(
				new Header($this->settings->getBookingFormTitle()),
			);

		if ($course->getDayPartDisplayType() !== DayPartDisplayTypes::AllSelectedImmutable){
			$outputGroup->addContents(
				$this->dayPartsFormFactory->getFormForObject($bookingForm, $course)
			);
		} else {
			$bookingForm->setDayParts( Collection::fromArray($course->getDayPartIds()) );
		}

		if ($this->materialSettings->isAskMaterialsBookingForm())
			$outputGroup->addContents(
				(new BasicGroup())
					->setIndex( BookingFieldsEnum::Materials->value )
					->addContents(
						$this->materialFormFactory->getFormForObject($bookingForm),
					)
			);

		if ($this->settings->getCompanyId() != 0){
			$bookingForm->setCompanyId($this->settings->getCompanyId());
		} elseif ($this->companySettings->isAllowCompanies()){
			$company = $bookingForm->getCompany() ?? new BookingCompany();
			$bookingForm->setCompany($company);

			$outputGroup->addContents(
				(new BasicGroup())
					->setIndex( BookingFieldsEnum::Company->value )
					->addContents(
						(new Header($t("Bedrijf"), 4)),
						$companyToggle = ( new RadioInput( new BoolType() ) )
							->setIndex( "toggle" )
							->setName( $t( "Inschrijven als bedrijf of particulier?" ) )
							->setDescription( $t("Schrijft u als bedrijf of particulier in?") )
							->setOptions([
								["value" => true, "label" => $t("Bedrijf")],
								["value" => false, "label" => $t("Particulier")],
							])
							->setDefaultValue($this->companySettings->isAsCompanyDefault())
							->addSetCallback(function (bool|null $value) use ($bookingForm, $company) {
								$bookingForm->setCompany(($value ?? false) ? $company : null );
							}),
						( new BasicGroup() )
							->addContents(
								$this->companyFormFactory->getFormForObject($company),
							)->addConditions(
								( new InputValueCondition( $companyToggle ) )->equals()->value( true)
							)
					)
			);
		}

		if (isset($companyToggle)){
			$outputGroup->addContents(
				(new RepeatingGroup())
					->setIndex( BookingFieldsEnum::Students->value )
					->setAddButtonText($t("Voeg deelnemer toe"))
					->withRemoveButton()
					->setRemoveButtonText($t("Verwijder onderstaande deelnemer"))
					->addContents(
						( new Header($t("Deelnemers"), 4) )->addConditions(( new InputValueCondition( $companyToggle ) )->equals()->value( true)),
						( new Header($t("Persoonsgegevens"), 4) )->addConditions(( new InputValueCondition( $companyToggle ) )->equals()->value( false))
					)
					->atLeastOne()
					->setMaxCount(
						$course->getUsersData()->getAvailableSpots($this->generalSettings->isIncludeOptionInCount())
					)->setRepeatingContents(function () use ($bookingForm, $course, $t, $companyToggle) {
						$students = $bookingForm->getStudents();
						$newStudent = new BookingStudent();
						$students[] = $newStudent;
						$bookingForm->setStudents($students);

						return (new BasicGroup())->addContents(
							(new Header($t("Persoonsgegevens"), 4))->addConditions(( new InputValueCondition( $companyToggle ) )->equals()->value( true)),
							$this->studentsFormFactory->getFormForObject($newStudent, $course)
						);
					})->addRepeatingConditions(
						( new InputValueCondition( $companyToggle ) )->equals()->value( true )
					)
			);
		} else {
			$newStudent = new BookingStudent();
			$bookingForm->setStudents(new Collection($newStudent));

			$outputGroup->addContents(
				( new BasicGroup() )
					->setIndex( BookingFieldsEnum::Students->value )
					->addContents(
						new Header($t("Persoonsgegevens"), 4),
						( new BasicGroup() )
							->setIndex( "0" )
							->addContents(
								$this->studentsFormFactory->getFormForObject($newStudent, $course),
							)
					)
			);
		}

		if (
			$this->settings->isAskApiAttributes() &&
		    count($course->getAttributes()) > 0
		) $outputGroup->addContents(
			$this->getApiAttributesFormItem($bookingForm, Map::fromArray($course->getAttributes())),
		);

		if ($this->settings->isMustAcceptTermsAndConditions())
			$outputGroup->addContents(
				(new BoolInput())
					->setIndex( BookingFieldsEnum::TermsAndConditions->value )
					->setName($t("Ik ga akkoord met de algemene voorwaarden"))
					->setDefaultValue(false)
					->addSetCallback(function (bool|null $value) use ($bookingForm) {
						$bookingForm->setTermsAndConditions($value ?? false);
					})
				->addValidator( (new Required( $t( "Algemene voorwaarden moeten geaccepteerd worden" )))->zeroIsInvalid() ),
				(new Html("<div>" . sprintf($t("De algemene voorwaarden zijn %s te vinden. "), "<a href='" . $this->settings->getTermsAndConditionsUrl() . "'>" . $t("Hier") . "</a>") . "</div>")),
			);

		if ($this->settings->isAskVoucher())
			$outputGroup->addContents(
				$this->voucherFormFactory->getFormForVoucher($bookingForm, $course)
			);

		if (
			$this->paymentSettings->isPaymentActive() &&
			$this->paymentSettings->isAllowCompanyInvoice() &&
			$this->settings->isGivePaymentOptionsCompany()
		) $outputGroup->addContents(
			( new BasicGroup() )
				->addContents(
					(new RadioInput( new BoolType() ))
						->setIndex( BookingFieldsEnum::PayWithInvoice->value )
						->setName($t("Hoe wilt u betalen?"))
						->setDefaultValue(false)
						->setOptions([
							["value" => true, "label" => $t("Achteraf met een factuur")],
							["value" => false, "label" => $t("Onmiddellijk met iDeal")],
						])
						->addSetCallback(function (bool|null $value) use ($bookingForm) {
							$bookingForm->setPayWithInvoice($value ?? false);
						})
				)->addConditions(
					...(isset($companyToggle) ? [
						( new InputValueCondition( $companyToggle ) )->equals()->value( true)
					] : [])
				)
		);

		return $outputGroup;
	}

	/**
	 * @param BookingForm $bookingForm
	 * @param ArrayAccess<array-key, ApiAttribute>&Traversable<array-key, ApiAttribute>&Countable $attributes
	 *
	 * @return BasicGroup
	 */
	protected function getApiAttributesFormItem(BookingForm $bookingForm, ArrayAccess&Traversable&Countable $attributes): BasicGroup{
		$bookingForm->setAttributes(new Map());
		$attributeQuestions = (new BasicGroup())
			->setIndex(BookingFieldsEnum::ApiAttributes->value);
		/** @var ApiAttribute $attribute */
		foreach ($attributes as $attribute) {
			if (!$attribute->isForBooking()) continue;
			if ($attribute->isMultipleValuesAllowed()){
				$input = ( new MultiSelectInput(new ArrayType(new StringType())));
			} else {
				$input = ( new RadioInput(new StringType()));
			}

			$input->setIndex($attribute->getCode())
				->setName($attribute->getName())
				->setDescription($attribute->getDescription())
				->setOptions(
					array_map(function (ApiAttributeValue $value) {
						return [
							"value" => $value->getName(),
							"label" => $value->getName(),
							...(!empty($value->getDescription()) ? [ "description" => $value->getDescription()] : [])
						];
					}, $attribute->getValues() )
				)
				->addSetCallback(function (array|string|null $value) use ($bookingForm, $attribute) {
					$updatedValues = $bookingForm->getAttributes();
					$updatedValues[$attribute->getCode()] = $value;
					$bookingForm->setAttributes($updatedValues);
				});

			if ($attribute->isRequired()) $input->addValidator(
				new Required( $this->localizationController->translate("Deze vraag is verplicht") )
			);

			$attributeQuestions->addContents(
				$input
			);
		}

		return (new BasicGroup())
			->addContents(
				new Header($this->localizationController->translate("Extra vragen"), 4),
				$attributeQuestions,
			);
	}
}