<?php
declare(strict_types=1);
require_once __DIR__.'/../old-driver-shadow-fetch-prototype/ShadowCore.php';

/** Experimental SELECT-only Statement; no production autoload/config references. */
final class IteratorShadowStatement extends PDOStatement implements IteratorAggregate
{
    private $registry;private $defaultMode;private $plan;private $lookedUp=false;private $metadata;private $blocked=false;
    protected function __construct(ShadowRegistry $registry,int $defaultMode)
    {$this->registry=$registry;$this->defaultMode=$defaultMode;}

    /** Explicit iteration must use the same exact-text recovery as fetch(). */
    public function getIterator(): Traversable
    {
        while (($row = $this->fetch()) !== false) {
            yield $row;
        }
    }

    private function plan(): ?ShadowPlan
    {
        if(!$this->lookedUp){
            if(!is_string($this->queryString)){throw new UnexpectedValueException('Statement SQL unavailable');}
            $this->plan=$this->registry->find($this->queryString);$this->lookedUp=true;
        }
        return $this->plan;
    }
    private function layout(): array
    {
        if($this->blocked){throw new UnexpectedValueException('Planned rowset invalidated');}
        if($this->metadata!==null){return $this->metadata;}
        $columns=[];$n=parent::columnCount();
        for($i=0;$i<$n;++$i){$m=parent::getColumnMeta($i);if(!is_array($m)){throw new UnexpectedValueException('Raw metadata unavailable');}$columns[]=$m;}
        $this->plan()->validateLayout($columns);return $this->metadata=$columns;
    }
    private function rowMode($mode): int
    {
        return ShadowMode::row($mode,$this->defaultMode);
    }
    private function nextVisible()
    {
        $this->layout();$raw=parent::fetch(PDO::FETCH_NUM);
        return $raw===false?false:$this->plan()->restore($raw);
    }
    public function execute($parameters=null)
    {
        $this->metadata=null;$this->blocked=false;
        return parent::execute(...func_get_args());
    }
    public function closeCursor()
    {$this->metadata=null;return parent::closeCursor(...func_get_args());}
    public function nextRowset()
    {
        if($this->plan()===null){return parent::nextRowset(...func_get_args());}
        $this->blocked=true;$this->metadata=null;throw new InvalidArgumentException('Planned multi-rowset unsupported');
    }
    public function fetch($mode=null,$orientation=PDO::FETCH_ORI_NEXT,$offset=0)
    {
        if($this->plan()===null){return parent::fetch(...func_get_args());}
        $mode=$this->rowMode($mode);
        if($orientation!==PDO::FETCH_ORI_NEXT||$offset!==0||func_num_args()>3){throw new InvalidArgumentException('Planned orientation unsupported');}
        $row=$this->nextVisible();return $row===false?false:$this->plan()->shape($row,$mode);
    }
    public function fetchAll($mode=null,$parameter=null,$ctorArgs=null)
    {
        if($this->plan()===null){return parent::fetchAll(...func_get_args());}
        if($mode===null||$mode===ShadowMode::DEFAULT_MODE){$mode=$this->defaultMode;}
        if($mode===PDO::FETCH_COLUMN){
            if(func_num_args()>2){throw new InvalidArgumentException('Planned COLUMN arguments unsupported');}
            $index=$parameter===null?0:$parameter;$this->plan()->rawIndex($index);
            $out=[];while(($row=$this->nextVisible())!==false){$out[]=$row[$index];}return $out;
        }
        if($mode===PDO::FETCH_KEY_PAIR){
            if(func_num_args()>1||$this->plan()->visibleCount()!==2){throw new InvalidArgumentException('Planned KEY_PAIR requires two visible columns');}
            $out=[];while(($row=$this->nextVisible())!==false){if(!is_int($row[0])&&!is_string($row[0])&&$row[0]!==null){throw new UnexpectedValueException('Unsupported planned key type');}$out[$row[0]]=$row[1];}return $out;
        }
        $mode=$this->rowMode($mode);
        if(func_num_args()>1){throw new InvalidArgumentException('Planned row arguments unsupported');}
        $out=[];while(($row=$this->nextVisible())!==false){$out[]=$this->plan()->shape($row,$mode);}return $out;
    }
    public function fetchColumn($index=0)
    {
        if($this->plan()===null){return parent::fetchColumn(...func_get_args());}
        if(func_num_args()>1){throw new InvalidArgumentException('Planned column arguments unsupported');}
        $this->plan()->rawIndex($index);$row=$this->nextVisible();return $row===false?false:$row[$index];
    }
    public function setFetchMode($mode,$params=null)
    {
        if($this->plan()===null){return parent::setFetchMode(...func_get_args());}
        $mode=$this->rowMode($mode);
        if(func_num_args()!==1){throw new InvalidArgumentException('Planned default arguments unsupported');}
        $ok=parent::setFetchMode($mode);if($ok){$this->defaultMode=$mode;}return $ok;
    }
    public function columnCount()
    {
        if($this->plan()===null){return parent::columnCount(...func_get_args());}
        if(parent::columnCount()===0){return 0;}$this->layout();return $this->plan()->visibleCount();
    }
    public function getColumnMeta($column)
    {
        if($this->plan()===null){return parent::getColumnMeta(...func_get_args());}
        $index=$this->plan()->rawIndex($column);$layout=$this->layout();return $layout[$index];
    }
    public function fetchObject($className='stdClass',$ctorArgs=[])
    {
        if($this->plan()===null){return parent::fetchObject(...func_get_args());}
        throw new InvalidArgumentException('Planned fetchObject unsupported; use FETCH_OBJ');
    }
    public function bindColumn($column,&$param,$type=PDO::PARAM_STR,$maxlen=0,$driverdata=null)
    {
        if($this->plan()===null){return parent::bindColumn($column,$param,...array_slice(func_get_args(),2));}
        throw new InvalidArgumentException('Planned bound columns unsupported');
    }
}
