<?php

namespace Laravel\Ai\PendingResponses;

use BackedEnum;
use Illuminate\Support\Traits\Conditionable;
use InvalidArgumentException;
use Laravel\Ai\Ai;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Events\ProviderFailedOver;
use Laravel\Ai\Exceptions\FailoverableException;
use Laravel\Ai\FakePendingDispatch;
use Laravel\Ai\Jobs\GenerateAudio;
use Laravel\Ai\Prompts\QueuedAudioPrompt;
use Laravel\Ai\Providers\Provider;
use Laravel\Ai\Responses\AudioResponse;
use Laravel\Ai\Responses\QueuedAudioResponse;

class PendingAudioGeneration
{
    use Conditionable;

    protected string $voice = 'default-female';

    protected ?string $instructions = null;

    protected int $timeout = 30;

    public function __construct(
        protected string $text,
    ) {
        if (blank($text)) {
            throw new InvalidArgumentException('Text content is required to generate audio.');
        }
    }

    /**
     * Specify a specific voice for the generated audio.
     */
    public function voice(BackedEnum|string $voice): self
    {
        $this->voice = $voice instanceof BackedEnum ? (string) $voice->value : $voice;

        return $this;
    }

    /**
     * Indicate that the voice should be male.
     */
    public function male(): self
    {
        $this->voice = 'default-male';

        return $this;
    }

    /**
     * Indicate that the voice should be female.
     */
    public function female(): self
    {
        $this->voice = 'default-female';

        return $this;
    }

    /**
     * Provide free-form instructions guiding how the audio should sound.
     */
    public function instructions(string $instructions): self
    {
        $this->instructions = $instructions;

        return $this;
    }

    /**
     * Specify the timeout (in seconds) for the audio generation.
     */
    public function timeout(int $seconds = 30): self
    {
        $this->timeout = $seconds;

        return $this;
    }

    /**
     * Generate the audio.
     *
     * @throws FailoverableException if every configured provider fails to generate the audio.
     */
    public function generate(Lab|array|string|null $provider = null, ?string $model = null): AudioResponse
    {
        $providers = Provider::formatProviderAndModelList(
            $provider ?? config('ai.default_for_audio'), $model
        );

        $lastException = null;

        foreach ($providers as $provider => $model) {
            $provider = Ai::fakeableAudioProvider($provider);

            $model ??= $provider->defaultAudioModel();

            try {
                return $provider->audio(
                    $this->text, $this->voice, $this->instructions, $model, $this->timeout
                );
            } catch (FailoverableException $e) {
                $lastException = $e;

                event(new ProviderFailedOver($provider, $model, $e));

                continue;
            }
        }

        throw $lastException;
    }

    /**
     * Queue the generation of the audio.
     */
    public function queue(Lab|array|string|null $provider = null, ?string $model = null): QueuedAudioResponse
    {
        if (Ai::audioIsFaked()) {
            Ai::recordAudioGeneration(
                new QueuedAudioPrompt(
                    $this->text,
                    $this->voice,
                    $this->instructions,
                    $provider,
                    $model,
                    $this->timeout,
                )
            );

            return new QueuedAudioResponse(new FakePendingDispatch);
        }

        return new QueuedAudioResponse(
            GenerateAudio::dispatch($this, $provider, $model),
        );
    }
}
