<?php

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

use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use TablePress\PhpOffice\PhpSpreadsheet\Style\Style;

abstract class WizardAbstract
{
	protected ?Style $style = null;

	protected string $expression;

	protected string $cellRange;

	protected string $referenceCell;

	protected int $referenceRow;

	protected bool $stopIfTrue = false;

	protected int $referenceColumn;

	public function __construct(string $cellRange)
	{
		$this->setCellRange($cellRange);
	}

	public function getCellRange(): string
	{
		return $this->cellRange;
	}

	public function setCellRange(string $cellRange): void
	{
		$this->cellRange = $cellRange;
		$this->setReferenceCellForExpressions($cellRange);
	}

	protected function setReferenceCellForExpressions(string $conditionalRange): void
	{
		$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange)));
		[$this->referenceCell] = $conditionalRange[0];

		[$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell);
	}

	public function getStopIfTrue(): bool
	{
		return $this->stopIfTrue;
	}

	public function setStopIfTrue(bool $stopIfTrue): void
	{
		$this->stopIfTrue = $stopIfTrue;
	}

	public function getStyle(): Style
	{
		return $this->style ?? new Style(false, true);
	}

	public function setStyle(Style $style): void
	{
		$this->style = $style;
	}

	protected function validateOperand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): string
	{
		if (
			$operandValueType === Wizard::VALUE_TYPE_LITERAL
			&& str_starts_with($operand, '"')
			&& str_ends_with($operand, '"')
		) {
			$operand = str_replace('""', '"', (string) substr($operand, 1, -1));
		} elseif ($operandValueType === Wizard::VALUE_TYPE_FORMULA && str_starts_with($operand, '=')) {
			$operand = (string) substr($operand, 1);
		}

		return $operand;
	}

	/** @param string[] $matches */
	protected static function reverseCellAdjustment(array $matches, int $referenceColumn, int $referenceRow): string
	{
		$worksheet = $matches[1];
		$column = $matches[6];
		$row = $matches[7];

		if (!str_contains($column, '$')) {
			$column = Coordinate::columnIndexFromString($column);
			$column -= $referenceColumn - 1;
			$column = Coordinate::stringFromColumnIndex($column);
		}

		if (!str_contains($row, '$')) {
			$row = (int) $row - ($referenceRow - 1);
		}

		return "{$worksheet}{$column}{$row}";
	}

	public static function reverseAdjustCellRef(string $condition, string $cellRange): string
	{
		$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellRange)));
		[$referenceCell] = $conditionalRange[0];
		[$referenceColumnIndex, $referenceRow] = Coordinate::indexesFromString($referenceCell);

		$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
		$i = false;
		foreach ($splitCondition as &$value) {
			//    Only count/replace in alternating array entries (ie. not in quoted strings)
			$i = $i === false;
			if ($i) {
				$value = (string) preg_replace_callback(
					'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
					fn ($matches): string => self::reverseCellAdjustment($matches, $referenceColumnIndex, $referenceRow),
					$value
				);
			}
		}
		unset($value);

		//    Then rebuild the condition string to return it
		return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
	}

	/** @param string[] $matches */
	protected function conditionCellAdjustment(array $matches): string
	{
		$worksheet = $matches[1];
		$column = $matches[6];
		$row = $matches[7];

		if (!str_contains($column, '$')) {
			$column = Coordinate::columnIndexFromString($column);
			$column += $this->referenceColumn - 1;
			$column = Coordinate::stringFromColumnIndex($column);
		}

		if (!str_contains($row, '$')) {
			$row = (int) $row + ($this->referenceRow - 1);
		}

		return "{$worksheet}{$column}{$row}";
	}

	protected function cellConditionCheck(string $condition): string
	{
		$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
		$i = false;
		foreach ($splitCondition as &$value) {
			//    Only count/replace in alternating array entries (ie. not in quoted strings)
			$i = $i === false;
			if ($i) {
				$value = (string) preg_replace_callback(
					'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
					[$this, 'conditionCellAdjustment'],
					$value
				);
			}
		}
		unset($value);

		//    Then rebuild the condition string to return it
		return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
	}

	/**
	 * @param mixed[] $conditions
	 *
	 * @return mixed[]
	 */
	protected function adjustConditionsForCellReferences(array $conditions): array
	{
		return array_map(
			[$this, 'cellConditionCheck'],
			$conditions
		);
	}
}
