<?php

namespace Planaday\Html\NodeTags\Course\ListShortcode;


use ArrayAccess;
use Countable;
use DOMNode;
use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Config\Course\General\GeneralSettings;
use Planaday\Config\FeatureToggles\FeatureToggleSettings;
use Planaday\Entities\Course\CourseController;
use Planaday\Entities\Course\CourseUtility;
use Planaday\Entities\Course\DTO\Course;
use Planaday\Entities\Course\DTO\CourseSearch;
use Planaday\Entities\Course\PostRequest\CourseSearchFieldEnum;
use Planaday\Entities\PostEntities;
use Planaday\Html\Nodes\NodeCouldNotBeCreatedException;
use Planaday\Html\Nodes\NodeData;
use Planaday\Html\Nodes\ParserNode;
use Planaday\Html\NodeTags\Feature;
use Planaday\Html\NodeTags\Tag;
use Planaday\Html\NodeTags\TagCore;
use Planaday\Html\NodeTags\TagNotFullyInitializedException;
use Planaday\Html\Parsers\NodeParsers\TagMarkers\TestDataTag;
use Planaday\Platform\PlatformAbstraction\DataHandler;
use Planaday\Utility\DataGrouping\Collection;
use Planaday\Utility\DataGrouping\LazyCollection;
use Planaday\Utility\DataGrouping\LazyFetchModifiable;
use Planaday\Utility\DependencyInjection\Attributes\Transient;
use Traversable;

#[Transient]
class ListShortcode implements Tag, Feature, TestDataTag {
	use TagCore;

	const FILTER_ATTRIBUTE = "filter";
	const SEARCH_ATTRIBUTE = "search";

	public function __construct(
		protected CourseController $courseController,
		protected DataHandler $dataHandler,
		protected CourseUtility $courseUtility,
		protected GeneralSettings $generalSettings,
		protected CourseListShortcodeSettings $courseListSettings,
	) {
		$this->config = [
			"noCourseText"    => $this->courseListSettings->getNoCoursesText(...),
			"noSearchText"    => $this->courseListSettings->getNoSearchText(...),
			"noResultsText"   => $this->courseListSettings->getNoResultsText(...),
		];
	}

	/**
	 * @throws TagNotFullyInitializedException
	 * @throws NodeCouldNotBeCreatedException
	 */
	public function getNewNode(): DOMNode&ParserNode {
		if ( !isset( $this->originalNode ) ){
			throw new TagNotFullyInitializedException("The OriginalNode is not set");
		}

		$nodeData = $this->nodeData ?? new NodeData();

		$this->prepareData($nodeData);

		$node = $this->originalNode->getNodeFromTemplate($this->getTemplate('', 'index'));
		$node->setExtendedNodeData( $nodeData );
		return $node;
	}

	protected function prepareData( NodeData $nodeData ): void {
		$nodeData->data["template"] = $this->attributes["pad-template"] ?? null;

		if (!isset($this->attributes[self::SEARCH_ATTRIBUTE])) {
			$this->attributes["start"] ??= ( "+" . $this->courseListSettings->getStart() . "months");
			$this->attributes["end"] ??= ( "+" . $this->courseListSettings->getEnd() . "months");
			$this->attributes["label"] ??= $this->courseListSettings->getLabels();
			$this->attributes["templateid"] ??= $this->courseListSettings->getTemplateId();
			$this->attributes["cities"] ??= $this->courseListSettings->getCities();
			if ($this->courseListSettings->getShowInCompanyCourses() > 0 )
				$this->attributes["company_id"] ??= $this->courseListSettings->getCompanies();
		}

		if (!(isset($this->attributes[self::SEARCH_ATTRIBUTE]) || isset($this->attributes[self::FILTER_ATTRIBUTE]))) {
			switch ($this->courseListSettings->getSearchBehaviour()){
				case 1:
					$this->attributes[self::FILTER_ATTRIBUTE] = true;
					break;
				case 2:
					$this->attributes[self::SEARCH_ATTRIBUTE] = true;
					break;
			}
		}

		if (isset($this->attributes[self::SEARCH_ATTRIBUTE])) {
			$nodeData->data["searchBehaviour"] = 2;
		} elseif (isset($this->attributes[self::FILTER_ATTRIBUTE])) {
			$nodeData->data["searchBehaviour"] = 1;
		} else {
			$nodeData->data["searchBehaviour"] = 0;
		}

		$this->attributes["showFull"] = $this->attributeIsTruthy("show-full") ?? $this->courseListSettings->isIncludeFullCourses();
		$this->attributes["showPast"] = $this->attributeIsTruthy("show-past") ?? $this->courseListSettings->isIncludePartiallyPassedCourses();

		$nodeData->data["courses"] ??= new LazyCollection(function () {return $this->getCourseListArray($this->attributes);});
	}

