<?php

namespace lib\Core\Database;

use PDO;

class QueryBuilder_Ver2
{
  private $_debug = false;
  protected PDO $pdo;
  protected string $table = '';
  protected array $selects = [];
  protected array $wheres = [];
  protected array $bindings = [];
  protected array $likes = [];
  protected array $likeIns = [];
  protected array $notLikeIns = [];
  protected string $orderBy = '';
  protected string $groupBy = '';
  protected string $limit = '';
  protected bool $debug = false;
  protected bool $inTransaction = false;

  public function __construct(PDO $pdo)
  {
    $this->pdo = $pdo;
  }

  final public function setDebug(bool $debug): void
  {
    $this->_debug = $debug;
  }

  public function table(string $table): self
  {
    $this->table = $table;
    return $this;
  }

  public function select(...$columns): self
  {
    $this->selects = array_merge($this->selects, $columns);
    return $this;
  }

  public function where(string $column, mixed $value = null, ?string $operator = null, string $boolean = "AND")
  {
    $this->wheres[] = "{$this->escapeIdentifier($column)} {$operator} ?";
    $this->bindings[] = $value;
    return $this;
  }

  public function like(string $column, string $value): self
  {
    $this->likes[] = $this->escapeIdentifier($column) . " LIKE ?";
    $this->bindings[] = "%" . $this->escapeLikeString($value) . "%";
    return $this;
  }

  public function orLike(string $column, string $value): self
  {
    $last = array_pop($this->likes);
    $condition = $last ? "({$last} OR {$this->escapeIdentifier($column)} LIKE ?)" : "{$this->escapeIdentifier($column)} LIKE ?";
    $this->likes[] = $condition;
    $this->bindings[] = "%" . $this->escapeLikeString($value) . "%";
    return $this;
  }

  public function likeIn(string $column, array $values): self
  {
    $clauses = [];
    foreach ($values as $val) {
      $clauses[] = $this->escapeIdentifier($column) . " LIKE ?";
      $this->bindings[] = "%" . $this->escapeLikeString($val) . "%";
    }
    $this->likeIns[] = '(' . implode(' OR ', $clauses) . ')';
    return $this;
  }

  public function notLikeIn(string $column, array $values): self
  {
    $clauses = [];
    foreach ($values as $val) {
      $clauses[] = $this->escapeIdentifier($column) . " NOT LIKE ?";
      $this->bindings[] = "%" . $this->escapeLikeString($val) . "%";
    }
    $this->notLikeIns[] = '(' . implode(' AND ', $clauses) . ')';
    return $this;
  }

  public function orderBy(string $column, string $direction = 'ASC'): self
  {
    $this->orderBy = "ORDER BY {$this->escapeIdentifier($column)} {$direction}";
    return $this;
  }

  public function groupBy(string $column): self
  {
    $this->groupBy = "GROUP BY {$this->escapeIdentifier($column)}";
    return $this;
  }

  public function limit(int $limit, int $offset = 0): self
  {
    $this->limit = "LIMIT {$offset}, {$limit}";
    return $this;
  }

  public function get(): array
  {
    $stmt = $this->execute();
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
  }

  public function first(): ?array
  {
    $this->limit(1);
    $stmt = $this->execute();
    return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
  }

  public function count(): int
  {
    $this->select("COUNT(*) as count");
    $stmt = $this->execute();
    return (int)($stmt->fetch(PDO::FETCH_ASSOC)['count'] ?? 0);
  }

  public function debug(bool $debug = true): self
  {
    $this->debug = $debug;
    return $this;
  }

  public function beginTransaction(): void
  {
    if (!$this->inTransaction) {
      $this->pdo->beginTransaction();
      $this->inTransaction = true;
    }
  }

  public function commit(): void
  {
    if ($this->inTransaction) {
      $this->pdo->commit();
      $this->inTransaction = false;
    }
  }

  public function rollback(): void
  {
    if ($this->inTransaction) {
      $this->pdo->rollBack();
      $this->inTransaction = false;
    }
  }

  public function execute(): \PDOStatement
  {
    $sql = $this->buildSelectQuery();

    if ($this->debug) {
      echo "[SQL]: {$sql}\n";
      echo "[Bindings]: " . json_encode($this->bindings, JSON_UNESCAPED_UNICODE) . "\n";
    }

    $stmt = $this->pdo->prepare($sql);
    $stmt->execute($this->bindings);
    return $stmt;
  }

  protected function buildSelectQuery(): string
  {
    $columns = $this->selects ? implode(', ', array_map([$this, 'escapeIdentifier'], $this->selects)) : '*';
    $sql = "SELECT {$columns} FROM {$this->escapeIdentifier($this->table)}";

    $conditions = array_merge($this->wheres, $this->likes, $this->likeIns, $this->notLikeIns);
    if (!empty($conditions)) {
      $sql .= " WHERE " . implode(' AND ', $conditions);
    }

    if ($this->groupBy) {
      $sql .= " {$this->groupBy}";
    }

    if ($this->orderBy) {
      $sql .= " {$this->orderBy}";
    }

    if ($this->limit) {
      $sql .= " {$this->limit}";
    }

    return $sql;
  }

  // Escape helpers

  public function escape(mixed $value): string
  {
    return $this->pdo->quote($value);
  }

  public function escapeIdentifier(string $identifier): string
  {
    return "`" . str_replace("`", "``", $identifier) . "`";
  }

  public function escapeLikeString(string $value): string
  {
    return str_replace(['%', '_'], ['\\%', '\\_'], $value);
  }

  public function escapeLikeIn(array $values): array
  {
    return array_map([$this, 'escapeLikeString'], $values);
  }
}
