<?php

declare(strict_types=1);

namespace core\handler;

use ArrayAccess;
use Exception;
use utils\CommandInterface;

final class ArraySetItemHandler extends AbstractHandler
{
    private const REQUIRED_PARAMETERS_COUNT = 3;

    /**
     * @return mixed
     */
    public function process(CommandInterface $command)
    {
        try {
            $this->validate($command, self::REQUIRED_PARAMETERS_COUNT, self::class);

            $payload = $command->getPayload();
            $data = &$payload[0];
            $indexesFromPayload = $payload[1];
            $value = $payload[2];

            $indexes = is_array($indexesFromPayload) ? $indexesFromPayload : [$indexesFromPayload];
            if (empty($indexes)) {
                throw new Exception('ArraySetItemHandler parameters mismatch!');
            }

            if (count($indexes) === 1) {
                if (is_array($data) || $data instanceof ArrayAccess) {
                    $data[$indexes[0]] = $value;

                    return $data;
                }

                throw new Exception(sprintf('Cannot set item on type: %s', gettype($data)));
            }

            if (!is_array($data)) {
                throw new Exception(sprintf('Cannot set nested item on type: %s', gettype($data)));
            }

            $this->setArrayValue($data, $indexes, $value);

            return $data;
        } catch (Exception $exception) {
            $className = get_class($exception);
            throw new $className($exception->getMessage(), (int) $exception->getCode());
        }
    }

    /**
     * @param mixed $value
     */
    private function setArrayValue(array &$data, array $indexes, $value): void
    {
        if (count($indexes) === 1) {
            $data[$indexes[0]] = $value;

            return;
        }

        $current = &$data;
        $lastIndex = count($indexes) - 1;
        for ($i = 0; $i < $lastIndex; $i++) {
            $index = $indexes[$i];
            if (!isset($current[$index]) || !is_array($current[$index])) {
                throw new Exception(sprintf('Invalid index path at: %s', (string) $index));
            }
            $current = &$current[$index];
        }

        $current[$indexes[$lastIndex]] = $value;
    }
}
