<?php

namespace Mautic\ApiBundle\Helper;

use Mautic\CoreBundle\Helper\CsvHelper;

class BatchIdToEntityHelper
{
    /**
     * @var array
     */
    private $ids = [];

    private array $originalKeys = [];

    private array $errors = [];

    private bool $isAssociative = false;

    /**
     * @param string $idKey
     */
    public function __construct(
        array $parameters,
        private $idKey = 'id',
    ) {
        $this->extractIds($parameters);
    }

    public function hasIds(): bool
    {
        return !empty($this->ids);
    }

    /**
     * @return array
     */
    public function getIds()
    {
        return $this->ids;
    }

    public function hasErrors(): bool
    {
        return !empty($this->errors);
    }

    /**
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * Reorder the entities based on the original keys
     * BC allowed a request to have associative keys (don't ask why; yes it's terrible implementation but we're keeping BC here)
     * The issue this solves is the response should match the format given by the request. If the request had associative keys, the response
     * will return with associative keys (json object). If the request was a sequential numeric array starting with 0, the response will
     * be a simple array (json array).
     */
    public function orderByOriginalKey(array $entities): array
    {
        if (!$this->isAssociative) {
            // The request was keyed by sequential numbers starting with 0
            return array_values($entities);
        }

        // Ensure entities are keyed by ID in order to find the original keys assuming that some entities are missing if the ID was not found
        $entitiesKeyedById = [];
        foreach ($entities as $entity) {
            $entitiesKeyedById[$entity->getId()] = $entity;
        }

        $orderedEntities = [];
        foreach ($this->ids as $key => $id) {
            if (!isset($entitiesKeyedById[$id])) {
                $hasPreviousId = array_filter(
                    $entities,
                    fn ($entity) => $id == $entity->getPreviousId()
                );

                if ($hasPreviousId) {
                    $orderedEntities[$key] = array_shift($hasPreviousId);
                }

                continue;
            }

            $originalKey                   = $this->originalKeys[$key];
            $orderedEntities[$originalKey] = $entitiesKeyedById[$id];
        }

        return $orderedEntities;
    }

    private function extractIds(array $parameters): void
    {
        $this->ids = [];

        if (isset($parameters['ids'])) {
            $this->extractIdsFromIdKey($parameters['ids']);

            return;
        }

        $this->extractIdsFromParams($parameters);
    }

    /**
     * @param mixed $ids
     */
    private function extractIdsFromIdKey($ids): void
    {
        // ['ids' => [1,2,3]]
        if (is_array($ids)) {
            $this->isAssociative = $this->isAssociativeArray($ids);
            $this->ids           = array_values($ids);
            $this->originalKeys  = array_keys($ids);

            return;
        }

        // ['ids' => '1,2,3'] OR ['ids' => '1']
        if (str_contains($ids, ',') || is_numeric($ids)) {
            $this->ids           = CsvHelper::strGetCsv($ids);
            $this->originalKeys  = array_keys($this->ids);
            $this->isAssociative = false;

            return;
        }

        // Couldn't parse the 'ids' key; not throwing an exception in order to keep BC with
        // the old CommonApiController code and the use of a foreach in extractIdsFromParams
        $this->errors[] = 'mautic.api.call.id_missing';
    }

    private function extractIdsFromParams(array $parameters): void
    {
        $this->isAssociative = $this->isAssociativeArray($parameters);
        $this->originalKeys  = array_keys($parameters);

        // [1,2,3]
        $firstKey            = array_key_first($parameters);
        if (!is_array($parameters[$firstKey])) {
            $this->ids = array_values($parameters);

            return;
        }

        // [ ['id' => 1, 'foo' => 'bar'], ['id' => 2, 'bar' => 'foo'] ]
        foreach ($parameters as $key => $params) {
            // Missing id column key in the array; terrible but keep BC
            if (!isset($params[$this->idKey])) {
                $this->errors[$key] = 'mautic.api.call.id_missing';

                continue;
            }

            $this->ids[] = $params[$this->idKey];
        }
    }

    private function isAssociativeArray(array $array): bool
    {
        if (empty($array)) {
            return false;
        }
        $firstKey = array_key_first($array);

        return array_keys($array) !== range(0, count($array) - 1) && 0 !== $firstKey;
    }

    public function setIsAssociative(bool $isAssociative): void
    {
        $this->isAssociative = $isAssociative;
    }
}
