<?php

namespace OtomaticAi\Modules;

use DOMXPath;
use Exception;
use OtomaticAi\Api\OpenAi\PoolClient as OpenAiPoolClient;
use OtomaticAi\Api\StabilityAi\PoolClient as StabilityAiPoolClient;
use OtomaticAi\Api\Ideogram\PoolClient as IdeogramPoolClient;
use OtomaticAi\Api\XAi\PoolClient as XAiPoolClient;
use OtomaticAi\Api\Flux\PoolClient as FluxPoolClient;
use OtomaticAi\Api\Gemini\PoolClient as GeminiPoolClient;
use OtomaticAi\Api\Gemini\Client as GeminiClient;
use OtomaticAi\Api\Unsplash\Client as UnsplashClient;
use OtomaticAi\Api\Pexels\Client as PexelsClient;
use OtomaticAi\Api\Pixabay\Client as PixabayClient;
use OtomaticAi\Api\GoogleImage\Client as GoogleImageClient;
use OtomaticAi\Api\BingImage\Client as BingImageClient;
use OtomaticAi\Content\Image\BingImage;
use OtomaticAi\Content\Image\DallE;
use OtomaticAi\Content\Image\Flux;
use OtomaticAi\Content\Image\Gemini;
use OtomaticAi\Content\Image\GoogleImage;
use OtomaticAi\Content\Image\Grok;
use OtomaticAi\Content\Image\Pexels;
use OtomaticAi\Content\Image\Pixabay;
use OtomaticAi\Content\Image\StableDiffusion;
use OtomaticAi\Content\Image\Ideogram;
use OtomaticAi\Content\Image\Unsplash;
use OtomaticAi\Models\Contracts\Publishable;
use OtomaticAi\Models\Presets\Preset;
use OtomaticAi\Modules\Contracts\Module as ModuleContract;
use OtomaticAi\Utils\Image;
use OtomaticAi\Utils\Language;
use OtomaticAi\Vendors\Illuminate\Support\Arr;
use OtomaticAi\Vendors\Illuminate\Support\Str;

class ProcessImageModule extends Module implements ModuleContract
{
    const SLUG_NAME = "process_image_module";

    /**
     * Array of used image urls
     *
     * @var array
     */
    private array $usedImages = [];

