"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { useEffect, useState } from "react";
import { OpenAIChatApi } from "llm-api";
import { completion } from "zod-gpt";

import { v4 } from "uuid";

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";

import { X } from "lucide-react";
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

const MIN_FEATURES_PER_PROJECT = 6;
const MAX_FEATURES_PER_PROJECT = 12;
const MIN_TASKS_PER_FEATURE = 6;
const MAX_TASKS_PER_FEATURE = 12;
const OPENAI_MODEL = "gpt-3.5-turbo-16k";

const formSchema = z.object({
  name: z.string().min(2, {
    message: "Project name must be at least 2 characters.",
  }),
  description: z.string().min(2, {
    message: "Project description must be at least 2 characters.",
  }),
  stack: z.array(z.string()).min(1, {
    message: "Project tech stack must have at least one item.",
  }),
});

console.log("OPENAI_API_KEY", process.env.OPENAI_API_KEY);

const openai = new OpenAIChatApi(
  {
    apiKey: "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",
  },
  {
    model: OPENAI_MODEL,
  }
);

type Feature = {
  name: string;
  description: string;
  uid: any;
  tasks?: Task[];
};

type Dependency = {
  uid: string;
  depUid: string;
};

type Task = {
  name: string;
  description: string;
  priority: "low" | "medium" | "high";
  order: number;
  uid: any;
  feature: any;
};

