import { disable } from 'colors';
import { resolveTxt } from 'dns';
import { videointelligence } from 'googleapis/build/src/apis/videointelligence';
import { isInteger } from 'lodash';
import * as React from 'react';
import { useEffect, useState, useCallback, useRef } from "react"
import "./ProgressBar.scss"
import { MediaSegment } from './RecordingView';

interface ProgressBarProps {
  videos: MediaSegment[],
  setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
  orderVideos: boolean,
  progress: number,
    recording: boolean,
    doUndo: boolean,
}

interface SegmentBox {
  endTime: number,
  startTime: number,
  order: number,
}
interface CurrentHover {
  index: number,
  minX: number,
  maxX: number
}

export function ProgressBar(props: ProgressBarProps) {

  const progressBarRef = useRef<HTMLDivElement | null>(null)

  // array for the order of video segments
  const [segments, setSegments] = useState<JSX.Element[]>([]);
    const [ordered, setOrdered] = useState<SegmentBox[]>([]);
    
    const [undoStack, setUndoStack] = useState<SegmentBox[]>([]);

  const [dragged, setDragged] = useState<number>(-1);

  // length of the time removed from the video, in seconds*100
  const [totalRemovedTime, setTotalRemovedTime] = useState<number>(0);

  // this holds the index of the videoc segment to be removed
  const [removed, setRemoved] = useState<number>(-1);
    
    
    // abstracted for other uses - brings back the most recently deleted segment
    const handleUndo = () => {
        // get the last element from the undo if it exists
        if (undoStack.length === 0) return;
        // get and remove the last element from the undo stack
        const last = undoStack.lastElement();
        setUndoStack(prevUndo => prevUndo.slice(0, -1));

        // update the removed time and place element back into ordered
        setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime));
        setOrdered(prevOrdered => [...prevOrdered, last]);
    }
    useEffect(() => handleUndo(), [props.doUndo])

  useEffect(() => {
      // get segments segment's html using it's id -> make them appeared disabled (or enabled)
      segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
      progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
      
      if (props.recording)
          setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'min-content' }}>{props.videos.length + 1}</div>]);
  }, [props.recording])
  

  useEffect(() => {
    const totalTime = props.progress * 1000 - totalRemovedTime;
    const segmentsJSX = ordered.map((seg, i) =>
        <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
      
    setSegments(segmentsJSX)
  }, [dragged, ordered]);

  // to imporve performance, we only want to update the width, not re-render the whole thing
    useEffect(() => {
      if (!props.recording) return
        const totalTime = props.progress * 1000 - totalRemovedTime;
        let remainingTime = totalTime;
        segments.forEach((seg, i) => {
            // for the last segment, we need to set that directly
            if (i === segments.length - 1) return;
            // update remaining time
            remainingTime -= (ordered[i].endTime - ordered[i].startTime);

        // update the width for this segment
            const htmlId = seg.props.id;
            const segmentHtml = document.getElementById(htmlId);
            if (segmentHtml) segmentHtml.style.width = `${((ordered[i].endTime - ordered[i].startTime) / totalTime) * 100}%`;
      });

        // update the width of the expanding segment using the remaining time
        const segExapandHtml = document.getElementById('segment-expanding');
        if (segExapandHtml)
            segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
        
  }, [props.progress]);
      


  useEffect(() => {
    // this useEffect fired when the videos are being rearragned to the order
    // in this case, do nothing.
    if (props.orderVideos) return;

    const order = props.videos.length - 1;
    // in this case, a new video is added -> push it onto ordered
    if (order >= ordered.length) {
      const { endTime, startTime } = props.videos.lastElement();
      setOrdered(prevOrdered => {
        return [...prevOrdered, { endTime, startTime , order }];
      });
    }

    // in this case, a video is removed
    else if (order < ordered.length) {
        console.error('warning: video removed from parent');
    }
  }, [props.videos]);

  useEffect(() => {
    props.setVideos(vids => ordered.map((seg) => vids[seg.order]));
  }, [props.orderVideos]);

  useEffect(() => {
    if (removed === -1) return;
      // update total removed time
      setTotalRemovedTime(prevRemoved => prevRemoved + (ordered[removed].endTime - ordered[removed].startTime));
        
      // put the element on the undo stack
      setUndoStack(prevUndo => [...prevUndo, ordered[removed]]);
      // remove the segment from the array
      setOrdered(prevOrdered => prevOrdered.filter((seg, i) => i !== removed));
      // reset to default/nullish state
      setRemoved(-1);
  }, [removed]);
  
  const updateLastHover = (segId: number): CurrentHover | null => {
    // get the segId of the segment that will become the new bounding area
    const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
    if (rect == null) return null
    return {
      index: segId,
      minX: rect.x,
      maxX: rect.x + rect.width,
    }
  }
  
  const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
    // don't move the videobox element
      e.stopPropagation()
      
      // if recording, do nothing
        if (props.recording) return;

    // get the segment the user clicked on to be dragged
    const clickedSegment = e.target as HTMLDivElement & EventTarget

    // get the profess bar ro add event listeners
    // don't do anything if null
    const progressBar = progressBarRef.current
    if (progressBar == null || clickedSegment.id === progressBar.id) return

    // if holding shift key, let's remove that segment
    // TODO: think of a way to accomodate touch -> maybe if pointerup isn't fired after x secs? or if dragged into a trash bin or smthn
    if (e.shiftKey) {
      const segId = parseInt(clickedSegment.id.split('-')[1]);
      setRemoved(segId);
      return
    }
    
      if (e.ctrlKey) {
        handleUndo();
        return;
    // todo: implement undo stack
    }

    const ptrId = e.pointerId;
    progressBar.setPointerCapture(ptrId)

    const rect = clickedSegment.getBoundingClientRect()
    // id for segment is like 'segment-1' or 'segment-10', 
    // so this works to get the id
    const segId = parseInt(clickedSegment.id.split('-')[1])
    // set the selected segment to be the one dragged
    setDragged(segId)

    // this is the logic for storing the lower X bound and upper X bound
    // to know whether a swap is needed between two segments
    let lastHover: CurrentHover = {
      index: segId,
      minX: rect.x,
      maxX: rect.x + rect.width,
    }

    // create the div element that tracks the cursor
    const detchedSegment = document.createElement("div")
    initDeatchSegment(detchedSegment, rect);

    const updateSegmentOrder = (event: PointerEvent): void => {
      event.stopPropagation();
      event.preventDefault();

      // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
      if (!progressBar.hasPointerCapture(ptrId)) {
        placeSegmentandCleanup();
        return;
      }

      followCursor(event, detchedSegment, rect)

      const curX = event.clientX;
      if (curX < lastHover.minX && lastHover.index > 0) {
        swapSegments(lastHover.index, lastHover.index - 1)
        lastHover = updateLastHover(lastHover.index - 1) ?? lastHover
      }
      else if (curX > lastHover.maxX && lastHover.index < segments.length - 1) {
        swapSegments(lastHover.index, lastHover.index + 1)
        lastHover = updateLastHover(lastHover.index + 1) ?? lastHover
      }
    }
    
    const placeSegmentandCleanup = (event?: PointerEvent): void => {
      event?.stopPropagation();
      event?.preventDefault();
      // remove the update event listener for pointermove
      progressBar.removeEventListener('pointermove', updateSegmentOrder), { once: true }
      // remove the floating segment from the DOM
      detchedSegment.remove()
      // dragged is -1 is equiv to nothing being dragged, so the normal state
      // so this will place the segment in it's location and update the segment bar
      setDragged(-1);
    }


    progressBar.addEventListener('pointermove', updateSegmentOrder)
    progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true })
  }

  const swapSegments = (oldIndex: number, newIndex: number) => {
      if (newIndex == null) return
      setOrdered(prevOrdered => {
        const cpy = [...prevOrdered]
        cpy[oldIndex] = cpy[newIndex]
        cpy[newIndex] = prevOrdered[oldIndex]
        return cpy
      })
      setDragged(newIndex)
    }


  // functions for the floating segment that tracks the cursor while grabbing it  
  const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
    dot.classList.add("segment-selected")
    dot.style.transitionDuration = '0s';
    dot.style.position = 'absolute';
    dot.style.zIndex = '999';
    dot.style.width = `${rect.width}px`;
    dot.style.height = `${rect.height}px`;
    dot.style.left = `${rect.x}px`;
    dot.style.top = `${rect.y}px`;
    dot.draggable = false;
    document.body.append(dot)
  }
  const followCursor = (event: PointerEvent, dot: HTMLDivElement, rect: DOMRect): void => {
    // event.stopPropagation()
    // const { width, height } = dot.getBoundingClientRect()
    const { width, height } = rect;
    dot.style.left = `${event.clientX - width/2}px`;
    dot.style.top = `${event.clientY - height/2}px`;
  }


    return (
    <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
        {segments}
    </div>
    )
}