import { Property, createAction } from '@activepieces/pieces-framework';
import { createClient, ExtendedReadableStream } from '../common';
import { elevenlabsAuth } from '../..';

export const textToSpeech = createAction({
  description: 'Convert text to speech using Elevenlabs',
  audience: 'both',
  aiMetadata: { description: 'Synthesizes spoken audio from a text string using a chosen ElevenLabs voice, returning an MP3 file. Use to turn written content into a narrated voiceover or speech clip. Requires a voice (selected from the account\'s available voices) and the text; the model is optional and defaults to ElevenLabs\' default. Not idempotent — each call generates a new audio file.', idempotent: false },
  displayName: 'Text to Speech',
  name: 'elevenlabs-text-to-speech',
  auth: elevenlabsAuth,
  props: {
    model: Property.Dropdown({
      auth: elevenlabsAuth,
      displayName: 'Model',
      required: false,
      refreshers: [],
      refreshOnSearch: false,
      options: async ({ auth }) => {
        const apiAuth = auth

        if (!apiAuth) {
          return {
            disabled: true,
            placeholder: 'Enter your API key first',
            options: [],
          };
        }

        try {
          const elevenlabs = createClient(apiAuth);
          const models = await elevenlabs.models.list()

          return {
            disabled: false,
            placeholder: 'Default model',
            options: models.map((template) => {
              return {
                // there are models with the same name
                label: `${template.name} (${template.modelId})`,
                value: template.modelId,
              };
            }),
          };
        } catch (error) {
          return {
            disabled: true,
            options: [],
            placeholder: "Couldn't load models, check your API key.",
          };
        }
      },
    }),
    voice: Property.Dropdown({
      auth: elevenlabsAuth,
      displayName: 'Voice',
      required: true,
      description: 'Select the voice for the text to speech',
      refreshers: [],
      refreshOnSearch: false,
      options: async ({ auth }) => {
        const apiAuth = auth

        if (!apiAuth) {
          return {
            disabled: true,
            placeholder: 'Enter your API key first',
            options: [],
          };
        }

        try {
          const elevenlabs = createClient(apiAuth);
          const response = await elevenlabs.voices.getAll()

          return {
            disabled: false,
            options: response.voices.map((template) => {
              return {
                label: `${template.name}`,
                value: template.voiceId,
              };
            }),
          };
        } catch (error) {
          return {
            disabled: true,
            options: [],
            placeholder: "Couldn't load voices, check your API key.",
          };
        }
      },
    }),
    text: Property.LongText({
      displayName: 'Text',
      required: true,
      description: 'The text to convert to speech',
    }),
  },
  async run({ auth, propsValue, files }) {
    const elevenlabs = createClient(auth);

    const audioStream = await elevenlabs.textToSpeech.stream(
      propsValue.voice,
      {
        modelId: propsValue.model || undefined,
        text: propsValue.text,
      }
      // node implementation of ReadableStream<Uint8Array> has asyncInterator
    ) as ExtendedReadableStream<Buffer>;

    const chunks: Buffer[] = [];
    for await (const chunk of audioStream) {
      chunks.push(chunk);
    }

    return files.write({
      fileName: 'audio.mp3',
      data: Buffer.concat(chunks),
    });
  },
});
