import { VariableStore } from '@typebot.io/forge/types'
import { ChatCompletionOptions } from './parseChatCompletionOptions'
import { executeFunction } from '@typebot.io/variables/executeFunction'
import { OpenAIStream, ToolCallPayload } from 'ai'
import OpenAI, { ClientOptions } from 'openai'
import { ChatCompletionTool } from 'openai/resources'
import { parseChatCompletionMessages } from '../helpers/parseChatCompletionMessages'
import { parseToolParameters } from '../helpers/parseToolParameters'

type Props = {
  credentials: { apiKey?: string }
  options: ChatCompletionOptions
  variables: VariableStore
  config: { baseUrl: string; defaultModel?: string }
}
export const runChatCompletionStream = async ({
  credentials: { apiKey },
  options,
  variables,
  config: openAIConfig,
}: Props) => {
  const model = options.model?.trim() ?? openAIConfig.defaultModel
  if (!model) return
  const config = {
    apiKey,
    baseURL: openAIConfig.baseUrl ?? options.baseUrl,
    defaultHeaders: {
      'api-key': apiKey,
    },
    defaultQuery: options.apiVersion
      ? {
          'api-version': options.apiVersion,
        }
      : undefined,
  } satisfies ClientOptions

  const openai = new OpenAI(config)

  const tools = options.tools
    ?.filter((t) => t.name && t.parameters)
    .map((t) => ({
      type: 'function',
      function: {
        name: t.name as string,
        description: t.description,
        parameters: parseToolParameters(t.parameters!),
      },
    })) satisfies ChatCompletionTool[] | undefined

  const messages = parseChatCompletionMessages({ options, variables })

  const response = await openai.chat.completions.create({
    model,
    temperature: options.temperature ? Number(options.temperature) : undefined,
    stream: true,
    messages,
    tools: (tools?.length ?? 0) > 0 ? tools : undefined,
  })

  return OpenAIStream(response, {
    experimental_onToolCall: async (
      call: ToolCallPayload,
      appendToolCallMessage
    ) => {
      for (const toolCall of call.tools) {
        const name = toolCall.func?.name
        if (!name) continue
        const toolDefinition = options.tools?.find((t) => t.name === name)
        if (!toolDefinition?.code || !toolDefinition.parameters) {
          messages.push({
            tool_call_id: toolCall.id,
            role: 'tool',
            content: 'Function not found',
          })
          continue
        }

        const { output } = await executeFunction({
          variables: variables.list(),
          args:
            typeof toolCall.func.arguments === 'string'
              ? JSON.parse(toolCall.func.arguments)
              : toolCall.func.arguments,
          body: toolDefinition.code,
        })

        // TO-DO: enable once we're out of edge runtime.
        // newVariables?.forEach((v) => variables.set(v.id, v.value))

        const newMessages = appendToolCallMessage({
          tool_call_id: toolCall.id,
          function_name: toolCall.func.name,
          tool_call_result: output,
        })

        return openai.chat.completions.create({
          messages: [
            ...messages,
            ...newMessages,
          ] as OpenAI.Chat.Completions.ChatCompletionMessageParam[],
          model,
          stream: true,
          tools,
        })
      }
    },
  })
}
