<?php

namespace Planaday\Utility\FormBuilder\Structures;

use Planaday\Utility\FormBuilder\Filterable;
use Planaday\Utility\FormBuilder\InvalidFormDataException;
use Planaday\Utility\FormBuilder\LayoutItem;
use Planaday\Utility\FormBuilder\LayoutItems\BasicLayoutItem;
use Planaday\Utility\FormBuilder\SettableData;

trait BasicStructure {
	use BasicLayoutItem{
		setParent as protected standardSetParent;
	}

	protected bool $isActive = true;
	protected bool $isFilterable = true;
	protected ?bool $displayDefaultNotice = null;

	/** @var LayoutItem[] */
	protected array $contents = [];
	/** @var string[] */
	protected array $classes = [];

	/**
	 * @return LayoutItem[]
	 */
	public function getContents(): array {
		return $this->contents;
	}

	public function blockFilters(): static {
		$this->isFilterable = false;
		return $this;
	}

	public function addContents( LayoutItem ...$contents ): static {
		foreach ($contents as $content) {
			$content->setParent($this);
			$this->contents[] = $content;
		}

		return $this;
	}

	public function setDisplayDefaultNotice(bool $displayDefaultNotice): static {
		$this->displayDefaultNotice = $displayDefaultNotice;
		return $this;
	}

	public function clearContents(): static {
		$this->contents = [];
		return $this;
	}

	public function addClasses(string ...$classes): static {
		$this->classes = array_merge($this->classes, $classes);
		return $this;
	}

	/**
	 * @param mixed $value
	 * @param bool $callSetCallback
	 *
	 * @return void
	 * @throws InvalidFormDataException
	 */
	public function setValue(mixed $value, bool $callSetCallback = true): void {
		$exception = new InvalidFormDataException();
		$shouldThrow = false;
		$value ??= [];

		foreach ($this->contents as $content) {
			if (!$content instanceof SettableData) {
				continue;
			}

			$index = $content->getIndex();
			if ($index === null) {
				$content->setValue($value);
				continue;
			}

			if (!is_array($value)) {
				throw $exception->addError('DataType', 'Value is not an array');
			}

			try {
				$content->setValue($value[$index] ?? null, $callSetCallback);
			} catch (InvalidFormDataException $e) {
				$exception->addError($index, $e->getErrors());
				$shouldThrow = true;
			}
		}

		if ($shouldThrow) {
			throw $exception;
		}
	}

	public function validate(): bool|InvalidFormDataException {
		if (!$this->validateConditions()) return true;
		$outcome = true;

		foreach ($this->contents as $content) {
			if (!$content instanceof SettableData) {
				continue;
			}

			$result = $content->validate();
			if ($result !== true) {
				if ($outcome === true){
					$outcome = new InvalidFormDataException();
				}

				$outcome->addError($content->getIndex(), $result->getErrors());
			}
		}

		return $outcome;
	}

	public function applyFilter(array $filters): ?bool {
		if (!$this->isFilterable) return true;
		$shouldStay = null;

		if ($this->index !== null) {
			$filters = $filters[$this->index] ?? [];
		}

		foreach ($this->contents as $content){
			if ($content instanceof Filterable) {
				$itemShown = $content->applyFilter($filters);
				if ($itemShown !== null) {
					$shouldStay = $itemShown || ($shouldStay ?? false);
				}
			}
		}

		$this->isActive = $shouldStay ?? true;
		return $shouldStay;
	}

	public function isPastFilter():bool{
		return $this->isActive;
	}

	protected function getFilteredContents(): array {
		$filteredContents = [];
		foreach ($this->contents as $content) {
			if (!$content instanceof Filterable || $content->isPastFilter()) {
				$filteredContents[] = $content;
			}
		}
		return $filteredContents;
	}

	public function setParent( ?LayoutItem $parent ): static {
		$this->standardSetParent($parent);

		foreach ($this->contents as $content) {
			$content->setParent($this);
		}

		return $this;
	}

	protected ?string $index = null;

	/**
	 * @param string|null $index
	 *
	 * @return $this
	 */
	public function setIndex( ?string $index ): static {
		$this->index = $index;
		return $this;
	}

	public function getIndex(): ?string {
		return $this->index;
	}

	public function jsonSerialize(): array {
		return [
			"type" => 'structure',
			"index" => $this->index,
			"classes" => $this->classes,
			"conditions" => $this->conditions,
			"id" => $this->getItemId(),
			"impl" => $this->getImplJson(),
			"contents" => $this->getFilteredContents(),
			"displayDefaultNotice" => $this->displayDefaultNotice
		];
	}

	public function getIndexTree(): array {
		$innerIndexes = [];

		foreach ($this->contents as $content) {
			if ($content instanceof SettableData) {
				$innerIndexes = array_merge($innerIndexes, $content->getIndexTree());
			}
		}

		if ($this->index === null) return $innerIndexes;
		return [
			$this->index => $innerIndexes
		];
	}

	public abstract function getImplJson(): mixed;
}