<?php
declare(strict_types=1);

namespace App\Service\Utils;

class ArrayUtils
{
    /**
     * Returns an array containing changes between the first array the keys/values from an array
     * which are absent or different from $initialData
     *
     * If recursive is true, when values are arrays, they'll get processed too to only keep changes. Else,
     * the whole value will be added to the changes if there is at least one difference between the former and the latter.
     *
     * @param mixed[] $initialArray
     * @param mixed[] $newArray
     * @param bool $recursive
     * @param callable|null $callback An optional callback method to test the equality between to values. The callback shall
     *                                accept two parameters (the values) and return true if the values are equals.
     * @return mixed[]
     */
    public function getChanges(array $initialArray, array $newArray, bool $recursive = false, ?callable $callback = null): array
    {
        $changes = [];
        foreach ($newArray as $field => $value) {
            if (!array_key_exists($field, $initialArray)) {
                $changes[$field] = $value;
            }
            elseif ($recursive && is_array($initialArray[$field]) && is_array($value)) {
                $newVal = $this->getChanges($initialArray[$field], $value, $recursive, $callback);
                if (!empty($newVal)) {
                    $changes[$field] = $newVal;
                }
            }
            elseif ($callback === null && $value !== $initialArray[$field]) {
                $changes[$field] = $value;
            }
            elseif ($callback !== null && !$callback($value, $initialArray[$field])) {
                $changes[$field] = $value;
            }
        }
        return $changes;
    }

    /**
     * If the given $key is set in the array and if the corresponding value is not null,
     * cast this value and returns it. Else, returns null.
     *
     * Ex:
     *
     *    $array = ['a' => '123']
     *
     *     ArrayUtils::getAndCast($array, 'a', 'int')  // => returns `123` as an integer
     *     ArrayUtils::getAndCast($array, 'b', 'int')  // => returns `null`
     *
     * @param mixed[] $array
     * @param string $key
     * @param string $type
     * @return mixed
     */
    public static function getAndCast(array $array, mixed $key, string $type): mixed
    {
        $value = $array[$key] ?? null;
        if ($value !== null) {
            settype($value, $type);
        }
        return $value;
    }
}
