<?php declare (strict_types = 1);

namespace MailPoet\Doctrine\WPDB;

if (!defined('ABSPATH')) exit;


use MailPoet\Doctrine\WPDB\Exceptions\MissingParameterException;
use MailPoetVendor\Doctrine\DBAL\ParameterType;
use MailPoetVendor\Doctrine\DBAL\SQL\Parser\Visitor;

class ConvertParameters implements Visitor {
  private const PARAM_TYPE_MAP = [
    ParameterType::STRING => '%s',
    ParameterType::INTEGER => '%d',
    ParameterType::ASCII => '%s',
    ParameterType::BINARY => '%s',
    ParameterType::BOOLEAN => '%d',
    ParameterType::NULL => '%s',
    ParameterType::LARGE_OBJECT => '%s',
  ];

  /** @var list<string> */
  private array $buffer = [];

  /** @var array<array-key, array{0: string, 1: mixed, 2: int}> */
  private array $params;

  private array $values = [];

  private int $cursor = 1;

  public function __construct(
    array $params
  ) {
    $this->params = $params;
  }

  public function acceptPositionalParameter(string $sql): void {
    $position = $this->cursor++;
    $this->acceptParameter($position);
  }

  public function acceptNamedParameter(string $sql): void {
    $this->acceptParameter(trim($sql, ':'));
  }

  public function acceptOther(string $sql): void {
    $this->buffer[] = $sql;
  }

  public function getSQL(): string {
    return implode('', $this->buffer);
  }

  public function getValues(): array {
    return $this->values;
  }

  /** @param array-key $key */
  private function acceptParameter($key): void {
    if (!array_key_exists($key, $this->params)) {
      throw new MissingParameterException(sprintf("Parameter '%s' was defined in the query, but not provided.", $key));
    }
    [, $value, $type] = $this->params[$key];

    // WPDB doesn't support NULL values. We need to handle them explicitly.
    if ($value === null) {
      $this->buffer[] = 'NULL';
      return;
    }

    // WPDB doesn't accept non-scalar values. We need to cast them (PDO-like behavior).
    if (!is_scalar($value)) {
      if ($type === ParameterType::INTEGER) {
        $value = (int)$value; // @phpstan-ignore-line -- cast may fail and that's OK
      } elseif ($type === ParameterType::BOOLEAN) {
        $value = (bool)$value;
      } else {
        $value = (string)$value; // @phpstan-ignore-line -- cast may fail and that's OK
      }
    }

    $this->values[] = $value;
    $this->buffer[] = self::PARAM_TYPE_MAP[$type] ?? '%s';
  }
}
