<?php

namespace XF\Mvc;

use XF\Db\AbstractAdapter;
use XF\Mvc\Entity\Entity;
use XF\PrintableException;
use XF\Service\AbstractService;

use function is_int;

class FormAction
{
	protected $setup = [];
	protected $validate = [];
	protected $apply = [];
	protected $complete = [];

	/**
	 * @var AbstractAdapter|null
	 */
	protected $dbTransaction;

	protected $errors = [];

	public function setup(\Closure $setup)
	{
		$this->setup[] = $setup;

		return $this;
	}

	public function setupEntityInput(Entity $entity, array $input)
	{
		$this->setup[] = function (FormAction $form) use ($entity, $input)
		{
			$entity->bulkSet($input);
		};

		return $this;
	}

	public function validate(\Closure $validate)
	{
		$this->validate[] = $validate;

		return $this;
	}

	public function validateEntity(Entity $entity)
	{
		$this->validate[] = function (FormAction $form) use ($entity)
		{
			$entity->preSave();
			$form->logErrors($entity->getErrors());
		};

		return $this;
	}

	public function apply(\Closure $apply)
	{
		$this->apply[] = $apply;

		return $this;
	}

	public function saveEntity(Entity $entity)
	{
		$this->apply[] = function (FormAction $form) use ($entity)
		{
			$entity->save(true, $form->isUsingTransaction() ? false : true);
		};

		return $this;
	}

	public function complete(\Closure $complete)
	{
		$this->complete[] = $complete;

		return $this;
	}

	public function basicEntitySave(Entity $entity, array $input)
	{
		$this->setupEntityInput($entity, $input)
			->validateEntity($entity)
			->saveEntity($entity);

		return $this;
	}

	public function basicValidateServiceSave(AbstractService $service, ?\Closure $setup = null)
	{
		if (!method_exists($service, 'validate') || !method_exists($service, 'save'))
		{
			throw new \LogicException("Requires a service that implements the ValidateAndSavableTrait");
		}

		if ($setup)
		{
			$this->setup[] = $setup;
		}

		$this->validate[] = function (FormAction $form) use ($service)
		{
			if (!$service->validate($errors))
			{
				$form->logErrors($errors);
			}
		};

		$this->apply[] = function (FormAction $form) use ($service)
		{
			$service->save();
		};

		return $this;
	}

	public function applyInTransaction(?AbstractAdapter $db = null)
	{
		$this->dbTransaction = $db;

		return $this;
	}

	public function isUsingTransaction()
	{
		return $this->dbTransaction ? true : false;
	}

	public function logError($error, $key = null)
	{
		if ($key !== null)
		{
			$this->errors[$key] = $error;
		}
		else
		{
			$this->errors[] = $error;
		}

		return $this;
	}

	public function logErrors(array $errors)
	{
		foreach ($errors AS $key => $error)
		{
			if (is_int($key))
			{
				$this->logError($error);
			}
			else
			{
				$this->logError($error, $key);
			}
		}
	}

	public function run($throwOnError = true)
	{
		foreach ($this->setup AS $setup)
		{
			$setup($this);
		}
		foreach ($this->validate AS $validate)
		{
			$validate($this);
		}

		if ($this->errors)
		{
			if ($throwOnError)
			{
				throw new PrintableException($this->errors);
			}
			return false;
		}

		if ($this->dbTransaction)
		{
			$this->dbTransaction->beginTransaction();
		}

		foreach ($this->apply AS $apply)
		{
			$apply($this);
		}

		if ($this->dbTransaction)
		{
			$this->dbTransaction->commit();
		}

		foreach ($this->complete AS $complete)
		{
			$complete($this);
		}

		return true;
	}
}
