import {
  Box,
  Menu,
  MenuButton,
  MenuItem,
  MenuList,
  Text,
  Button,
  Flex,
  Checkbox,
  Input,
  Grid,
  GridItem,
  HStack,
  Portal,
} from "@chakra-ui/react";
import { defineComponent } from "@qorebase/app-component";
import {
  InsightSchema,
  Row,
  TableSchema,
  ViewSchema,
} from "@qorebase/app-runtime/src/interfaces";
import React from "react";
import { ChevronCheck, ChevronDown } from "@feedloop/icon";
import {
  RuntimeContext,
  useVariablesContext,
  VariablesProvider,
  useEvaluateObject,
  useDisableCondition,
} from "@qorebase/app-component";
import useSWR from "swr";
import { GetSchema200ResponseTablesInnerColumnsInner } from "@qorebase/sdk";
import { tableParams } from "@qorebase/app-runtime/src/utils/tableParams";
import produce from "immer";
import { useFormSubmitHook } from "@qorebase/app-runtime/src/hooks/useFormSubmitHook";
import { useSourceView } from "../../../src/app/hooks/useSourceView";
import format from "@qorebase/app-runtime/src/utils/format";
import InputComponentLabel from "@qorebase/app-runtime/src/components/InputComponentLabel";

const MAX_DISPLAY_OPTION = 5;

const ManyToManyOption = (
  props: {
    item: Row;
    value: number[];
    onChange: (value: number[]) => void;
    search: string;
  } & typeof DropdownRelation["Component"]["defaultProps"]
) => {
  const label = props.hooks.useTemplate(props.properties.itemLabel);
  const isSelected = props.value.includes(props.item.id);
  const showOption = label.toLowerCase().startsWith(props.search.toLowerCase());

  const onChange = React.useCallback(
    (event: React.SyntheticEvent) => {
      event.preventDefault();
      props.onChange(
        isSelected
          ? props.value.filter((id) => id !== props.item.id)
          : props.value.concat(props.item.id)
      );
    },
    [props.onChange, isSelected, props.value, props.item.id]
  );

  return (
    showOption && (
      <MenuItem onClick={onChange} backgroundColor={isSelected && "gray.100"}>
        <Flex>
          <Checkbox
            borderColor="gray"
            isChecked={isSelected}
            mr={2}
            onChange={onChange}
          />
          <Text>{label || props.item.id}</Text>
        </Flex>
      </MenuItem>
    )
  );
};

const Option = (
  props: {
    item: Row;
    value: number;
    onChange: (value: number) => void;
    search: string;
  } & typeof DropdownRelation["Component"]["defaultProps"]
) => {
  const label = props.hooks.useTemplate(props.properties.itemLabel);
  const isSelected = props.value === props.item.id;
  const showOption = label.toLowerCase().startsWith(props.search.toLowerCase());

  return (
    showOption && (
      <MenuItem
        onClick={() => props.onChange(props.item.id)}
        backgroundColor={isSelected && "gray.100"}
      >
        {label || props.item.id}
      </MenuItem>
    )
  );
};

const MenuButtonValue = React.forwardRef<
  HTMLButtonElement,
  typeof DropdownRelation["Component"]["defaultProps"]
>(function MenuButtonValue(props, ref) {
  const label = props.hooks.useTemplate(props.properties.itemLabel);
  const placeholder = props.hooks.useEvaluate(props.properties.placeholder);
  // @ts-ignore
  const { isDisabled } = useDisableCondition(props.instance);

  return (
    <MenuButton
      as={Button}
      style={{
        border: "1px solid rgb(226, 232, 240)",
        pointerEvents: isDisabled ? "none" : "auto",
        background: isDisabled && "#E7E7E7",
        color: isDisabled && "#A2A2A2",
      }}
      bgColor={"surface.900"}
      width="100%"
      ref={ref}
      textAlign="left"
      borderRadius="md"
      variant="outline"
      fontWeight="normal"
      _focus={{ shadow: "0px" }}
      rightIcon={<ChevronDown />}
    >
      <Text noOfLines={1} color={isDisabled && "#A2A2A2"}>
        {props.children || label || placeholder}
      </Text>
    </MenuButton>
  );
});

