<?php

namespace Openguru\OpenCartFramework\CriteriaBuilder\Rules;

use Openguru\OpenCartFramework\CriteriaBuilder\Criterion;
use Openguru\OpenCartFramework\QueryBuilder\Builder;

abstract class BaseRule
{
    public const CRITERIA_OPTION_STRING = 'string';
    public const CRITERIA_OPTION_NUMBER = 'number';
    public const CRITERIA_OPTION_BOOLEAN = 'boolean';
    public const CRITERIA_OPTION_PRODUCT_CATEGORIES = 'product_categories';
    public const CRITERIA_OPTION_PRODUCT_CATEGORY = 'product_category';
    public const CRITERIA_OPTION_PRODUCT_MANUFACTURER = 'product_manufacturer';
    public const CRITERIA_OPTION_PRODUCT_ATTRIBUTE = 'product_attribute';
    public const CRITERIA_OPTION_PRODUCT_MODEL = 'product_model';
    public const CRITERIA_OPTION_PRODUCT_FOR_MAIN_PAGE = 'product_for_main_page';
    public const CRITERIA_OPTION_PRODUCT_OPTION = 'product_option';

    public const CRITERIA_OPERATOR_CONTAINS = 'contains';
    public const CRITERIA_OPERATOR_NOT_CONTAINS = 'not_contains';
    public const CRITERIA_OPERATOR_GREATER_OR_EQUAL = 'greater_or_equals';
    public const CRITERIA_OPERATOR_EQUALS = 'equals';

    protected static $numberCompareOperators = [
        'between' => 'BETWEEN',
        'equals' => '=',
        'greater' => '>',
        'greater_or_equals' => '>=',
        'less' => '<',
        'less_or_equals' => '<=',
        'not_equals' => '<>',
    ];

    protected static $stringCompareOperators = [
        'contains' => 'LIKE',
        'not_contains' => 'NOT LIKE',
        'equals' => '=',
        'not_equals' => '<>',
        'is_empty' => 'is_empty',
        'is_not_empty' => 'is_not_empty',
    ];

    /**
     * @var string
     */
    public $id;
    /**
     * @var array<Criterion>
     */
    public $criteria;

    public function __construct(
        string $id,
        array $criteria
    ) {
        $this->id = $id;
        $this->criteria = $criteria;
    }

    abstract public static function initWithDefaults(): BaseRule;

    public function metaAttributes(): array
    {
        return [
            'group' => 'other',
        ];
    }

    abstract public function apply(Builder $builder, $operand);

    public function criterionStringCondition(
        Builder $builder,
        Criterion $criterion,
        string $field,
        string $operand,
        string $joinAlias = ''
    ): void {
        $operator = static::$stringCompareOperators[$criterion->params['operator']];
        $keyword = $criterion->params['keyword'];

        if ($operator === 'is_empty') {
            if ($joinAlias) {
                $builder->whereNested(function (Builder $query) use ($field, $joinAlias) {
                    $query->whereRaw("TRIM($field) = '' OR {$joinAlias}.product_id IS NULL");
                });
            } else {
                $builder->whereRaw("TRIM($field) = ''");
            }

            return;
        }

        if ($operator === 'is_not_empty') {
            $builder->whereRaw("TRIM($field) <> ''");
            return;
        }

        if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
            $keyword = "%$keyword%";
        }

        $builder->where($field, $operator, $keyword, $operand);
    }
}
