<?php

namespace App\Core;

use Illuminate\Support\Collection;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Http\Exceptions\HttpResponseException;

class Responder
{
    private bool $success = true;

    private ?string $message = 'Operation done successfully!';

    private mixed $data = null;

    private ?array $meta = null;

    private ?string $jsonResourceClass = null;

    private int $httpCode = 200;

    private bool $isElasticSearchData = false;

    public function unSuccess(): static
    {
        $this->success = false;

        if (empty($this->message)) {
            $this->message = 'Operation is unsuccessful!';
        }

        return $this;
    }

    public function message(string $message): static
    {
        $this->message = $message;

        return $this;
    }

    public function data(mixed $data): static
    {
        $this->data = $data;

        $this->setMetaDataRelatedCurrentData();

        return $this;
    }

    public function meta(array $meta): static
    {
        $this->meta = $meta;

        return $this;
    }

    public function dataTable(array $data, string $resource, $resourceStaticExtraData = null): static
    {
        [$query, $meta] = $data;

        if (!is_null($resourceStaticExtraData)) {
            $resource::$extraData = $resourceStaticExtraData;
        }

        $this->data = $resource::collection($query);
        $this->meta = $meta;

        return $this;
    }

    public function setHttpCode($code): static
    {
        $this->httpCode = $code;

        return $this;
    }

    public function notAcceptable(): static
    {
        $this->setHttpCode(Response::HTTP_NOT_ACCEPTABLE);

        return $this;
    }

    public function created(): static
    {
        $this->setHttpCode(Response::HTTP_CREATED);

        return $this;
    }


    public function send(): void
    {
        $this->sendNormalResponse();
    }

    private function sendNormalResponse()
    {
        $response = [
            'success' => $this->success,
            'message' => trans($this->message),
        ];

        // just not null parameters set
        if (!is_null($this->data)) {
            $response['data'] = $this->normalizeData();
        }

        $this->meta && $response['meta'] = $this->meta;

        $this->throwResponseException($response, $this->httpCode);
    }

    private function throwResponseException(array $response, int $httpCode)
    {
        throw new HttpResponseException(response()->json($response, $httpCode));
    }

    private function isElasticSearchDataWithPagination(): bool
    {
        $elasticPaginationKeys = ['total_records', 'last_page', 'current_page', 'next_link', 'prev_link'];

        $data = $this->data;

        if ($data instanceof Collection) {
            $data = $this->data->toArray();
        }
        foreach ($elasticPaginationKeys as $elasticPaginationKey) {
            if (!is_array($data)) {
                return false;
            }
            if (!array_key_exists($elasticPaginationKey, $data)) {
                return false;
            }
        }

        return true;
    }

    private function setMetaPaginationFromElasticSearchData(): void
    {
        $this->meta([
            'pagination' => [
                'total' => $this->data['total_records'],
                'per_page' => request('page'),
                'current_page' => $this->data['current_page'],
                'last_page' => $this->data['current_page'],
            ]
        ]);
    }

    public function collectionFrom(string $jsonResourceClass): static
    {
        $this->jsonResourceClass = $jsonResourceClass;

        return $this;
    }

    private function normalizeData(): mixed
    {
        if ($this->isElasticSearchData) {
            $this->data = $this->data['data']->map(fn($singleData) => $singleData->getAttributes());
        }
        $jsonResourceClass = $this->jsonResourceClass;

        return $jsonResourceClass ? $jsonResourceClass::collection($this->data) : $this->data;
    }

    private function setMetaDataRelatedCurrentData(): void
    {
        if ($this->isElasticSearchDataWithPagination()) {
            $this->isElasticSearchData = true;
            $this->setMetaPaginationFromElasticSearchData();
        }
    }
}