    /**
     * 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("Image module started.", self::SLUG_NAME);

        // get length
        $length = $this->getLength();

        // create image placeholders
        $placeholders = $this->createElementPlaceholdersMultiTags("image", ["otoimage", "image", "img"]);

        // generate images
        $images = [];
        switch (Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "provider")) {
            case "stable_diffusion":
                $images = $this->getStableDiffusionImage($this->getTitle(), $length, $placeholders);
                break;
            case "ideogram":
                $images = $this->getIdeogramImage($this->getTitle(), $length, $placeholders);
                break;
            case "flux":
                $images = $this->getFluxImage($this->getTitle(), $length, $placeholders);
                break;
            case "dall_e":
                $images = $this->getDallEImage($this->getTitle(), $length, $placeholders);
                break;
            case "grok":
                $images = $this->getGrokImage($this->getTitle(), $length, $placeholders);
                break;
            case "gemini":
                $images = $this->getGeminiImage($this->getTitle(), $length, $placeholders);
                break;
            case "unsplash":
                $images = $this->getUnsplashImage($this->getTitle(), $length);
                break;
            case "pexels":
                $images = $this->getPexelsImage($this->getTitle(), $length);
                break;
            case "pixabay":
                $images = $this->getPixabayImage($this->getTitle(), $length);
                break;
            case "google_image":
                $images = $this->getGoogleImage($this->getTitle(), $length);
                break;
            case "bing_image":
                $images = $this->getBingImage($this->getTitle(), $length);
                break;
        }

        $initialLength = $length;
        $successfulGenerations = 0;

        // add images to sections
        if (!empty($images)) {
            $successfulGenerations = count($images);

            // thumbnail image
            if ($this->isThumbnailEnabled()) {
                $this->generationState->setArtifact("thumbnail", array_shift($images)->toArray());
            }

            // replace content image placeholders with the generated images
            $this->replaceElementPlaceholders("image", function ($attributes) use ($images) {
                $index = Arr::get($attributes, "index");
                if ($index !== null && isset($images[$index])) {
                    $image = $images[$index];

                    $el = $image->toHtmlElement($this->html);
                    if ($el) {
                        return $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
        if ($successfulGenerations === $initialLength) {
            $this->publication->addLog("Images module completed successfully.", self::SLUG_NAME, "success");
        } else {
            $this->publication->addLog("Images module completed with errors. " . $successfulGenerations . " on " . $initialLength . " images generated.", self::SLUG_NAME, "warning");
        }
    }

    /**
     * Generate a stable diffusion image for the provided search
     *
     * @param string $search
     * @param int $length
     * @param array $placeholders
     * @return StableDiffusion|StableDiffusion[]|null
     */
    private function getStableDiffusionImage(string $search, int $length, array $placeholders)
    {
        // log the image generation
        $this->addStartLog("Stable Diffusion");

        try {

            // get model id
            $model = Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "model", "core");

            // make the default prompt
            $defaultPrompt = $this->makeStableDiffusionPrompt($search);

            $prompts = [];

            // add the thumbnail prompt
            if ($this->isThumbnailEnabled()) {
                $prompts[] = $defaultPrompt;
            }

            // get the content image prompts
            foreach ($placeholders as $placeholder) {
                $prompts[] = Arr::get($placeholder, "prompt", $defaultPrompt);
            }

            $prompts = array_slice($prompts, 0, $length);

            // make the pool settings
            $payloads = [];
            for ($i = 0; $i < count($prompts); $i++) {

                if ($model === "sdxl") {
                    // sdxl model
                    $payload = [
                        "text_prompts" => [
                            [
                                "text" => $prompts[$i],
                                "weight" => 1
                            ]
                        ]
                    ];
                } else {
                    // others models
                    $payload = [
                        "prompt" => $prompts[$i],
                        "model" => $model
                    ];
                }
                $payloads[] = $payload;
            }

            // call the stability ai api
            $api = new StabilityAiPoolClient();
            if ($model === "sdxl") {
                // sdxl model
                $result = [];
                foreach ($api->textToImage($payloads) as $artifact) {
                    if (Arr::get($artifact, "artifacts.0.finishReason") === "SUCCESS") {
                        $result[] = [
                            "image" => Arr::get($artifact, "artifacts.0.base64")
                        ];
                    }
                }
            } else {
                // others models
                $result = $api->stableImageGenerate($payloads);
            }

            // make the Stable Diffusion Images
            $images = [];
            foreach ($result as $index => $res) {

                if (!empty(Arr::get($res, "image", null))) {
                    // description
                    $description = $this->makeImageDescription($search);

                    $images[] = StableDiffusion::make(Arr::get($res, "image"), $search, $description, null, Arr::get($prompts, $index), Image::imageProviderAndModelToModel("stable_diffusion", $model));
                }
            }
            return $images;
        } catch (Exception $e) {
            $this->addFailedLog("Stable Diffusion", $e->getMessage());
        }
    }

    /**
     * Generate a ideogram image for the provided search
     *
     * @param string $search
     * @param int $length
     * @param array $placeholders
     * @return Ideogram|Ideogram[]|null
     */
    private function getIdeogramImage(string $search, int $length, array $placeholders)
    {
        // log the image generation
        $this->addStartLog("Ideogram");

        try {

            // get model id
            $model = Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "model", "V_1_TURBO");

            // make the default prompt
            $defaultPrompt = $this->makeIdeogramPrompt($search);

            $prompts = [];

            // add the thumbnail prompt
            if ($this->isThumbnailEnabled()) {
                $prompts[] = $defaultPrompt;
            }

            // get the content image prompts
            foreach ($placeholders as $placeholder) {
                $prompts[] = Arr::get($placeholder, "prompt", $defaultPrompt);
            }

            $prompts = array_slice($prompts, 0, $length);
            $imageData = Image::imageModelToProviderAndModel($this->getModuleValue("models.image"));

            // make the pool settings
            $payloads = [];
            for ($i = 0; $i < count($prompts); $i++) {
                $payload = [
                    "prompt" => $prompts[$i],
                    "rendering_speed" => "TURBO"
                ];

                $payload["rendering_speed"] = Arr::get($imageData, "params.rendering_speed", "TURBO");

                $payloads[] = $payload;
            }

            // call the ideogram api
            $api = new IdeogramPoolClient();
            $result = $api->generate($payloads);

            // make the Ideogram Images
            $images = [];
            foreach ($result as $index => $res) {

                if (!empty(Arr::get($res, "data.0.url", null))) {
                    // description
                    $description = $this->makeImageDescription($search);

                    $images[] = Ideogram::make(Arr::get($res, "data.0.url"), $search, $description, null, Arr::get($prompts, $index), Image::imageProviderAndModelToModel("ideogram", $model));
                }
            }
            return $images;
        } catch (Exception $e) {
            $this->addFailedLog("Ideogram", $e->getMessage());
        }
    }

    /**
     * Generate a grok image for the provided search
     *
     * @param string $search
     * @param int $length
     * @param array $placeholders
     * @return Grok|Grok[]|null
     */
    private function getGrokImage(string $search, int $length, array $placeholders)
    {
        // log the image generation
        $this->addStartLog("Grok (xAI)");

        try {

            // get model id
            $model = Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "model", "grok-2-image");

            // make the default prompt
            $defaultPrompt = $this->makeGrokPrompt($search);

            $prompts = [];

            // add the thumbnail prompt
            if ($this->isThumbnailEnabled()) {
                $prompts[] = $defaultPrompt;
            }

            // get the content image prompts
            foreach ($placeholders as $placeholder) {
                $prompts[] = Arr::get($placeholder, "prompt", $defaultPrompt);
            }

            $prompts = array_slice($prompts, 0, $length);

            // make the pool settings
            $payloads = [];
            for ($i = 0; $i < count($prompts); $i++) {
                $payload = [
                    "model" => $model,
                    "prompt" => $prompts[$i],
                ];

                $payloads[] = $payload;
            }

            // call the grok api
            $api = new XAiPoolClient();
            $result = $api->image($payloads);

            // make the Grok Images
            $images = [];
            foreach ($result as $index => $res) {

                if (!empty(Arr::get($res, "data.0.b64_json", null))) {
                    // description
                    $description = $this->makeImageDescription($search);

                    $images[] = Grok::make(Arr::get($res, "data.0.b64_json"), $search, $description, null, Arr::get($prompts, $index), Image::imageProviderAndModelToModel("grok", $model));
                }
            }
            return $images;
        } catch (Exception $e) {
            $this->addFailedLog("Grok (xAI)", $e->getMessage());
        }
    }

    /**
     * Generate a gemini image for the provided search
     *
     * @param string $search
     * @param int $length
     * @param array $placeholders
     * @return Gemini|Gemini[]|null
     */
    private function getGeminiImage(string $search, int $length, array $placeholders)
    {
        // log the image generation
        $this->addStartLog("Gemini");

        try {

            // get model id
            $model = Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "model", "gemini-2.5-flash-image");

            // make the default prompt
            $defaultPrompt = $this->makeGrokPrompt($search);

            $prompts = [];

            // add the thumbnail prompt
            if ($this->isThumbnailEnabled()) {
                $prompts[] = $defaultPrompt;
            }

            // get the content image prompts
            foreach ($placeholders as $placeholder) {
                $prompts[] = Arr::get($placeholder, "prompt", $defaultPrompt);
            }

            $prompts = array_slice($prompts, 0, $length);

            // make the pool settings
            $payloads = [];
            for ($i = 0; $i < count($prompts); $i++) {
                $payload = [
                    "model" => $model,
                    "contents" => [
                        ["parts" => [["text" => $prompts[$i]]]]
                    ],
                    "generationConfig" => [
                        "imageConfig" => [
                            "aspectRatio" => "16:9"
                        ]
                    ]
                ];

                $payloads[] = $payload;
            }

            // call the gemini api
            $api = new GeminiPoolClient();
            $result = $api->image($payloads);

            // make the Gemini Images
            $images = [];
            foreach ($result as $index => $res) {

                $base64 = GeminiClient::getImageInlineData($res);
                if (!empty($base64)) {
                    // description
                    $description = $this->makeImageDescription($search);

                    $images[] = Gemini::make($base64, $search, $description, null, Arr::get($prompts, $index), Image::imageProviderAndModelToModel("gemini", $model));
                }
            }
            return $images;
        } catch (Exception $e) {
            $this->addFailedLog("Gemini", $e->getMessage());
        }
    }

    /**
     * Generate a Flux image for the provided search
     *
     * @param string $search
     * @param int $length
     * @param array $placeholders
     * @return Flux|Flux[]|null
     */
    private function getFluxImage(string $search, int $length, array $placeholders)
    {
        // log the image generation
        $this->addStartLog("Flux");

        try {

            // get model id
            $model = Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "model", "flux-dev");

            // make the default prompt
            $defaultPrompt = $this->makeFluxPrompt($search);

            $prompts = [];

            // add the thumbnail prompt
            if ($this->isThumbnailEnabled()) {
                $prompts[] = $defaultPrompt;
            }

            // get the content image prompts
            foreach ($placeholders as $placeholder) {
                $prompts[] = Arr::get($placeholder, "prompt", $defaultPrompt);
            }

            $prompts = array_slice($prompts, 0, $length);

            // make the pool settings
            $payloads = [];
            for ($i = 0; $i < count($prompts); $i++) {

                // others models
                $payload = [
                    "prompt" => $prompts[$i],
                    "model" => $model
                ];

                $payloads[] = $payload;
            }

            $api = new FluxPoolClient();

            // create the tasks
            $responses = $api->createTasks($payloads);

            // get the tasks ids
            $pollingUrls = array_map(function ($response) {
                return $response["polling_url"];
            }, $responses);

            // get the tasks results
            $results = [];
            while (true) {
                sleep(3);

                $results = $api->getResults($pollingUrls);

                $again = false;
                foreach ($results as $result) {
                    if (Arr::get($result, "status") === "Pending") {
                        $again  = true;
                        break;
                    }
                }

                if (!$again) {
                    break;
                }
            }

            // make the Flux Images
            $images = [];
            foreach ($results as $index => $res) {

                if (!empty(Arr::get($res, "result.sample", null))) {
                    // description
                    $description = $this->makeImageDescription($search);

                    $images[] = Flux::make(Arr::get($res, "result.sample"), $search, $description, null, Arr::get($prompts, $index), Image::imageProviderAndModelToModel("flux", $model));
                }
            }

            return $images;
        } catch (Exception $e) {
            $this->addFailedLog("Flux", $e->getMessage());
        }
    }

    /**
     * Generate a dall-e image for the provided search
     *
     * @param string $search
     * @param int $length
     * @param array $placeholders
     * @return DallE|DallE[]|null
     */
    private function getDallEImage(string $search, int $length, array $placeholders)
    {
        // log the image generation
        $this->addStartLog("OpenAI");

        try {

            // get model id
            $model = Arr::get(Image::imageModelToProviderAndModel($this->getModuleValue("models.image")), "model", "dall-e-2");

            // make the default prompt
            $defaultPrompt = $this->makeDallEPrompt($search);

            $prompts = [];

            // add the thumbnail prompt
            if ($this->isThumbnailEnabled()) {
                $prompts[] = $defaultPrompt;
            }

            // get the content image prompts
            foreach ($placeholders as $placeholder) {
                $prompts[] = Arr::get($placeholder, "prompt", $defaultPrompt);
            }

            $prompts = array_slice($prompts, 0, $length);

            $imageData = Image::imageModelToProviderAndModel($this->getModuleValue("models.image"));

            // make the pool settings
            $settings = [
                "prompts" => [],
                "settings" => [],
            ];
            for ($i = 0; $i < count($prompts); $i++) {
                $settings["prompts"][] = Arr::get($prompts, $i);

                $payload = [
                    "model" => $model,
                    "quality" => Arr::get($imageData, "params.quality", "auto"),
                    "size" => $this->isThumbnailEnabled() && $i === 0 ? "1536x1024" : "auto",
                    "moderation" => "low",
                ];

                $settings["settings"][] = $payload;
            }

            // call the openai api
            $api = new OpenAiPoolClient();
            $result = $api->image($settings["prompts"], $settings["settings"]);

            // make the DallE Images
            $images = [];
            foreach ($result as $index => $res) {

                // description
                $description = $this->makeImageDescription($search);

                $images[] = DallE::make(Arr::get($res, "data.0.b64_json"), $search, $description, null, Arr::get($prompts, $index), Image::imageProviderAndModelToModel("dall_e", $model));
            }
            return $images;
        } catch (Exception $e) {
            $this->addFailedLog("OpenAI", $e->getMessage());
        }
    }

    /**
     * Generate an Unsplash image for the provided search
     *
     * @param string $search
     * @param int $length
     * @return Unsplash|Unsplash[]|null
     */
    private function getUnsplashImage(string $search, int $length)
    {
        // log the image generation
        $this->addStartLog("Unsplash");

        try {

            // make the search for unsplash
            $search = $this->makeUnsplashSearch($search);

            // call unsplash api
            $api = new UnsplashClient;
            $response = $api->searchPhotos([
                "query" => $search,
                "per_page" => 20,
            ]);

            // get a random image
            $result = Arr::get($response, "results", []);

            // filter to remove used images
            $result = array_filter($result, function ($image) {
                return Arr::has($image, "urls.regular") && !in_array($image["urls"]["regular"], $this->usedImages);
            });
            $result = array_values($result);

            if (!empty($result)) {

                $images = [];
                $result = Arr::random($result, min(count($result), $length));

                foreach ($result as $res) {

                    // description
                    $description = $this->makeImageDescription($search);

                    // add image to used images
                    $this->usedImages[] = $res["urls"]["regular"];

                    $images[] = Unsplash::make($res["urls"]["regular"], $search, $description, null, $search);
                }

                return $images;
            } else {
                $this->publication->addLog("Image generation failed. No images found.", self::SLUG_NAME, "warning");
            }
        } catch (Exception $e) {
            $this->addFailedLog("Unsplash", $e->getMessage());
        }
    }

    /**
     * Generate an Pexels image for the provided search
     *
     * @param string $search
     * @param int $length
     * @return Pexel|Pexel[]|null
     */
    private function getPexelsImage(string $search, int $length)
    {
        // log the image generation
        $this->addStartLog("Pexels");

        try {

            // make the search for pexels
            $search = $this->makePexelsSearch($search);

            // call pexels api
            $api = new PexelsClient();
            $response = $api->searchPhotos([
                "query" => $search,
                "per_page" => 20,
            ]);

            // get a random image
            $result = Arr::get($response, "photos", []);

            // filter to remove used images
            $result = array_filter($result, function ($image) {
                return !in_array($image["src"]["landscape"], $this->usedImages);
            });
            $result = array_values($result);

            if (!empty($result)) {

                $images = [];
                $result = Arr::random($result, min(count($result), $length));
                foreach ($result as $res) {

                    // description
                    $description = $this->makeImageDescription($search);

                    // add image to used images
                    $this->usedImages[] = $res["src"]["landscape"];

                    $images[] = Pexels::make($res["src"]["landscape"], $search, $description, null, $search);
                }

                return $images;
            } else {
                $this->publication->addLog("Image generation failed. No images found.", self::SLUG_NAME, "warning");
            }
        } catch (Exception $e) {
            $this->addFailedLog("Pexels", $e->getMessage());
        }
    }

    /**
     * Generate an Pixabay image for the provided search
     *
     * @param string $search
     * @param int $length
     * @return Pixabay|Pixabay[]|null
     */
    private function getPixabayImage(string $search, int $length)
    {
        // log the image generation
        $this->addStartLog("Pixabay");

        try {

            // make the search for pixabay
            $search = $this->makePixabaySearch($search);

            // call pixabay api
            $api = new PixabayClient();
            $response = $api->images([
                "q" => $search,
            ]);

            // get a random image
            $result = Arr::get($response, "hits", []);

            // filter to remove used images
            $result = array_filter($result, function ($image) {
                return !in_array($image["largeImageURL"], $this->usedImages);
            });
            $result = array_values($result);

            if (!empty($result)) {

                $images = [];
                $result = Arr::random($result, min(count($result), $length));
                foreach ($result as $res) {

                    // description
                    $description = $this->makeImageDescription($search);

                    // add image to used images
                    $this->usedImages[] = $res["largeImageURL"];

                    $images[] = Pixabay::make($res["largeImageURL"], $search, $description, null, $search);
                }

                return $images;
            } else {
                $this->publication->addLog("Image generation failed. No images found.", self::SLUG_NAME, "warning");
            }
        } catch (Exception $e) {
            $this->addFailedLog("Pixabay", $e->getMessage());
        }
    }

    /**
     * Generate an Google image for the provided search
     *
     * @param string $search
     * @param int $length
     * @return GoogleImage|GoogleImage[]|null
     */
    private function getGoogleImage(string $search, int $length)
    {
        // log the image generation
        $this->addStartLog("Google Image");

        try {

            // make the search for google image
            $search = $this->makeGoogleImageSearch($search);

            // call google image api
            $api = new GoogleImageClient();
            $result = $api->images([
                "q" => $search,
            ]);

            // filter to remove used images
            $result = array_filter($result, function ($url) {
                return !in_array($url, $this->usedImages);
            });
            $result = array_values($result);

            // get a random image
            if (!empty($result)) {

                $images = [];
                $result = Arr::random($result, min(count($result), $length));
                foreach ($result as $res) {

                    // description
                    $description = $this->makeImageDescription($search);

                    // add image to used images
                    $this->usedImages[] = $res;

                    $images[] = GoogleImage::make($res, $search, $description, null, $search);
                }

                return $images;
            } else {
                $this->publication->addLog("Image generation failed. No images found.", self::SLUG_NAME, "warning");
            }
        } catch (Exception $e) {
            $this->addFailedLog("Google Image", $e->getMessage());
        }
    }

    /**
     * Generate an Bing image for the provided search
     *
     * @param string $search
     * @param int $length
     * @return BingImage|BingImage[]|null
     */
    private function getBingImage(string $search, int $length)
    {
        // log the image generation
        $this->addStartLog("Bing Image");

        try {

            // make the search for bing image
            $search = $this->makeBingImageSearch($search);

            // call google image api
            $api = new BingImageClient();
            $result = $api->images([
                "q" => $search,
            ]);

            // filter to remove used images
            $result = array_filter($result, function ($url) {
                return !in_array($url, $this->usedImages);
            });
            $result = array_values($result);

            // get a random image
            if (!empty($result)) {

                $images = [];
                $result = Arr::random($result, min(count($result), $length));
                foreach ($result as $res) {

                    // description
                    $description = $this->makeImageDescription($search);

                    // add image to used images
                    $this->usedImages[] = $res;

                    $images[] = BingImage::make($res, $search, $description, null, $search);
                }

                return $images;
            } else {
                $this->publication->addLog("Image generation failed. No images found.", self::SLUG_NAME, "warning");
            }
        } catch (Exception $e) {
            $this->addFailedLog("Bing Image", $e->getMessage());
        }
    }

    /**
     * Generate the stable diffusion prompt for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeStableDiffusionPrompt(string $search): string
    {
        $search = Str::lower($search);
        $customInstructions = $this->getCustomInstructions();

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

        $response = $preset->process([
            "language" => "anglais",
            "request" => $search,
            "has_custom_instructions" => !empty($customInstructions),
            "custom_instructions" => $customInstructions,
        ]);

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

        if (!empty($content)) {
            return $content;
        }

        return $search;
    }

    /**
     * Generate the Flux prompt for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeFluxPrompt(string $search): string
    {
        $search = Str::lower($search);
        $customInstructions = $this->getCustomInstructions();

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

        $response = $preset->process([
            "language" => "anglais",
            "request" => $search,
            "has_custom_instructions" => !empty($customInstructions),
            "custom_instructions" => $customInstructions,
        ]);

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

        if (!empty($content)) {
            return $content;
        }

        return $search;
    }

    /**
     * Generate the ideogram prompt for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeIdeogramPrompt(string $search): string
    {
        $search = Str::lower($search);
        $customInstructions = $this->getCustomInstructions();

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

        $response = $preset->process([
            "language" => "anglais",
            "request" => $search,
            "has_custom_instructions" => !empty($customInstructions),
            "custom_instructions" => $customInstructions,
        ]);

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

        if (!empty($content)) {
            return $content;
        }

        return $search;
    }

    /**
     * Generate the grok prompt for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeGrokPrompt(string $search): string
    {
        $search = Str::lower($search);
        $customInstructions = $this->getCustomInstructions();

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

        $response = $preset->process([
            "language" => "anglais",
            "request" => $search,
            "has_custom_instructions" => !empty($customInstructions),
            "custom_instructions" => $customInstructions,
        ]);

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

        if (!empty($content)) {
            return $content;
        }

        return $search;
    }

    /**
     * Generate the dall-e prompt for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeDallEPrompt(string $search): string
    {
        $search = Str::lower($search);
        $customInstructions = $this->getCustomInstructions();

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

        $response = $preset->process([
            "language" => $this->getLanguage()->value,
            "request" => $search,
            "has_custom_instructions" => !empty($customInstructions),
            "custom_instructions" => $customInstructions,
        ]);

        // get the response content
        $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
        $content = Arr::get($response, "value");
        $content = Str::clean($content);

        // custom instructions
        $customInstructions =  $this->getCustomInstructions();
        if (!empty($customInstructions)) {
            $content = $content . " " . $customInstructions;
        }

        return Str::lower($content);
    }

    /**
     * Make the Unsplash search terms for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeUnsplashSearch(string $search): string
    {
        $search = Str::lower($search);

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

        $response = $preset->process([
            "language" => Language::find("en")->value,
            "request" => $search,
        ]);

        // get the response content
        $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
        $content = Arr::get($response, "value");
        $content = Str::clean($content);

        return Str::lower($content);
    }

    /**
     * Make the Pexels search terms for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makePexelsSearch(string $search): string
    {
        $search = Str::lower($search);

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

        $response = $preset->process([
            "language" => Language::find("en")->value,
            "request" => $search,
        ]);

        // get the response content
        $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
        $content = Arr::get($response, "value");
        $content = Str::clean($content);

        return Str::lower($content);
    }

    /**
     * Make the Pixabay search terms for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makePixabaySearch(string $search): string
    {
        $search = Str::lower($search);

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

        $response = $preset->process([
            "language" => Language::find("en")->value,
            "request" => $search,
        ]);

        // get the response content
        $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
        $content = Arr::get($response, "value");
        $content = Str::clean($content);

        return Str::lower($content);
    }

    /**
     * Make the Google Image search terms for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeGoogleImageSearch(string $search): string
    {
        $search = Str::lower($search);

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

        $response = $preset->process([
            "language" => $this->getLanguage()->value,
            "request" => $search,
        ]);

        // get the response content
        $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
        $content = Arr::get($response, "value");
        $content = Str::clean($content);

        return Str::lower($content);
    }

    /**
     * Make the Bong Image search terms for the provided search
     *
     * @param string $search
     * @return string
     * @throws Exception
     */
    private function makeBingImageSearch(string $search): string
    {
        $search = Str::lower($search);

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

        $response = $preset->process([
            "language" => $this->getLanguage()->value,
            "request" => $search,
        ]);

        // get the response content
        $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
        $content = Arr::get($response, "value");
        $content = Str::clean($content);

        return Str::lower($content);
    }

    /**
     * Generate an image description from the title
     *
     * @param string $title
     * @return string
     * @throws Exception
     */
    private function makeImageDescription(string $title): string
    {
        $preset = Preset::findFromAPI("generate_alt_image");

        $response = $preset->process([
            "language" => $this->getLanguage()->value,
            "request" => $title,
        ]);

        // get the response content
        $response = json_decode(Arr::get($response, 'choices.0.message.content'), true, 512, JSON_THROW_ON_ERROR);
        $content = Arr::get($response, "value");
        $content = Str::clean($content);

        return Str::lower($content);
    }

    /**
     * Get custom instructions
     *
     * @return string
     */
    private function getCustomInstructions()
    {
        return $this->getModuleValue("content.custom_instructions.images", "");
    }

    /**
     * Determine if the thumbnail is enabled
     *
     * @return boolean
     */
    private function isThumbnailEnabled(): bool
    {
        return $this->getModuleValue("wordpress.thumbnail.enabled", false);
    }

    /**
     * Get the number of image elements + thumbnail
     *
     * @return integer
     */
    private function getLength(): int
    {
        $length = 0;

        // thumbnail image
        if ($this->isThumbnailEnabled()) {
            $length++;
        }

        // content images
        if ($this->getModuleValue("content.automatic.enabled", false)) {
            if ($this->getModuleValue("content.automatic.images.enabled", false)) {
                $elementsCount = $this->countElements("image") + $this->countElements("image", "image") + $this->countElements("image", "img");
                $length += min(intval($this->getModuleValue("content.automatic.images.max_images", 1)), $elementsCount);
            }
        } else {
            $length += $this->countElements("image");
        }

        return $length;
    }

    /**
     * Determine if the module is runnable
     *
     * @return boolean
     */
    public function isRunnable(): bool
    {
        // must have image elements
        return $this->getLength() > 0;
    }

    static public function isEnabled(Publishable $publishable): bool
    {
        if (!self::getPublicationModuleValue($publishable, "wordpress.thumbnail.enabled", false) && !self::getPublicationModuleValue($publishable, "content.automatic.images.enabled", false)) {
            return false;
        }

        return true;
    }

    private function addStartLog($provider)
    {
        $this->publication->addLog("Image generation with " . $provider . " started.", self::SLUG_NAME);
    }

    private function addFailedLog($provider, $error)
    {
        $this->publication->addLog("Image generation with " . $provider . " failed. " . $error, self::SLUG_NAME, "warning");
    }
}
