<?php
namespace boru\boruai\Models;

use boru\boruai\BoruAI;
use boru\boruai\Openai\OpenAIChat;
use boru\query\Query;
use boru\query\Entity;
use boru\query\models\EntityDefinition;
use boru\boruai\Tools\PromptTemplate;

class Template extends Entity {
    /** @var PromptTemplate */
    private $promptTemplate;
    /** @var PromptTemplate */
    private $instructionsTemplate;

    private $chatCallback;
    private $dataUsed = [];

    public static function entityDefinition() {
        $tableName = BoruAI::table('templates');
        return new EntityDefinition([
            "className" => static::class,
            "name" => "Template",
            "queryBase" => Query::create()->select('*')->from($tableName),
            "createdField" => 'created_at',
            "jsonFields" => ["tools","files"],
        ]);
    }

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

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

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

    public function current($current=null) {
        if($current !== null) {
            $this->set("current",$current ? 1 : 0);
        }
        return $this->get("current") ? true : false;
    }

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

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

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

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

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

    public function tools($tools=null) {
        if($tools !== null) {
            $this->set("tools",$tools);
        }
        $tools = $this->get("tools",[]);
        if(!is_array($tools) && !empty($tools)) {
            $tools = json_decode($tools,true);
        }
        if(!is_array($tools)) {
            $tools = [];
        }
        return $tools;
    }

    public function files($files=null) {
        if($files !== null) {
            $this->set("files",$files);
        }
        $files = $this->get("files",[]);
        if(!is_array($files) && !empty($files)) {
            $files = json_decode($files,true);
        }
        if(!is_array($files)) {
            $files = [];
        }
        return $files;
    }

    public function versions($limit=0) {
        $versions = [];
        $query = Query::create()->select('*')->from(BoruAI::table('templates'));
        $query->where("name = ?", $this->name());
        $query->orderBy("version DESC");
        if($limit > 0) {
            $query->limit($limit);
        }
        while($record = $query->toRecords()) {
            $version = new Template($record);
            $versions[] = $version;
        }
        return $versions;
    }
    public function latestVersion() {
        $versions = $this->versions(1);
        if(!empty($versions)) {
            return $versions[0];
        }
        return false;
    }
    public function nextVersion() {
        $versions = $this->versions(1);
        if(!empty($versions)) {
            return $versions[0]->version() + 1;
        }
        return 1;
    }

    public function save() {
        $nextVersion = $this->nextVersion();
        if($this->version() == 0) {
            $this->version($nextVersion);
        }
        return parent::save();
    }

    public function delete() {
        $query = Query::create()->delete()->from(BoruAI::table('templates'));
        $query->where("id = ?", $this->id());
        $query->run();
        return true;
    }

    public function renderInstructions($data=[]) {
        if($this->instructionsTemplate === null) {
            $this->instructionsTemplate = new PromptTemplate($this->instructions());
        }
        return $this->instructionsTemplate->render($data);
    }
    public function renderPrompt($data=[]) {
        if($this->promptTemplate === null) {
            $this->promptTemplate = new PromptTemplate($this->prompt());
        }
        return $this->promptTemplate->render($data);
    }

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

    public function run($referenceType,$referenceId=null,$data=[]) {
        if($this->chatCallback()) {
            $callback = $this->chatCallback();
            if(is_callable($callback)) {
                $data = call_user_func($callback,$referenceType,$referenceId,$data,$this);
            }
        }
        $chat =$this->chat($data);
        
    }