export default function Home() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: "",
      description: "",
      stack: [],
    },
  });

  const [stackInput, setStackInput] = useState("");
  const [features, setFeatures] = useState<Feature[]>([]);

  const { setValue } = form;

  async function onSubmit(values: z.infer<typeof formSchema>) {
    try {
      const feature_gen_prompt = `You are an AI software project manager. You use agile methodology and the best software project management techniques to plan software projects for indie developers.

I am an indie developer creating a new software project. Here are the details:
"""
Project Name: ${values.name}
Project Description: ${values.description}
Tech Stack: ${values.stack.join(", ")}
"""

Instructions: Generate a list of features for the project. Each feature should have the following:
"""
Name
Description
"""`;
      console.log(`prompt: ${feature_gen_prompt}`);
      const res = await completion(openai, feature_gen_prompt, {
        schema: z.object({
          features: z
            .array(
              z.object({
                name: z.string().describe("The name of the feature"),
                description: z
                  .string()
                  .describe("The description of the feature"),
              })
            )
            .min(MIN_FEATURES_PER_PROJECT)
            .max(MAX_FEATURES_PER_PROJECT),
        }),
      });
      console.log("RAW RES", res.data.features);
      const _features: Feature[] = res.data.features.map((feature) => {
        return {
          ...feature,
          uid: v4(),
        };
      });
      setFeatures(_features);
      generateDependencies(
        values.name,
        values.description,
        values.stack,
        _features
      );
    } catch (err: any) {
      console.error(err);
      return new Error(err);
    }
  }

  async function generateDependencies(
    p_name: string,
    p_desc: string,
    p_stack: string[],
    _features: Feature[]
  ) {
    try {
      const dep_gen_prompt = `You are an AI software project manager. You use agile methodology and the best software project management techniques to plan software projects for indie developers.

I am an indie developer creating a new software project. Here are the details:
"""
Project Name: ${p_name}
Project Description: ${p_desc}
Tech Stack: ${p_stack.join(", ")}
"""

I have created a list of different features that I'd like to implement:
"""
${_features.map((feature) => {
  return `Name: ${feature.name}, Description: ${feature.description}, ID: ${feature.uid}.\n`;
})}
"""

Instructions: For each feature, we need to figure out what other features need to be made first, and therefore need to create a list of dependencies (e.g. if you have 'user auth' and another feature that requires user auth, then that feature would have the dependency of 'user auth'). When returning features, return the feature UID and the dependencies UID's don't return anything else.`;

      // TODO: CATCH CIRCLES IN DEP GRAPH
      const featureUids: readonly [string, ...string[]] = _features.map(
        (feature) => feature.uid.toString()
      ) as [string, ...string[]];

      console.log(`featureUids: ${featureUids}`);
      console.log(`prompt: ${dep_gen_prompt}`);

      console.log(featureUids);
      const res = await completion(openai, dep_gen_prompt, {
        schema: z.object({
          dependencies: z
            .array(
              z.object({
                uid: z.enum(featureUids),
                dependencies: z
                  .array(
                    z.object({
                      uid: z
                        .enum(featureUids)
                        .describe("The UID of the feature"),
                    })
                  )
                  .describe("The UID of the dependencies of the feature"),
              })
            )
            .describe("The dependencies of the features"),
        }),
      });

      console.log("===>", res.data.dependencies);

      let deps: Dependency[] = [];

      // res.data.dependences: [
      //                         {
      //                          uid: "123",
      //                          dependencies: [{uid: "456"}, {uid: "789"}]
      //                         },
      //                         {
      //                          uid: "456",
      //                          dependencies: [{uid: "111"}, {uid: "222"}]
      //                         }
      //                      ]

      console.log("gathering dependencies");
      res.data.dependencies.forEach((dep) => {
        const feature = _features.find((feature) => feature.uid === dep.uid);
        console.log(`feature: ${feature}`);
        if (feature) {
          dep.dependencies.forEach((d) => {
            const dependency = _features.find(
              (feature) => feature.uid === d.uid
            );
            console.log(`dependency: ${dependency}`);
            if (dependency) {
              deps.push({
                uid: feature.uid,
                depUid: dependency.uid,
              });
            }
          });
        }
      });
      console.log("done gathering dependencies => ", deps);
      generateTasks(p_name, p_desc, p_stack, _features, deps);
    } catch (err) {
      console.error(err);
    }
  }

  async function generateTasks(
    p_name: string,
    p_desc: string,
    p_stack: string[],
    _features: Feature[],
    _dependencies: Dependency[]
  ) {
    const featurePromises = _features.map((feature) => {
      const related_features: (Feature | undefined)[] = _dependencies
        .filter((dep) => dep.uid === feature.uid)
        .map((dep) => _features.find((f) => f.uid === dep.depUid));

      generateTask(
        p_name,
        p_desc,
        p_stack,
        related_features,
        _features,
        feature
      );
    });

    const tasks = await Promise.all(featurePromises);
    console.log("all tasks", tasks);
  }

  async function generateTask(
    p_name: string,
    p_desc: string,
    p_stack: string[],
    related_features: (Feature | undefined)[],
    _features: Feature[],
    _feature: Feature
  ): Promise<Task[]> {
    try {
      const deps = _features.filter((feature) => feature.uid !== _feature.uid);

      const prompt = `You are an AI software project manager. You use agile methodology and the best software project management techniques to plan software projects for indie developers.

I am an indie developer creating a new software project. Here are the details:
"""
Project Name: ${p_name}
Project Description: ${p_desc}
Tech Stack: ${p_stack.join(", ")}
"""
###
I have already written the tasks for the following features:

${related_features.join(", ")}
"""

Generate tasks for the following feature in the context of the project. I have already written the tasks for the configuration of the project with the specified tech stack, so don’t generate any configuration-related tasks. Only generate tasks specific to the feature. Also, look back at the user story and the features I already wrote tasks for. Try your best to not generate any tasks that I may have already written for my other features.

Feature:
"""
Name: ${_feature.name}
Description: ${_feature.description}
ID: ${_feature.uid}
"""

Each task should have the following:
"""
Name
Description
Priority (low, medium, high)
Dependency-Based Order (numeric)
"""
`;

      const res = await completion(openai, prompt, {
        schema: z.object({
          tasks: z
            .array(
              z.object({
                name: z.string().describe("The task name"),
                description: z.string().describe("The task description"),
                priority: z
                  .enum(["low", "medium", "high"])
                  .describe("The task priority"),
                order: z
                  .number()
                  .describe(
                    "The order in which the task should be implemented"
                  ),
              })
            )
            .min(MIN_TASKS_PER_FEATURE)
            .max(MAX_TASKS_PER_FEATURE),
        }),
      });

      const tasks: Task[] = res.data.tasks.map((task) => {
        return {
          ...task,
          uid: v4(),
          feature: _feature.uid,
        };
      });

      console.log("tasks", tasks);

      return tasks;
    } catch (err) {
      console.error(err);
      return [];
    }
  }

  const keyHandler = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if ((e.key === "Enter" || e.key === "Tab") && stackInput !== "") {
      e.preventDefault();
      setValue("stack", [...form.getValues("stack"), stackInput]);
      setStackInput("");
    }
  };
  return (
    <div className="p-5">
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
          <FormField
            control={form.control}
            name="name"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Project Name</FormLabel>
                <FormControl>
                  <Input placeholder="" {...field} />
                </FormControl>
                <FormDescription>
                  This is the name of your project.
                </FormDescription>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="description"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Project Description</FormLabel>
                <FormControl>
                  <Input placeholder="" {...field} />
                </FormControl>
                <FormDescription>
                  This is your project description.
                </FormDescription>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="stack"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Tech Stack</FormLabel>
                <FormControl>
                  <Input
                    value={stackInput}
                    onChange={(e) => setStackInput(e.target.value)}
                    onKeyDown={keyHandler}
                  />
                </FormControl>
                <FormDescription>
                  This is your project tech stack.
                  {form.getValues("stack").map((stack) => (
                    <Badge
                      key={stack}
                      className="mr-2 font-normal rounded-md"
                      variant="outline"
                    >
                      <span className="mr-1">{stack}</span>
                      <X
                        className="inline font-light text-red-500"
                        size={16}
                        onClick={() =>
                          setValue(
                            "stack",
                            form.getValues("stack").filter((s) => s !== stack)
                          )
                        }
                      />
                    </Badge>
                  ))}
                </FormDescription>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button type="submit">Submit</Button>
        </form>
      </Form>
      <h1></h1>
      <Tabs defaultValue="features" className="w-75 m-auto">
        <TabsList>
          <TabsTrigger value="features">Features</TabsTrigger>
          <TabsTrigger value="tasks">Tasks</TabsTrigger>
        </TabsList>
        <TabsContent value="features">
          <Table>
            <TableCaption>A list of all the generated features.</TableCaption>
            <TableHeader>
              <TableRow>
                <TableHead>UID</TableHead>
                <TableHead>Name</TableHead>
                <TableHead>Description</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {features!.map((feature) => {
                return (
                  <TableRow key={feature.name}>
                    <TableCell
                      className="max-w-xs whitespace-nowrap"
                      style={{
                        maxWidth: "100px",
                        overflowX: "scroll",
                        overflowY: "hidden",
                      }}
                    >
                      {feature.uid}
                    </TableCell>
                    <TableCell>{feature.name}</TableCell>
                    <TableCell>{feature.description}</TableCell>
                  </TableRow>
                );
              })}
            </TableBody>
          </Table>
        </TabsContent>
        <TabsContent value="tasks">{}</TabsContent>
      </Tabs>
    </div>
  );
}

