import { v4 as uuidv4 } from "uuid";
import { NodeRect } from './node-rect';
import { useStateStore, ServiceNode, ServiceTypes, nodeLabel, BoundEnvVar, AppState, nodeIsConnectable, GatewayTCPNode, GatewayHttpsNode, AppNode } from '@/lib/state';
import { KeyboardEvent, FocusEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { z } from "zod";
import { DeepPartial, EventType, useForm, ControllerRenderProps, FieldPath } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Form, FormControl, FormField, FormItem, FormMessage } from './ui/form';
import { Input } from './ui/input';
import { Button } from './ui/button';
import { Handle, Position, useNodes } from "@xyflow/react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { PencilIcon, XIcon } from "lucide-react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
import { Textarea } from "./ui/textarea";

export function NodeApp(node: ServiceNode) {
  const { id, selected } = node;
  const isConnectablePorts = useMemo(() => nodeIsConnectable(node, "ports"), [node]);
  const isConnectableRepository = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
  return (
    <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
      <div style={{ padding: '10px 20px' }}>
        {nodeLabel(node)}
        <Handle
          id="repository"
          type={"target"}
          position={Position.Left}
          isConnectableStart={isConnectableRepository}
          isConnectableEnd={isConnectableRepository}
          isConnectable={isConnectableRepository}
        />
        <Handle
          id="ports"
          type={"source"}
          position={Position.Top}
          isConnectableStart={isConnectablePorts}
          isConnectableEnd={isConnectablePorts}
          isConnectable={isConnectablePorts}
        />
        <Handle
          id="env_var"
          type={"target"}
          position={Position.Bottom}
          isConnectableStart={true}
          isConnectableEnd={true}
          isConnectable={true}
        />
      </div>
    </NodeRect>
  );
}

const schema = z.object({
  name: z.string().min(1, "requried"),
  type: z.enum(ServiceTypes),
});

const portSchema = z.object({
  name: z.string().min(1, "required"),
  value: z.coerce.number().gt(0, "can not be negative"),
});

const sourceSchema = z.object({
  id: z.string().min(1, "required"),
  branch: z.string(),
  rootDir: z.string(),
});

