<?php

namespace Lightscale\LaralightTables;

use Lightscale\LaralightTables\Columns\Column;

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\Support\Str;
use Illuminate\Support\Collection;

use Exception;

abstract class TableComponent extends Component
{
    use WithPagination;

    // Config
    protected $paginationTheme = 'bootstrap';
    protected $model = null;
    protected int $defaultPageSize = 10;

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

    #[Url]
    public int $pageSize;

    public array $activeColumns = [];

    #[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->getToolbar()->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';
        }
    }

    protected function toolbar(): ?Toolbar
    {
        return null;
    }

    private ?Toolbar $toolbarCache;

    protected function getToolbar(): ?Toolbar
    {
        if (!isset($this->_toolbar)) {
            $this->toolbar = $this->toolbar();
        }
        return $this->toolbar;
    }

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

        return $this->model::query();
    }

    abstract protected function columns() : array;

    protected function search(Builder $builder, string $search) : void {}

    protected function getFilters(): Collection
    {
        return $this->getToolbar()?->getFilters() ?? collect();
    }


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

    protected function applyOrder(Builder $query): void
    {
        $column = $this->getOrderColumn();
        if ($column) {
            $column->applySort($query, $this->orderDirection);
        }
    }

    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;
    }

    private ?Collection $columnsCache = null;
    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()
    {
        $data = $this->buildQuery()->paginate($this->pageSize);
        $allColumns = $this->getColumns();
        $columns = $allColumns->filter(fn($c) => in_array($c->name,$this->activeColumns));
        $toolbar = $this->toolbar();

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

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