import { z } from "zod";
import { openAI } from "./clients/open-ai";
import { openAICompletionsCallWithFallback } from "./completions-call-with-fallback";

type Functions = Exclude<
  Parameters<typeof openAI.chat.completions.create>[0]["functions"],
  undefined
>;

type FunctionCall = Functions[number];

const productInquiryFunctionName = "productInquiry";
const productRecommendationFunctionName = "productRecommendation";
const postPurchaseSupportFunctionName = "postPurchaseSupport";

type AvailableFunctions =
  | typeof productInquiryFunctionName
  | typeof productRecommendationFunctionName
  | typeof postPurchaseSupportFunctionName;

const ProductInquiryResponseSchema = z.object({
  name: z.string(),
  description: z.string(),
  price: z.number(),
});

type ProductInquiryResponse = z.infer<typeof ProductInquiryResponseSchema>;

const productInquiryFunctionCall: FunctionCall = {
  name: productInquiryFunctionName,
  description: "Retrieve information about a product",
  parameters: {
    type: "object",
    required: ["name", "description", "price"],
    properties: {
      name: {
        type: "string",
        description: "Name of the product",
      },
      description: {
        type: "string",
        description: "Description of the product",
      },
      price: {
        type: "number",
        description: "Price of the product",
      },
    },
  },
};

const handleProductInquiry = async (inquiry: ProductInquiryResponse) => {
  // do something here
};

const ProductRecommendationResponseSchema = z.object({
  products: z.array(
    z.object({
      name: z.string(),
      description: z.string(),
      productLink: z.string().url(),
    })
  ),
});

type ProductRecommendationResponse = z.infer<
  typeof ProductRecommendationResponseSchema
>;

const productRecommendationFunctionCall: FunctionCall = {
  name: productRecommendationFunctionName,
  description: "Retrieve recommended products",
  parameters: {
    type: "object",
    required: ["products"],
    properties: {
      products: {
        type: "array",
        description: "Recommended products",
        items: {
          type: "object",
          properties: {
            name: {
              type: "string",
              description: "Name of the product",
            },
            description: {
              type: "string",
              description: "Short description of the product",
            },
            productLink: {
              type: "string",
              description: "Link to the product",
            },
          },
        },
      },
    },
  },
};

const handleProductRecommendation = async (
  recommendation: ProductRecommendationResponse
) => {
  //typesafe
  recommendation.products.forEach((product) => {
    console.log(`${product.name} - ${product.description}`);
  });
};

const PostPurchaseSupportResponseSchema = z.object({
  description: z.string(),
  troubleshootingSteps: z.array(z.string()),
  contactInfo: z.string().optional(),
});

type PostPurchaseSupportResponse = z.infer<
  typeof PostPurchaseSupportResponseSchema
>;

const postPurchaseSupportFunctionCall: FunctionCall = {
  name: postPurchaseSupportFunctionName,
  description: "Retrieve support information for a product",
  parameters: {
    type: "object",
    required: ["description", "troubleshootingSteps"],
    properties: {
      description: {
        type: "string",
        description: "Description of the issue",
      },
      troubleshootingSteps: {
        type: "array",
        description: "Steps to troubleshoot the issue",
        items: {
          type: "string",
          description: "Step to troubleshoot the issue",
        },
      },
      contactInfo: {
        type: "string",
        description: "Contact info for support",
      },
    },
  },
};

const handlePostPurchaseSupport = async (
  support: PostPurchaseSupportResponse
) => {
  // typesafe
  support.contactInfo;
  support.description;
  support.troubleshootingSteps;
};

interface MapParserToFunction<T = any> {
  function: (parsedResponse: T) => Promise<void>;
  parse: (data: unknown) => T;
}

const parserAndFunction: Record<AvailableFunctions, MapParserToFunction> = {
  [productInquiryFunctionName]: {
    parse: ProductInquiryResponseSchema.parse,
    function: handleProductInquiry,
  },
  [productRecommendationFunctionName]: {
    parse: ProductRecommendationResponseSchema.parse,
    function: handleProductRecommendation,
  },
  [postPurchaseSupportFunctionName]: {
    parse: PostPurchaseSupportResponseSchema.parse,
    function: handlePostPurchaseSupport,
  },
} as const;

const functions: Functions = [
  productInquiryFunctionCall,
  productRecommendationFunctionCall,
  postPurchaseSupportFunctionCall,
];

const prompt = (userQuestion: string) => `
Help this user with their question about our product:

User Question: ${userQuestion}
`;

const answerUserQuestionAboutOurEcommerceProduct = async (question: string) => {
  const competitorData = await openAICompletionsCallWithFallback({
    messages: [
      {
        role: "system",
        content:
          "You are an assistant helping us answer questions from users on our ecommerce website",
      },
      { role: "user", content: prompt(question) },
    ],
    function_call: "auto",
    functions,
  });

  await Promise.all(
    competitorData.choices.map((res) => {
      const functionName = res.message.function_call?.name as
        | AvailableFunctions
        | undefined;

      if (functionName) {
        const { parse, function: functionToExecute } =
          parserAndFunction[functionName];

        const functionResponse = res.message.function_call?.arguments;

        if (functionResponse) {
          const parsedResponse = parse(JSON.parse(functionResponse));

          return functionToExecute(parsedResponse);
        }
      }
    })
  );
};