<?php

namespace OtomaticAi\Controllers\OneClick;

use DOMDocument;
use DOMXPath;
use JsonException;
use OtomaticAi\Models\Persona;
use OtomaticAi\Models\Presets\Preset;
use OtomaticAi\Vendors\Illuminate\Support\Arr;
use OtomaticAi\Controllers\Controller;
use OtomaticAi\Models\Template;
use OtomaticAi\Utils\GoogleTrends;
use OtomaticAi\Utils\Language;
use OtomaticAi\Utils\Parsedown;

class ChatController extends Controller
{

    /**
     * Chat
     *
     * @return void
     */
    public function chat()
    {

        $this->verifyNonce();

        $this->validate([
            "messages" => ["required", "array"],
            "language" => ["required", "string"],
        ]);

        // testing ------

        // $response = "Generation de l'image : \n\n<image_generation>Une tomate rouge brillante, bien ronde, placée sur une surface en bois avec un éclairage naturel.</image_generation>";
        // // get json content
        // try {
        //     $json = $this->getResponseElement($response, "json_response");
        //     $json = trim(Arr::get($json, "value"));
        //     $settings = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        // } catch (JsonException $e) {
        //     $settings = null;
        // }

        // // get message
        // $message = preg_replace('/<json_response(.*?)>(.*?)<\/json_response>/is', '<JsonResponse$1>$2</JsonResponse>', $response);
        // $message = preg_replace('/<suggestion_box(.*?)>(.*?)<\/suggestion_box>/is', '<SuggestionBox$1>$2</SuggestionBox>', $message);
        // $message = preg_replace('/<suggestion(.*?)>(.*?)<\/suggestion>/is', '<Suggestion$1>$2</Suggestion>', $message);
        // $message = preg_replace('/<image_generation(.*?)>(.*?)<\/image_generation>/is', '<ImageGeneration$1>$2</ImageGeneration>', $message);

        // $message = (new Parsedown())->line($message);

        // $this->response([
        //     "is_terminated" => Arr::get($settings, "is_terminated", true),
        //     "settings" => $settings ? Arr::except($settings, "is_terminated") : null,
        //     "message" => $message,
        //     "full_response" => $response,
        // ]);

        // testing ------

        $language = Language::find($this->input("language"));

        $trends = GoogleTrends::fetch($language);

        $personas = Persona::all();
        $personas = $personas->map(function ($persona) {
            return [
                "id" => "prf_" . $persona->id,
                "first_name" => $persona->user_first_name,
                "last_name" => $persona->user_last_name,
                "age" => $persona->age,
                "job" => $persona->job,
                "biography" => $persona->user_description,
            ];
        })->toArray();

        $categories = get_categories(['hide_empty' => 0]);
        $categories = Arr::map($categories, function ($category) {
            return [
                "id" => "cat_" . $category->term_id,
                "name" => $category->name,
            ];
        });

        $templates = Template::all()->filter(function (Template $template) {
            return version_compare($template->plugin_version, '4.0.0', '>=');
        })->values();
        $templates = $templates->map(function ($template) {
            return [
                "id" => "tpl_" . $template->id,
                "name" => $template->name,
            ];
        })->toArray();

        $preset = Preset::findFromAPI("one_click_chat");

        $messages = $preset->messages;
        $messages = array_merge($messages, $this->input("messages", []));
        $preset->messages = $messages;

        $response = $preset->process([
            "has_categories" => count($categories) > 0,
            "categories" => json_encode($categories),
            "has_profiles" => count($personas) > 0,
            "profiles" => json_encode($personas),
            "has_templates" => count($templates) > 0,
            "templates" => json_encode($templates),
            "has_trends" => count($trends) > 0,
            "trends" => implode("\n", Arr::map($trends, function ($trend) {
                return "   - " . $trend["title"];
            })),
        ]);

        $response = Arr::get($response, 'choices.0.message.content');

        // get json content
        try {
            $json = $this->getResponseElement($response, "json_response");
            $json = trim(Arr::get($json, "value"));
            $settings = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException $e) {
            $settings = null;
        }

        // get message
        $message = preg_replace('/<json_response(.*?)>(.*?)<\/json_response>/is', '<JsonResponse$1>$2</JsonResponse>', $response);
        $message = preg_replace('/<suggestion_box(.*?)>(.*?)<\/suggestion_box>/is', '<SuggestionBox$1>$2</SuggestionBox>', $message);
        $message = preg_replace('/<suggestion(.*?)>(.*?)<\/suggestion>/is', '<Suggestion$1>$2</Suggestion>', $message);
        $message = preg_replace('/<image_generation(.*?)>(.*?)<\/image_generation>/is', '<ImageGeneration$1>$2</ImageGeneration>', $message);

        $message = (new Parsedown())->line($message);

        $this->response([
            "is_terminated" => true,
            "settings" => $settings ? Arr::except($settings, "is_terminated") : null,
            "message" => $message,
            "full_response" => $response,
        ]);
    }

