<?php

namespace OtomaticAi\Modules;

use DOMDocument;
use DOMXPath;
use Exception;
use OtomaticAi\Models\Contracts\Publishable;
use OtomaticAi\Modules\Contracts\Module as ModuleContract;
use OtomaticAi\Models\Presets\Preset;
use OtomaticAi\Vendors\Illuminate\Support\Arr;
use OtomaticAi\Vendors\Illuminate\Support\Str;
use OtomaticAi\Utils\ContentData;
use OtomaticAi\Utils\DataSync;
use OtomaticAi\Utils\Image;
use OtomaticAi\Utils\PresetCollection;
use OtomaticAi\Utils\ProjectSettings\ProjectSettingsFactory;
use OtomaticAi\Utils\Settings;
use OtomaticAi\Utils\Support;
use OtomaticAi\Vendors\GuzzleHttp\Client;
use OtomaticAi\Vendors\Illuminate\Support\Collection;

class ProcessTextModule extends Module implements ModuleContract
{
    const SLUG_NAME = "process_text_module";

    /**
     * Execute the job.
     *
     * @return void
     * @throws Exception
     */
    public function handle(): void
    {
        // verify that the job is runnable
        if (!$this->isRunnable()) {
            return;
        }

        // log the start of the job
        $this->publication->addLog("Text module started.", self::SLUG_NAME);

        if ($this->getModuleValue("content.automatic.enabled", false)) {
            // create the automatic template
            $this->createAutomaticTemplate();

            // add missing custom links
            $this->addMissingCustomLinks();
        } else {
            // generate with the blocks
            $this->generateWithBlocks();
        }

        // add source link if enabled for news type
        if (($this->getType() === "news" || $this->getType() === "news-now") && $this->getModuleValue("content.sources.enabled", false)) {
            $url = Arr::get($this->publication->meta, 'url');
            $host = parse_url($url, PHP_URL_HOST);

            if (!empty($host)) {
                $link = $this->html->createElement("a");
                $link->setAttribute("href", $url);
                $link->setAttribute("rel", $this->getModuleValue("content.sources.no_follow", false) ? "nofollow" : "");
                $link->setAttribute("target", $this->getModuleValue("content.sources.blank", false) ? "_blank" : "");
                $link->nodeValue = $host;

                $el = $this->html->createElement("p");
                $el->appendChild($this->html->createTextNode("Source: "));
                $el->appendChild($link);

                $this->html->getElementsByTagName("body")->item(0)->appendChild($el);
            }
        }

        // add the html content to the generation state
        $this->generationState->setArtifact("html_content", trim(preg_replace("/^<body>|<\/body>$/", "", $this->html->saveHTML($this->html->getElementsByTagName("body")->item(0)))));

        // log the end of the job
        $this->publication->addLog("Text module completed successfully.", self::SLUG_NAME, "success");
    }

