import "./ProgramBoi.sass";

import {useContext, useEffect, useMemo, useReducer} from "react";
import RuntimeContext from "../../contexts/RuntimeContext";
import {ProgramStep, weighting} from "../../models/Programs";
import {firstKey, WorkoutState} from "../../models/Workouts";
import {diffLinearValues, formatValue} from "../../models/Shared";
import {Boi} from "../../primitives/boi/Boi";
import {useLastMilestoneValue} from "../../hooks/milestones";

interface StepMeta {
  actualDuration?: WorkoutState
}

interface ProgressState {
  steps: (ProgramStep & StepMeta)[]
  currentIndex: number
  lastTransition: WorkoutState
  lastValue: WorkoutState
  toNext: ToNext
  stopped: boolean
}

interface ToNext {
  current: number,
  max: number,
}

interface ProgressChange {
  skip?: boolean
  workoutState?: WorkoutState
}

function calculateToNext(
  step: ProgramStep & StepMeta,
  lastValue: WorkoutState,
  lastTransition: WorkoutState,
): ToNext {
  if (step.duration) {
    if (step.duration.time) {
      return {
        current: lastValue.time - lastTransition.time,
        max: step.duration.time,
      };
    } else if (step.duration.calories && lastTransition.calories !== undefined && lastValue.calories !== undefined) {
      return {
        current: lastValue.calories - lastTransition.calories,
        max: step.duration.calories,
      };
    } else if (step.duration.distance && lastTransition.distance !== undefined && lastValue.distance !== undefined) {
      return {
        current: lastValue.distance - lastTransition.distance,
        max: step.duration.distance,
      };
    }
  }

  return {
    current: 0,
    max: 1,
  }
}

function programReducer(state: ProgressState, change: ProgressChange) {
  let {steps, currentIndex, lastTransition, lastValue, toNext, stopped} = state;

  // Stop working if after program
  if (stopped) {
    return state;
  } else if (currentIndex > steps.length - 1) {
    return {...state, stopped: true};
  }

  // Skip
  if (change.skip) {
    steps[currentIndex].actualDuration = diffLinearValues(lastValue, lastTransition);

    return {
      ...state,
      steps,
      lastTransition: lastValue,
      currentIndex: currentIndex + 1,
    };
  }

  // Workout state
  if (change.workoutState) {
    lastValue = change.workoutState;
    const step = steps[currentIndex];

    if (step.duration) {
      toNext = calculateToNext(step, lastValue, lastTransition);

      if (toNext.current >= toNext.max) {
        steps[currentIndex].actualDuration = diffLinearValues(lastValue, lastTransition);
        currentIndex += 1;
        lastTransition = lastValue;

        if (steps.length > currentIndex) {
          toNext = calculateToNext(steps[currentIndex], lastValue, lastTransition);
        }
      }
    }
  }

  return {steps, currentIndex, lastTransition, lastValue, toNext, stopped};
}

export default function ProgramBoi() {
  const {workout, lastEvent} = useContext(RuntimeContext);
  const program = workout!.program!;
  const [progress, dispatch] = useReducer(programReducer, {
    steps: program.steps,
    currentIndex: 0,
    lastTransition: {time: 0, distance: 0, calories: 0},
    lastValue: {time: 0},
    toNext: {current: 0, max: 1},
    stopped: false,
  });

  useEffect(() => {
    if (lastEvent) {
      for (const workoutState of lastEvent?.workoutStates || []) {
        dispatch({workoutState});
      }

      if (lastEvent?.event?.name === "Skipped") {
        dispatch({skip: true});
      }
    }
  }, [lastEvent]);

  if (progress.steps.some(s => weighting(s) === 0)) {
    return <InfiniteProgress progress={progress}/>;
  }

  return <HealthBarProgress progress={progress}/>;
}

interface ProgressProps {
  progress: ProgressState
}

type FromTo = {
  count: false
} | {
  count: true
  rawCurr: number
  rawTo: number
  strCurr: string
  strTo: string
}

