import { AxiosResponse } from "axios"
import { Box, Button, CircularProgress, DialogActions, DialogContent, DialogTitle, Divider, Typography } from "@mui/material"
import { useEffect, useState } from "react"
import { CheckCircle, Error, Pending } from "@mui/icons-material"
import React from "react"

export interface Stage {
  title: string
  steps: Step[]
}

export interface Step {
  title: string
  promise: Promise<AxiosResponse>
  onComplete?: () => void
  onError?: () => void
}

interface Props {
  stages: Stage[]
  description?: string
  failFast?: boolean
  onStart?: () => void
  onComplete?: () => void
}

enum progress {
  Waiting = 1,
  InProgress = 2,
  Complete = 3,
  Failed = 4
}

export function PromiseProgress(props: Props){
  const {stages, failFast, onStart, onComplete, description } = props
  const [completion, setCompletion] = useState<Map<string, progress>>(new Map())

  const execute = () => {
    let completionMap: Map<string, progress> = new Map()
    stages.forEach((stage, i) => {
      stage.steps.forEach((_, k) => {
        completionMap.set(i + "-" + k, progress.Waiting)
      })
    })
    setCompletion(completionMap)

    const runStages = async () => {
      let progMap = new Map(completionMap)
      for (const [i, stage] of stages.entries()) {
        // For each stage, map the steps into their own promise chains
        const stepPromises = stage.steps.map((step, k) => {
          // mark step as in progress
          progMap.set(i+"-"+k, progress.InProgress);
          setCompletion(new Map(progMap));
          return step.promise
            .then(() => {
              // mark step as completed
              progMap.set(i+"-"+k, progress.Complete);
              setCompletion(new Map(progMap));
              if (step.onComplete) step.onComplete();
            })
            .catch(err => {
              // mark step as failed
              progMap.set(i+"-"+k, progress.Failed);
              setCompletion(new Map(progMap));
              if(failFast){
                throw err; // this rejects this step promise
              }
            })
          });

        // Wait for all steps in the current stage to finish
        // (they run in parallel)
        await Promise.all(stepPromises);
      }
    };
    if(onStart){
      onStart()
    }
    runStages().catch(err => {
      console.error("Execution stopped due to error:", err);
    }).finally(() => {
      if(onComplete) onComplete()
    });
  }

  const getCompletion = (completion?: progress) => {
    switch(completion){
      case progress.InProgress:
        return <CircularProgress />
      case progress.Complete:
        return <CheckCircle />
      case progress.Failed:
        return <Error />
      default://default will run if it is waiting or there was no completion
        return <Pending />
    }
  }

  const displayStage = (stage: Stage, number: number) => {
    return (
      <Box key={number}>
        <Typography>Stage {stage.title}</Typography>
        {stage.steps.map((step, i) => (
          <Box key={number+"-"+i} display="flex" flexDirection="row" justifyContent="space-between" margin={2} alignContent="center" alignItems="center">
            <Typography>{step.title}</Typography>
            {getCompletion(completion.get(number + "-" + i))}
          </Box>
        ))}
      </Box>
    )
  }

  return (
    <React.Fragment>
      <DialogTitle>
        Progress
      </DialogTitle>
      <DialogContent>
        <Typography>{description}</Typography>
        <Box marginTop={2}>
          {stages.map((stage, number) => displayStage(stage, number))}
        </Box>
      </DialogContent>
      <DialogActions>
      <Button variant="contained" color="primary" onClick={() => {
        execute()
      }}>
          Start
      </Button>
      </DialogActions>
    </React.Fragment>
  )

}