	/**
	 * @param array $attributes
	 * @param int $limit number of items to fetch (-1 for no limit)
	 *
	 * @return ArrayAccess<int|string, Course>&Traversable<int|string, Course>&Countable
	 */
	protected function getCourseListArray(array $attributes, int $limit = -1): ArrayAccess&Traversable&Countable {
		try {
			$allSearchArgs = $this->getAllSearchArgs(
				$this->getPostSearchArgs($attributes),
				$this->getAttributeSearchArgs($attributes),
				isset($attributes[self::SEARCH_ATTRIBUTE])
			);

			$this->logInfo("Search arguments: " . json_encode($allSearchArgs));

			$courses = $this->getCourseList($allSearchArgs, isset($attributes[self::SEARCH_ATTRIBUTE]));

			$coursesArray = [];
			foreach ($courses as $course){
				if ($limit === 0) break;
				$limit--;
				$coursesArray[] = $course;
			}

			return Collection::fromArray($coursesArray);
		} catch (InvalidResponseException){
			return new Collection();
		}
	}

	/**
	 * @return string[]
	 */
	protected function getAttributeSearchArgs($attributes):array {
		$searchArguments = [];

		foreach ($attributes as $field => $value){
			$constName = strtoupper($field);
			$fieldName = CourseSearchFieldEnum::class::get($constName);

			if ($field == 'start') $fieldName = CourseSearchFieldEnum::DATE_MIN;
			if ($field == 'end') $fieldName = CourseSearchFieldEnum::DATE_MAX;
			if ($field == 'label' && !empty($value)) $fieldName = CourseSearchFieldEnum::LABELS;
			if ($field == 'templateid' && !empty($value)) $fieldName = CourseSearchFieldEnum::TEMPLATE_IDS;
			if ($field == 'company_id' && !empty($value)) $fieldName = CourseSearchFieldEnum::COMPANIES;
			if ($field == 'cities' && !empty($value)) $fieldName = CourseSearchFieldEnum::CITIES;

			if ($fieldName !== null){
				$searchArguments[$fieldName] = $value;
			}
		}

		return $searchArguments;
	}

	/**
	 * @return string[]
	 */
	protected function getPostSearchArgs($attributes):array {
		$searchArguments = [];

		if (isset($attributes[self::FILTER_ATTRIBUTE]) || isset($attributes[self::SEARCH_ATTRIBUTE])){
			$postData = $this->dataHandler->getPostField(PostEntities::SEARCH_FORM);
			$searchableFields = CourseSearchFieldEnum::getEnumArray();

			if (is_array($postData)){
				$this->nodeData->data["hasSearch"] = true;
				foreach ($postData as $postField => $value){
					if(in_array($postField, $searchableFields)){
						$searchArguments[$postField] = $value;
					}
				}
			}
		}

		return $searchArguments;
	}

	/**
	 * @param string[] $postSearchArgs
	 * @param string[] $attrSearchArgs
	 *
	 * @return string[]
	 */
	protected function getAllSearchArgs(array $postSearchArgs, array $attrSearchArgs, bool $isSearch):array {
		$secondaryArray = $isSearch ? $attrSearchArgs : $postSearchArgs;
		$primaryArray = $isSearch ? $postSearchArgs : $attrSearchArgs;

		return array_merge($secondaryArray, $primaryArray);
	}

