<?php

namespace Lightscale\LaralightTables;

use Lightscale\LaralightTables\Columns\Column;
use Lightscale\LaralightTables\Toolbar\Filter;
use Lightscale\LaralightAssets\Facades\Assets;

use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;

use Illuminate\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\View\View;

use Exception;


/**
 * @template TModel of Model
 */
abstract class TableComponent extends Component
{
    use WithPagination;

    // Config

    /**
     * @var string
     */
    protected $paginationTheme = 'bootstrap';


    /**
     * @var ?class-string<TModel>
     */
    protected $model = null;

    protected int $defaultPageSize = 10;

    // Properties
    #[Url]
    public string $search = '';

    #[Url]
    public int $pageSize;

    /**
     * @var array<string>
     */
    public array $activeColumns = [];

    /**
     * @var array<string, string>
     */
    #[Url]
    public array $filters = [];

    #[Url]
    public ?string $order = null;

    #[Url]
    public ?string $orderDirection = null;

    public function mount(): void
    {
        $this->setDefaultActiveColumns();
        $this->setDefaultPageSize();

        Assets::queueStyle('laralight-tables::css/main.css');
    }

    protected function setDefaultPageSize(): void
    {
        if (!isset($this->pageSize)) {
            $this->pageSize = $this->defaultPageSize;
        }
    }

    protected function setDefaultActiveColumns(): void
    {
        foreach($this->getColumns() as $column) {
            $this->activeColumns[] = $column->name;
        }
    }

    protected function isSearching(): bool
    {
        $search = $this
            ->getToolbars()
            ->first(fn(Toolbar $tb) => $tb->getSearch() !== null)
            ?->getSearch();
        return $search !== null && Str::length($this->search) >= $search->getMinLength();
    }

    public function updatedSearch(): void
    {
        if ($this->isSearching()) {
            $this->resetPage();
        }
    }

    public function updatedFilters(): void
    {
        $this->resetPage();
    }

    public function orderBy(string $column): void
    {
        if ($column === $this->order) {
            if ($this->orderDirection === 'desc') {
                $this->order = null;
                $this->orderDirection = null;
            }
            else {
                $this->orderDirection = 'desc';
            }
        }
        else {
            $this->order = $column;
            $this->orderDirection = 'asc';
        }
    }

    /**
     * @return array<Toolbar>
     */
    protected function toolbars(): array
    {
        return [];
    }

    /**
     * @var Collection<int, Toolbar>
     */
    private Collection $toolbarsCache;

    /**
     * @return Collection<int, Toolbar>
     */
    protected function getToolbars(): Collection
    {
        if (!isset($this->toolbarsCache)) {
            $this->toolbarsCache = collect($this->toolbars());
        }
        return $this->toolbarsCache;
    }

    /**
     * @return Builder<TModel>
     * @phpstan-return Builder<TModel>
     */
    protected function query(): Builder
    {
        if($this->model === null) {
            throw new Exception('Requires $model to be set or query() method to be overridden');
        }

        /** @var Builder<TModel> */
        $query = $this->model::query();
        return $query;
    }

    /**
     * @return array<Column>
     */
    abstract protected function columns(): array;

    /**
     * @param Builder<TModel> $builder
     * @param string $search
     */
    protected function search(Builder $builder, string $search) : void {}

    /**
     * @return Collection<int, Filter>
     */
    protected function getFilters(): Collection
    {
        return $this->getToolbars()
            ->map(fn(Toolbar $toolbar): Collection => $toolbar->getFilters())
            ->flatten();
    }


    protected function getOrderColumn(): ?Column
    {
        return ($name = $this->order) === null ? null : $this->getColumns()[$name] ?? null;
    }

    /**
     * @param Builder<TModel> $query
     */
    protected function applyOrder(Builder $query): void
    {
        $column = $this->getOrderColumn();
        if ($column) {
            $column->applySort($query, $this->orderDirection);
        }
    }

    /**
     * @return Builder<TModel>
     */
    protected function buildQuery() : Builder
    {
        $query = $this->query();

        if ($this->isSearching()) {
            $this->search($query, $this->search);
        }

        foreach ($this->getFilters() as $filter) {
            $filter->applyFilter($query);
        }

        $this->applyOrder($query);

        return $query;
    }

    /**
     * @var ?Collection<int, Column>
     */
    private ?Collection $columnsCache = null;

    /**
     * @return Collection<int, Column>
     */
    public function getColumns(): Collection
    {
        if($this->columnsCache === null) {
            $this->columnsCache = collect($this->columns())->each(
                fn($c) => $c->setTable($this)
            )->keyBy('name');
        }

        return $this->columnsCache;
    }

    public function render(): View
    {
        $data = $this->buildQuery()->paginate($this->pageSize);
        $allColumns = $this->getColumns();
        $columns = $allColumns->filter(fn($c) => in_array($c->name,$this->activeColumns));
        $toolbars = $this->getToolbars();

        Paginator::defaultView('laralight-tables::pagination');

        return view('laralight-tables::table', compact(
            'data', 'allColumns', 'columns', 'toolbars',
        ));
    }
}