<?php

namespace Planaday\Entities\Booking\Form\Students;

use DateTime;
use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Entities\Booking\BookingController;
use Planaday\Entities\Booking\DTO\Address;
use Planaday\Entities\Booking\DTO\BookingStudent;
use Planaday\Entities\Booking\DTO\ExtraField;
use Planaday\Entities\Booking\Form\Address\AddressFormFactory;
use Planaday\Entities\Course\DTO\Course;
use Planaday\Platform\PlatformAbstraction\LocalizationController;
use Planaday\Utility\DataGrouping\Collection;
use Planaday\Utility\DependencyInjection\Attributes\Scoped;
use Planaday\Utility\FormBuilder\DataTypes\ArrayType;
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\InputFields\SelectionInput;
use Planaday\Utility\FormBuilder\InputFields\StringInput;
use Planaday\Utility\FormBuilder\LayoutItem;
use Planaday\Utility\FormBuilder\LayoutItems\Header;
use Planaday\Utility\FormBuilder\LayoutItems\Text;
use Planaday\Utility\FormBuilder\Structures\BasicGroup;
use Planaday\Utility\FormBuilder\Validators\RegexValidator;
use Planaday\Utility\FormBuilder\Validators\Required;
use Planaday\Utility\RegexUtility;

#[Scoped]
class StudentFormFactory {
	public function __construct(
		protected LocalizationController $localizationController,
		protected AddressFormFactory $addressFormFactory,
		protected BookingController $bookingController,
		protected StudentSettings $settings,
	) {}

