<?php

namespace App\Models;

use Illuminate\Database\Query\Builder as QueryBuilder;

class CiQueryBuilder
{
    protected QueryBuilder $builder;
    protected array $setData = [];

    protected array $tableMethods = [
        'join',
        'leftJoin',
        'rightJoin',
        'crossJoin',
        'from',
        'table',
        'joinWhere',
        'leftJoinWhere',
    ];

    public function __construct(QueryBuilder $builder)
    {
        $this->builder = $builder;
    }

    public function __call($method, $arguments)
    {
        $arguments = $this->normalizeTableArgument($method, $arguments);
        $result = $this->builder->$method(...$arguments);

        if ($result instanceof QueryBuilder) {
            $this->builder = $result;
            return $this;
        }

        return $result;
    }

    public function where($column, $operator = null, $value = null, $boolean = 'and')
    {
        if ($operator !== null && $value === null && preg_match('/^(.*)\s(>=|<=|<>|!=|>|<)$/', $column, $matches)) {
            return $this->where(trim($matches[1]), $matches[2], $operator, $boolean);
        }

        $this->builder = $this->builder->where($column, $operator, $value, $boolean);
        return $this;
    }

    public function like($column, $match, string $side = 'both')
    {
        $pattern = $this->wrapLike($match, $side);
        $this->builder = $this->builder->where($column, 'like', $pattern);
        return $this;
    }

    public function set($key, $value = null, bool $escape = true)
    {
        if (is_array($key)) {
            $this->setData = array_merge($this->setData, $key);
        } else {
            $this->setData[$key] = $value;
        }

        return $this;
    }

    public function update($values = [])
    {
        $data = $values ?: $this->setData;
        $this->setData = [];
        return $this->builder->update($data);
    }

    public function findAll(int $limit = 0, int $offset = 0)
    {
        if ($limit > 0) {
            $this->builder = $this->builder->limit($limit);
        }

        if ($offset > 0) {
            $this->builder = $this->builder->offset($offset);
        }

        return $this->getResultArray();
    }

    public function get($columns = ['*'])
    {
        $collection = $this->builder->get($columns);
        return new CiQueryResult($collection);
    }

    public function getResultArray(): array
    {
        return $this->get()->getResultArray();
    }

    public function getRowArray(): ?array
    {
        return $this->get()->getRowArray();
    }

    public function getRow(?string $column = null)
    {
        return $this->get()->getRow($column);
    }

    public function countAllResults(): int
    {
        return $this->builder->count();
    }

    protected function wrapLike($match, string $side): string
    {
        $pattern = "%{$match}%";

        switch (strtolower($side)) {
            case 'before':
                return "%{$match}";
            case 'after':
                return "{$match}%";
            default:
                return $pattern;
        }
    }

    protected function normalizeTableArgument(string $method, array $arguments): array
    {
        if (in_array($method, $this->tableMethods, true) && isset($arguments[0]) && is_string($arguments[0])) {
            $arguments[0] = $this->normalizeTableName($arguments[0]);
        }

        return $arguments;
    }

    protected function normalizeTableName(string $table): string
    {
        $normalized = trim($table);

        if (stripos($normalized, ' as ') !== false) {
            return $normalized;
        }

        if (strpos($normalized, ' ') !== false) {
            [$name, $alias] = preg_split('/\s+/', $normalized, 2);
            return "{$name} as {$alias}";
        }

        return $normalized;
    }
}