const DropdownRelation = defineComponent({
  name: "Dropdown Relation",
  type: "none",
  icon: "SelectInput",
  group: "input",
  display: "inline",
  defaultProps: {
    label: "Dropdown Relation",
    placeholder: "Placeholder here",
    hidden: false,
    defaultValue: "",
    data: {
      inputID: "DropdownRelation",
      field: { type: "relation", options: {} },
      validation: {
        required: false,
      },
    },
    itemLabel: "",
    options: {
      target: "",
      params: {},
    },
  },
  propDefinition: {
    label: { group: "Design", type: "expression", options: {} },
    placeholder: {
      group: "Design",
      type: "expression",
      options: {},
    },
    hidden: {
      group: "Design",
      type: "boolean",
      options: { format: "boolean" },
    },
    data: {
      group: "Design",
      type: "state",
      options: {
        inputID: "DropdownRelation",
        field: { type: "relation", options: {} },
      },
    },
    defaultValue: {
      group: "Design",
      type: "expression",
      options: {},
    },
    itemLabel: { group: "Design", type: "custom", options: {} },
    options: {
      type: "custom",
      group: "Design",
      options: {},
      style: {
        width: "full-width",
      },
    },
  },
  Editor: async () => {
    return {
      itemLabel: (props) => {
        const inputID = props.values?.data.inputID;
        const tables = RuntimeContext.useSelectState(
          (state) => state.schema.tables
        );
        const view = useSourceView();
        const column = React.useMemo(
          () => view?.columns.find((column) => column.name === inputID),
          [view?.columns, inputID]
        );
        const targetTable = React.useMemo(
          () =>
            tables.find(
              (table) => table.name === column?.references.targetTable
            ),
          [tables, inputID]
        );
        const items = React.useMemo(
          () =>
            targetTable?.columns
              .map((field) => ({
                display: `item.${field.name}`,
                id: `item.${field.name}`,
              }))
              .concat({
                display: "item.id",
                id: "item.id",
              }),
          [targetTable?.columns]
        );
        return (
          <VariablesProvider variables={items}>
            {/* @ts-ignore */}
            <props.PropertyEditor.string {...props} />
          </VariablesProvider>
        );
      },
      options: (props) => {
        const schema = RuntimeContext.useSelectState((state) => state.schema);

        const selectedColumn = React.useMemo(() => {
          if (!!props.view && Array.isArray(props.view.columns)) {
            return props.view.columns.find(
              (col) => col.name === props.values?.data.inputID
            ) as GetSchema200ResponseTablesInnerColumnsInner;
          }
          return;
        }, [props.view, props.values]);

        const selectedSourceTable: TableSchema = React.useMemo(() => {
          return schema.tables.find(
            (t) => t.name === selectedColumn?.references.targetTable
          );
        }, [selectedColumn, schema.tables]);

        const optionsMap = React.useMemo(() => {
          if (!selectedSourceTable) return [];
          const views = schema.views
            .filter((view) => view.query.table === selectedSourceTable.name)
            .map((v) => ({
              label: v.name,
              value: v.name,
            }));
          const insights = schema.insights
            .filter((insight) => insight.table === selectedSourceTable.name)
            .map((v) => ({
              label: v.name,
              value: v.name,
            }));
          const table = {
            label: selectedSourceTable.name,
            value: selectedSourceTable.name,
          };
          return [table, ...views, ...insights];
        }, [schema, selectedSourceTable]);

        const [search, setSearch] = React.useState("");

        const isValidTable = React.useMemo(() => {
          return (
            !!selectedSourceTable &&
            (selectedSourceTable.name === props.value?.target ||
              !!schema.views.find(
                (v) =>
                  v.query.table === selectedSourceTable.name &&
                  v.name === props.value?.target
              ) ||
              !!schema.insights.find(
                (i) =>
                  i.table === selectedSourceTable.name &&
                  i.name === props.value?.target
              ))
          );
        }, [schema, selectedSourceTable, props.value?.target]);

        const handleSearchInput = React.useCallback<
          React.ChangeEventHandler<HTMLInputElement>
        >((e) => setSearch(e.target.value), []);
        const resetSearch = React.useCallback(() => setSearch(""), []);

        const source: TableSchema | ViewSchema | InsightSchema =
          React.useMemo(() => {
            if (!isValidTable) return;
            if (selectedSourceTable?.name === props.value?.target) {
              return selectedSourceTable;
            }
            const view = schema.views.find(
              (v) => v.name === props.value?.target
            );
            if (view) return view;
            const insight = schema.insights.find(
              (i) => i.name === props.value?.target
            );
            if (insight) return insight;
            return;
          }, [schema, props.value?.target, selectedSourceTable, isValidTable]);

        const params = React.useMemo(() => {
          if (source) {
            return (
              tableParams(source).map((param) => ({
                slug: param.name,
                type: param.type,
                required: true,
              })) || []
            );
          }
          return [];
        }, [source]);

        const handleUpdateParams = React.useCallback(
          (slug: string, value: string) => {
            props.onChange(
              produce(props.value, (draft) => {
                if (value === "0") {
                  draft.params[slug] = "";
                } else {
                  draft.params[slug] = value;
                }
              })
            );
          },
          [props.value]
        );

        React.useEffect(() => {
          if (!isValidTable && !!selectedSourceTable) {
            props.onChange({ target: selectedSourceTable.name, params: {} });
          }
        }, [isValidTable, selectedSourceTable]);

        if (!selectedSourceTable) return null;

        return (
          <Grid templateColumns="repeat(12, 1fr)" py={"10px"} gap={2}>
            <GridItem colSpan={3} maxW="full" display={"flex"}>
              <HStack alignItems="start" pt="10px">
                <Text fontSize="sm" color="#6F809A" textTransform="capitalize">
                  Options
                </Text>
              </HStack>
            </GridItem>
            <GridItem
              colSpan={9}
              maxW="full"
              alignItems="center"
              justifyContent="flex-end"
            >
              <Box>
                <Box>
                  <Menu onClose={resetSearch} isLazy>
                    <MenuButton
                      as={Button}
                      style={{ border: "1px solid rgb(226, 232, 240)" }}
                      bgColor={"surface.900"}
                      width="100%"
                      textAlign="left"
                      borderRadius="md"
                      variant="outline"
                      fontWeight="normal"
                      size="sm"
                      _focus={{ shadow: "0px", background: "none" }}
                      _hover={{ background: "none" }}
                      _active={{ background: "none" }}
                      rightIcon={
                        <Box bg="white" padding="4px" pr="0">
                          <ChevronDown />
                        </Box>
                      }
                    >
                      {props.value?.target || "Select options"}
                    </MenuButton>
                    <MenuList maxW="200px">
                      <Box px="8px" pb="8px">
                        <Input
                          placeholder="Search"
                          onChange={handleSearchInput}
                          value={search}
                        />
                      </Box>
                      {optionsMap.map((opt) => (
                        <MenuItem
                          key={opt.value}
                          display={!opt.value.includes(search) && "none"}
                          onClick={() => {
                            props.onChange({
                              ...props.value,
                              target: opt.value || "",
                            });
                          }}
                        >
                          <Flex>
                            <Text
                              flex={1}
                              color={
                                props.value?.target === opt.value && "#0065FF"
                              }
                            >
                              {opt.label}
                            </Text>
                            {props.value?.target === opt.value && (
                              <ChevronCheck
                                color={
                                  props.value?.target === opt.value && "#0065FF"
                                }
                              />
                            )}
                          </Flex>
                        </MenuItem>
                      ))}
                    </MenuList>
                  </Menu>
                </Box>
                <Box pt="1rem">
                  {params.length > 0 && (
                    <>
                      <Text fontWeight="semibold" fontSize="sm" color="#A6B0C1">
                        Parameters
                      </Text>
                      {params.map((param) => {
                        return (
                          <Grid
                            key={param.slug}
                            templateColumns="repeat(12, 1fr)"
                            gap={2}
                            my="8px"
                          >
                            <GridItem
                              colSpan={3}
                              display="flex"
                              alignItems="center"
                            >
                              <Text fontSize="sm" color="#6F809A">
                                {param.slug}
                              </Text>
                            </GridItem>
                            <GridItem colSpan={9} my="8px">
                              <props.PropertyEditor.string
                                propDef={{
                                  type: "string",
                                  options: { format: "text" },
                                }}
                                propName={param.slug}
                                value={props.value?.params[param.slug]}
                                onChange={(value) => {
                                  handleUpdateParams(param.slug, value);
                                }}
                              />
                            </GridItem>
                          </Grid>
                        );
                      })}
                    </>
                  )}
                </Box>
              </Box>
            </GridItem>
          </Grid>
        );
      },
    };
  },
  Component: (props) => {
    const boxWidth = React.useRef<HTMLButtonElement>(null);
    const placeholder = props.hooks.useEvaluate(props.properties.placeholder);
    const defaultValue = props.hooks.useEvaluate(props.properties.defaultValue);
    const title = props.hooks.useEvaluate(props.properties.label);
    const params = useEvaluateObject(props.properties.options?.params || {});
    const ref = React.useRef(null);
    const data = props.hooks.useRuntimeInput<"number">(
      props.properties.data,
      props.data.page.row,
      {
        validate: (value) => {
          if (typeof value === "undefined") {
            return { error: "" };
          } else if (!value) {
            return { error: "Required field, Please enter the data" };
          }
        },
      }
    );
    const [search, setSearch] = React.useState("");

    const { values } = useVariablesContext();

    React.useEffect(() => {
      const value = parseInt(defaultValue);
      data.onChange(isNaN(value) ? undefined : value);
    }, [defaultValue, data.onChange]);

    const tableName = React.useMemo(
      () =>
        props.app.schema.tables.find((t) => t.name === props.pageSource.target)
          ?.name ||
        props.app.schema.views.find((v) => v.name === props.pageSource.target)
          ?.query.table,
      [props.app.schema, props.pageSource.target]
    );

    const relationData = React.useMemo(() => {
      const currentView = props.app.schema.tables.find(
        (table) => table.name === tableName
      );
      const field = currentView?.columns.find(
        (field) => field.name === props.properties.data.inputID
      );
      const table =
        field &&
        props.app.schema.tables.find(
          (table) => table.name === field.references.targetTable
        );
      return { table, field };
    }, [props.app.schema, props.properties.data.inputID, tableName]);

    const isOneToMany = React.useMemo(
      () =>
        relationData.field?.references.type === "OneToMany" &&
        !relationData.field?.references.origin,
      [relationData]
    );

    const isManyToMany = React.useMemo(
      () => relationData.field?.references.type === "ManyToMany",
      [relationData]
    );

    const [selectedIds, setSelectedIds] = React.useState<number[]>([]);

    const client = props.hooks.useClient();

    const fieldName = React.useMemo(() => {
      if (isOneToMany) {
        const lastIndex = props.properties.data.inputID.lastIndexOf("_items");
        return props.properties.data.inputID.slice(
          0,
          lastIndex === -1 ? Infinity : lastIndex
        );
      }
      const lastIndex = props.properties.data.inputID.lastIndexOf(
        relationData.field?.references.targetTable
      );
      return props.properties.data.inputID
        .slice(0, lastIndex)
        .concat(
          tableName,
          props.properties.data.inputID.slice(
            lastIndex + (relationData.field?.references.targetTable.length || 0)
          )
        );
    }, [
      props.properties.data.inputID,
      relationData.field?.references.targetTable,
      tableName,
    ]);

    const options = useSWR(
      [client, relationData.table?.name, props.properties.options, params],
      async () => {
        const table =
          relationData.table?.name === props.properties.options?.target
            ? client.table(relationData.table?.name)
            : client.view(props.properties.options?.target);
        const data = await table
          .readRows(
            { params },
            {
              skip:
                !props.properties.options?.target || !relationData.table?.name,
            }
          )
          .toPromise();
        return data.data?.nodes || [];
      },
      {
        suspense: !!props.pageSource.prefetch,
      }
    );

    const selectedData = useSWR(
      [
        client,
        isManyToMany,
        isOneToMany,
        relationData.field,
        fieldName,
        props.data.page?.row?.id,
      ],
      () =>
        isManyToMany || isOneToMany
          ? client
              .table(relationData.field?.references.targetTable)
              .readRows(
                {
                  condition: {
                    [fieldName]: {
                      id: { $eq: props.data.page?.row?.id },
                    },
                  },
                },
                {
                  skip: !props.data.page?.row?.id,
                }
              )
              .toPromise()
              .then((data) => {
                return data.data?.nodes || [];
              })
          : Promise.resolve([]),
      {
        suspense: !!props.pageSource.prefetch,
      }
    );

    React.useEffect(() => {
      if (isManyToMany || isOneToMany) {
        setSelectedIds(selectedData.data?.map((data) => data.id) || []);
        return;
      }
      if (props.data.page.row[props.properties.data.inputID]) {
        data.onChange(props.data.page.row[props.properties.data.inputID]);
      }
    }, [
      props.data.page.row?.id,
      selectedData.data,
      isManyToMany,
      isOneToMany,
      props.properties.data.inputID,
    ]);

    useFormSubmitHook(
      async (row) => {
        if (!(isManyToMany || isOneToMany)) return;
        const addRelations = selectedIds
          .filter(
            (id) => !(selectedData.data || []).find((row) => row.id === id)
          )
          .map((id) => ({
            operation: "AddRelation",
            instruction: {
              table: tableName,
              name: `addRelation_${tableName}_${props.properties.data.inputID}_${id}`,
              relation: {
                name: props.properties.data.inputID,
                data: {
                  origin: row.id,
                  target: id,
                },
              },
            },
          }));

        const removeRelations = (selectedData.data || [])
          .map((row) => row.id)
          .filter((id) => !selectedIds.includes(id))
          .map((id) => ({
            operation: "RemoveRelation",
            instruction: {
              table: tableName,
              name: `removeRelation_${tableName}_${props.properties.data.inputID}_${id}`,
              relation: {
                name: props.properties.data.inputID,
                data: {
                  origin: row.id,
                  target: id,
                },
              },
            },
          }));

        await client.project.axios({
          baseURL: client.project.config.endpoint,
          url: "/v1/execute",
          method: "POST",
          data: {
            operations: addRelations.concat(removeRelations),
          },
        });
      },
      [
        isManyToMany,
        selectedIds,
        selectedData.data,
        tableName,
        props.properties.data.inputID,
        client,
      ]
    );

    const itemRow = options.data?.find((row) => row.id === data.value);

    return (
      <Box display={props.properties.hidden ? "none" : undefined}>
        <InputComponentLabel
          title={title}
          name="dropdown-relation"
          isRequired={props.properties.data.validation?.required}
          isEmpty={title.length === 0}
        />
        <Box ref={ref}>
          <Portal containerRef={ref}>
            <Menu isLazy>
              <props.components.ListItemVariables variables={itemRow || {}}>
                {/* @ts-ignore */}
                <MenuButtonValue ref={boxWidth} {...props}>
                  {isManyToMany || isOneToMany
                    ? (options.data || [])
                        .filter((row) => selectedIds.includes(row.id))
                        .map((row) => {
                          return format(props.properties.itemLabel, {
                            ...values,
                            ...Object.fromEntries(
                              Object.entries(row).map(([key, value]) => [
                                `item.${key}`,
                                value,
                              ])
                            ),
                          });
                        })
                        .join(", ")
                    : null}
                </MenuButtonValue>
              </props.components.ListItemVariables>
              <Portal appendToParentPortal={true}>
                <MenuList
                  border="0px"
                  width={boxWidth?.current?.offsetWidth}
                  zIndex={3}
                >
                  <Text
                    color="gray"
                    py={2}
                    px={4}
                    style={{ cursor: "default" }}
                  >
                    {placeholder}
                  </Text>
                  {options.data?.length > MAX_DISPLAY_OPTION && (
                    <Input
                      value={search}
                      onChange={(e) => setSearch(e.target.value)}
                      placeholder="Search..."
                      mb={2}
                    />
                  )}
                  <Box maxH={MAX_DISPLAY_OPTION * 8} overflowY="auto">
                    {options.data?.map((option) => (
                      <props.components.ListItemVariables
                        key={option.id}
                        variables={option}
                      >
                        {isManyToMany || isOneToMany ? (
                          <ManyToManyOption
                            item={option}
                            onChange={setSelectedIds}
                            value={selectedIds}
                            search={search}
                            {...props}
                          />
                        ) : (
                          <Option
                            item={option}
                            onChange={data.onChange}
                            value={data.value as number}
                            search={search}
                            {...props}
                          />
                        )}
                      </props.components.ListItemVariables>
                    ))}
                  </Box>
                </MenuList>
              </Portal>
            </Menu>
          </Portal>
        </Box>
        {!!data.validation.error && (
          <Text fontSize="sm" color="red">
            {data.validation.error}
          </Text>
        )}
      </Box>
    );
  },
});

export default DropdownRelation;