	/**
	 * @param BookingStudent $bookingStudent
	 * @param Course $course
	 *
	 * @return BasicGroup
	 */
	public function getFormForObject(BookingStudent $bookingStudent, Course $course): BasicGroup{
		$t = function (string $text) {
			return $this->localizationController->translate($text);
		};

		$outputGroup = (new BasicGroup());

		$callingNameRequired = $this->settings->isNickNameRequired();
		$callingName = (new StringInput())
			->setIndex(StudentFieldsEnum::NickName->value)
			->setName($t("Roepnaam") . ($callingNameRequired ? " *" : ""))
			->setDescription($t("Voer hier de roepnaam van de cursist in"))
			->setDefaultValue("")
			->setPlaceholder($t("Roepnaam"))
			->setAutocomplete("nickname")
			->addSetCallback(function ($value) use ($bookingStudent) {
				$bookingStudent->setNickName($value);
			});

		if ($callingNameRequired) $callingName->addValidator(
			new Required($t("Roepnaam is verplicht"))
		);

		$maidenNameRequired = $this->settings->isMaidenNameRequired();
		$maidenName = (new StringInput())
			->setIndex(StudentFieldsEnum::MaidenName->value)
			->setName($t("Meisjesnaam") . ($maidenNameRequired ? " *" : ""))
	        ->setDescription($t("Voer hier de meisjesnaam van de cursist in"))
	        ->setDefaultValue("")
	        ->setPlaceholder($t("Meisjesnaam"))
	        ->addSetCallback(function ($value) use ($bookingStudent) {
	            $bookingStudent->setMaidenName($value);
	        });

		if ($maidenNameRequired) $maidenName->addValidator(
			new Required($t("Meisjesnaam is verplicht"))
		);


		$outputGroup->addContents(
			( new BasicGroup() )
				->addContents(
					...($this->settings->isAskGender() ? [
						(new SelectionInput(new StringType()))
							->setIndex(StudentFieldsEnum::Gender->value)
							->setName($t("Geslacht") . " *")
							->setDescription($t("Wat is het geslacht van deze deelnemer?"))
							->setDefaultValue("")
							->setOptions([
								["value" => "", "label" => $t("Selecteer een geslacht")],
								["value" => "m", "label" => $t("Dhr")],
								["value" => "f", "label" => $t("Mevr")],
								["value" => "nvt", "label" => $t("Nvt")],
							])
							->addSetCallback(function ($value) use ($bookingStudent) {
								$bookingStudent->setGender($value);
							})
							->addValidator(
								...($this->settings->isGenderRequired() ? [
									new Required($t("Geslacht is verplicht"))
								] : [])
							),
					] : []),
					...($this->settings->isAskInitials() ? [
						( new StringInput() )
							->setIndex(StudentFieldsEnum::Initials->value)
							->setName($t("Initialen"))
							->setDescription($t("Voer hier de initialen van de cursist in"))
							->setDefaultValue("")
							->setPlaceholder($t("Initialen"))
							->addSetCallback(function ($value) use ($bookingStudent) {
								$bookingStudent->setInitials($value);
							})
							->addValidator(
								...($this->settings->isInitialsRequired() ? [
									new Required($t("Initialen is verplicht"))
								] : [])
							),
				] : []),
					...([
						( new StringInput() )
							->setIndex(StudentFieldsEnum::FirstName->value)
							->setName($t("Voornaam") . " *")
							->setDescription($t("Voer hier de voornaam van de cursist in"))
							->setDefaultValue("")
							->setPlaceholder($t("Voornaam"))
							->setAutocomplete("given-name")
							->addSetCallback(function ($value) use ($bookingStudent) {
								$bookingStudent->setFirstName($value);
							})
							->addValidator(
								new Required($t("Voornaam is verplicht"))
							),
						( new StringInput() )
							->setIndex(StudentFieldsEnum::Prefix->value)
							->setName($t("Tussenvoegsel"))
							->setDescription($t("Voer hier het tussenvoegsel van de cursist in"))
							->setDefaultValue("")
							->setPlaceholder($t("Tussenvoegsel"))
							->addSetCallback(function ($value) use ($bookingStudent) {
								$bookingStudent->setPrefix($value);
							}),
						( new StringInput() )
							->setIndex(StudentFieldsEnum::LastName->value)
							->setName($t("Achternaam") . " *")
							->setDescription($t("Voer hier de achternaam van de cursist in"))
							->setDefaultValue("")
							->setPlaceholder($t("Achternaam"))
							->setAutocomplete("family-name")
							->addSetCallback(function ($value) use ($bookingStudent) {
								$bookingStudent->setLastName($value);
							})
							->addValidator(
								new Required($t("Achternaam is verplicht"))
							),
					]),
					...($this->settings->isAskNickName() ? [$callingName]:[]),
					...($this->settings->isAskMaidenName() ? [$maidenName]:[])
				)->addClasses("pad-full-name"),
		);

		if ($this->settings->isAskAddress()){
			$address = $bookingStudent->getAddress() ?? new Address();
			$bookingStudent->setAddress($address);
			$outputGroup->addContents(
				(new BasicGroup())
				->setIndex(StudentFieldsEnum::Address->value)
				->addContents(
					$this->addressFormFactory->getFormForObject($address, $this->settings->isAddressRequired())
				)
			);
		}

		$outputGroup->addContents(
			(new StringInput())
				->setIndex(StudentFieldsEnum::Email->value)
				->setName($t("Email") . " *")
				->setDescription($t("Voer hier het Email adres van de cursist in"))
				->setDefaultValue("")
				->setPlaceholder($t("Email"))
				->setAutocomplete("email")
				->addSetCallback(function ($value) use ($bookingStudent) {
					$bookingStudent->setEmail($value ?? "");
				})
				->addValidator(
					new Required($t("Telefoonnummer is verplicht")),
					new RegexValidator("email", RegexUtility::SIMPLE_EMAIL_REGEX, $t("Email is niet geldig"))
				)
		);

		if ($this->settings->isAskPhoneNumber()){
			$isRequired = $this->settings->isPhoneNumberRequired();
			$outputGroup->addContents(
				(new StringInput())
					->setIndex(StudentFieldsEnum::PhoneNumber->value)
					->setName($t("Telefoonnummer") . ($isRequired ? " *" : ""))
					->setDescription($t("Voer hier het telefoonnummer van de cursist in"))
					->setDefaultValue("")
					->setPlaceholder($t("Telefoonnummer"))
					->setAutocomplete("tel")
					->addSetCallback(function ($value) use ($bookingStudent) {
						$bookingStudent->setPhoneNumber($value);
					})
					->addValidator(
						...($isRequired ? [new Required($t("Telefoonnummer is verplicht"))]:[])
					)
			);
		}

		if ($this->settings->isAskDateOfBirth()){
			$isRequired = $this->settings->isDateOfBirthRequired();
			$outputGroup->addContents(
				(new StringInput())
					->setIndex(StudentFieldsEnum::BirthDate->value)
					->setName($t("Geboortedatum") . ($isRequired ? " *" : ""))
					->setDescription($t("Voer hier de geboortedatum van de cursist in"))
					->setDefaultValue("")
					->setPlaceholder($t("dd-mm-jjjj of jjjj-mm-dd"))
					->setAutocomplete("bday")
					->addSetCallback(function ($value) use ($bookingStudent) {
						$bookingStudent->setDateOfBirth(new DateTime($value));
					})
					->addValidator(
						new RegexValidator('date', RegexUtility::EUROPEAN_DATE_REGEX, $t("Datum is niet geldig")),
						...($isRequired ? [new Required($t("Geboortedatum is verplicht"))]:[]),
					)
			);
		}

		if ($this->settings->isAskPlaceOfBirth()){
			$isRequired = $this->settings->isPlaceOfBirthRequired();
			$outputGroup->addContents(
				(new StringInput())
					->setIndex(StudentFieldsEnum::BirthPlace->value)
					->setName($t("Geboorteplaats") . ($isRequired ? " *" : ""))
					->setDescription($t("Voer hier de geboorteplaats van de cursist in"))
					->setDefaultValue("")
					->setPlaceholder($t("Geboorteplaats"))
					->addSetCallback(function ($value) use ($bookingStudent) {
						$bookingStudent->setPlaceOfBirth($value);
					})
					->addValidator(
						...($isRequired ? [new Required($t("Geboorteplaats is verplicht"))]:[])
					)
			);
		}

		if ($this->settings->isAskCompanyPosition()){
			$isRequired = $this->settings->isCompanyPositionRequired();
			$outputGroup->addContents(
				(new StringInput())
					->setIndex(StudentFieldsEnum::CompanyPosition->value)
					->setName($t("Functie") . ($isRequired ? " *" : ""))
					->setDescription($t("Voer hier de functie van de cursist in"))
					->setDefaultValue("")
					->setPlaceholder($t("Functie"))
					->setAutocomplete("organization-title")
					->addSetCallback(function ($value) use ($bookingStudent) {
						$bookingStudent->setCompanyPosition($value);
					})
					->addValidator(
						...($isRequired ? [new Required($t("Functie is verplicht"))]:[])
					)
			);
		}

		if ($this->settings->isAskCostCenterCode()){
			$isRequired = $this->settings->isCostCenterCodeRequired();
			$cCCName = $this->settings->getCostCenterCodeName();
			$outputGroup->addContents(
				(new BasicGroup())
					->addContents(
						(new StringInput())
							->setIndex(StudentFieldsEnum::CostCenterCode->value)
							->setName($cCCName . ($isRequired ? " *" : ""))
							->setDescription(sprintf($t("Voer hier de %s van de cursist in"), $cCCName))
							->setDefaultValue("")
							->setPlaceholder($cCCName)
							->addSetCallback(function ($value) use ($bookingStudent) {
								$bookingStudent->setCostCenterCode($value);
							})
							->addValidator(
								...($isRequired ? [new Required($t("Kostenplaats is verplicht"))]:[])
							),
						(new Text(sprintf($t("Let op: deze %s moet bekend zijn bij de opleider"), $cCCName)))
					)
			);
		}

		if ($this->settings->isAskEmployeeNumber()){
			$isRequired = $this->settings->isEmployeeNumberRequired();
			$outputGroup->addContents(
				(new StringInput())
					->setIndex(StudentFieldsEnum::EmployeeNumber->value)
					->setName($t("Personeelsnummer") . ($isRequired ? " *" : ""))
					->setDescription($t("Voer hier het personeelsnummer van de cursist in"))
					->setDefaultValue("")
					->setPlaceholder($t("Personeelsnummer"))
					->addSetCallback(function ($value) use ($bookingStudent) {
						$bookingStudent->setEmployeeNumber($value);
					})
					->addValidator(
						...($isRequired ? [new Required($t("Personeelsnummer is verplicht"))]:[])
					)
			);
		}

		if ($this->settings->isAskInternalReference()){
			$isRequired = $this->settings->isInternalReferenceRequired();
			$outputGroup->addContents(
				(new StringInput())
					->setIndex(StudentFieldsEnum::InternalReference->value)
					->setName($t("Interne referentie") . ($isRequired ? " *" : ""))
					->setDescription($t("Voer hier de interne referentie van de cursist in"))
					->setDefaultValue("")
					->setPlaceholder($t("Interne referentie"))
					->addSetCallback(function ($value) use ($bookingStudent) {
						$bookingStudent->setInternalReference($value);
					})
					->addValidator(
						...($isRequired ? [new Required($t("Interne referentie is verplicht"))]:[])
					)
			);
		}

		if($this->settings->isAskRemark()) $outputGroup->addContents(
			(new StringInput())
				->setIndex(StudentFieldsEnum::AdditionalRemark->value)
				->setName($t("Aanvullende opmerking"))
				->setDescription($t("Voer hier een aanvullende opmerking van de cursist in"))
				->setDefaultValue("")
				->setPlaceholder($t("Aanvullende opmerking"))
				->addSetCallback(function ($value) use ($bookingStudent) {
					$bookingStudent->setRemark($value);
				})
		);

		if($this->settings->isAskCode95() && $course->isHasCode95()){
			$outputGroup->addContents(
				(new BoolInput())
					->setIndex(StudentFieldsEnum::Code95->value)
					->setName($t("Code 95"))
					->setDescription($this->settings->getCode95Text())
					->setDefaultValue(false)
					->addSetCallback(function ($value) use ($bookingStudent) {
						$bookingStudent->setProcessCode95($value ?? false);
					})
			);
		}

		if($this->settings->isAskSoob() && $course->isHasSoob()){
			$outputGroup->addContents(
				(new BoolInput())
				->setIndex(StudentFieldsEnum::Soob->value)
				->setName($t("SOOB"))
				->setDescription($this->settings->getSoobText())
				->setDefaultValue(false)
				->addSetCallback(function ($value) use ($bookingStudent) {
					$bookingStudent->setProcessSoob($value ?? false);
				})
			);
		}

		try{
			$extraFields = $this->bookingController->getExtraFields();
			if (count($extraFields) > 0){
				$bookingStudent->setExtraFields(new Collection());
				$outputGroup->addContents(
					(new BasicGroup())
						->setIndex(StudentFieldsEnum::ExtraFields->value)
						->addContents(
							(new Header($t("Extra persoonsvragen"), 6)),
							...array_map(function (ExtraField $extraField) use ($bookingStudent){
								return $this->getFormForExtraField($bookingStudent, $extraField);
							}, [...$extraFields])
						)
				);
			}
		} catch (InvalidResponseException){}

		return $outputGroup->addClasses("pad-student");
	}

