<?php

namespace Openguru\OpenCartFramework\Support;

use InvalidArgumentException;

class Arr
{
    /**
     * Transform an array to use a specified field as keys.
     *
     * @param array $array Input array of items.
     * @param string $keyField Field name to be used as keys.
     * @return array Transformed array with specified field as keys.
     * @throws InvalidArgumentException If the key field is missing in any item.
     */
    public static function keyByField(array $array, string $keyField): array
    {
        $result = [];
        foreach ($array as $item) {
            if (! array_key_exists($keyField, $item)) {
                throw new InvalidArgumentException("Key field '{$keyField}' is missing in one of the items.");
            }
            $result[$item[$keyField]] = $item;
        }

        return $result;
    }

    /**
     * Group array by a specific key and collect values into sub-arrays.
     *
     * @param array $data Array to group.
     * @param string $groupKey Key to group by.
     * @param string $valueKey Key to collect values from.
     * @return array Grouped array.
     */
    public static function groupByKey(array $data, string $groupKey, string $valueKey): array
    {
        $result = [];

        foreach ($data as $item) {
            $result[$item[$groupKey]][] = $item[$valueKey];
        }

        return $result;
    }

    public static function has(array $items, string $key): bool
    {
        if (empty($items)) {
            return false;
        }

        if (array_key_exists($key, $items)) {
            return true;
        }

        $segments = explode('.', $key);
        foreach ($segments as $segment) {
            if (! is_array($items) || ! array_key_exists($segment, $items)) {
                return false;
            }

            $items = $items[$segment];
        }

        return true;
    }

    public static function get(array $items, string $key, $default = null)
    {
        if (! $items) {
            return $default;
        }

        if (array_key_exists($key, $items)) {
            return $items[$key];
        }

        $segments = explode('.', $key);
        foreach ($segments as $segment) {
            if (! is_array($items) || ! array_key_exists($segment, $items)) {
                return $default;
            }

            $items = &$items[$segment];
        }

        return $items;
    }

    public static function set(array &$array, string $key, $value): void
    {
        $keys = explode('.', $key);
        foreach ($keys as $k) {
            if (! isset($array[$k]) || ! is_array($array[$k])) {
                $array[$k] = [];
            }
            $array = &$array[$k];
        }
        $array = $value;
    }

    public static function unset(array &$array, string $key): void
    {
        $keys = explode('.', $key);
        while (count($keys) > 1) {
            $k = array_shift($keys);
            if (! isset($array[$k]) || ! is_array($array[$k])) {
                return;
            }
            $array = &$array[$k];
        }
        unset($array[array_shift($keys)]);
    }

    public static function find(array $array, callable $callback)
    {
        foreach ($array as $key => $value) {
            if ($callback($value, $key) === true) {
                return $value;
            }
        }

        return null;
    }

    public static function pluck(array $value, string $field): array
    {
        return array_map(
            static function ($item) use ($field) {
                if (is_object($item)) {
                    return $item->{$field};
                }

                return $item[$field];
            },
            $value
        );
    }

    public static function mergeArraysRecursively(array $base, array $override): array
    {
        $result = $base;

        foreach ($override as $key => $value) {
            if (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
                $result[$key] = static::mergeArraysRecursively($result[$key], $value);
            } else {
                $result[$key] = $value;
            }
        }

        return $result;
    }

    /**
     * Объединяет массивы рекурсивно, где ключи override могут быть в dot notation.
     * Преобразует dot notation ключи в вложенную структуру перед объединением.
     *
     * @param array $base Базовый массив
     * @param array $override Массив с ключами в dot notation (например, ['app.logs.path' => '/var/log'])
     * @return array Объединенный массив
     *
     * @example
     * $base = ['app' => ['name' => 'MyApp']];
     * $override = ['app.logs.path' => '/var/log'];
     * // Результат: ['app' => ['name' => 'MyApp', 'logs' => ['path' => '/var/log']]]
     */
    public static function mergeArraysRecursivelyWithDotNotation(array $base, array $override): array
    {
        $overrideNested = [];
        foreach ($override as $key => $value) {
            if (strpos($key, '.') !== false) {
                // Используем существующий метод set для создания вложенной структуры
                static::set($overrideNested, $key, $value);
            } else {
                // Обычный ключ без dot notation
                $overrideNested[$key] = $value;
            }
        }

        // Объединяем с базовым массивом
        return static::mergeArraysRecursively($base, $overrideNested);
    }

    /**
     * Возвращает массив только с указанными ключами в dot notation
     * Сохраняет структуру вложенности из исходного массива
     *
     * @param array $array Исходный массив
     * @param array $keys Массив ключей в dot notation (например, ['app.name', 'app.logs.path', 'telegram.bot_token'])
     * @return array Отфильтрованный массив с сохранением структуры
     */
    public static function getWithKeys(array $array, array $keys): array
    {
        $filtered = [];

        foreach ($keys as $key) {
            if (static::has($array, $key)) {
                $value = static::get($array, $key);
                static::set($filtered, $key, $value);
            }
        }

        return $filtered;
    }

    /**
     * Возвращает массив без указанных ключей.
     *
     * @param array $array Исходный массив
     * @param array $keys Массив ключей, которые нужно исключить
     * @return array Массив без исключенных ключей
     */
    public static function except(array $array, array $keys): array
    {
        if (empty($keys)) {
            return $array;
        }

        return array_diff_key($array, array_flip($keys));
    }
}
