import { getBeatsFromZero } from '@suno/studiokit/timeMapping';

import { CanvasRegion } from '@/components/edit2025/canvasRenderer/CanvasRenderer';

import { StudioContextType } from '../StudioContext';
import {
  getDerivedTiming,
  getSongEndBeats,
  getSongEndSeconds,
  getSongStartBeats,
  getStudioDisplayedEndBeats,
  getStudioDisplayedEndSeconds,
  getStudioDisplayedStartBeats,
} from '../selectors';
import { RULER_HEIGHT } from './makeStudioBeatGridRegion';

interface Timestamp {
  seconds: number;
  position: number;
  label: string;
}

interface ScrollbarDimensions {
  x: number;
  width: number;
  height: number;
  top: number;
  separatorEdge: number;
  scrollbarEdge: number;
  grabberEdge: number;
}

let draggingScrollBar = false;

export const SCROLLBAR_HEIGHT = 32;

let lastScrollTopPx = 0;
let lastScrollbarUpdateTimeMs = Date.now();
const scrollbarShowTimeMs = 1000;

export default function makeStudioScrollbarRegions(
  studioContext: StudioContextType
) {
  const songStartBeats =
    studioContext.mode === 'studio'
      ? getStudioDisplayedStartBeats(studioContext.state)
      : getSongStartBeats(studioContext.state);
  const songEndBeats =
    studioContext.mode === 'studio'
      ? getStudioDisplayedEndBeats(studioContext.state)
      : getSongEndBeats(studioContext.state);
  const songEndSeconds =
    studioContext.mode === 'studio'
      ? getStudioDisplayedEndSeconds(studioContext.state)
      : getSongEndSeconds(studioContext.state);
  const timing = getDerivedTiming(studioContext.state);
  const timelineWrapperRect = studioContext.timelineController.getWrapperRect();
  const timelineStartBeats =
    studioContext.timelineController.getTimelineStartBeats();
  const timelineEndBeats =
    studioContext.timelineController.getTimelineEndBeats();

  const calculateTimestamps = (): Timestamp[] => {
    const timestamps: Timestamp[] = [];
    const timelineWidth = timelineWrapperRect.width;
    const targetSpacing = 150;

    const niceIntervals = [5, 10, 15, 20, 30, 60, 120, 300, 600];
    const chosenInterval = findBestInterval(
      niceIntervals,
      targetSpacing,
      timelineWidth
    );

    // Add start timestamp
    timestamps.push(createTimestamp(0, 0));

    // Add interval timestamps
    for (
      let seconds = chosenInterval;
      seconds < songEndSeconds;
      seconds += chosenInterval
    ) {
      const beats = getBeatsFromZero(seconds, timing);
      const position =
        ((beats - songStartBeats) / (songEndBeats - songStartBeats)) *
        timelineWidth;
      timestamps.push(createTimestamp(seconds, position));
    }

    // Add end timestamp if not too close
    const endPosition = timelineWidth;
    if (
      timestamps.length > 1 &&
      endPosition - timestamps[timestamps.length - 1].position <
        targetSpacing * 0.75
    ) {
      timestamps.pop();
    }
    timestamps.push(createTimestamp(songEndSeconds, endPosition));

    return timestamps;
  };

  const createTimestamp = (seconds: number, position: number): Timestamp => {
    const minutes = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return {
      seconds,
      position,
      label: `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`,
    };
  };

  const findBestInterval = (
    intervals: number[],
    targetSpacing: number,
    timelineWidth: number
  ): number => {
    for (const interval of intervals) {
      const minSpacing = calculateMinSpacingOptimized(interval, timelineWidth);
      if (minSpacing >= targetSpacing) return interval;
    }
    // timeline is probably zoomed extremely far out
    return Infinity;
  };

  const calculateMinSpacingOptimized = (
    interval: number,
    timelineWidth: number
  ): number => {
    // Instead of testing every interval position, sample fewer positions
    // This dramatically reduces calls to expensive getBeatsFromZero
    const maxSamples = Math.min(20, Math.ceil(songEndSeconds / interval));
    const sampleStep =
      Math.ceil(songEndSeconds / interval / maxSamples) * interval;

    let minSpacing = Infinity;
    let prevPosition: number | null = null;

    for (let seconds = 0; seconds <= songEndSeconds; seconds += sampleStep) {
      const beats = getBeatsFromZero(seconds, timing);
      const position = (beats / songEndBeats) * timelineWidth;

      if (prevPosition !== null) {
        // Calculate the actual spacing per interval based on this sample
        const sampleIntervalCount =
          seconds / interval - (seconds - sampleStep) / interval;
        const spacing = (position - prevPosition) / sampleIntervalCount;
        minSpacing = Math.min(minSpacing, spacing);
      }

      prevPosition = position;
    }

    return minSpacing;
  };

  // Calculate scrollbar dimensions
  const calculateScrollbarDimensions = (): ScrollbarDimensions | null => {
    let x =
      (Math.max(songStartBeats, timelineStartBeats) / songEndBeats) *
      timelineWrapperRect.width;

    const targetWidth = Math.min(
      timelineWrapperRect.width - x,
      ((Math.min(songEndBeats, timelineEndBeats) -
        Math.max(songStartBeats, timelineStartBeats)) /
        songEndBeats) *
        timelineWrapperRect.width
    );

    const width = Math.min(
      timelineWrapperRect.width,
      Math.max(16, targetWidth)
    );
    x = Math.max(0, x - (width - targetWidth) / 2);

    return {
      x,
      width,
      height: SCROLLBAR_HEIGHT,
      top: timelineWrapperRect.height - SCROLLBAR_HEIGHT,
      separatorEdge: 12,
      scrollbarEdge: 3,
      grabberEdge: 10,
    };
  };

  const dimensions = calculateScrollbarDimensions();
  if (!dimensions) return [];

  const mainRegion: CanvasRegion = {
    touchTarget: createTouchTarget(dimensions, studioContext),
    renderTop: (ctx: CanvasRenderingContext2D, hovered: boolean) => {
      const renderer = new ScrollbarRenderer(
        ctx,
        dimensions,
        calculateTimestamps(),
        studioContext,
        hovered
      );
      renderer.render();
    },
  };

  const leftEdgeRegion: CanvasRegion = {
    touchTarget: createEdgeTouchTarget('left', dimensions, studioContext),
  };

  const rightEdgeRegion: CanvasRegion = {
    touchTarget: createEdgeTouchTarget('right', dimensions, studioContext),
  };

  const verticalScrollbarThumb: CanvasRegion = {
    touchTarget: {
      bounds: {
        left: timelineWrapperRect.width - 16,
        right: timelineWrapperRect.width,
        top: 0,
        bottom: timelineWrapperRect.height,
      },
      hoverCursor: 'ns-resize',
      onMouseDown: studioContext.handleClickDrag(({ downEvent }) => {
        let lastY = downEvent.clientY;
        const trackListContainer =
          studioContext.timelineController.trackHeaderListRef.current?.querySelector(
            '.track-list-container'
          );
        return {
          onMouseMove: ({ moveEvent }) => {
            const deltaY = moveEvent.clientY - lastY;
            if (deltaY === 0) return;
            lastY = moveEvent.clientY;
            studioContext.timelineController.scrollYRef.current += deltaY;
            trackListContainer?.scrollBy(0, deltaY);
            studioContext.timelineController.fixBounds();
          },
        };
      }),
    },
    renderTop: (ctx: CanvasRenderingContext2D, hovered: boolean) => {
      if (
        studioContext.timelineController.scrollYRef.current !== lastScrollTopPx
      ) {
        lastScrollbarUpdateTimeMs = Date.now();
        lastScrollTopPx = studioContext.timelineController.scrollYRef.current;
      }
      if (
        Date.now() - lastScrollbarUpdateTimeMs > scrollbarShowTimeMs &&
        !hovered
      )
        return;

      const contentHeightPx =
        studioContext.timelineController.trackListHeightRef.current;
      if (!contentHeightPx) return;
      const visibleHeightPx =
        timelineWrapperRect.height - (SCROLLBAR_HEIGHT + RULER_HEIGHT);

      const scrollTopProportion =
        studioContext.timelineController.scrollYRef.current /
        (contentHeightPx - (SCROLLBAR_HEIGHT + RULER_HEIGHT));
      const scrollbarTopPx =
        RULER_HEIGHT + scrollTopProportion * visibleHeightPx;

      const scrollbarHeightPx =
        (visibleHeightPx /
          (contentHeightPx - (SCROLLBAR_HEIGHT + RULER_HEIGHT))) *
        visibleHeightPx;

      ctx.fillStyle = `rgba(255, 255, 255, ${hovered ? 0.25 : 0.125})`;
      ctx.beginPath();
      ctx.roundRect(
        timelineWrapperRect.width - 12,
        scrollbarTopPx,
        8,
        scrollbarHeightPx,
        4
      );
      ctx.closePath();
      ctx.fill();
    },
  };

  return [mainRegion, leftEdgeRegion, rightEdgeRegion, verticalScrollbarThumb];
}

