<?php
namespace boru\boruai\Vector;

use boru\boruai\BoruAI;
use boru\boruai\Models\Embedding;
use boru\qdrant\Models\Collection;
use boru\qdrant\Models\Point;
use boru\query\Entity;
use boru\query\Factory;
use boru\query\models\EntityDefinition;
use boru\query\Query;

class CollectionRecord extends Entity {

    private static $isInit = [];

    protected static $collectionName = "contact_address";
    
    /** @var string */
    protected $embeddingString;

    /** @var VectorCollection */
    private $collection;

    /**
     * Holds the vector and payload
     * @var Point
     */
    private $point;

    public static function entityDefinition() {
        $tableName = BoruAI::table('collections');
        $query = Query::create()->select('id', 'collection', 'record_id', 'string_id', 'uid', 'synced', 'vector','payload')->from($tableName);
        return new EntityDefinition([
            "className" => static::class,
            "name" => "VectorCollection",
            "queryBase" => $query,
            "updatedField" => 'synced',
            "jsonFields" => ['vector','payload'],
        ]);
    }

    public static function doInit() {
        $calledClass = get_called_class();
        if(isset(self::$isInit[$calledClass])) {
            return;
        }
        self::$isInit[$calledClass] = true;
        Factory::register(static::entityDefinition());
    }

    public function __construct($idOrData=null) {
        parent::__construct($idOrData);
        self::doInit();

        if(!$this->point) {
            $this->point = new Point();
        }
    }

    /**
     * @param VectorCollection|Collection|string|array|null $collection
     * @return VectorCollection|null
     */
    public function collection($collection=null) {
        if($collection !== null) {
            if($collection instanceof VectorCollection) {
                $this->collection = $collection;
            } elseif($collection instanceof Collection) {
                $this->collection = new VectorCollection($collection);
            } elseif(is_array($collection)) {
                $this->collection = new VectorCollection($collection);
            } elseif(is_string($collection)) {
                $this->collection = new VectorCollection($collection);
            } else {
                $this->collection = null;
            }
            if($this->collection) {
                $this->set("collection",$this->collection->name());
            }
        }
        if(!$this->collection) {
            $cname = $this->get("collection");
            if(!$cname) {
                $cname = static::getCollectionName();
            }
            if($cname) {
                $this->collection = new VectorCollection($cname);
            }
        }
        return $this->collection;
    }

    public function id($id=null) {
        if ($id !== null) {
            $this->set("id",$id);
        }
        return $this->get("id");
    }
    public function collectionName($collectionName=null) {
        if ($collectionName !== null) {
            $this->set("collection",$collectionName);
        }
        $collectionName = $this->get("collection");
        if($collectionName) {
            $this->collection($collectionName);
        }
        return $this->get("collection");
    }
    public function recordId($recordId=null) {
        if ($recordId !== null) {
            $this->set("record_id",$recordId);
        }
        return $this->get("record_id");
    }
    public function stringId($stringId=null) {
        if ($stringId !== null) {
            $this->set("string_id",$stringId);
        }
        return $this->get("string_id");
    }
    public function synced($synced=null) {
        if ($synced !== null) {
            $this->set("synced",$synced);
        }
        return $this->get("synced");
    }

    public function uid($uid=null) {
        if ($uid !== null) {
            $this->point->id($uid);
            $this->set("uid",$this->point->id());
        }
        $uid = $this->get("uid");
        if(!$uid) {
            $uid = $this->point->id();
            $this->set("uid",$uid);
        }
        return $uid;
    }

    public function vector($vector=null) {
        if ($vector !== null) {
            $this->point->vector($vector);
            $this->set("vector",$this->point->vector());
        }
        $vector = $this->get("vector");
        if(!$vector) {
            $vector = $this->point->vector();
            $this->set("vector",$vector);   
            if(!$vector && $this->embeddingString) {
                $vector = Embedding::embed($this->embeddingString);
                $this->point->vector($vector);
                $this->set("vector",$this->point->vector());
            }
        }
        return $vector;
    }

    public function payload($payload=null) {
        if ($payload !== null) {
            $this->point->payload($payload);
            $this->set("payload",$this->point->payload());
        }
        $payload = $this->get("payload");
        if(!$payload) {
            $payload = $this->point->payload();
            $this->set("payload",$payload);
        }
        return $payload;
    }
    public function point($point=null) {
        if ($point !== null) {
            $this->point = $point;
            $this->uid($point->id());
            $this->vector($point->vector());
            $this->payload($point->payload());
        }
        if(!$this->point) {
            $this->point = new Point($this->uid(),$this->vector());
        }
        return $this->point;
    }
    public function score() {
        return $this->point->score();
    }
    public function version() {
        return $this->point->version();
    }