	/**
	 * @param string[] $searchArguments
	 *
	 * @return ArrayAccess<int|string, Course>&Traversable<int|string, Course>&Countable
	 * @throws InvalidResponseException
	 */
	protected function getCourseList(array $searchArguments, bool $isSearch):ArrayAccess&Traversable&Countable {
		if (count($searchArguments) > 0){
			$courses = $this->courseController->getCoursesBySearch(new CourseSearch($searchArguments));
		} else {
			$courses = [];
		}

		if (!$isSearch){
			$courses = $this->filterList($courses);
		}

		return $courses;
	}

	/**
	 * @param ArrayAccess<int|string, Course>&Traversable<int|string, Course>&Countable $courses
	 *
	 * @return ArrayAccess<int|string, Course>&Traversable<int|string, Course>&Countable
	 */
	protected function filterList(ArrayAccess&Traversable&Countable $courses):ArrayAccess&Traversable&Countable {
		$includeOptionsAvailability = $this->generalSettings->isIncludeOptionInCount();
		$skipELearningOnly = $this->generalSettings->isSkipELearningOnly();
		$includeFull = $this->attributeIsTruthy("showFull") ?? $this->courseListSettings->isIncludeFullCourses();
		$includePast = $this->attributeIsTruthy("showPast") ?? $this->courseListSettings->isIncludeFullCourses();

		if ($courses instanceof LazyFetchModifiable){
			$courses->addFetchAction(
				function (Course $course) use ($includeOptionsAvailability, $includeFull, $includePast, $skipELearningOnly) {
					if ( $this->coursePassesFilter( $course, $includeOptionsAvailability, $includeFull, $includePast, $skipELearningOnly ))
						return $course;
					else
						return null;
				}
			);

			return $courses;
		}

		$courseArray = [];

		foreach ($courses as $course){
			if ( $this->coursePassesFilter( $course, $includeOptionsAvailability, $includeFull, $includePast, $skipELearningOnly ))
				$courseArray[] = $course;
		}

		return Collection::fromArray($courseArray);
	}

	protected function coursePassesFilter(
		Course $course,
		bool $includeOptionsAvailability, bool $showFull, bool $showPast, bool $skipELearningOnly
	): bool {
		switch ($this->courseListSettings->getShowInCompanyCourses() ?? 0) {
			case 1:
				if ($course->getType() === "open") return false;
				break;
			case 2:
				break;
			default:
				if ($course->getType() !== "open") return false;
				break;
		}

		if (
			$skipELearningOnly &&
			$this->courseUtility->getDayPartCount($course, false) == 0) return false;
		if (
			!($showFull) &&
			$course->getUsersData()->getAvailableSpots($includeOptionsAvailability) <= 0
		) return false;

		if (
			!($showPast) && $course->hasPastDayParts($this->generalSettings->isELearningCountsAsStart())
		) return false;

		return true;
	}

	protected function attributeIsTruthy(string $attribute):?bool {
		$attribute = $this->attributes[$attribute] ?? null;
		if ($attribute === null) return null;
		return $attribute == '1' || $attribute == 'true' || $attribute == 'yes' || $attribute == 'ja' || $attribute == 'y';
	}

	public function getTagDir(): string {
		return __DIR__;
	}

	public static function getAllowedTemplates(FeatureToggleSettings $featureToggles): array {
		return [
			"Lijst" => "listWithStyling",
			"Block" => "blockWithStyling",
			"Tabel" => "table",
			"Kalender" => "calendar",
			...($featureToggles->showShortcodeSettings() ? [
				"Lijst (legacy shortcode)" => "list",
				"Block (legacy shortcode)" => "block",
				"Block 2 (legacy shortcode)" => "block2",
			] : [])
		];
	}

	public function injectTestData(): void {
		$this->nodeData->data["courses"] = function () {
			$this->courseListSettings->getShowInCompanyCourses();
			$this->courseListSettings->getCompanies();
			$this->generalSettings->isIncludeOptionInCount();
			$this->generalSettings->isSkipELearningOnly();
			$this->attributes["showFull"] ?? $this->courseListSettings->isIncludeFullCourses();
			$this->attributes["showPast"] ?? $this->courseListSettings->isIncludeFullCourses();
		};
	}
}