class ScrollbarRenderer {
  private ctx: CanvasRenderingContext2D;
  private dims: ScrollbarDimensions;
  private timestamps: Timestamp[];
  private studioContext: StudioContextType;
  private hovered: boolean;

  constructor(
    ctx: CanvasRenderingContext2D,
    dimensions: ScrollbarDimensions,
    timestamps: Timestamp[],
    studioContext: StudioContextType,
    hovered: boolean
  ) {
    this.ctx = ctx;
    this.dims = dimensions;
    this.timestamps = timestamps;
    this.studioContext = studioContext;
    this.hovered = hovered;
  }

  render() {
    this.drawBackground();
    this.drawScrollbarThumb();
    this.drawTimestampLabels();
    this.drawMinorTicks();
    if (this.hovered || draggingScrollBar) {
      this.drawScrollbarGrabbers();
    }
    this.drawLoopHighlight();
    this.drawSelectionHighlight();
  }

  private drawBackground() {
    this.ctx.fillStyle = '#101012';
    this.ctx.fillRect(
      0,
      this.dims.top,
      this.studioContext.timelineController.getWrapperRect().width,
      this.dims.height
    );
  }

  private drawTimestampLabels() {
    this.ctx.fillStyle = '#ffffff88';
    this.ctx.font =
      '11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
    this.ctx.textAlign = 'center';
    this.ctx.textBaseline = 'middle';

    this.timestamps.forEach(({ position, label }, index) => {
      const x =
        index === 0
          ? position + 20
          : index === this.timestamps.length - 1
            ? position - 20
            : position;

      this.ctx.fillText(label, x, this.dims.top + this.dims.height * 0.5);
    });
  }

