import { gql, Reference, useMutation } from "@apollo/client";
import { useRouter } from "@curi/react-dom";
import {
  Button,
  Grid,
  IconButton,
  LinearProgress,
  makeStyles,
  Paper,
  Portal,
  Typography,
} from "@material-ui/core";
import { Delete } from "@material-ui/icons";
import { FormikHelpers, Formik, Form, Field } from "formik";
import { TextField, TextFieldProps } from "formik-material-ui";
import { useConfirm } from "material-ui-confirm";
import { useSnackbar } from "notistack";
import { not, omit } from "ramda";
import { ChangeEvent, FC } from "react";
import {
  Pipeline,
  PipelineUnits,
} from "../generated/graphql";
import { useHeaderContainer } from "../layouts";
import { CREATE_PIPELINE, DELETE_PIPELINE, UPDATE_PIPELINE } from "./mutations";
import * as Yup from "yup";

type Values = Partial<Pipeline>;

interface Props {
  pipeline: Values;
}

const useStyles = makeStyles((theme) => ({
  root: {
    flex: "1 1 100%",
  },
  nested: {
    paddingLeft: theme.spacing(4),
  },
  form: {
    margin: 16,
  },
  metadataList: {
    padding: 0,
  },
  metadataItem: {
    listStyle: "none",
  },
  metadataContainer: {
    padding: "10px 30px",
    margin: "10px 0",
  },
}));

export const PipelineEditor: FC<Props> = ({ pipeline }) => {
  const { enqueueSnackbar } = useSnackbar();

  const isCreate = not("id" in pipeline);

  const [createPipeline] = useMutation<{ createPipeline: Pipeline }>(
    CREATE_PIPELINE,
    {
      update(cache, { data }) {
        cache.modify({
          fields: {
            pipelines(exiting = []) {
              const pipelineRef = cache.writeFragment({
                data: data!.createPipeline,
                fragment: gql`
                  fragment newPipeline on Pipeline {
                    id
                    projectId
                    branch
                    name
                    workUnitMetadata {
                      version
                      units {
                        type
                        scripts
                      }
                    }
                  }
                `,
              });
              return [pipelineRef, ...exiting];
            },
          },
        });
      },
    }
  );
  const [updatePipeline] = useMutation(UPDATE_PIPELINE);

  const router = useRouter();

  const submitForm = async (
    values: Values,
    formikHelpers: FormikHelpers<Values>
  ) => {
    try {
      let pipelineId = pipeline.id;
      let projectId = pipeline.projectId;
      if (isCreate) {
        await createPipeline({
          variables: {
            pipeline: values,
          },
        }).then(({ data }) => {
          pipelineId = data!.createPipeline.id;
          projectId = data!.createPipeline.projectId;
        });
      } else {
        await updatePipeline({
          variables: {
            pipeline: omit(["projectId"], values),
          },
        });
      }
      enqueueSnackbar("Saved successfully", {
        variant: "success",
      });
      router.navigate({
        url: router.url({
          name: "pipeline-commits",
          params: {
            pipelineId,
            projectId,
          },
        }),
        method: "replace",
      });
    } finally {
      formikHelpers.setSubmitting(false);
    }
  };

  const [deletePipeline, { loading: deleting }] = useMutation(DELETE_PIPELINE, {
    variables: { id: pipeline.id },
    update(cache) {
      cache.modify({
        fields: {
          projects(exiting: Reference[] = [], { readField }) {
            return exiting.filter(
              (ref) => pipeline.id !== readField("id", ref)
            );
          },
        },
      });
    },
  });

  const confirm = useConfirm();
  const handleDelete = async () => {
    try {
      await confirm({ description: `This will delete ${pipeline.name}.` });
      await deletePipeline();
      enqueueSnackbar("Deleted successfully", {
        variant: "success",
      });
      router.navigate({
        url: router.url({
          name: "dashboard",
        }),
      });
    } catch {}
  };

  const headerContainer = useHeaderContainer();

  const units = [
    PipelineUnits.Checkout,
    PipelineUnits.InstallDependencies,
    PipelineUnits.Test,
    PipelineUnits.Deploy,
    PipelineUnits.CleanUp,
  ];
  const classes = useStyles();
  return (
    <Paper className={classes.root}>
      <Portal container={headerContainer}>
        <Grid container justify="space-between" alignItems="center">
          <Typography variant="h6" component="h1">
            {isCreate ? "Create" : "Edit"} Pipeline
          </Typography>
          {isCreate ? null : (
            <IconButton
              color="inherit"
              onClick={handleDelete}
              disabled={deleting}
            >
              <Delete />
            </IconButton>
          )}
        </Grid>
      </Portal>
      <Formik
        initialValues={pipeline}
        validationSchema={Yup.object({
          name: Yup.string()
            .max(32, "Must be 32 characters or less")
            .required("Required"),
          branch: Yup.string()
            .max(32, "Must be 32 characters or less")
            .required("Required"),
        })}
        onSubmit={submitForm}
      >
        {({ submitForm, isSubmitting, values }) => {
          return (
            <Form className={classes.form}>
              <Field component={TextField} name="name" label="Name" fullWidth />
              <Field
                component={TextField}
                name="branch"
                label="Branch"
                fullWidth
                margin="normal"
              />
              <Paper className={classes.metadataContainer}>
                <Field
                  component={TextField}
                  name="workUnitMetadata.version"
                  label="Version"
                  fullWidth
                  readonly
                  margin="normal"
                />
                <ol className={classes.metadataList}>
                  {units.map((unit, index) => {
                    return (
                      <li key={unit} className={classes.metadataItem}>
                        <Field
                          name={`workUnitMetadata.units[${index}].scripts]`}
                          component={ScriptsField}
                          label={`${unit} Scripts`}
                          fullWidth
                          multiline
                          margin="normal"
                        />
                      </li>
                    );
                  })}
                </ol>
              </Paper>
              {isSubmitting && <LinearProgress />}
              <Button
                variant="contained"
                color="primary"
                disabled={isSubmitting}
                onClick={submitForm}
                fullWidth
              >
                Submit
              </Button>
            </Form>
          );
        }}
      </Formik>
    </Paper>
  );
};

const ScriptsField: FC<TextFieldProps> = ({ field, form, meta, ...props }) => {
  return (
    <TextField
      {...props}
      form={form}
      meta={meta}
      field={{
        ...field,
        value: field.value?.join("\n") ?? "",
        onChange: (ev: ChangeEvent<HTMLInputElement>) =>
          form.setFieldValue(
            field.name,
            ev.target.value?.split("\n").map((it) => it) ?? []
          ),
      }}
    />
  );
};