    /**
     * Create the automatic template
     *
     * @return void
     */
    private function createAutomaticTemplate(): void
    {
        // make data for the preset
        $data = $this->makePayloadData();

        // get preset
        $preset = Preset::findFromAPI("generate_automatic_content", $this->getModuleValue("models.text", "gpt-4o-mini"));

        // transform preset with the correct model
        $attributes = $preset->toArray();
        Arr::set($attributes, "model", $this->getModuleValue("models.text", "gpt-4o-mini"));
        $preset = Preset::make($attributes);

        // make the payload
        $payload = [

            // language
            "language" => $this->getLanguage()->value,
            "image_prompt_language" => $data["image_prompt_language"],

            // title
            "title" => $this->getTitle(),

            // global systems
            "has_global_systems_text" => !empty(Settings::get("global_systems.text_instructions")),
            "global_systems_text" => Settings::get("global_systems.text_instructions"),

            // generation brief
            "has_generation_brief" => !empty($data["generation_brief"]),
            "generation_brief" => $data["generation_brief"],

            // custom instructions
            "has_text_custom_instructions" => !empty($this->getModuleValue("content.custom_instructions.text")),
            "text_custom_instructions" => $this->getModuleValue("content.custom_instructions.text"),

            // content options
            "has_lists_enabled" => $this->getModuleValue("content.automatic.lists.enabled", false),
            "has_tables_enabled" => $this->getModuleValue("content.automatic.tables.enabled", false),
            "has_bold_words_enabled" => $this->getModuleValue("content.automatic.bold_words.enabled", false),
            "has_emojis_enabled" => $this->getModuleValue("content.automatic.emojis.enabled", false),
            "has_summary_enabled" => $this->getModuleValue("content.automatic.summary.enabled", false),
            "has_brief_enabled" => $this->getModuleValue("content.automatic.brief.enabled", false),
            "has_faq_enabled" => $this->getModuleValue("content.automatic.faq.enabled", false),

            "has_toolbox_automatic_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "automatic",
            "has_toolbox_charts_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "charts",
            "has_toolbox_comparison_table_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "comparison_table",
            "has_toolbox_timeline_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "timeline",
            "has_toolbox_treemap_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "treemap",
            "has_toolbox_quizz_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "quizz",
            "has_toolbox_infographic_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "infographic",
            "has_toolbox_simulator_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "simulator",
            "has_toolbox_calculator_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "calculator",
            "has_toolbox_converter_enabled" => $this->getModuleValue("content.automatic.toolbox.enabled", false) && $this->getModuleValue("content.automatic.toolbox.template", "automatic") === "converter",

            "has_amazon_enabled" => $this->getModuleValue("content.automatic.amazon.enabled", false),

            // content length
            "has_small_text_enabled" => $this->getModuleValue("content.automatic.text_length", "medium") === "small",
            "has_medium_text_enabled" => $this->getModuleValue("content.automatic.text_length", "medium") === "medium",
            "has_large_text_enabled" => $this->getModuleValue("content.automatic.text_length", "medium") === "large",

            // keywords
            "has_keywords" => !empty($data["keywords"]),
            "keywords" => implode("\n", $data["keywords"]),

            // persona
            "has_persona" => !empty($data["persona"]),
            "persona" => $data["persona"],
            "persona_name" => $data["persona_name"],
            "persona_age" => $data["persona_age"],
            "persona_job" => $data["persona_job"],
            "persona_style" => $data["persona_style"],
            "has_persona_custom_instructions" => !empty($data["persona_custom_instructions"]),
            "persona_custom_instructions" => $data["persona_custom_instructions"],

            // writing style
            "has_writing_style" => !empty($data["writing_style"]),
            "writing_style" => $data["writing_style"],

            // images
            "has_images_enabled" => $this->getModuleValue("content.automatic.images.enabled", false),
            "max_content_images" => $this->getModuleValue("content.automatic.images.max_images", 1),
            "has_images_custom_instructions" => !empty($this->getModuleValue("content.custom_instructions.images")),
            "images_custom_instructions" => $this->getModuleValue("content.custom_instructions.images"),

            // search intentions
            "has_search_intentions" => !empty($data["search_intentions"]),
            "search_intentions" => implode("\n", $data["search_intentions"]),

            // content data
            "has_content_data" => !empty($data["content_data"]),
            "content_data" => implode("\n", $data["content_data"]),

            // external data
            "has_external_links" => $this->getModuleValue("content.automatic.external_links.enabled", true) && !empty($data["external_links"]) && !$this->getModuleValue("content.automatic.external_links.no_follow", false),
            "has_external_links_no_follow" => $this->getModuleValue("content.automatic.external_links.enabled", true) && !empty($data["external_links"]) && $this->getModuleValue("content.automatic.external_links.no_follow", false),
            "external_links" => implode("\n", $data["external_links"]),

            // internal data
            "has_internal_links" => $this->getModuleValue("content.automatic.internal_links.enabled", true) && !empty($data["internal_links"]) && !$this->getModuleValue("content.automatic.internal_links.no_follow", false),
            "has_internal_links_no_follow" => $this->getModuleValue("content.automatic.internal_links.enabled", true) && !empty($data["internal_links"]) && $this->getModuleValue("content.automatic.internal_links.no_follow", false),
            "internal_links" => implode("\n", $data["internal_links"]),

            // custom links
            "has_custom_links" => $this->getModuleValue("content.automatic.custom_links.enabled", false) && !empty($this->getModuleValue("content.automatic.custom_links.links", [])),
            "custom_links" => implode("\n", array_map(function ($link) {
                $o = [];
                if (!empty($link["anchor"])) {
                    $o[] = "Ancre: " . $link["anchor"];
                }
                if (!empty($link["url"])) {
                    $o[] = "URL: " . $link["url"];
                }
                if ($link["blank"]) {
                    $o[] = "Target _blank: " . $link["blank"] ? "Oui" : "Non";
                }
                if ($link["nofollow"]) {
                    $o[] = "No follow: " . $link["nofollow"] ? "Oui" : "Non";
                }
                return implode(" | ", $o);
            }, $this->getModuleValue("content.automatic.custom_links.links", []))),

            "has_links" => !empty($data["external_links"]) || !empty($data["internal_links"]) || ($this->getModuleValue("content.automatic.custom_links.enabled", false) && !empty($this->getModuleValue("content.automatic.custom_links.links", []))),
        ];

        // make project types variables
        $projectTypesVariables = [];
        $projectTypes = ProjectSettingsFactory::types();
        foreach ($projectTypes as $projectType) {
            $projectTypesVariables["has_" . $projectType . "_type_enabled"] = $this->getType() === $projectType;
        }

        $payload = array_merge($payload, $projectTypesVariables);

        // call the preset
        $response = $preset->process($payload);

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

        // remove some markdown
        if (!empty($html)) {

            // convert markdown
            $html = trim(Support::convertMarkdown($html));

            // remove emojis if disabled
            if (!$this->getModuleValue("content.automatic.emojis.enabled", false)) {
                $html = Support::removeEmojis($html);
            }

            // encode emojis for wordpress
            $html = wp_encode_emoji($html);
        }

        libxml_use_internal_errors(true);
        $this->html->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_NOWARNING);
        libxml_use_internal_errors(false);

