<?php

namespace XF\Behavior;

use XF\CustomField\Set;
use XF\Mvc\Entity\Behavior;

use function is_array, is_string;

class CustomFieldsHolder extends Behavior
{
	protected function getDefaultConfig()
	{
		return [
			'column' => 'custom_fields',
			'valueTable' => null,
			'keyColumn' => null,
			'checkForUpdates' => null,
			'getAllowedFields' => null,
		];
	}

	protected function verifyConfig()
	{
		if (!$this->config['valueTable'])
		{
			throw new \LogicException("The valueTable configuration must be specified");
		}

		if (!$this->config['keyColumn'])
		{
			$key = $this->entity->structure()->primaryKey;
			if (is_string($key))
			{
				$this->config['keyColumn'] = $key;
			}
			else
			{
				throw new \LogicException("The keyColumn configuration could not be derived and must be specified");
			}
		}

		if ($this->config['checkForUpdates'] && !is_callable($this->config['getAllowedFields']))
		{
			throw new \LogicException("getAllowedFields must be specified to use checkForUpdates");
		}
	}

	public function preSave()
	{
		$entity = $this->entity;
		$checkForUpdates = $this->config['checkForUpdates'];

		if ($checkForUpdates)
		{
			if ($entity->isUpdate() && $entity->isChanged($checkForUpdates))
			{
				/** @var Set $fieldSet */
				$fieldSet = $entity->get($this->config['column']);
				if (!($fieldSet instanceof Set))
				{
					throw new \LogicException("Primary column must return a XF\CustomField\Set object (via a getter)");
				}

				$getAllowedFields = $this->config['getAllowedFields'];
				$allowedFields = $getAllowedFields($entity);

				foreach ($fieldSet->getFieldValues() AS $fieldId => $null)
				{
					if (!isset($allowedFields[$fieldId]))
					{
						$fieldSet->removeFieldValue($fieldId);
					}
				}
			}
		}
	}

	public function postSave()
	{
		$column = $this->config['column'];
		$entity = $this->entity;

		if ($entity->isChanged($column))
		{
			$newSet = $entity->getValue($column);
			$oldSet = $entity->isUpdate() ? $entity->getExistingValue($column) : [];

			if ($entity->isInsert() && !$newSet)
			{
				// nothing to do
				return;
			}

			$removedKeys = [];
			$replacements = [];

			foreach ($oldSet AS $key => $oldValue)
			{
				if (!isset($newSet[$key]))
				{
					$removedKeys[] = $key;
				}
				else
				{
					$newValue = $newSet[$key];
					if ($oldValue !== $newValue)
					{
						// updated value
						$replacements[$key] = $newValue;
					}
				}
			}

			foreach ($newSet AS $key => $newValue)
			{
				if (isset($oldSet[$key]))
				{
					// handled above
					continue;
				}

				// new value
				$replacements[$key] = $newValue;
			}

			$db = $entity->db();
			$keyColumn = $this->config['keyColumn'];
			$id = $entity->getEntityId();

			if ($removedKeys)
			{
				$db->delete(
					$this->config['valueTable'],
					"`$keyColumn` = ? AND field_id IN (" . $db->quote($removedKeys) . ")",
					$id
				);
			}

			if ($replacements)
			{
				$insert = [];
				foreach ($replacements AS $key => $value)
				{
					$insert[] = [
						$keyColumn => $id,
						'field_id' => $key,
						'field_value' => is_array($value) ? serialize($value) : $value,
					];
				}

				$db->insertBulk($this->config['valueTable'], $insert, false, 'field_value = VALUES(field_value)');
			}
		}
	}

	public function postDelete()
	{
		$db = $this->entity->db();
		$keyColumn = $this->config['keyColumn'];

		$db->delete(
			$this->config['valueTable'],
			"`$keyColumn` = ?",
			$this->entity->getEntityId()
		);
	}
}