    public function embeddingString($embeddingString=null) {
        if ($embeddingString !== null) {
            $this->embeddingString = $embeddingString;
        }
        return $this->embeddingString;
    }

    /**
     * Embeds the string using the embedding model and sets the vector.
     * @param string|null $embeddingString The string to embed. If null, the current embedding string is returned.
     * @return array|null The embedded vector.
     * @throws \Exception
     */
    public function embed($embeddingString=null) {
        if ($embeddingString !== null) {
            $this->embeddingString = $embeddingString;
        }
        if(!$this->collection || !$this->embeddingString) {
            throw new \Exception("Collection and embedding string must be set before embedding.");
        }
        $this->vector(Embedding::embed($embeddingString));
        return $this->vector();
    }

    public function save($forceEmbedding=false) {
        if(!$this->vector() || $forceEmbedding) {
            $this->embed();
        }
        if(!$this->vector()) {
            return false;
        }
        try {
            $this->collection()->upsert($this);
        } catch (\Exception $e) {
            return false;
        }
        $this->uid($this->point->id());
        $this->vector($this->point->vector());
        $this->payload($this->point->payload());
        return parent::save();
    }

    public function delete() {
        if($this->collection) {
            $this->collection()->delete($this);
        }
        $query = Factory::query(static::entityName());
        $query->delete()->where('id','=',$this->id());
        $query->run();
    }

    public static function setCollectionName($collectionName) {
        static::$collectionName = $collectionName;
    }
    public static function getCollectionName() {
        if(!static::$collectionName) {
            throw new \Exception(static::class . " static::setCollectionName() must be called, or static::\$collectionName must be extended, before using this class in this way.");
        }
        return static::$collectionName;
    }
    public static function makeUuid() {
        static::doInit();
        return Point::makeUuid();
    }
    public static function fromRecordId($id,$collection=null) {
        static::doInit();
        $conditions[] = ['recordId', '=', $id];
        $conditions = static::addCollectionCondition($conditions,$collection);
        $search = Factory::searchOne(static::entityName(),...$conditions);
        if($search) {
            $record = new static($search->id());
            return $record;
        }
        return null;
    }
    public static function fromUid($uid,$collection=null) {
        static::doInit();
        $conditions = [];
        $conditions[] = ['uid', '=', $uid];
        $conditions = static::addCollectionCondition($conditions,$collection);
        $search = Factory::searchOne(static::entityName(),...$conditions);
        if($search) {
            $record = new static($search->id());
            return $record;
        }
        return null;
    }
    public static function fromPoint($point) {
        static::doInit();
        if($point instanceof Point) {
            $record = new static();
            $record->point($point);
            return $record;
        }
        return null;
    }
    public static function search($recordOrString=null,$filter=[],$limit=10,$collectionOrName=null,$withPayload=true,$withVector=false) {
        static::doInit();
        /** @var VectorCollection */
        $collection = null;
        $collectionName = null;
        $vector = null;
        if($recordOrString instanceof CollectionRecord) {
            $record = $recordOrString->point();
            $vector = $record->vector();
            if(!$collectionName) {
                $collectionName = $record->collectionName();
                $collection = $record->collection();
            }
        } elseif($recordOrString instanceof Point) {
            $record = $recordOrString;
            $vector = $record->vector();
            if(!$collectionName) {
                if($record->collectionName()) {
                    $collectionName = $record->collectionName();
                    $collection = $record->collection();
                }
            }
        }
        if(!$collectionName && $collectionOrName) {
            if($collectionOrName instanceof VectorCollection) {
                $collection = $collectionOrName;
                $collectionName = $collection->name();
            } elseif($collectionOrName instanceof Collection) {
                $collection = new VectorCollection($collectionOrName);
                $collectionName = $collection->name();
            } elseif(is_string($collectionOrName)) {
                $collection = new VectorCollection($collectionOrName);
                $collectionName = $collection->name();
            }
        }
        if(!$collectionName) {
            $record = Factory::instance(static::entityName());
            $collectionName = $record->collectionName();
            $collection = $record->collection();
        }
        if(!$collection) {
            return null;
        }
        if(!$vector && is_string($recordOrString)) {   
            $vector = Embedding::embed($recordOrString);
        }
        if($vector) {
            $collection->recordClassName(get_called_class());
            $points = $collection->search($vector,$filter,$limit,$withPayload,$withVector);
            return $points;
        }
        return null;
    }

    public static function entityName() {
        return static::entityDefinition()->name();
    }

    private static function addCollectionCondition($conditions,$collection=null) {
        if($collection) {
            if($collection instanceof VectorCollection) {
                $collection = $collection->name();
            }
            $conditions[] = ['collection','=',$collection];
        }
        return $conditions;
    }
}