<?php

namespace Planaday\Html;

use Exception;
use Planaday\Config\ConfigFetcher;
use Planaday\Config\EnumMappedSettings;
use Planaday\Config\FeatureToggles\FeatureToggleSettings;
use Planaday\Config\GlobalSetting;
use Planaday\Config\HiddenFromNodeTags;
use Planaday\Config\OptionGroupEnum;
use Planaday\Config\SettingsLoader;
use Planaday\Config\SettingsWithForm;
use Planaday\Config\SettingsWithUsageLog;
use Planaday\Html\Nodes\NodeData;
use Planaday\Html\NodeTags\Feature;
use Planaday\Html\Parsers\BlockParser;
use Planaday\Html\Parsers\ParseFailedException;
use Planaday\Html\Parsers\StdDOMParser;
use Planaday\Platform\PlatformAbstraction\LocalizationController;
use Planaday\Utility\DataGrouping\Collection;
use Planaday\Utility\DataGrouping\Map;
use Planaday\Utility\Debug\Log\Logging;
use Planaday\Utility\DependencyInjection\Attributes\Scoped;
use Planaday\Utility\DependencyInjection\Container;
use Planaday\Utility\DependencyInjection\InjectionImpossibleException;
use Planaday\Utility\DependencyInjection\Scope;
use Planaday\Utility\FormBuilder\Form;
use Planaday\Utility\FormBuilder\LayoutItems\Text;
use Planaday\Utility\FormBuilder\Structures\BasicGroup;
use Planaday\Utility\FormBuilder\Structures\FullScreenPopup;
use Planaday\Utility\FormBuilder\Structures\TabbedGroups;

#[Scoped]
class FeatureService {
	use Logging;

	public function __construct(
		protected Container $container,
		protected LocalizationController $translator,
		protected ConfigFetcher $configFetcher,
		protected FeatureToggleSettings $featureToggleSettings
	) {}


	/**
	 * @param class-string<Feature> $blockName
	 * @param array<string, mixed> $attributes
	 *
	 * @throws Exception
	 */
	public function getParsed(string $blockName, array $attributes) : string {
		$scope = $this->container->getNewScope();
		try{
			$settingsLoader = $this->container->getInstance(SettingsLoader::class, $scope);
			$settingsLoader->setSettingsOverride($this->getOverridesFromAttributes($attributes));

			$this->logDebug("Set setting overrides, now parsing: " . $blockName);

			return $this->container->getInstance(StdDOMParser::class, $scope)
			                ->preloadData(
				                new NodeData(
					                [
										'template' => $attributes['template'] ??
										              $this->getFirst($blockName::getAllowedTemplates($this->featureToggleSettings)) ??
										              '',
						                'feature_attributes' => [ ...$attributes, 'config' => null]
					                ],
					                [ 'target_block' => $blockName ]
				                )
			                )
			                ->parse("<pad-target_block pad-template='{{template}}'/>");
		} catch (InjectionImpossibleException | ParseFailedException $e){
			throw new Exception($e->getMessage());
		} finally {
			$scope->destroy();
		}
	}

	protected function getFirst(array $array): mixed{
		foreach ($array as $value) {
			return $value;
		}
		return null;
	}

		/**
	 * @param class-string<Feature> $blockName
	 * @param array<string, mixed> $attributes
	 *
	 * @throws Exception
	 */
	public function getSettingsForm(string $blockName, array $attributes) : Form {
		$scope = $this->container->getNewScope();
		try{
			$this->logDebug("Getting settings form for block " . $blockName);
			$settingsLoader = $this->container->getInstance(SettingsLoader::class, $scope);

			$this->container->getInstance(BlockParser::class, $scope)
							->preloadData(
								new NodeData(
									[ 'template' => $attributes['template'] ?? $this->getFirst(
										$blockName::getAllowedTemplates(
											$this->featureToggleSettings
										)) ?? '' ],
									[ 'target_block' => $blockName ]
								)
							)
			                ->parse("<pad-target_block pad-template='{{template}}'/>");

			$loadedSettings = $settingsLoader->getLoadedSettings();

			$otherSettings = new Collection();
			$usageLoggedSettings = new Collection();
			foreach ($loadedSettings as $setting) {
				/** @var class-string<OptionGroupEnum> $setting */
				$setting = $setting::getClassName();
				if (is_a($setting, SettingsWithUsageLog::class, true)){
					/** @var SettingsWithUsageLog $settingInstance */
					$settingInstance = $this->container->getInstance($setting, $scope);
					foreach ($settingInstance->getUsageLog() as $usage) {
						$usageLoggedSettings->append($usage);
					}
				} else {
					$otherSettings->append($setting);
				}
			}

			return $this->createForm($usageLoggedSettings, $otherSettings, $scope);
		} catch (InjectionImpossibleException | ParseFailedException $e){
			throw new Exception($e->getMessage());
		} finally {
			$scope->destroy();
		}
	}