    /**
     * @param array $data optional data to pass to the chat
     * @return Response
     */
    public function toResponse($data=[],$response=null) {
        if($response === null) {
            $response = new Response();
        }
        $response->templateId($this->id());
        if(!empty($data)) {
            $instructions = $this->renderInstructions($data);
            $prompt = $this->renderPrompt($data);
        } else {
            $instructions = $this->instructions();
            $prompt = $this->prompt();
        }
        if($instructions) {
            $response->instructions($instructions);
        }
        if($this->files()) {
            $files = $this->files();
            foreach($files as $file) {
                $response->addFile($file);
            }
            
        }
        if($prompt) {
            $response->addMessage($prompt);
        }
        if(!empty($this->tools())) {
            $tools = $this->tools();
            foreach($tools as $tool) {
                if(is_array($tool)) {
                    $response->addTools($tool);
                } else {
                    $response->addTool($tool);
                }
            }
        }
        if($this->output()) {
            $outputFormat = $this->output();
            if(!is_array($outputFormat)) {
                $check = json_decode($outputFormat,true);
                if(is_array($check)) {
                    $response->text($check);
                    return;
                }
            }
            $response->addMessage($this->output());
        }
        return $response;
    }
    
    /**
     * @param array $data optional data to pass to the chat
     * @return OpenAIChat
     */
    public function chat($data=[]) {
        $chat = new OpenAIChat();
        if(!empty($data)) {
            $instructions = $this->renderInstructions($data);
            $prompt = $this->renderPrompt($data);
        } else {
            $instructions = $this->instructions();
            $prompt = $this->prompt();
        }
        if(!empty($instructions)) {
            $chat->addMessage("system", $instructions);
        }
        if(!empty($this->files())) {
            $files = $this->files();
            foreach($files as $file) {
                if(is_array($file)) {
                    $fileId = false;
                    $type = "file";
                    $fileName = false;
                    if(isset($file["id"])) {
                        $fileId = $file["id"];
                    }
                    if(isset($file["type"])) {
                        $type = $file["type"];
                    }
                    if(isset($file["name"])) {
                        $fileName = $file["name"];
                    }
                    if($fileId) {
                        $chat->addFile($fileId, $type);
                    } elseif($fileName) {
                        $chat->addFile($fileName, $type);
                    }
                } else {
                    $chat->addFile($file);
                }
            }
        }
        if(!empty($this->tools())) {
            $tools = $this->tools();
            foreach($tools as $tool) {
                if(is_array($tool)) {
                    $chat->addTools($tool);
                } else {
                    $chat->addTool($tool);
                }
            }
        }
        if(!empty($prompt)) {
            $chat->addMessage("user", $prompt);
        }
        return $chat;
    }

    public function history($response,$referenceId=0,$referenceType="unknown") {
        $history = new PromptHistory();
        $history->promptId($this->id());
        $history->version($this->version());
        if(is_numeric($referenceId)) {
            $history->referenceId($referenceId);
        } else {
            $history->referenceStringId($referenceId);
        }
        $history->referenceType($referenceType);
        $history->response($response);
        $history->prompt($this->prompt());
        $history->save();
        return $history;
    }


    public static function fromId($id) {
        $query = Query::create()->select('*')->from(BoruAI::table('templates'));
        $query->where("id","=",$id);
        $record = $query->toRecord();
        if($record) {
            return new Template($record->toArray());
        }
        return false;
    }

    public static function fromName($name,$currentVersion=true) {
        $query = Query::create()->select('*')->from(BoruAI::table('templates'));
        $query->where("name","=",$name);
        if($currentVersion) {
            $query->where("current","=",1);
        }
        $record = $query->toRecord();
        if($record) {
            return new Template($record->toArray());
        } elseif($currentVersion) {
            $query = Query::create()->select('*')->from(BoruAI::table('templates'));
            $query->where("name","=",$name);
            $query->orderBy("version DESC");
            $record = $query->toRecord();
            if($record) {
                return new Template($record->toArray());
            }
        }
        return false;
    }

    public static function getAll($currentVersion=true) {
        $query = Query::create()->select('*')->from(BoruAI::table('templates'));
        if($currentVersion) {
            $query->where("current","=",1);
        }
        $records = $query->toRecords();
        if($records) {
            $templates = [];
            foreach($records as $record) {
                $templates[] = new Template($record->toArray());
            }
            return $templates;
        }
        return [];
    }
    
}