import {
  Checkbox,
  CheckboxGroup,
  FormControl,
  FormErrorMessage,
  Heading,
  HStack,
  Input,
  NumberDecrementStepper,
  NumberIncrementStepper,
  NumberInput,
  NumberInputField,
  NumberInputStepper,
  Radio,
  RadioGroup,
  Select,
  Stack,
  Textarea,
} from "@chakra-ui/react";
import { type Dispatch, type SetStateAction, useState } from "react";

interface component {
  id: string;
  max_length?: number;
  options?: { default?: boolean; value: string }[];
  required: boolean;
  title: string;
  type: string;
  value?: number | string | string[];
}

export default function ({
  components,
  read_only = true,
}: {
  components: { [k: number]: component[] };
  read_only: boolean;
}) {
  function isNumberElemInvalid(e: HTMLInputElement): boolean {
    return !(
      e.value ||
      isNaN(e.valueAsNumber) ||
      e.valueAsNumber <= Number.MAX_SAFE_INTEGER ||
      e.valueAsNumber >= Number.MIN_SAFE_INTEGER
    );
  }

  function updateState(
    state: { [k: string]: string | string[] },
    setState: Dispatch<SetStateAction<{}>>,
    id: string,
    value: string,
  ) {
    const newState = { ...state };
    newState[id] = value;

    setState(newState);
  }

  function renderCheckboxOptions(
    c: component,
    state: { [k: string]: string | string[] },
    setState: Dispatch<SetStateAction<{}>>,
  ) {
    if (!c.options) throw new Error("Options for checkbox are undefined");

    const boxes = [];
    const checkedBoxes = [];

    for (const option of c.options) {
      if (
        option.default ||
        (read_only && Array.isArray(c.value) && c.value.includes(option.value))
      )
        checkedBoxes.push(option.value);

      boxes.push(
        <Checkbox
          isReadOnly={read_only}
          onChange={(e) => {
            const newState = { ...state };
            const groupValues = newState[c.id] ?? [];

            if (!Array.isArray(groupValues))
              throw new Error("Expected CheckboxGroup values to be an array");

            e.target.checked
              ? groupValues.push(e.target.value)
              : groupValues.splice(
                  groupValues.findIndex((v) => v === e.target.value),
                  1,
                );

            newState[c.id] = groupValues;
            setState(newState);
          }}
          value={option.value}
        >
          {option.value}
        </Checkbox>,
      );
    }

    return (
      <CheckboxGroup defaultValue={checkedBoxes}>
        <HStack spacing={5}>{boxes}</HStack>
      </CheckboxGroup>
    );
  }

  function renderRadioElements(
    c: component,
    state: { [k: string]: string | string[] },
    setState: Dispatch<SetStateAction<{}>>,
  ) {
    if (!c.options) throw new Error("Options for radio buttons are undefined!");
    const buttons = [];

    for (const option of c.options) {
      buttons.push(
        <Radio checked={option.default} value={option.value}>
          {option.value}
        </Radio>,
      );
    }

    return (
      <RadioGroup
        id={c.id}
        onChange={(e) => {
          const newState = { ...state };
          newState[c.id] = e;

          setState(newState);
        }}
      >
        <Stack direction="row">{buttons}</Stack>
      </RadioGroup>
    );
  }

  function renderSelectElements(
    c: component,
    state: { [k: string]: string | string[] },
    setState: Dispatch<SetStateAction<{}>>,
  ) {
    if (!c.options) throw new Error("Options for select are undefined!");

    const selectOptions = [];

    for (const option of c.options) {
      selectOptions.push(<option value={option.value}>{option.value}</option>);
    }

    return (
      <Select
        onChange={(e) => {
          const newState = { ...state };
          newState[c.id] = e.target.value;

          setState(newState);
        }}
        placeholder="Select option"
      >
        {selectOptions}
      </Select>
    );
  }

  function generateReactComponents(
    components: component[],
    state: { [k: string]: string | string[] },
    setState: Dispatch<SetStateAction<{}>>,
  ): JSX.Element[] {
    const fragmentsList = [];

    for (const component of components) {
      fragmentsList.push(
        <Heading size="md">{component.title}</Heading>,
        <br />,
      );

      switch (component.type) {
        case "checkbox":
          fragmentsList.push(renderCheckboxOptions(component, state, setState));
          break;

        case "input":
          fragmentsList.push(
            <FormControl
              isInvalid={
                !(document.getElementById(component.id) as HTMLInputElement)
                  .value.length
              }
              isReadOnly={read_only}
            >
              <Input
                id={component.id}
                maxLength={component.max_length}
                onChange={(e) =>
                  updateState(state, setState, component.id, e.target.value)
                }
                placeholder="Your response"
                value={component.value}
              />
              <FormErrorMessage>Field is required</FormErrorMessage>
            </FormControl>,
          );
          break;

        case "number":
          fragmentsList.push(
            <NumberInput
              isInvalid={isNumberElemInvalid(
                document.getElementById(component.id) as HTMLInputElement,
              )}
              isReadOnly={read_only}
            >
              <NumberInputField
                id={component.id}
                onChange={(e) =>
                  updateState(state, setState, component.id, e.target.value)
                }
                value={component.value}
              />
              <NumberInputStepper>
                <NumberIncrementStepper />
                <NumberDecrementStepper />
              </NumberInputStepper>
            </NumberInput>,
          );
          break;

        case "radio":
          fragmentsList.push(renderRadioElements(component, state, setState));
          break;

        case "select":
          fragmentsList.push(renderSelectElements(component, state, setState));
          break;
      }

      fragmentsList.push(<br />, <br />, <br />);
    }

    return fragmentsList;
  }

  const pages = [];
  const [responses, setResponses] = useState({});

  for (const [page, componentList] of Object.entries(components)) {
    pages.push(
      <div
        id={`form-page-${page}`}
        style={{ display: page ? "none" : undefined }}
      >
        {generateReactComponents(componentList, responses, setResponses)}
      </div>,
    );
  }

  return <></>;
}