<?php

namespace TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine;

use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception;

class ArrayArgumentHelper
{
	protected int $indexStart = 0;

	/** @var mixed[] */
	protected array $arguments;

	protected int $argumentCount;

	/** @var int[] */
	protected array $rows;

	/** @var int[] */
	protected array $columns;

	/** @param mixed[] $arguments */
	public function initialise(array $arguments): void
	{
		$keys = array_keys($arguments);
		$this->indexStart = (int) array_shift($keys);
		$this->rows = $this->rows($arguments);
		$this->columns = $this->columns($arguments);

		$this->argumentCount = count($arguments);
		$this->arguments = $this->flattenSingleCellArrays($arguments, $this->rows, $this->columns);

		$this->rows = $this->rows($arguments);
		$this->columns = $this->columns($arguments);

		if ($this->arrayArguments() > 2) {
			throw new Exception('Formulae with more than two array arguments are not supported');
		}
	}

	/** @return mixed[] */
	public function arguments(): array
	{
		return $this->arguments;
	}

	public function hasArrayArgument(): bool
	{
		return $this->arrayArguments() > 0;
	}

	public function getFirstArrayArgumentNumber(): int
	{
		$rowArrays = $this->filterArray($this->rows);
		$columnArrays = $this->filterArray($this->columns);

		for ($index = $this->indexStart; $index < $this->argumentCount; ++$index) {
			if (isset($rowArrays[$index]) || isset($columnArrays[$index])) {
				return ++$index;
			}
		}

		return 0;
	}

	public function getSingleRowVector(): ?int
	{
		$rowVectors = $this->getRowVectors();

		return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
	}

	/** @return int[] */
	private function getRowVectors(): array
	{
		$rowVectors = [];
		for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
			if ($this->rows[$index] === 1 && $this->columns[$index] > 1) {
				$rowVectors[] = $index;
			}
		}

		return $rowVectors;
	}

	public function getSingleColumnVector(): ?int
	{
		$columnVectors = $this->getColumnVectors();

		return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
	}

	/** @return int[] */
	private function getColumnVectors(): array
	{
		$columnVectors = [];
		for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
			if ($this->rows[$index] > 1 && $this->columns[$index] === 1) {
				$columnVectors[] = $index;
			}
		}

		return $columnVectors;
	}

	/** @return int[] */
	public function getMatrixPair(): array
	{
		for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) {
			for ($j = $i + 1; $j < $this->argumentCount; ++$j) {
				if (isset($this->rows[$i], $this->rows[$j])) {
					return [$i, $j];
				}
			}
		}

		return [];
	}

	public function isVector(int $argument): bool
	{
		return $this->rows[$argument] === 1 || $this->columns[$argument] === 1;
	}

	public function isRowVector(int $argument): bool
	{
		return $this->rows[$argument] === 1;
	}

	public function isColumnVector(int $argument): bool
	{
		return $this->columns[$argument] === 1;
	}

	public function rowCount(int $argument): int
	{
		return $this->rows[$argument];
	}

	public function columnCount(int $argument): int
	{
		return $this->columns[$argument];
	}

	/**
	 * @param mixed[] $arguments
	 *
	 * @return int[]
	 */
	private function rows(array $arguments): array
	{
		return array_map(
			fn ($argument): int => is_countable($argument) ? count($argument) : 1,
			$arguments
		);
	}

	/**
	 * @param mixed[] $arguments
	 *
	 * @return int[]
	 */
	private function columns(array $arguments): array
	{
		return array_map(
			fn ($argument): int => is_array($argument) && is_array($argument[array_keys($argument)[0]])
					? count($argument[array_keys($argument)[0]])
					: 1,
			$arguments
		);
	}

	public function arrayArguments(): int
	{
		$count = 0;
		foreach (array_keys($this->arguments) as $argument) {
			if ($this->rows[$argument] > 1 || $this->columns[$argument] > 1) {
				++$count;
			}
		}

		return $count;
	}

	/**
	 * @param mixed[] $arguments
	 * @param int[] $rows
	 * @param int[] $columns
	 *
	 * @return mixed[]
	 */
	private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array
	{
		foreach ($arguments as $index => $argument) {
			if ($rows[$index] === 1 && $columns[$index] === 1) {
				while (is_array($argument)) {
					$argument = array_pop($argument);
				}
				$arguments[$index] = $argument;
			}
		}

		return $arguments;
	}

	/**
	 * @param mixed[] $array
	 *
	 * @return mixed[]
	 */
	private function filterArray(array $array): array
	{
		return array_filter(
			$array,
			fn ($value): bool => $value > 1
		);
	}
}
