import { Story, Preview, Props, Meta } from "@storybook/addon-docs/blocks";
import { Accessibility, ComponentHeading } from "../../storybook-components";
import { useDisclosure, useStep } from "../../hooks";
import { JUSTIFY } from "../../types";
import { EnumTable } from "../../storybook-components/EnumTable";
import { Box } from "../Box";
import { Button, BUTTON_SIZE, BUTTON_VARIANT } from "../Button";
import { Heading } from "../Heading";
import { FormGroup } from "../FormGroup";
import { Label } from "../Label";
import { Input } from "../Input";
import { Paragraph } from "../Paragraph";
import { Stack } from "../Stack";
import { Checkbox } from "../Checkbox";
import { Tab } from "../Tab";
import { Table } from "../Table";
import { Select } from "../Select";
import { Modal, MODAL_SIZE } from "./Modal";

<Meta title="Components/Popovers/Modal" component={Modal} />

<ComponentHeading
  componentName="Modal (Dialog)"
  description="An overlay to show content that prevents interaction with the rest of the application"
  sourcePath="src/components/Modal/Modal.tsx"
/>

The modal contains a portal by default, so there is no need to wrap it in one in
the application.

[Skip to examples](#demos)

```jsx
import { Modal } from "@aptible/arrow-ds";
```

export const Fragment = (props) => <div {...props} />;

export const ExampleModalContent = () => (
  <Fragment>
    <Heading.H3>This is the Modal component</Heading.H3>
    <Paragraph>
      Duis vulputate dui quis commodo cursus. In fermentum efficitur vehicula.
      Integer nec felis varius, ultrices erat ac, volutpat lectus. Proin eu
      dolor sed magna accumsan blandit a ac nisl. Nulla eros ante, blandit et
      suscipit id, ultrices vitae ligula. Vestibulum egestas orci nec orci
      finibus varius. Nullam sit amet ex vel arcu fermentum mattis et in risus.
      Integer blandit semper consequat. Aenean lectus quam, tristique convallis
      est varius, commodo aliquam ipsum.
    </Paragraph>
    <Select
      placeholder="Favorite ice cream?"
      options={[
        { value: "chocolate", label: "Chocolate" },
        { value: "strawberry", label: "Strawberry" },
        { value: "vanilla", label: "Vanilla" },
      ]}
      menuInPortal
    />
  </Fragment>
);

<Preview>
  <Story name="Default">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Default Modal</Button>
          <Modal title="Modal component" isOpen={isOpen} onClose={onClose}>
            <Modal.Body>
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button autoFocus size={BUTTON_SIZE.LARGE}>
                  Confirm
                </Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

## Props

<Props of={Modal} />
<EnumTable enums={{ MODAL_SIZE }} />

<Accessibility
  keyboardInteractions={[
    {
      key: "Escape",
      description: "Dismisses the modal.",
    },
  ]}
/>

## Demos

### Sizes

Modals can be one of three sizes: “default”, “large” or “full”.

All modals have a maximum height of 100vh - 88px margin.

| Size      | Width                |
| --------- | -------------------- |
| `default` | 640px                |
| `large`   | 818px                |
| `full`    | 100vw - 88 px margin |

<Preview>
  <Story name="Default size modal">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Default Size Modal</Button>
          <Modal
            title="Modal component"
            isOpen={isOpen}
            onClose={onClose}
            size={MODAL_SIZE.DEFAULT}
          >
            <Modal.Body>
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button autoFocus size={BUTTON_SIZE.LARGE}>
                  Confirm
                </Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
  <Story name="Large size modal">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Large Size Modal</Button>
          <Modal
            title="Modal component"
            isOpen={isOpen}
            onClose={onClose}
            size={MODAL_SIZE.LARGE}
          >
            <Modal.Body>
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button autoFocus size={BUTTON_SIZE.LARGE}>
                  Confirm
                </Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
  <Story name="Full size modal">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Full Size Modal</Button>
          <Modal
            title="Modal component"
            isOpen={isOpen}
            onClose={onClose}
            size={MODAL_SIZE.FULL}
          >
            <Modal.Body>
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button autoFocus size={BUTTON_SIZE.LARGE}>
                  Confirm
                </Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### With secondary title

You can add a secondary title to the modal which will be displayed in the
header.

<Preview>
  <Story name="With secondary title">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Modal with Secondary Title</Button>
          <Modal
            title="Modal component"
            secondaryTitle="An optional secondary title"
            isOpen={isOpen}
            onClose={onClose}
          >
            <Modal.Body>
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Button.Group justify={JUSTIFY.END}>
                <Button autoFocus onClick={onClose} size={BUTTON_SIZE.LARGE}>
                  Cancel and close
                </Button>
              </Button.Group>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### With scrolling body

The body of the modal scrolls when there is too much content to display in view.
No prop is required. This example is just to illustrate the behavior.

<Preview>
  <Story name="With scrolling body">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Modal with Scrolling Body</Button>
          <Modal title="Modal component" isOpen={isOpen} onClose={onClose}>
            <Modal.Body>
              <ExampleModalContent />
              <ExampleModalContent />
              <ExampleModalContent />
              <ExampleModalContent />
              <ExampleModalContent />
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button autoFocus size={BUTTON_SIZE.LARGE}>
                  Confirm
                </Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### Non-closable modal

For mandatory workflows and notifications where specific actions must be taken,
all methods to close the modal can be removed. In these instances, the modal is
usually dimissed by a button within the modal footer.

Non-closable modals use a combination of props to establish this pattern. Each
of the props can be used on their own as well, depending on which close
functionality you need to disable.

For example, if a modal contains a select component, you may want to disable
the escape-to-close feature as the escape key is also used to dismiss the
select popover menu.

<Preview>
  <Story name="Non-closable modal">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Non-closable Modal</Button>
          <Modal
            title="Please confirm this action"
            isOpen={isOpen}
            onClose={onClose}
            hideCloseButton
            disableEscClose
            disableOutsideClick
          >
            <Modal.Body>
              <Paragraph>
                Duis vulputate dui quis commodo cursus. In fermentum efficitur
                vehicula. Integer nec felis varius, ultrices erat ac, volutpat
                lectus. Proin eu dolor sed magna accumsan blandit a ac nisl.
              </Paragraph>
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button autoFocus size={BUTTON_SIZE.LARGE} onClick={onClose}>
                  Confirm
                </Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### Enable auto focus

Setting the autoFocus prop on the modal will automatically add focus to the
first focusable element, which in many cases is the close button. This behavior
is acceptable when the modal doesn't have a form.

<Preview>
  <Story name="Enable auto focus">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Auto Focus Modal</Button>
          <Modal
            title="Modal component"
            isOpen={isOpen}
            onClose={onClose}
            autoFocus
          >
            <Modal.Body>
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button size={BUTTON_SIZE.LARGE}>Confirm</Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### Center modal

The modal can be centered vertically.

<Preview>
  <Story name="Modal center">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Center Modal</Button>
          <Modal
            title="Modal component"
            isOpen={isOpen}
            onClose={onClose}
            center
          >
            <Modal.Body>
              <ExampleModalContent />
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button autoFocus size={BUTTON_SIZE.LARGE}>
                  Confirm
                </Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### Wizard example

The modal can be used to display a set up wizard or any kind of UI where
multiple steps are involved. To do this, you would use the `useStep` hook.

You can also use the `Modal.FooterMultiStep` component in place of the regular
`Modal.Footer`. The multi-step footer provides some additional UI; a progress
indicator and optional next / previous button labels.

<Preview>
  <Story name="Wizard example">
    {() => {
      const wizardSteps = [
        {
          id: "1",
          title: "Create a Procedure",
          label: "Step 1. This is text for the first step",
          content: (
            <>
              <Box>Step 1</Box>
              <ExampleModalContent />
            </>
          ),
        },
        {
          id: "2",
          title: "Select some options",
          label: "Step 2. This is text for the second step",
          content: (
            <>
              <Box>Step 2</Box>
              <ExampleModalContent />
            </>
          ),
        },
        {
          id: "3",
          title: "Add configuration",
          label: "Step 3. This is text for the third step",
          content: (
            <>
              <Box>Step 3</Box>
              <ExampleModalContent />
            </>
          ),
        },
        {
          id: "4",
          title: "Finish up",
          label: "Step 4. This is text for the fourth step",
          content: (
            <>
              <Box>Step 4</Box>
              <ExampleModalContent />
            </>
          ),
        },
      ];
      const totalSteps = wizardSteps.length;
      const { isOpen, onOpen, onClose } = useDisclosure();
      const {
        step,
        goToNextStep,
        goToPreviousStep,
        goToStep,
        currentStepIndex,
        nextStepIndex,
        previousStepIndex,
        isFirstStep,
        isLastStep,
      } = useStep({
        steps: totalSteps,
      });
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Wizard Modal</Button>
          <Modal
            title={wizardSteps[currentStepIndex].title}
            secondaryTitle={`Step ${step} of ${totalSteps}`}
            isOpen={isOpen}
            onClose={onClose}
          >
            <Modal.Body>{wizardSteps[currentStepIndex].content}</Modal.Body>
            <Modal.FooterMultiStep
              steps={totalSteps}
              currentStep={step}
              leftButtonLabel={
                isFirstStep ? "" : wizardSteps[previousStepIndex].label
              }
              rightButtonLabel={
                isLastStep ? "" : wizardSteps[nextStepIndex].label
              }
            >
              <Button.Group justify={JUSTIFY.END}>
                {!isFirstStep && (
                  <Button.Group className="mr-auto">
                    <Button
                      size={BUTTON_SIZE.LARGE}
                      variant={BUTTON_VARIANT.SECONDARY}
                      onClick={goToPreviousStep}
                    >
                      Previous
                    </Button>
                  </Button.Group>
                )}
                {isLastStep ? (
                  <Button
                    size={BUTTON_SIZE.LARGE}
                    onClick={() => {
                      onClose();
                      goToStep(1);
                    }}
                  >
                    Create
                  </Button>
                ) : (
                  <Button size={BUTTON_SIZE.LARGE} onClick={goToNextStep}>
                    Next
                  </Button>
                )}
              </Button.Group>
            </Modal.FooterMultiStep>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### Form example

One of the main use cases for the modal is to show a form. In these cases, the
first input of the form should be focused by adding `autoFocus` to the input.

<Preview>
  <Story name="Form example">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      const onSubmit = (event) => {
        console.log("onSubmit");
        event.preventDefault();
        return false;
      };
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Form Modal</Button>
          <Modal title="Modal component" isOpen={isOpen} onClose={onClose}>
            <form onSubmit={onSubmit}>
              <Modal.Body>
                <FormGroup>
                  <Label htmlFor="name">Name</Label>
                  <Input name="name" id="name" type="text" autoFocus />
                </FormGroup>
                <FormGroup>
                  <Label htmlFor="email">Email</Label>
                  <Input name="email" id="email" type="email" />
                </FormGroup>
                <FormGroup>
                  <Label htmlFor="email">Favorite ice cream</Label>
                  <Select
                    options={[
                      { value: "chocolate", label: "Chocolate" },
                      { value: "strawberry", label: "Strawberry" },
                      { value: "vanilla", label: "Vanilla" },
                    ]}
                    menuInPortal
                  />
                </FormGroup>
              </Modal.Body>
              <Modal.Footer>
                <Stack reverse>
                  <Button type="submit" size={BUTTON_SIZE.LARGE}>
                    Submit
                  </Button>
                  <Button
                    size={BUTTON_SIZE.LARGE}
                    variant={BUTTON_VARIANT.SECONDARY}
                    onClick={onClose}
                  >
                    Cancel
                  </Button>
                </Stack>
              </Modal.Footer>
            </form>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### With tabs

This example shows how you can use tabbed content inside of a modal.

<Preview>
  <Story name="With tabs">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      const [tab, setTab] = React.useState(0);
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Tabs Modal</Button>
          <Modal
            title="Modal component"
            isOpen={isOpen}
            onClose={() => {
              onClose();
              setTab(0);
            }}
          >
            <Modal.Tabs>
              <Tab active={tab === 0} autoFocus onClick={() => setTab(0)}>
                First Tab
              </Tab>
              <Tab active={tab === 1} onClick={() => setTab(1)}>
                Second Tab
              </Tab>
            </Modal.Tabs>
            {tab === 0 && (
              <Fragment>
                <Modal.Body>
                  <ExampleModalContent />
                </Modal.Body>
                <Modal.Footer>
                  <Stack reverse>
                    <Button size={BUTTON_SIZE.LARGE}>Confirm</Button>
                    <Button
                      size={BUTTON_SIZE.LARGE}
                      variant={BUTTON_VARIANT.SECONDARY}
                      onClick={onClose}
                    >
                      Cancel
                    </Button>
                  </Stack>
                </Modal.Footer>
              </Fragment>
            )}
            {tab === 1 && (
              <Fragment>
                <Modal.Body>
                  <Stack spacing={6}>
                    <ExampleModalContent />
                    <ExampleModalContent />
                    <ExampleModalContent />
                  </Stack>
                </Modal.Body>
                <Modal.Footer>
                  <Stack reverse>
                    <Button size={BUTTON_SIZE.LARGE}>
                      Some other important action
                    </Button>
                  </Stack>
                </Modal.Footer>
              </Fragment>
            )}
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>

### With table

Show a table in a modal. Set Modal.Body padding to false.

<Preview>
  <Story name="With table">
    {() => {
      const { isOpen, onOpen, onClose } = useDisclosure();
      const [filters, setFilters] = React.useState({
        number: [],
        evenOrOdd: [],
      });
      const [
        numberFilterOptionSearch,
        setNumberFilterOptionSearch,
      ] = React.useState("");
      const rowData = [];
      for (let i = 0; i < 10; i += 1) {
        rowData.push({ number: i, evenOrOdd: i % 2 === 0 ? "even" : "odd" });
      }
      const handleFilterOptionChange = (e, column) => {
        if (e.target.checked) {
          setFilters({
            ...filters,
            [column]: [...filters[column], e.target.value],
          });
        } else {
          const index = filters[column].indexOf(e.target.value);
          if (index > -1) {
            const filterColumnCopy = [...filters[column]];
            filterColumnCopy.splice(index, 1);
            setFilters({ ...filters, [column]: filterColumnCopy });
          }
        }
      };
      const FilterOption = ({ option, column, ...rest }) => (
        <Checkbox
          label={option}
          onChange={(e) => handleFilterOptionChange(e, column)}
          value={option}
          {...rest}
        />
      );
      const getReducedFilterOptions = (column) =>
        rowData.reduce((acc, row) => {
          if (numberFilterOptionSearch) {
            if (!`${row[column]}`.includes(numberFilterOptionSearch)) {
              return acc;
            }
          }
          return acc.some((item) => row[column] === item)
            ? acc
            : [...acc, row[column]];
        }, []);
      const getVisibleFilterOptions = (column) => {
        return getReducedFilterOptions(column)
          .slice(0, 7)
          .map((option) => (
            <FilterOption
              option={`${option}`}
              key={option}
              column={column}
              checked={filters[column].some((filter) => filter === `${option}`)}
            />
          ));
      };
      const handleSelectAllNumberFilters = () => {
        setFilters({
          ...filters,
          number: rowData.map((row) => `${row.number}`),
        });
      };
      const handleClearAllNumberFilters = () => {
        setFilters({ ...filters, number: [] });
      };
      return (
        <Fragment>
          <Button onClick={onOpen}>Show Table Modal</Button>
          <Modal
            title="Modal component"
            isOpen={isOpen}
            onClose={onClose}
            size={MODAL_SIZE.FULL}
          >
            <Modal.Body padded={false}>
              <Table renderInModal>
                <Table.Head>
                  <Table.Row>
                    <Table.HeaderCell
                      visibleFilterOptions={getVisibleFilterOptions("number")}
                      totalFilterOptions={
                        getReducedFilterOptions("number").length
                      }
                      filterApplied={filters.number.length !== 0}
                      onSelectAllFilters={handleSelectAllNumberFilters}
                      onClearFilters={handleClearAllNumberFilters}
                      searchInputValue={numberFilterOptionSearch}
                      onSearchInputChange={setNumberFilterOptionSearch}
                      sortable={false}
                    >
                      Number
                    </Table.HeaderCell>
                    <Table.HeaderCell
                      sortable={false}
                      visibleFilterOptions={getVisibleFilterOptions(
                        "evenOrOdd",
                      )}
                      totalFilterOptions={
                        getReducedFilterOptions("evenOrOdd").length
                      }
                      filterApplied={filters.evenOrOdd.length !== 0}
                      hideFilterSearch
                    >
                      Even or Odd
                    </Table.HeaderCell>
                  </Table.Row>
                </Table.Head>
                <Table.Body>
                  {rowData
                    .filter((row) => {
                      if (
                        filters.number.length === 0 &&
                        filters.evenOrOdd.length === 0
                      ) {
                        return true;
                      }
                      const filterMatch = { number: false, evenOrOdd: false };
                      Object.keys(row).forEach((column) => {
                        if (
                          filters[column].length === 0 ||
                          filters[column].some(
                            (filter) => filter === `${row[column]}`,
                          )
                        ) {
                          filterMatch[column] = true;
                        }
                      });
                      return filterMatch.number && filterMatch.evenOrOdd;
                    })
                    .map((row) => {
                      return (
                        <Table.Row key={row.number}>
                          <Table.Cell>{row.number}</Table.Cell>
                          <Table.Cell>{row.evenOrOdd}</Table.Cell>
                        </Table.Row>
                      );
                    })}
                </Table.Body>
              </Table>
            </Modal.Body>
            <Modal.Footer>
              <Stack reverse>
                <Button size={BUTTON_SIZE.LARGE}>Confirm</Button>
                <Button
                  size={BUTTON_SIZE.LARGE}
                  variant={BUTTON_VARIANT.SECONDARY}
                  onClick={onClose}
                >
                  Cancel
                </Button>
              </Stack>
            </Modal.Footer>
          </Modal>
        </Fragment>
      );
    }}
  </Story>
</Preview>