    public function rewritePrompt()
    {
        $this->verifyNonce();

        $this->validate([
            "prompt" => ["required", "string"],
        ]);

        $preset = Preset::findFromAPI("one_click_rewrite_user_prompt");

        $response = $preset->process([
            "user_prompt" => $this->input("prompt"),
        ]);

        $response = Arr::get($response, 'choices.0.message.content');
        $response = json_decode($response, true, 512, JSON_THROW_ON_ERROR);

        $this->response([
            "original_prompt" => $this->input("prompt"),
            "rewritten_prompt" => Arr::get($response, "rewritten_prompt", $this->input("prompt")),
        ]);
    }

    /**
     * Get response element
     *
     * @param string $content
     * @param string $key
     * @param bool $single
     * @return mixed
     */
    private function getResponseElement(string $content, string $key, bool $single = true)
    {
        $elements = [];

        // force utf-8 encoding
        $content = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $content;

        // parse document
        $doc = new DOMDocument('1.0', 'UTF-8');
        libxml_use_internal_errors(true);
        $doc->loadHTML($content, LIBXML_NOWARNING);
        libxml_use_internal_errors(false);

        // find nodes by key
        $xpath = new DOMXPath($doc);
        $nodes = $xpath->query("//" . $key);

        // explore each nodes
        foreach ($nodes as $node) {

            // create the element
            $element = [
                "attributes" => [],
                "value" => $node->nodeValue
            ];

            // add the attributes if exists
            foreach ($node->attributes as $attribute) {
                $element["attributes"][$attribute->name] = $attribute->value;
            }

            $elements[] = $element;
        }


        // return the first value if single is true
        if ($single) {
            return Arr::first($elements);
        } else {
            return $elements;
        }
    }

    /**
     * Get response text
     *
     * @param string $content
     * @return string
     */
    private function getResponseText(string $content, array $tagsToRemove = []): string
    {
        // force utf-8 encoding
        $content = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $content;

        // parse document
        $doc = new DOMDocument('1.0', 'UTF-8');
        libxml_use_internal_errors(true);
        $doc->loadHTML($content, LIBXML_NOWARNING);
        libxml_use_internal_errors(false);

        // get all nodes
        $xpath = new DOMXPath($doc);
        $nodes = $xpath->query("//*");

        // explore each nodes to remove tags
        foreach ($nodes as $node) {
            if (in_array($node->nodeName, $tagsToRemove)) {
                $node->parentNode->removeChild($node);
            }
        }

        $texts = [];

        // get all text nodes
        $nodes = $xpath->query("//text()");
        foreach ($nodes as $node) {
            $node->nodeValue = trim($node->nodeValue);
            if (strlen($node->nodeValue) > 0) {
                $texts[] = $node->nodeValue;
            }
        }

        return implode("\n\n", $texts);
    }
}
