<?php

namespace TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;

use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use TablePress\PhpOffice\PhpSpreadsheet\Exception;
use TablePress\PhpOffice\PhpSpreadsheet\Style\Conditional;
use TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;

/**
 * @method TextValue contains(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
 * @method TextValue doesNotContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
 * @method TextValue doesntContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
 * @method TextValue beginsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
 * @method TextValue startsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
 * @method TextValue endsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
 */
class TextValue extends WizardAbstract implements WizardInterface
{
	protected const MAGIC_OPERATIONS = [
		'contains' => Conditional::OPERATOR_CONTAINSTEXT,
		'doesntContain' => Conditional::OPERATOR_NOTCONTAINS,
		'doesNotContain' => Conditional::OPERATOR_NOTCONTAINS,
		'beginsWith' => Conditional::OPERATOR_BEGINSWITH,
		'startsWith' => Conditional::OPERATOR_BEGINSWITH,
		'endsWith' => Conditional::OPERATOR_ENDSWITH,
	];

	protected const OPERATORS = [
		Conditional::OPERATOR_CONTAINSTEXT => Conditional::CONDITION_CONTAINSTEXT,
		Conditional::OPERATOR_NOTCONTAINS => Conditional::CONDITION_NOTCONTAINSTEXT,
		Conditional::OPERATOR_BEGINSWITH => Conditional::CONDITION_BEGINSWITH,
		Conditional::OPERATOR_ENDSWITH => Conditional::CONDITION_ENDSWITH,
	];

	protected const EXPRESSIONS = [
		Conditional::OPERATOR_CONTAINSTEXT => 'NOT(ISERROR(SEARCH(%s,%s)))',
		Conditional::OPERATOR_NOTCONTAINS => 'ISERROR(SEARCH(%s,%s))',
		Conditional::OPERATOR_BEGINSWITH => 'LEFT(%s,LEN(%s))=%s',
		Conditional::OPERATOR_ENDSWITH => 'RIGHT(%s,LEN(%s))=%s',
	];

	protected string $operator;

	protected string $operand;

	protected string $operandValueType;

	public function __construct(string $cellRange)
	{
		parent::__construct($cellRange);
	}

	protected function operator(string $operator): void
	{
		if (!isset(self::OPERATORS[$operator])) {
			throw new Exception('Invalid Operator for Text Value CF Rule Wizard');
		}

		$this->operator = $operator;
	}

	protected function operand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void
	{
		$operand = $this->validateOperand($operand, $operandValueType);

		$this->operand = $operand;
		$this->operandValueType = $operandValueType;
	}

	protected function wrapValue(string $value): string
	{
		return '"' . $value . '"';
	}

	protected function setExpression(): void
	{
		$operand = $this->operandValueType === Wizard::VALUE_TYPE_LITERAL
			? $this->wrapValue(str_replace('"', '""', $this->operand))
			: $this->cellConditionCheck($this->operand);

		if (
			$this->operator === Conditional::OPERATOR_CONTAINSTEXT
			|| $this->operator === Conditional::OPERATOR_NOTCONTAINS
		) {
			$this->expression = sprintf(self::EXPRESSIONS[$this->operator], $operand, $this->referenceCell);
		} else {
			$this->expression = sprintf(self::EXPRESSIONS[$this->operator], $this->referenceCell, $operand, $operand);
		}
	}

	public function getConditional(): Conditional
	{
		$this->setExpression();

		$conditional = new Conditional();
		$conditional->setConditionType(self::OPERATORS[$this->operator]);
		$conditional->setOperatorType($this->operator);
		$conditional->setText(
			$this->operandValueType !== Wizard::VALUE_TYPE_LITERAL
				? $this->cellConditionCheck($this->operand)
				: $this->operand
		);
		$conditional->setConditions([$this->expression]);
		$conditional->setStyle($this->getStyle());
		$conditional->setStopIfTrue($this->getStopIfTrue());

		return $conditional;
	}

	public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
	{
		if (!in_array($conditional->getConditionType(), self::OPERATORS, true)) {
			throw new Exception('Conditional is not a Text Value CF Rule conditional');
		}

		$wizard = new self($cellRange);
		$wizard->operator = (string) array_search($conditional->getConditionType(), self::OPERATORS, true);
		$wizard->style = $conditional->getStyle();
		$wizard->stopIfTrue = $conditional->getStopIfTrue();

		// Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
		$wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL;
		$condition = $conditional->getText();
		if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
			$wizard->operandValueType = Wizard::VALUE_TYPE_CELL;
			$condition = self::reverseAdjustCellRef($condition, $cellRange);
		} elseif (
			preg_match('/\(\)/', $condition)
			|| preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition)
		) {
			$wizard->operandValueType = Wizard::VALUE_TYPE_FORMULA;
		}
		$wizard->operand = $condition;

		return $wizard;
	}

	/**
	 * @param mixed[] $arguments
	 */
	public function __call(string $methodName, array $arguments): self
	{
		if (!isset(self::MAGIC_OPERATIONS[$methodName])) {
			throw new Exception('Invalid Operation for Text Value CF Rule Wizard');
		}

		$this->operator(self::MAGIC_OPERATIONS[$methodName]);
		//$this->operand(...$arguments);
		if (count($arguments) < 2) {
			/** @var string */
			$arg0 = $arguments[0];
			$this->operand($arg0);
		} else {
			/** @var string */
			$arg0 = $arguments[0];
			/** @var string */
			$arg1 = $arguments[1];
			$this->operand($arg0, $arg1);
		}

		return $this;
	}
}
