<?php
declare(strict_types=1);

require_once __DIR__ . '/../../../work_store/apifox/2026-09-30-dm8-interface-acceptance/parallel-odbc-typed-fetch/TypedFetchNormalizer.php';

/**
 * Isolated PDO_ODBC prototype. Requires the private column-metadata module.
 * It is not installed in either application or a global PHP configuration.
 */
class TypedStatement extends PDOStatement
{
    private $metadata;
    private $defaultMode = PDO::FETCH_ASSOC;

    protected function __construct()
    {
    }

    public function execute($parameters = null)
    {
        $this->metadata = null;
        return func_num_args() === 0
            ? $this->rawExecute()
            : $this->rawExecute($parameters);
    }

    public function nextRowset()
    {
        $next = parent::nextRowset();
        if ($next) {
            $this->metadata = null;
        }
        return $next;
    }

    public function setFetchMode($mode, $params = null)
    {
        $this->checkRowMode($mode);
        if ($params !== null || func_num_args() > 2) {
            throw new InvalidArgumentException('Additional fetch-mode arguments are unsupported');
        }
        $success = $this->rawSetFetchMode($mode);
        if ($success) {
            $this->defaultMode = $mode;
        }
        return $success;
    }

    public function fetch($how = null, $orientation = PDO::FETCH_ORI_NEXT, $offset = 0)
    {
        $mode = $this->mode($how);
        $this->checkFetchMode($mode);
        $raw = $this->rawFetch($orientation, $offset);
        if ($raw === false) {
            return false;
        }
        $values = $this->convertedValues($raw);
        if ($mode === PDO::FETCH_COLUMN) {
            return $values[0];
        }
        return $this->shape($raw, $values, $mode);
    }

    public function fetchAll($how = null, $column = null, $ctorArgs = null)
    {
        $mode = $this->mode($how);
        $this->checkFetchMode($mode);
        if ($ctorArgs !== null || func_num_args() > 3) {
            throw new InvalidArgumentException('Constructor fetch arguments are unsupported');
        }
        if ($mode === PDO::FETCH_COLUMN) {
            $index = $column === null ? 0 : $column;
            $this->checkColumn($index);
            $rows = [];
            while (($raw = $this->rawFetch(PDO::FETCH_ORI_NEXT, 0)) !== false) {
                $rows[] = $this->convertedValues($raw)[$index];
            }
            return $rows;
        }
        if ($column !== null) {
            throw new InvalidArgumentException('Column argument requires FETCH_COLUMN');
        }
        $rows = [];
        while (($row = $this->fetch($mode)) !== false) {
            $rows[] = $row;
        }
        return $rows;
    }

    public function fetchColumn($columnNumber = 0)
    {
        $this->checkColumn($columnNumber);
        $raw = $this->rawFetch(PDO::FETCH_ORI_NEXT, 0);
        if ($raw === false) {
            return false;
        }
        return $this->convertedValues($raw)[$columnNumber];
    }

    protected function rawFetch($orientation, $offset)
    {
        return parent::fetch(PDO::FETCH_BOTH, $orientation, $offset);
    }

    protected function rawColumnCount(): int
    {
        return parent::columnCount();
    }

    protected function rawColumnMeta(int $index)
    {
        return parent::getColumnMeta($index);
    }

    protected function rawSetFetchMode($mode): bool
    {
        return parent::setFetchMode($mode);
    }

    protected function rawExecute($parameters = null)
    {
        return func_num_args() === 0
            ? parent::execute()
            : parent::execute($parameters);
    }

    private function mode($how): int
    {
        return $how === null || $how === 0
            ? $this->defaultMode
            : $how;
    }

    private function checkRowMode($mode): void
    {
        if (!in_array($mode, [PDO::FETCH_ASSOC, PDO::FETCH_NUM, PDO::FETCH_BOTH], true)) {
            throw new InvalidArgumentException('Fetch mode unsupported by typed prototype');
        }
    }

    private function checkFetchMode($mode): void
    {
        if ($mode === PDO::FETCH_COLUMN) {
            return;
        }
        $this->checkRowMode($mode);
    }

    private function checkColumn($index): void
    {
        if (!is_int($index) || $index < 0 || $index >= count($this->columns())) {
            throw new InvalidArgumentException('Invalid column index');
        }
    }

    private function columns(): array
    {
        if ($this->metadata !== null) {
            return $this->metadata;
        }
        $columns = [];
        $count = $this->rawColumnCount();
        for ($index = 0; $index < $count; ++$index) {
            $meta = $this->rawColumnMeta($index);
            if (!is_array($meta) || !isset($meta['name'])
                || !is_string($meta['name']) || $meta['name'] === ''
                || !isset($meta['odbc:sql_type']) || !is_int($meta['odbc:sql_type'])) {
                throw new UnexpectedValueException('ODBC column metadata unavailable');
            }
            $columns[] = $meta;
        }
        return $this->metadata = $columns;
    }

    private function convertedValues($raw): array
    {
        if (!is_array($raw)) {
            throw new UnexpectedValueException('PDO result row is not an array');
        }
        $values = [];
        foreach ($this->columns() as $index => $meta) {
            if (!array_key_exists($index, $raw)) {
                throw new UnexpectedValueException('PDO result row is missing a numeric column');
            }
            $decision = TypedFetchNormalizer::normalize($raw[$index], $meta);
            $values[$index] = $decision['value'];
        }
        return $values;
    }

    private function shape(array $raw, array $values, int $mode): array
    {
        if ($mode === PDO::FETCH_NUM) {
            return $values;
        }
        $metadata = $this->columns();
        $indexByName = [];
        foreach ($metadata as $index => $meta) {
            $folded = strtolower($meta['name']);
            if (isset($indexByName[$folded])) {
                throw new UnexpectedValueException('Ambiguous result column name');
            }
            $indexByName[$folded] = $index;
        }
        $result = [];
        $seen = [];
        foreach ($raw as $key => $_) {
            if (is_int($key)) {
                if (!array_key_exists($key, $values)) {
                    throw new UnexpectedValueException('PDO result has an unexpected numeric column');
                }
                if ($mode === PDO::FETCH_BOTH) {
                    $result[$key] = $values[$key];
                }
                continue;
            }
            $folded = strtolower($key);
            if (!isset($indexByName[$folded])) {
                throw new UnexpectedValueException('Result key has no matching column metadata');
            }
            $index = $indexByName[$folded];
            if ($raw[$key] !== $raw[$index]) {
                throw new UnexpectedValueException('PDO numeric and associative values disagree');
            }
            $result[$key] = $values[$index];
            $seen[$index] = true;
        }
        if (count($seen) !== count($metadata)) {
            throw new UnexpectedValueException('Associative result keys are incomplete');
        }
        return $result;
    }
}