function InfiniteProgress({progress}: ProgressProps) {
  const offset = 6 - progress.steps.length + progress.currentIndex;
  const ft: FromTo = useMemo(() => {
    const currentStep = progress.steps[progress.currentIndex];
    if (currentStep) {
      const key = firstKey(currentStep.duration as WorkoutState);
      if (key !== null) {
        const duration = progress.steps[progress.currentIndex].duration![key]!;
        const numStr = formatValue(progress.toNext.current, key);
        const toStr = formatValue(duration, key);

        return {
          count: true,
          rawCurr: progress.toNext.current,
          rawTo: duration,
          strCurr: numStr,
          strTo: toStr,
        } as FromTo
      }
    }

    return {count: false};
  }, [progress]);

  if (progress.currentIndex >= progress.steps.length) {
    return null;
  }

  return (
    <Boi
      vertical="bottom" horizontal="center" unchunky
      style={{fontSize: "2vmax", minWidth: "24vmax"}}
    >
      <div style={{padding: "0.5vmax 0.5vmax 0", fontWeight: 400}}>
        Steg {progress.currentIndex + 1} / {progress.steps.length}
      </div>
      {ft.count && (
        <div>
          <div style={{margin: "0.5vmax"}}>
            {ft.strCurr} / {ft.strTo}
          </div>
          <div className="ProgressBar">
            <div className={`ProgressBar-level-${offset}`} style={{flex: ft.rawCurr}}/>
            <div className={`ProgressBar-bg`} style={{flex: ft.rawTo - ft.rawCurr}}/>
          </div>
        </div>
      )}
      {!ft.count && (
        <div style={{margin: "0.5vmax"}}>
          (Custom)
        </div>
      )}
    </Boi>
  );
}

function HealthBarProgress({progress}: ProgressProps) {
  const offset = 6 - progress.steps.length;
  const steps = progress.steps.reduce((acc, b) => ([b, ...acc]), [] as typeof progress.steps);

  return (
    <div className="HealthBar">
      {steps.map((step, i) => <HealthBarProgressStep key={i} progress={progress} step={step} offset={offset}/>)}
    </div>
  );
}

interface HealthBarProgressStepProps {
  progress: ProgressState
  step: ProgramStep & StepMeta
  offset: number
}

function HealthBarProgressStep({progress, step, offset}: HealthBarProgressStepProps) {
  const stepIndex = progress.steps.indexOf(step);
  const level = progress.steps.indexOf(step) + offset;

  const max = Math.max(1, progress.toNext.max);
  const key = firstKey(step.duration as WorkoutState);
  const lastMilestone = useLastMilestoneValue(key!, "time");

  const duration = step.duration![key!]!;
  const durationStr = formatValue(duration - progress.toNext.current, key!);

  const currentVal = Math.max(0, progress.toNext.current);

  const preDiffed = key !== "time" ? Math.max(0, (progress.lastValue[key!] || 0) - lastMilestone) : -1;
  const diffed = Math.min(preDiffed, currentVal);

  return (
    <div key={step.index} className="HealthBar-entry" style={{flex: weighting(step)}}>
      {stepIndex === progress.currentIndex && (
        <div className="HealthBar-entry-text">
          {durationStr}
        </div>
      )}
      <div className="ProgressBar">
        {stepIndex > progress.currentIndex && (
          <div className={`ProgressBar-inactive`} style={{flex: 1}}/>
        )}
        {stepIndex === progress.currentIndex && (
          <>
            <div className={`ProgressBar-level-${level}`} style={{flex: max - currentVal}}/>
            {diffed >= 0 && (
              <div className={`ProgressBar-level-${level} diff`} style={{flex: diffed}}/>
            )}
            <div className={`ProgressBar-bg`} style={{flex: currentVal - diffed}}/>
          </>
        )}
        {stepIndex < progress.currentIndex && (
          <div className={`ProgressBar-bg`} style={{flex: 1}}/>
        )}
      </div>
    </div>
  );
}
