<?php

namespace XF\Service;

use XF\App;
use XF\Mvc\Entity\Entity;
use XF\Tree;

class RebuildNestedSetService extends AbstractService
{
	/**
	 * @var Tree
	 */
	protected $tree;

	protected $entityType;
	protected $config;

	public function __construct(App $app, $entityType, array $config = [])
	{
		parent::__construct($app);
		$this->entityType = $entityType;
		$this->config = array_replace($this->getDefaultConfig(), $config);
	}

	protected function getDefaultConfig()
	{
		return [
			'parentField' => 'parent_id',
			'orderField' => 'display_order',
			'titleField' => 'title',
			'breadcrumbField' => 'breadcrumb_data',
			'rootId' => 0,
		];
	}

	protected function setupTree()
	{
		if ($this->tree)
		{
			return;
		}

		$entities = $this->getEntities();
		$this->tree = new Tree($entities, $this->config['parentField'], $this->config['rootId']);
	}

	protected function getEntities()
	{
		return $this->finder($this->entityType)
			->order($this->config['orderField'])
			->fetch();
	}

	public function rebuildNestedSetInfo()
	{
		$this->setupTree();

		$passDown = $this->getBasePassableData();

		$this->db()->beginTransaction();
		$this->_rebuildNestedSetInfo($this->config['rootId'], $passDown);
		$this->db()->commit();
	}

	protected function _rebuildNestedSetInfo($id, array $passDown, $depth = -1, &$counter = 0)
	{
		/** @var Entity $entity */
		$entity = $this->tree->getData($id);

		if ($entity)
		{
			$counter++;
		}
		$left = $counter;

		if ($entity)
		{
			$selfData = $this->getSelfData($passDown, $entity, $depth, $left);
			$childPassDown = $this->getChildPassableData($passDown, $entity, $depth, $left);
		}
		else
		{
			$selfData = [];
			$childPassDown = $passDown;
		}

		foreach ($this->tree->childIds($id) AS $childId)
		{
			$this->_rebuildNestedSetInfo($childId, $childPassDown, $depth + 1, $counter);
		}

		if ($entity)
		{
			$counter++;
		}
		$right = $counter;

		if ($entity)
		{
			$updateData = $selfData + [
				'lft' => $left,
				'rgt' => $right,
				'depth' => $depth,
			];

			$entity->fastUpdate($updateData);
		}
	}

	protected function getBasePassableData()
	{
		return [
			$this->config['breadcrumbField'] => [],
		];
	}

	protected function getSelfData(array $passData, Entity $entity, $depth, $left)
	{
		return $passData;
	}

	protected function getChildPassableData(array $passData, Entity $entity, $depth, $left)
	{
		$breadcrumbField = $this->config['breadcrumbField'];

		$passData[$breadcrumbField][$entity->getEntityId()] = $this->getBreadcrumbEntry($entity, $depth, $left);

		return $passData;
	}

	protected function getBreadcrumbEntry(Entity $entity, $depth, $left)
	{
		$titleField = $this->config['titleField'];
		$title = $entity[$titleField];
		$id = $entity->getEntityId();
		$idField = $entity->structure()->primaryKey;

		return [
			$idField => $id,
			$titleField => $title,
			'depth' => $depth,
			'lft' => $left,
		];
	}
}
