<?php
declare(strict_types=1);

/**
 * Logic to apply filter actions (for feeds, categories, user configuration...).
 */
trait FreshRSS_FilterActionsTrait {

	/** @var list<FreshRSS_FilterAction>|null $filterActions */
	private ?array $filterActions = null;

	/**
	 * @return list<FreshRSS_FilterAction>
	 */
	private function filterActions(): array {
		if (empty($this->filterActions)) {
			$this->filterActions = [];
			$filters = $this->attributeArray('filters') ?? [];
			foreach ($filters as $filter) {
				$filterAction = FreshRSS_FilterAction::fromJSON($filter);
				if ($filterAction != null) {
					$this->filterActions[] = $filterAction;
				}
			}
		}
		return $this->filterActions;
	}

	/**
	 * @param array<FreshRSS_FilterAction>|null $filterActions
	 */
	private function _filterActions(?array $filterActions): void {
		$this->filterActions = is_array($filterActions) ? array_values($filterActions) : null;
		if ($this->filterActions !== null && !empty($this->filterActions)) {
			$this->_attribute('filters', array_map(
				static fn(?FreshRSS_FilterAction $af) => $af == null ? null : $af->toJSON(),
				$this->filterActions));
		} else {
			$this->_attribute('filters', null);
		}
	}

	/** @return list<FreshRSS_BooleanSearch> */
	public function filtersAction(string $action): array {
		$action = trim($action);
		if ($action == '') {
			return [];
		}
		$filters = [];
		$filterActions = $this->filterActions();
		for ($i = count($filterActions) - 1; $i >= 0; $i--) {
			$filterAction = $filterActions[$i];
			if (in_array($action, $filterAction->actions(), true)) {
				$filters[] = $filterAction->booleanSearch();
			}
		}
		return $filters;
	}

	/**
	 * @param array<string> $filters
	 */
	public function _filtersAction(string $action, array $filters): void {
		$action = trim($action);
		if ($action === '') {
			return;
		}
		$filters = array_values(array_unique(array_map('trim', $filters), SORT_STRING));
		$filterActions = $this->filterActions();

		//Check existing filters
		for ($i = count($filterActions) - 1; $i >= 0; $i--) {
			$filterAction = $filterActions[$i];
			if ($filterAction === null || !is_array($filterAction->actions()) || $filterAction->booleanSearch()->toString() === '') {
				array_splice($filterActions, $i, 1);
				continue;
			}
			$actions = $filterAction->actions();
			//Remove existing rules with same action
			for ($j = count($actions) - 1; $j >= 0; $j--) {
				if ($actions[$j] === $action) {
					array_splice($actions, $j, 1);
				}
			}
			//Update existing filter with new action
			for ($k = count($filters) - 1; $k >= 0; $k--) {
				$filter = $filters[$k];
				if ($filter === $filterAction->booleanSearch()->toString()) {
					$actions[] = $action;
					array_splice($filters, $k, 1);
				}
			}
			//Save result
			if (empty($actions)) {
				array_splice($filterActions, $i, 1);
			} else {
				$filterAction->_actions($actions);
			}
		}

		//Add new filters
		for ($k = count($filters) - 1; $k >= 0; $k--) {
			$filter = $filters[$k];
			if ($filter != '') {
				$filterAction = FreshRSS_FilterAction::fromJSON([
					'search' => $filter,
					'actions' => [$action],
				]);
				if ($filterAction != null) {
					$filterActions[] = $filterAction;
				}
			}
		}

		if (empty($filterActions)) {
			$filterActions = null;
		}
		$this->_filterActions($filterActions);
	}

	/**
	 * @param bool $applyLabel Parameter by reference, which will be set to true if the callers needs to apply a label to the article entry.
	 * @param-out bool $applyLabel
	 */
	public function applyFilterActions(FreshRSS_Entry $entry, ?bool &$applyLabel = null): void {
		$applyLabel = false;
		foreach ($this->filterActions() as $filterAction) {
			if ($entry->matches($filterAction->booleanSearch())) {
				foreach ($filterAction->actions() as $action) {
					switch ($action) {
						case 'read':
							if (!$entry->isRead()) {
								$entry->_isRead(true);
								Minz_ExtensionManager::callHook(Minz_HookType::EntryAutoRead, $entry, 'filter');
							}
							break;
						case 'star':
							if (!$entry->isUpdated()) {
								// Do not apply to updated articles, to avoid overruling a user manual action
								$entry->_isFavorite(true);
							}
							break;
						case 'label':
							if (!$entry->isUpdated()) {
								// Do not apply to updated articles, to avoid overruling a user manual action
								$applyLabel = true;
							}
							break;
					}
				}
			}
		}
	}
}