	/**
	 * @param Collection<OptionGroupEnum> $usageLoggedSettings
	 * @param Collection<class-string<EnumMappedSettings>> $otherSettings
	 */
	protected function createForm(Collection $usageLoggedSettings, Collection $otherSettings, Scope $scope): Form{
		$settingObjects = $this->configFetcher->getHighestMappedSettings($usageLoggedSettings->toArray(), $filterToApply);

		$globalSettings = new TabbedGroups();
		$tagSettings = new BasicGroup();
		$tagSettings->addClasses("pad-block-settings");
		$hasGlobalSettings = false;

		foreach ($otherSettings as $settingClass) {
			try{
				$settingObject = $this->container->getInstance($settingClass, $scope);

				if (!$settingObject instanceof SettingsWithForm) continue;
				if ($settingObject instanceof HiddenFromNodeTags) continue;
				if ($settingObject instanceof GlobalSetting) {
					$hasGlobalSettings = true;
					$globalSettings->addTab(
						$settingObject->getTitle(),
						$settingObject->getFormGroup()->blockFilters()
					);
				} else {
					$tagSettings->addContents($settingObject->getFormGroup()->blockFilters());
				}
			} catch (InjectionImpossibleException){
				continue;
			}
		}

		foreach ($settingObjects as $settingObject) {
			if (!$settingObject instanceof SettingsWithForm) continue;
			if ($settingObject instanceof GlobalSetting) {
				$hasGlobalSettings = true;
				$globalSettings->addTab(
					$settingObject->getTitle(),
					$settingObject->getFormGroup()
				);
			} else {
				$tagSettings->addContents($settingObject->getFormGroup());
			}
		}

		$form = new Form();
		$form->setName("settings");
		$form->addContents($tagSettings);
		if ($hasGlobalSettings)
			$form->addContents(
				(new FullScreenPopup())
					->setButtonTitle($this->translator->translate("Globale Instellingen Overschrijven"))
					->setCloseButtonText($this->translator->translate("Sluiten"))
					->addContents(
						(new Text($this->translator->translate("Alle wijzigingen worden automatisch " .
						                                       "bijgehouden en gelden alleen voor dit block. Sluit dit " .
						                                       "venster en sla de pagina op om de wijzigingen te " .
						                                       "bevestigen.")))->addClasses("pad-popup-notice"),
						$globalSettings,
					)
					->setDisplayDefaultNotice(true)
			);
		$form->setWriteDefaultData(false)
		     ->setIsDefaultText($this->translator->translate("Waarde volgt de globale of standaard instelling"))
		     ->setIsChangedText($this->translator->translate("Waarde is aangepast."))
		     ->setResetButtonText($this->translator->translate("Reset waarde naar standaard?"));

		$form->applyFilter($filterToApply);
		return $form;
	}


	/**
	 * @param array<mixed> $attributes
	 * @param Map<OptionGroupEnum, mixed> $configObject
	 *
	 * @return Map<OptionGroupEnum, mixed>
	 */
	public function getOverridesFromAttributes(
		array $attributes,
		Map $configObject = new Map()
	): Map {
		$config = $attributes['config'] ?? null;

		if ( !is_array($config) ) {
			$this->logWarning("Config is not an array: " . print_r($config, true));
			return new Map();
		}

		$this->setConfigLayer(null, $config, $configObject);
		return $configObject;
	}

	/**
	 * @param null|class-string<OptionGroupEnum> $currentClass
	 * @param mixed[] $layer
	 * @param Map<OptionGroupEnum, mixed> $output
	 */
	protected function setConfigLayer(?string $currentClass, array $layer, Map $output): void {
		foreach ($layer as $key => $value) {
			if (is_a($key, OptionGroupEnum::class, true)){
				$this->setConfigLayer($key, $value, $output);
				continue;
			}

			if ($currentClass === null){
				if (is_array($value)) $this->setConfigLayer($currentClass, $value, $output);
				continue;
			}

			$enum = $currentClass::tryFrom($key);
			if ($enum === null) {
				if (is_array($value)) $this->setConfigLayer($currentClass, $value, $output);
				continue;
			}

			if ($value === 'false') $value = false;
			if ($value === 'true') $value = true;
			$output->offsetSet($enum, $value);
		}
	}

}