	protected function getFormForExtraField(BookingStudent $bookingStudent, ExtraField $extraField): LayoutItem{
		$input = match ($extraField->getType()){
			'textfield' => (new StringInput()),
			'checkbox' => (new MultiSelectInput(new ArrayType(new StringType()))),
			'selectbox' => (new SelectionInput(new StringType())),
			'radiobutton', 'radio' => (new RadioInput(new StringType())),
			default => (new StringInput())
		};

		$input->setName($extraField->getName() . ($extraField->isRequired() ? " *" : ""))
			->setIndex($extraField->getId())
			->setDescription($extraField->getDescription())
			->setDefaultValue(
				is_array($extraField->getDefaultValue()) ?
					array_map(function ($value) {return "" . $value;}, $extraField->getDefaultValue()):
					"" . $extraField->getDefaultValue()
			)->addSetCallback(function ($value) use ($bookingStudent, $extraField) {
				$updatedValue = $bookingStudent->getExtraFields();
				$updatedValue[$extraField->getId()] = $value;
				$bookingStudent->setExtraFields($updatedValue);
			});

		if ($input instanceof MultiSelectInput || $input instanceof RadioInput){
			$input->setOptions(array_map(function ($value) {return "" . $value;}, $extraField->getOptions()));
		}

		if ($extraField->isRequired()){
			$input->addValidator(new Required($extraField->getName() . " is verplicht"));
		}

		return $input;
	}
}