        // populate the ignore list
        $ignoreList = [];

        // remove image blocks if disable_image_generation is true
        if (!$this->getModuleValue("content.automatic.images.enabled", false)) {
            $ignoreList[] = "image";
        }

        // remove youtube blocks if disable_youtube_generation is true
        if (!$this->getModuleValue("content.automatic.youtube.enabled", false)) {
            $ignoreList[] = "youtube";
        }

        // remove embeds blocks if disable_embeds_generation is true
        if (!$this->getModuleValue("content.automatic.embeds.enabled", false)) {
            $ignoreList = array_merge($ignoreList, ["facebook", "tiktok", "instagram", "twitter"]);
        }

        // remove the ignored media elements
        $html = $this->removeIgnoredMediaElements($ignoreList);

        if (empty(trim(preg_replace("/^<body>|<\/body>$/", "", $this->html->saveHTML($this->html->getElementsByTagName("body")->item(0)))))) {
            throw new Exception("Text module completed with errors. No content generated.");
        }
    }

    /**
     * Generate the content with the blocks
     * 
     * @return void
     */
    private function generateWithBlocks(): void
    {
        $blocks = $this->getModuleValue("content.blocks", []);

        // html parts
        $htmlParts = [];

        if (count($blocks) > 0) {

            // make data for the preset
            $data = $this->makePayloadData();

            $payload = [
                "language" => $this->getLanguage()->value,
                "title" => $this->getTitle(),
                "request" => $this->getTitle(),
                "has_persona" => !empty($data["persona"]),
                "persona" => $data["persona"],
                "has_writing_style" => !empty($data["writing_style"]),
                "writing_style" => $data["writing_style"],
            ];

            // count per block external and internal links
            $externalLinksEnabledBlocks = array_values(array_filter($blocks, function ($block) {
                return $block["block"] === "text" && Arr::get($block, "settings.external_links_enabled", false);
            }));
            $internalLinksEnabledBlocks =  array_values(array_filter($blocks, function ($block) {
                return $block["block"] === "text" && Arr::get($block, "settings.internal_links_enabled", false);
            }));
            $perBlockExternalLinks = min(ceil(count($data["external_links"]) / max(1, count($externalLinksEnabledBlocks))), 5);
            $perBlockInternalLinks = min(ceil(count($data["internal_links"]) / max(1, count($internalLinksEnabledBlocks))), 5);

            $blocksPresets = [];

            // get the system
            $textBlockSystem = $this->getTextBlockSystem();

            // for each blocks set by the user
            foreach ($blocks as $blockIndex => $block) {

                if ($block["block"] === "text") {
                    $prompt = Arr::get($block, "settings.prompt", "");
                    if (empty($prompt)) {
                        continue;
                    }

                    // create the block preset
                    $preset = Preset::make([
                        "model" => $this->getModuleValue("models.text", 'gpt-4o-mini'),
                        "messages" => [
                            [
                                "role" => "system",
                                "content" => $textBlockSystem,
                            ],
                            [
                                "role" => "user",
                                "content" => $prompt,
                            ]
                        ],
                    ]);

                    // create the data variable with scraped content, scraped external data and internal data
                    $contentData = [];
                    if (Arr::get($block, "settings.use_live_data", false)) {
                        $contentData = $data["content_data"];
                    } else if (!empty($data["scraped_content_data"])) {
                        $contentData[] = $data["scraped_content_data"];
                    }

                    $contentData = implode("\n", $contentData);

                    // create the links variable with scraped external data and internal data
                    $links = [];
                    if (Arr::get($block, "settings.external_links_enabled", false)) {
                        $links = array_merge($links, array_splice($data["external_links"], 0, $perBlockExternalLinks));
                    }
                    if (Arr::get($block, "settings.internal_links_enabled", false)) {
                        $links = array_merge($links, array_splice($data["internal_links"], 0, $perBlockInternalLinks));
                    }
                    shuffle($links);
                    $links = implode("\n", $links);

                    // add to the array of presets
                    $blocksPresets[] = [
                        $preset,
                        array_merge($payload, [
                            "has_data" => !empty($contentData),
                            "data" => $contentData,
                            "has_links" => !empty($links),
                            "links" => $links,
                        ]),
                        function (array $response) use (&$htmlParts, $blockIndex, $blocks) {
                            // callback of the preset 
                            // get the response content
                            $content = Arr::get($response, 'choices.0.message.content');

                            if ($content) {

                                $blockDocument = new DOMDocument('1.0', 'UTF-8');
                                $blockDocument->loadHTML('<?xml encoding="UTF-8">' . $content);
                                $content = trim(preg_replace("/^<body>|<\/body>$/", "", $blockDocument->saveHTML($blockDocument->getElementsByTagName("body")->item(0))));

                                $content = trim(Support::convertMarkdown($content));
                                $content = wp_encode_emoji($content);
                                $content = trim(Support::removeFootprintHeaders($content, [Str::lower($this->getTitle())]));

                                if (!empty($content)) {
                                    $htmlParts[$blockIndex] = $content;
                                }
                            }
                        }
                    ];
                } else {
                    switch ($block["block"]) {
                        case "image":
                            $htmlParts[$blockIndex] = "<otoimage></otoimage>";
                            break;
                        case "youtube":
                            $htmlParts[$blockIndex] = "<otoyoutube></otoyoutube>";
                            break;
                        case "facebook":
                            $htmlParts[$blockIndex] = "<otofacebook></otofacebook>";
                            break;
                        case "instagram":
                            $htmlParts[$blockIndex] = "<otoinstagram></otoinstagram>";
                            break;
                        case "twitter":
                            $htmlParts[$blockIndex] = "<ototwitter></ototwitter>";
                            break;
                        case "tiktok":
                            $htmlParts[$blockIndex] = "<ototiktok></ototiktok>";
                            break;
                    }
                }
            }

            // run the block preset collection
            if (!empty($blocksPresets)) {
                PresetCollection::run($blocksPresets);
            }
        }

        ksort($htmlParts);

        libxml_use_internal_errors(true);
        $this->html->loadHTML('<?xml encoding="UTF-8">' . implode("\n", $htmlParts), LIBXML_NOWARNING);
        libxml_use_internal_errors(false);
    }

    /**
     * Add the missing custom links
     * 
     * @return void
     */
    private function addMissingCustomLinks(): void
    {
        // verify if the custom links are enabled
        if (!$this->getModuleValue("content.automatic.custom_links.enabled", false)) {
            return;
        }

        // get the custom links
        $links = $this->getModuleValue("content.automatic.custom_links.links", []);

        // filter the links that are not in the html
        $links = array_values(array_filter($links, function ($link) {
            $xpath = new DOMXPath($this->html);
            $nodes = $xpath->query("//a[@href='" . $link["url"] . "']");
            if ($nodes->length > 0) {
                return false;
            }

            return true;
        }));

        // if there is no links to add, return
        if (empty($links)) {
            return;
        }

        try {

            // get the preset
            $preset = Preset::findFromAPI("generate_custom_external_link_section");

            // make the payloads
            $payloads = [];
            foreach ($links as $link) {

                // get url title
                $client = new Client(
                    [
                        "headers" => [
                            "User-Agent" => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
                        ]
                    ]
                );
                $response = $client->get($link["url"]);
                $response = $response->getBody()->getContents();

                $dom = new DOMDocument();
                @$dom->loadHTML($response);
                $xpath = new DOMXPath($dom);

                $title = null;

                $nodes = $xpath->query('//h1');
                if ($nodes->count() > 0) {
                    $title = $nodes[0]->textContent;
                } else {
                    $nodes = $xpath->query('//title');
                    if ($nodes->count() > 0) {
                        $title = $nodes[0]->textContent;
                    }
                }

                $payloads[] = [
                    "language" => $this->getLanguage()->value,
                    "source_title" => empty($title) ? $this->getTitle() : $title,
                    "source_url" => $link["url"],
                    "source_anchor" => $link["anchor"],
                    "has_blank_option" => $link["blank"],
                    "has_nofollow_option" => $link["nofollow"],
                ];
            }

            // process the pool
            $responses = $preset->processPool($payloads);

            // get the responses html content
            $responses = array_map(function ($response) {
                $content = Arr::get($response, 'choices.0.message.content');
                if (!empty($content)) {
                    $content = trim(Support::convertMarkdown($content));
                }
                return $content;
            }, $responses);

            // get the nodes
            $xpath = new DOMXPath($this->html);
            $nodes = $xpath->query("//h2");
            if ($nodes->length === 0) {
                $nodes = $xpath->query("//p");
            }

            // if there is responses and nodes, add the links
            if (!empty($responses) && $nodes->length > 0) {

                // create the indexes by taking alternately the first and last node
                $indexes = [];
                $prepend = false;
                foreach ($nodes as $index => $node) {
                    if ($prepend) {
                        array_unshift($indexes, $index);
                    } else {
                        array_push($indexes, $index);
                    }
                    $prepend = !$prepend;
                }

                // chunk the responses
                $chunks = (new Collection($responses))->chunk(ceil(count($responses) / $nodes->length))->toArray();

                // for each chunk, add the links
                foreach ($chunks as $index => $chunk) {
                    $position = $indexes[$index];

                    // create the DomElements
                    $elements = [];
                    foreach ($chunk as $response) {
                        $fragment = $this->html->createDocumentFragment();
                        $fragment->appendXML($response);
                        $elements[] = $fragment;
                    }

                    // if the position is the last node, add the elements to the last node
                    if ($position === $nodes->length - 1) {
                        foreach ($elements as $element) {
                            $nodes->item($nodes->length - 1)->appendChild($element);
                        }
                    } else {
                        // add the elements to the node after the position
                        foreach ($elements as $element) {
                            $nodes->item($position + 1)->parentNode->insertBefore($element, $nodes->item($position + 1));
                        }
                    }
                }
            }
        } catch (Exception $e) {
        }
    }

    /**
     * Make the payload data
     * 
     * @return array
     */
    private function makePayloadData(): array
    {
        $data = [
            "persona" => null,
            "persona_name" => null,
            "persona_age" => null,
            "persona_job" => null,
            "persona_style" => null,
            "persona_custom_instructions" => null,
            "writing_style" => null,
            "keywords" => [],

            "content_data" => [],
            "scraped_content_data" => null,
            "external_links" => [],
            "search_intentions" => [],
            "internal_links" => [],
            "generation_brief" => null,

            "image_prompt_language" => "anglais",
        ];

        // get data from url or youtube video
        switch ($this->getType()) {
            case "news":
            case "news-now":
            case "rss":
            case "rss-now":
            case "sitemap":
            case "url":
                $content = ContentData::getUrlContent(Arr::get($this->publication->meta, "url"));
                if (!empty($content)) {
                    $data["content_data"][] = $content;
                    $data["scraped_content_data"] = $content;
                }
                break;
            case "youtube":
                $captions = ContentData::getYoutubeVideoCaptions(Arr::get($this->publication->meta, "videoId"), $this->getLanguage());
                if (!empty($captions)) {
                    $data["content_data"][] = $captions;
                    $data["scraped_content_data"] = $captions;
                }
                break;
        }

        // persona
        $persona = $this->getPersona();
        if ($persona) {
            $data["persona"] = $persona->instructions;
            $data["persona_name"] = $persona->user_full_name;
            $data["persona_age"] = $persona->age;
            $data["persona_job"] = $persona->job;
            $data["persona_style"] = $persona->writing_style;
            $data["persona_custom_instructions"] = $persona->custom_instructions;
        }

        if (!empty($this->getModuleValue("content.custom_instructions.profile", ""))) {
            $data["persona_custom_instructions"] = $this->getModuleValue("content.custom_instructions.profile", "");
        }

        // writing style
        $data["writing_style"] = ContentData::getWritingStyle($this->getModuleValue("content.writing_style"));

        // get external data
        $externalData = ContentData::getExternalData($this->getTitle(), $this->getLanguage());

        shuffle($externalData);
        $data["external_links"] = array_merge($data["external_links"], array_values(array_filter(Arr::pluck($externalData, "link"))));

        // get internal data
        $internalData = ContentData::getInternalData($this->getTitle(), $this->getLanguage(), $this->publication->post_id);
        shuffle($internalData);
        $data["internal_links"] = array_merge($data["internal_links"], array_values(array_filter(Arr::pluck($internalData, "link"))));

        // get search intentions
        $data["search_intentions"] = array_values(array_filter(Arr::pluck($externalData, "content")));

        // get a random url content from external links
        if (!in_array($this->getType(), [
            "news",
            "news-now",
            "rss",
            "rss-now",
            "sitemap",
            "url",
            "youtube",
        ])) {

            // first get the custom links
            $urls = $this->getModuleValue("content.data.links", []);
            $urls = array_values(array_filter($urls, function ($url) {
                return $url !== null && !empty(trim($url));
            }));


            // if there is no custom links, get the competitors links
            if (empty($urls) && !empty($data["external_links"])) {
                $urls = Arr::random($data["external_links"], min(2, count($data["external_links"])));
            }

            foreach ($urls as $url) {
                try {
                    $url = trim($url);
                    if (empty($url)) {
                        continue;
                    }
                    $content = ContentData::getUrlContent($url);
                    $content = trim($content);
                    if (!empty($content)) {
                        $data["content_data"][] = $content;
                    }
                } catch (Exception $e) {
                }
            }
        }

        // add generation brief
        if (!empty(trim($this->getModuleValue("content.generation_brief", "")))) {
            $data["generation_brief"] = $this->getModuleValue("content.generation_brief", "");
        }

        // keywords
        if ($this->getModuleValue("content.automatic.keywords.automatic.enabled", false)) {
            $data["keywords"] = ContentData::getKeywords($this->getTitle(), $this->getLanguage());
        } else {
            $data["keywords"] = $this->getModuleValue("content.automatic.keywords.custom", []);
        }

        // image prompt language
        if (Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "provider") === "dall_e") {
            $data["image_prompt_language"] = $this->getLanguage()->value;
        }

        return $data;
    }

    /**
     * Remove the ignored media elements from the html
     *
     * @param array $ignore
     * @return void
     */
    private function removeIgnoredMediaElements(array $ignore = [])
    {
        $elements = [
            "image" => ["otoimage", "image", "img"],
            "youtube" => "otoyoutube",
            "facebook" => "otofacebook",
            "instagram" => "otoinstagram",
            "tiktok" => "ototiktok",
            "twitter" => "ototwitter",
        ];

        // create xpath
        $xpath = new DOMXPath($this->html);

        foreach ($elements as $elementKey => $tagName) {

            // get the nodes
            if (is_array($tagName)) {
                $nodes = $xpath->query(implode(" | ", array_map(function ($tag) {
                    return "//{$tag}";
                }, $tagName)));
            } else {
                $nodes = $xpath->query("//{$tagName}");
            }

            foreach ($nodes as $node) {

                // verify if the element is in the ignore list
                if (in_array($elementKey, $ignore)) {

                    // remove the element
                    $node->parentNode->removeChild($node);
                }
            }
        }
    }

    /**
     * get the text block system from cache or from the api
     *
     * @return string
     */
    private function getTextBlockSystem(): string
    {
        $prompt = implode("\n", [
            "=====================",
            "Instructions de génération:",
            "=====================",
            "La sortie du texte doit être en \"[LANGUAGE]\" et en HTML. C'est très important.",
            "---",
            "[IF;HAS_PERSONA]",
            "Ce texte est rédigé par: [PERSONA]",
            "Ne cite pas le persona qui a ecrit ce texte dans le paragraphe.",
            "---",
            "[ENDIF]",
            "[IF;HAS_BUYER_PERSONA]",
            "Ce texte cible ce type de buyer persona, voici un exemple de buyer persona: [BUYER_PERSONA]",
            "Important : ce n'est qu'un exemple ne buyer persona. Ne reprend et ne cite pas  le persona ou le nom cité dans l'exemple.",
            "---",
            "[ENDIF]",
            "[IF;HAS_WRITING_STYLE]",
            "Ecrit avec ce style d'écriture : [WRITING_STYLE]",
            "---",
            "[ENDIF]",
            "[IF;HAS_DATA]",
            "Utilise des élément de ce texte pour alimenter l'article. Il est important que l'article soit différent du texte source, en changeant au maximum e style du texte: ",
            "[DATA]",
            "---",
            "[ENDIF]",
            "[IF;HAS_LINKS]",
            "Inclu ces lien dans le texte de, maniere naturel.",
            "[LINKS]",
            "---",
            "[ELSE]",
            "N'ajoute pas de lien externe (https// ...)  dans le texte ! ",
            "---",
            "[ENDIF]",
            "Enlève le formatage Markdown du texte, ne décrit pas ce que tu génères.",
            "---",
            "Highlight important keywords in <strong>.",
            "=====================",
        ]);

        $remoteTextBlockSystem = DataSync::getData("text_block_system");
        if (!empty($remoteTextBlockSystem) && !empty(Arr::get($remoteTextBlockSystem, "prompt"))) {
            $prompt = Arr::get($remoteTextBlockSystem, "prompt");
        }

        return $prompt;
    }

    /**
     * Determine if the module is runnable
     *
     * @return boolean
     */
    public function isRunnable(): bool
    {
        return true;
    }

    static public function isEnabled(Publishable $publishable): bool
    {
        return true;
    }
}