export function NodeAppDetails({ id, data }: ServiceNode) {
  const store = useStateStore();
  const nodes = useNodes<AppNode>();
  const form = useForm<z.infer<typeof schema>>({
    resolver: zodResolver(schema),
    mode: "onChange",
    defaultValues: {
      name: data.label,
      type: data.type,
    }
  });
  const portForm = useForm<z.infer<typeof portSchema>>({
    resolver: zodResolver(portSchema),
    mode: "onSubmit",
    defaultValues: {
      name: "",
      value: 0,
    }
  });
  const onSubmit = useCallback((values: z.infer<typeof portSchema>) => {
    const portId = uuidv4();
    store.updateNodeData<"app">(id, {
      ports: (data.ports || []).concat({
        id: portId,
        name: values.name,
        value: values.value,
      }),
      envVars: (data.envVars || []).concat({
        id: uuidv4(),
        source: null,
        portId,
        name: `DODO_PORT_${values.name.toUpperCase()}`,
      }),
    });
    portForm.reset();
  }, [id, data, portForm, store]);
  useEffect(() => {
    const sub = form.watch((value: DeepPartial<z.infer<typeof schema>>, { name, type }: { name?: keyof z.infer<typeof schema> | undefined, type?: EventType | undefined }) => {
      console.log({ name, type });
      if (type !== "change") {
        return;
      }
      switch (name) {
        case "name":
          if (!value.name) {
            break;
          }
          store.updateNodeData<"app">(id, {
            label: value.name,
          });
          break;
        case "type":
          if (!value.type) {
            break;
          }
          store.updateNodeData<"app">(id, {
            type: value.type,
          })
          break;
      }
    });
    return () => sub.unsubscribe();
  }, [id, form, store]);
  const focus = useCallback((field: ControllerRenderProps<z.infer<typeof schema>, FieldPath<z.infer<typeof schema>>>, name: string) => {
    return (e: HTMLElement | null) => {
      field.ref(e);
      if (e != null && name === data.activeField) {
        console.log(e);
        e.focus();
        store.updateNodeData(id, {
          activeField: undefined,
        });
      }
    }
  }, [id, data, store]);
  const [typeProps, setTypeProps] = useState({});
  useEffect(() => {
    if (data.activeField === "type") {
      setTypeProps({
        open: true,
        onOpenChange: () => store.updateNodeData(id, { activeField: undefined }),
      });
    } else {
      setTypeProps({});
    }
  }, [id, data, store, setTypeProps]);
  const editAlias = useCallback((e: BoundEnvVar) => {
    return () => {
      store.updateNodeData(id, {
        ...data,
        envVars: data.envVars!.map((o) => {
          if (o.id !== e.id) {
            return o;
          } else return {
            ...o,
            isEditting: true,
          }
        }),
      });
    };
  }, [id, data, store]);
  const saveAlias = useCallback((e: BoundEnvVar, value: string, store: AppState) => {
    store.updateNodeData(id, {
      ...data,
      envVars: data.envVars!.map((o) => {
        if (o.id !== e.id) {
          return o;
        }
        if (value) {
          return {
            ...o,
            isEditting: false,
            alias: value.toUpperCase(),
          }
        }
        console.log(o);
        if ("alias" in o) {
          const { alias: _, ...rest } = o;
          console.log(rest);
          return {
            ...rest,
            isEditting: false,
          };
        }
        return {
          ...o,
          isEditting: false,
        };
      }),
    });
  }, [id, data]);
  const saveAliasOnEnter = useCallback((e: BoundEnvVar) => {
    return (event: KeyboardEvent<HTMLInputElement>) => {
      if (event.key === "Enter") {
        event.preventDefault();
        saveAlias(e, event.currentTarget.value, store);
      }
    }
  }, [store, saveAlias]);
  const saveAliasOnBlur = useCallback((e: BoundEnvVar) => {
    return (event: FocusEvent<HTMLInputElement>) => {
      saveAlias(e, event.currentTarget.value, store);
    }
  }, [store, saveAlias]);
  const removePort = useCallback((portId: string) => {
    // TODO(gio): this is ugly
    const tcpRemoved = new Set<string>();
    console.log(store.edges);
    store.setEdges(store.edges.filter((e) => {
      if (e.source !== id || e.sourceHandle !== "ports") {
        return true;
      }
      const tn = store.nodes.find((n) => n.id == e.target)!;
      if (e.targetHandle === "https") {
        const t = tn as GatewayHttpsNode;
        if (t.data.https?.serviceId === id && t.data.https.portId === portId) {
          return false;
        }
      }
      if (e.targetHandle === "tcp") {
        const t = tn as GatewayTCPNode;
        if (tcpRemoved.has(t.id)) {
          return true;
        }
        if (t.data.exposed.find((e) => e.serviceId === id && e.portId === portId)) {
          tcpRemoved.add(t.id);
          return false;
        }
      }
      if (e.targetHandle === "env_var") {
        if (tn && (tn.data.envVars || []).find((ev) => ev.source === id && "portId" in ev && ev.portId === portId)) {
          return false;
        }
      }
      return true;
    }));
    store.nodes.filter((n) => n.type === "gateway-https" && n.data.https && n.data.https.serviceId === id && n.data.https.portId === portId).forEach((n) => {
      store.updateNodeData<"gateway-https">(n.id, {
        https: undefined,
      });
    });
    store.nodes.filter((n) => n.type === "gateway-tcp").forEach((n) => {
      const filtered = n.data.exposed.filter((e) => {
        if (e.serviceId === id && e.portId === portId) {
          return false;
        } else {
          return true;
        }
      })
      if (filtered.length != n.data.exposed.length) {
        store.updateNodeData<"gateway-tcp">(n.id, {
          exposed: filtered,
        });
      }
    });
    store.nodes.filter((n) => n.type === "app" && n.data.envVars).forEach((n) => {
      store.updateNodeData<"app">(n.id, {
        envVars: n.data.envVars.filter((ev) => {
          if (ev.source === id && "portId" in ev && ev.portId === portId) {
            return false;
          }
          return true;
        })
      });
    });
    store.updateNodeData<"app">(id, {
      ports: (data.ports || []).filter((p) => p.id !== portId),
      envVars: (data.envVars || []).filter((ev) => !(ev.source === null && "portId" in ev && ev.portId === portId)),
    });
  }, [id, data, store]);
  const setPreBuildCommands = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
    store.updateNodeData<"app">(id, {
      preBuildCommands: e.currentTarget.value,
    });
  }, [id, store]);

  const sourceForm = useForm<z.infer<typeof sourceSchema>>({
    resolver: zodResolver(sourceSchema),
    mode: "onChange",
    defaultValues: {
      id: data?.repository?.id,
      branch: data.repository && "branch" in data.repository ? data.repository.branch : undefined,
      rootDir: data.repository && "rootDir" in data.repository ? data.repository.rootDir : undefined,
    },
  });
  useEffect(() => {
    const sub = sourceForm.watch((value: DeepPartial<z.infer<typeof sourceSchema>>, { name }: { name?: keyof z.infer<typeof sourceSchema> | undefined, type?: EventType | undefined }) => {
      console.log(value);
      if (name === "id") {
        let edges = store.edges;
        if (data?.repository?.id !== undefined) {
          edges = edges.filter((e) => {
            if (e.target === id && e.targetHandle === "repository" && e.source === data.repository.id) {
              return false;
            } else {
              return true;
            }
          });
        }
        if (value.id !== undefined) {
          edges = edges.concat({
            id: uuidv4(),
            source: value.id,
            sourceHandle: "repository",
            target: id,
            targetHandle: "repository",
          });
        }
        store.setEdges(edges);
        store.updateNodeData<"app">(id, {
          repository: {
            id: value.id,
          },
        });
      } else if (name === "branch") {
        store.updateNodeData<"app">(id, {
          repository: {
            ...data?.repository,
            branch: value.branch,
          },
        });
      } else if (name === "rootDir") {
        store.updateNodeData<"app">(id, {
          repository: {
            ...data?.repository,
            rootDir: value.rootDir,
          },
        });
      }
    });
    return () => sub.unsubscribe();
  }, [id, data, sourceForm, store]);

  return (
    <>
      <Form {...form}>
        <form>
          <FormField
            control={form.control}
            name="name"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <Input placeholder="name" className="border border-black" {...field} ref={focus(field, "name")} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="type"
            render={({ field }) => (
              <FormItem>
                <Select onValueChange={field.onChange} defaultValue={field.value} {...typeProps}>
                  <FormControl>
                    <SelectTrigger>
                      <SelectValue placeholder="Runtime" />
                    </SelectTrigger>
                  </FormControl>
                  <SelectContent>
                    {ServiceTypes.map((t) => (
                      <SelectItem key={t} value={t}>{t}</SelectItem>
                    ))}
                  </SelectContent>
                </Select>
                <FormMessage />
              </FormItem>
            )}
          />
        </form>
      </Form>
      Source
      <Form {...sourceForm}>
        <form className="space-y-2">
          <FormField
            control={sourceForm.control}
            name="id"
            render={({ field }) => (
              <FormItem>
                <Select onValueChange={field.onChange} defaultValue={field.value}>
                  <FormControl>
                    <SelectTrigger>
                      <SelectValue placeholder="Repository" />
                    </SelectTrigger>
                  </FormControl>
                  <SelectContent>
                    {(nodes.filter((n) => n.type === "github" && n.data.repository?.id !== undefined) as GithubNode[]).map((n) => (
                      <SelectItem key={n.id} value={n.id}>{`${n.data.repository?.sshURL}`}</SelectItem>
                    ))}
                  </SelectContent>
                </Select>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={sourceForm.control}
            name="branch"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <Input placeholder="master" className="border border-black" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={sourceForm.control}
            name="rootDir"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <Input placeholder="/" className="border border-black" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
        </form>
      </Form>
      Ports
      <ul>
        {data && data.ports && data.ports.map((p) => (<li key={p.id}><Button size={"icon"} variant={"ghost"} onClick={() => removePort(p.id)}><XIcon /></Button> {p.name} - {p.value}</li>))}
      </ul>
      <Form {...portForm}>
        <form className="flex flex-row space-x-1" onSubmit={portForm.handleSubmit(onSubmit)}>
          <FormField
            control={portForm.control}
            name="name"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <Input placeholder="name" className="border border-black" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={portForm.control}
            name="value"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <Input placeholder="value" className="border border-black" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button type="submit">Add Port</Button>
        </form>
      </Form>
      Env Vars
      <ul>
        {data && data.envVars && data.envVars.map((v) => {
          if ("name" in v) {
            const value = "alias" in v ? v.alias : v.name;
            if (v.isEditting) {
              return (<li key={v.id}><Input type="text" className="border border-black" defaultValue={value} onKeyUp={saveAliasOnEnter(v)} onBlur={saveAliasOnBlur(v)} autoFocus={true} /></li>);
            }
            return (
              <li key={v.id} onClick={editAlias(v)}>
                <TooltipProvider>
                  <Tooltip>
                    <TooltipTrigger>
                      <Button size={"icon"} variant={"ghost"}><PencilIcon /></Button>
                      {value}
                    </TooltipTrigger>
                    <TooltipContent>
                      {v.name}
                    </TooltipContent>
                  </Tooltip>
                </TooltipProvider>
              </li>
            );
          }
        })}
      </ul>
      Pre-Build Commands
      <Textarea placeholder="new line separated list of commands to run before running the service" value={data.preBuildCommands} onChange={setPreBuildCommands} />
    </>);
}