<?php

namespace App\Traits;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
use Illuminate\Support\Collection;

trait GetModelsPaginated
{
    protected function paginateFromService(
        Request $request,
        callable $serviceCallback,
        array $options = []
    ): JsonResponse {
        $start = (int) $request->input('start', 0);
        $length = max(1, (int) $request->input('length', 10));
        $status = $request->input('status');
        $draw = (int) $request->input('draw', 1);
        $searchValue = $request->input('search.value');
        $page = (int) floor($start / $length) + 1;

        $result = $serviceCallback($page, $length, $searchValue, $status, $request);
        $paginator = $this->normalizePaginator($result, $page, $length, $request);

        $items = collect($paginator->items());
        $items = $this->applyClientOrdering($items, $request);
        $fields = $this->resolveFields($items, $options['fields'] ?? null);
        $data = $this->mapDataByFields($items, $fields);

        return response()->json([
            'model' => $options['model'] ?? null,
            'fields' => $fields,
            'draw' => $draw,
            'recordsTotal' => $paginator->total(),
            'recordsFiltered' => $paginator->total(),
            'data' => $data,
            'next_page_url' => $paginator->nextPageUrl(),
            'prev_page_url' => $paginator->previousPageUrl(),
        ]);
    }

    protected function normalizePaginator(
        mixed $result,
        int $page,
        int $length,
        Request $request
    ): LengthAwarePaginator {
        if ($result instanceof LengthAwarePaginator) {
            return $result->appends($request->query());
        }

        $collection = $result instanceof Collection ? $result : collect($result);
        $total = $collection->count();
        $items = $collection->forPage($page, $length)->values();

        return new Paginator(
            $items,
            $total,
            $length,
            $page,
            ['path' => $request->url(), 'query' => $request->query()]
        );
    }

    protected function resolveFields(Collection $items, ?array $configuredFields): array
    {
        if (! empty($configuredFields)) {
            return $configuredFields;
        }

        $first = $items->first();

        if (is_array($first)) {
            return array_keys($first);
        }

        if (is_object($first)) {
            return array_keys((array) $first->toArray());
        }

        return [];
    }

    protected function mapDataByFields(Collection $items, array $fields): array
    {
        if (empty($fields)) {
            return $items->map(function ($item) {
                return is_object($item) && method_exists($item, 'toArray')
                    ? $item->toArray()
                    : (array) $item;
            })->toArray();
        }

        return $items->map(function ($item) use ($fields) {
            $raw = is_object($item) && method_exists($item, 'toArray')
                ? $item->toArray()
                : (array) $item;

            return collect($raw)->only($fields)->toArray();
        })->toArray();
    }

    protected function applyClientOrdering(Collection $items, Request $request): Collection
    {
        $orderColumnIndex = $request->input('order.0.column');
        $orderDir = strtolower((string) $request->input('order.0.dir', 'asc'));

        if ($orderColumnIndex === null) {
            return $items;
        }

        $columnKey = $request->input("columns.{$orderColumnIndex}.name");
        if (empty($columnKey)) {
            $columnKey = $request->input("columns.{$orderColumnIndex}.data");
        }

        if (empty($columnKey) || $columnKey === 'action' || $columnKey === 'edit') {
            return $items;
        }

        $sorted = $items->sortBy(function ($item) use ($columnKey) {
            $raw = is_object($item) && method_exists($item, 'toArray')
                ? $item->toArray()
                : (array) $item;

            $value = data_get($raw, $columnKey);

            if ($value === null) {
                return '';
            }

            if (is_numeric($value)) {
                return (float) $value;
            }

            return mb_strtolower((string) $value);
        });

        return $orderDir === 'desc' ? $sorted->reverse()->values() : $sorted->values();
    }
}