  private drawMinorTicks() {
    this.ctx.fillStyle = '#ffffff44';

    for (let i = 1; i < this.timestamps.length; i++) {
      const spacing =
        this.timestamps[i].position - this.timestamps[i - 1].position;

      for (let j = 1; j < 4; j++) {
        const x = this.timestamps[i - 1].position + (spacing * j) / 4;
        this.ctx.fillRect(
          x - 0.5,
          this.dims.top + this.dims.separatorEdge,
          1,
          this.dims.height - this.dims.separatorEdge * 2
        );
      }
    }
  }

  private drawLoopHighlight() {
    const loop = this.studioContext.state.loop;
    if (!loop?.enabled || loop.endBeats <= loop.startBeats) return;

    const { width } = this.studioContext.timelineController.getWrapperRect();
    const songEndBeats = getSongEndBeats(this.studioContext.state);

    const startX = (loop.startBeats / songEndBeats) * width;
    const endX = (loop.endBeats / songEndBeats) * width;

    this.ctx.fillStyle = '#FFFFFF0A';
    this.drawRoundedRect(
      startX,
      this.dims.top,
      Math.max(1, endX - startX),
      this.dims.height
    );
  }

  private drawSelectionHighlight() {
    const selection = this.studioContext.getEffectiveSelection?.();
    if (
      !selection?.startBeats ||
      !selection?.endBeats ||
      selection.endBeats <= selection.startBeats
    )
      return;

    const { width } = this.studioContext.timelineController.getWrapperRect();
    const songEndBeats = getSongEndBeats(this.studioContext.state);

    const startX = Math.max(0, (selection.startBeats / songEndBeats) * width);
    const endX = Math.min(width, (selection.endBeats / songEndBeats) * width);
    const selectionWidth = Math.max(1, endX - startX);

    // Fill
    this.ctx.fillStyle = 'rgba(255, 255, 255, 0)';
    this.drawRoundedRect(
      startX,
      this.dims.top + 2,
      selectionWidth,
      this.dims.height - 4
    );

    // Border
    this.ctx.strokeStyle = '#ffffff66';
    this.ctx.lineWidth = 1;
    this.ctx.stroke();
  }

