<?php

declare(strict_types=1);

namespace ACP\Filtering\RequestHandler;

use AC\Column\ColumnLabelTrait;
use AC\ListScreen;
use AC\Message;
use AC\Message\Notice;
use AC\Request;
use AC\Type\ColumnId;
use ACP\Column;
use ACP\Filtering\EmptyOptions;
use ACP\Query\Bindings;
use ACP\Query\QueryRegistry;
use ACP\Search\Comparison;
use ACP\Search\Operators;
use ACP\Search\Value;
use Exception;

class Filters
{

    use ColumnLabelTrait;

    public const KEY = 'acp_filter';

    private ListScreen $list_screen;

    public function __construct(ListScreen $list_screen)
    {
        $this->list_screen = $list_screen;
    }

    private function is_request(Request $request): bool
    {
        return (bool)$request->get(self::KEY);
    }

    public function handle(Request $request): void
    {
        if ( ! $this->is_request($request)) {
            return;
        }

        $filters = $request->filter(self::KEY, [], FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);

        if ( ! $filters) {
            return;
        }

        $bindings = $this->create_bindings($filters);

        if ( ! $bindings) {
            return;
        }

        QueryRegistry::create($this->list_screen->get_table_screen(), $bindings)->register();
    }

    private function use_operator_contains(Comparison $comparison): bool
    {
        return ! $comparison instanceof Comparison\RemoteValues &&
               ! $comparison instanceof Comparison\SearchableValues &&
               ! $comparison instanceof Comparison\Values &&
               $comparison->get_operators()->search(Operators::CONTAINS);
    }

    private function create_bindings_by_column(ColumnId $column_id, $request_value): ?Bindings
    {
        if ('' === $request_value) {
            return null;
        }

        $column = $this->list_screen->get_column($column_id);

        if ( ! $column instanceof Column) {
            return null;
        }

        $comparison = $column->search();

        if ( ! $comparison) {
            return null;
        }

        $value = new Value($request_value, $comparison->get_value_type());

        $operator = $this->use_operator_contains($comparison)
            ? Operators::CONTAINS
            : Operators::EQ;

        $filter_format = $column->get_setting('filter_format');

        if ($filter_format) {
            switch ($filter_format->get_input()->get_value()) {
                case '':
                case 'daily':
                    return $comparison->get_query_bindings(Operators::EQ, $value);
                case 'monthly':
                    return $comparison->get_query_bindings(Operators::EQ_MONTH, $value);
                case 'yearly':
                    return $comparison->get_query_bindings(Operators::EQ_YEAR, $value);
                case 'future_past':
                    $operator = 'future' === $value->get_value()
                        ? Operators::FUTURE
                        : Operators::PAST;

                    return $comparison->get_query_bindings($operator, $value);
                case 'range':
                    return $this->get_query_bindings_range($value, $comparison);
                default:
                    return null;
            }
        }

        if (is_array($value->get_value())) {
            return $this->get_query_bindings_range($value, $comparison);
        }

        switch ($value->get_value()) {
            case EmptyOptions::IS_EMPTY:
                return $comparison->get_query_bindings(Operators::IS_EMPTY, new Value(null, $value->get_type()));
            case EmptyOptions::NOT_IS_EMPTY:
                return $comparison->get_query_bindings(Operators::NOT_IS_EMPTY, new Value(null, $value->get_type()));
        }

        return $comparison->get_query_bindings($operator, $value);
    }

    private function get_query_bindings_range(Value $value, Comparison $comparison): ?Bindings
    {
        $min = $value->get_value()[0] ?? null;
        $max = $value->get_value()[1] ?? null;

        switch (true) {
            case $min && $max:
                return $comparison->get_query_bindings(Operators::BETWEEN, $value);
            case $min:
                return $comparison->get_query_bindings(Operators::GTE, new Value($min, $value->get_type()));
            case $max:
                return $comparison->get_query_bindings(Operators::LTE, new Value($max, $value->get_type()));
            default:
                return null;
        }
    }

    private function create_bindings(array $filters): array
    {
        $bindings = [];

        foreach ($filters as $column_name => $value) {
            try {
                $binding = $this->create_bindings_by_column(
                    new ColumnId((string)$column_name),
                    $value
                );
            } catch (Exception $e) {
                $this->display_notice($column_name, $e->getMessage());
                continue;
            }

            if ( ! $binding) {
                continue;
            }

            $bindings[] = $binding;
        }

        return $bindings;
    }

    private function display_notice(string $column_name, string $message): void
    {
        $column = $this->list_screen->get_column(new ColumnId($column_name));

        $label = $this->get_column_label($column);

        $message = sprintf('Filter %s: %s', sprintf('<strong>%s</strong>', $label), $message);

        (new Notice($message, Message::WARNING))->register();
    }

}