const TaskTable = (props: any) => {
  return (
    <Table>
      <TableCaption>Tasks for the feature {props.feature.name}.</TableCaption>
      <TableHeader>
        <TableRow>
          <TableHead>UID</TableHead>
          <TableHead>Name</TableHead>
          <TableHead>Description</TableHead>
          <TableHead>Priority</TableHead>
          <TableHead>Order</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {props.tasks.map((task: Task) => {
          return (
            <TableRow key={task.name}>
              <TableCell
                className="max-w-xs whitespace-nowrap"
                style={{
                  maxWidth: "100px",
                  overflowX: "scroll",
                  overflowY: "hidden",
                }}
              >
                {task.uid}
              </TableCell>
              <TableCell>{task.name}</TableCell>
              <TableCell>{task.description}</TableCell>
              <TableCell>{task.priority}</TableCell>
              <TableCell>{task.order}</TableCell>
            </TableRow>
          );
        })}
      </TableBody>
    </Table>
  );
};

/*

Electronic Medical Record System

The Shrivatsa Center for Interdimensional Healing requires a tailored open source electronic medical record (EMR) system designed with an intuitive user interface that supports the seamless management of appointments, comprehensive patient records, laboratory results, and integrated billing. This platform should prioritize security and patient confidentiality while providing real-time access to health data, facilitating efficient communication between departments, and incorporating advanced features such as appointment reminders, customizable fields for interdimensional treatment modalities, and automated billing processes that can adapt to a variety of insurance providers and self-pay scenarios. The system must be scalable and flexible to evolve with the center’s expanding interdimensional healthcare services.

*/