  private drawScrollbarThumb() {
    // Fill
    this.ctx.fillStyle = '#2A2A2B';
    this.drawRoundedRect(
      this.dims.x,
      this.dims.top + this.dims.scrollbarEdge,
      this.dims.width,
      this.dims.height - this.dims.scrollbarEdge * 2
    );
  }

  private drawScrollbarGrabbers() {
    if (this.dims.width > 28) {
      this.ctx.strokeStyle = '#aaa';
      const middleX = this.dims.x + this.dims.width / 2;
      const xs = [middleX - 6, middleX, middleX + 6];

      this.ctx.fillStyle = '#2A2A2B66';
      xs.forEach((x) => {
        this.ctx.fillRect(
          x - 2,
          this.dims.top + this.dims.grabberEdge - 1,
          5,
          this.dims.height - 2 * (this.dims.grabberEdge - 1)
        );
      });

      this.ctx.lineWidth = 1;
      this.ctx.beginPath();
      xs.forEach((x) => {
        this.ctx.moveTo(x, this.dims.top + this.dims.grabberEdge);
        this.ctx.lineTo(
          x,
          this.dims.top + this.dims.height - this.dims.grabberEdge
        );
      });
      this.ctx.stroke();
    }

    this.ctx.fillStyle = '#ffffff';
    this.drawRoundedRect(
      this.dims.x - 1,
      this.dims.top + this.dims.grabberEdge,
      2,
      this.dims.height - 2 * this.dims.grabberEdge
    );

    this.ctx.fillStyle = '#ffffff';
    this.drawRoundedRect(
      this.dims.x + this.dims.width - 1,
      this.dims.top + this.dims.grabberEdge,
      2,
      this.dims.height - 2 * this.dims.grabberEdge
    );
  }

  private drawRoundedRect(x: number, y: number, width: number, height: number) {
    this.ctx.beginPath();
    this.ctx.roundRect(x, y, width, height, 3);
    this.ctx.fill();
  }
}

function createTouchTarget(
  dims: ScrollbarDimensions,
  studioContext: StudioContextType
) {
  return {
    bounds: {
      left: dims.x,
      right: dims.x + dims.width,
      top: dims.top,
      bottom: dims.top + dims.height,
    },
    hoverCursor: 'grab',
    dragCursor: 'grabbing',
    hoverGroup: 'scrollbar',
    onMouseDown: (e: MouseEvent) => {
      const startX = e.clientX;
      const originalScrollX =
        studioContext.timelineController.scrollXRef.current;
      draggingScrollBar = true;

      const handleMouseMove = (e: MouseEvent) => {
        const songStartBeats = getSongStartBeats(studioContext.state);
        const songEndBeats = getSongEndBeats(studioContext.state);

        const { pxPerBeatRef } = studioContext.timelineController;
        const scrollbarPxPerBeat =
          (studioContext.timelineController.getWrapperRect().width - 4) /
          (songEndBeats - songStartBeats);
        const deltaXFromStart = e.clientX - startX;
        const beatsDragged = deltaXFromStart / scrollbarPxPerBeat;
        const timelinePxDragged = beatsDragged * pxPerBeatRef.current;
        studioContext.timelineController.scrollXRef.current =
          originalScrollX + timelinePxDragged;
        studioContext.timelineController.fixBounds();
        studioContext.timelineController.frameCountRef.current++;
      };

      const handleMouseUp = () => {
        draggingScrollBar = false;
        window.removeEventListener('mouseup', handleMouseUp);
        window.removeEventListener('mousemove', handleMouseMove);
      };

      window.addEventListener('mouseup', handleMouseUp);
      window.addEventListener('mousemove', handleMouseMove);
    },
  };
}

function createEdgeTouchTarget(
  side: 'left' | 'right',
  dims: ScrollbarDimensions,
  studioContext: StudioContextType
) {
  const edgeWidth = 12; // px grab area
  const bounds =
    side === 'left'
      ? {
          left: Math.max(0, dims.x - edgeWidth / 2),
          right: dims.x + edgeWidth / 2,
          top: dims.top,
          bottom: dims.top + dims.height,
        }
      : {
          left: dims.x + dims.width - edgeWidth / 2,
          right: dims.x + dims.width + edgeWidth / 2,
          top: dims.top,
          bottom: dims.top + dims.height,
        };

  const cursor = 'ew-resize';

  const onMouseDown = ({ downEvent }: { downEvent: MouseEvent }) => {
    const startX = downEvent.clientX;

    const controller = studioContext.timelineController;
    const { pxPerBeatRef, scrollXRef, frameCountRef, fixBounds } = controller;

    const wrapperWidth = controller.getWrapperRect().width;

    const songStartBeats = getSongStartBeats(studioContext.state);
    const songEndBeats = getSongEndBeats(studioContext.state);

    const initialSongDuration = songEndBeats - songStartBeats;

    const timelineStartBeatsAnchor = controller.getTimelineStartBeats();
    const timelineEndBeatsAnchor = controller.getTimelineEndBeats();

    const visibleRangeBeatsInitial =
      timelineEndBeatsAnchor - timelineStartBeatsAnchor;

    return {
      onMouseMove: ({ moveEvent }: { moveEvent: MouseEvent }) => {
        const deltaPxRaw = moveEvent.clientX - startX;
        const deltaPx = side === 'right' ? deltaPxRaw : -deltaPxRaw;

        const beatsPerScrollbarPx = initialSongDuration / wrapperWidth;
        const deltaBeats = deltaPx * beatsPerScrollbarPx;

        const newVisibleRangeBeats = Math.max(
          0.0001,
          visibleRangeBeatsInitial + deltaBeats
        );
        const newPxPerBeat =
          controller.getWrapperRect().width / newVisibleRangeBeats;

        pxPerBeatRef.current = newPxPerBeat;

        if (side === 'right') {
          scrollXRef.current = timelineStartBeatsAnchor * newPxPerBeat;
        } else {
          scrollXRef.current =
            timelineEndBeatsAnchor * newPxPerBeat -
            controller.getWrapperRect().width;
        }

        frameCountRef.current++;
        fixBounds();
      },
    };
  };

  return {
    bounds,
    hoverCursor: cursor,
    dragCursor: cursor,
    hoverGroup: 'scrollbar',
    onMouseDown: studioContext.handleClickDrag(onMouseDown),
  };
}
