import { modulo } from '@suno/studiokit/audioEngineeringUtils';

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

import { StudioContextType } from '../StudioContext';
import getCanvasRelativeRect from '../getCanvasRelativeRect';
import {
  getSelectionConnectedTrackAndTakelaneIds,
  getTakeLaneParentsByTrackId,
} from '../selectors';
import { AFTER_LAST_TRACK } from '../types';

export const RULER_HEIGHT = 30;
const TALL_LINE_HEIGHT = 13;
const SHORT_LINE_HEIGHT = 5;

export default function makeStudioBeatGridRegion(
  studioContext: StudioContextType
): {
  topSection: CanvasRegion;
  lines: CanvasRegion;
  shadedRegions: CanvasRegion;
} {
  const {
    getTimelineStartBeats,
    getTimelineEndBeats,
    pxPerBeatRef,
    scrollXRef,
  } = studioContext.timelineController;
  const timelineStartBeats = getTimelineStartBeats();
  const timelineEndBeats = getTimelineEndBeats();
  const timelineWrapperRect = studioContext.timelineController.getWrapperRect();
  const beatsToCanvasPx = (beats: number) =>
    beats * pxPerBeatRef.current - scrollXRef.current;

  const minPxPerBar = 35 * studioContext.gridMultiplier;

  let beatsPerLine = 1 / 128;
  let beatsPerLabel = 1 / 128;
  while (beatsPerLine * pxPerBeatRef.current < minPxPerBar) {
    beatsPerLine *= 2;
  }
  while (beatsPerLabel * pxPerBeatRef.current < 35) {
    beatsPerLabel *= 2;
  }
  beatsPerLabel = Math.max(
    beatsPerLine * (beatsPerLine === 1 ? 4 : 2),
    beatsPerLabel
  );
  const barXs: [number, number][] = [];
  const labelBeats: Record<number, boolean> = {};

  const shadedRegionBounds: [number, number][] = [];
  const beatsPerTwoShadedRegions = Math.max(2, 8 * beatsPerLine);
  for (
    let i = Math.floor(timelineStartBeats / beatsPerLabel) * beatsPerLabel;
    i <= timelineEndBeats;
    i += beatsPerLabel
  ) {
    labelBeats[i] = true;
  }

  for (
    let i = Math.floor(timelineStartBeats / beatsPerLine) * beatsPerLine;
    i <= timelineEndBeats;
    i += beatsPerLine
  ) {
    barXs.push([beatsToCanvasPx(i), i]);
  }

  for (
    let i =
      Math.floor(timelineStartBeats / beatsPerTwoShadedRegions) *
      beatsPerTwoShadedRegions;
    i <= timelineEndBeats;
    i += beatsPerTwoShadedRegions
  ) {
    shadedRegionBounds.push([
      beatsToCanvasPx(i),
      beatsToCanvasPx(i + beatsPerTwoShadedRegions / 2),
    ]);
  }

  const takeLaneParentsByTrackId = getTakeLaneParentsByTrackId(
    studioContext.state
  );
  const selectionConnectedTrackAndTakelaneIds =
    getSelectionConnectedTrackAndTakelaneIds(studioContext.state);

  studioContext.gridSizeRef.current = beatsPerLine;

  return {
    topSection: {
      touchTarget: {
        bounds: {
          top: 0,
          bottom: RULER_HEIGHT,
          left: 0,
          right: timelineWrapperRect.width,
        },
        onMouseDown: studioContext.handleClickDrag(
          ({ downBeats, downEvent }) => {
            const snappedDownBeats = snapWithEvent(
              downEvent,
              downBeats,
              studioContext.gridSizeRef.current,
              null,
              studioContext.getMaxShift()
            );
            studioContext.playbackController.seek(snappedDownBeats);
            return {};
          }
        ),
        hoverCursor: 'text',
        dragCursor: 'text',
      },
      renderTop: (ctx: CanvasRenderingContext2D) => {
        ctx.fillStyle = '#101012';
        ctx.fillRect(0, 0, timelineWrapperRect.width, RULER_HEIGHT);

        ctx.font = '12px "PP Neue Montreal", sans-serif';
        ctx.textBaseline = 'alphabetic';
        ctx.textAlign = 'left';
        barXs.forEach(([x, beats]) => {
          if (beats < 0) return;

          ctx.fillStyle = 'rgba(255,255,255,0.25)';
          if (beats % 1 === 0 && labelBeats[beats]) {
            const barString = `${beats < 0 ? ~~(beats / 4) : ~~(beats / 4) + 1}`;
            if (beats % 4 === 0) {
              ctx.fillText(barString, x + 5, RULER_HEIGHT - 4);
            } else {
              const beatString = modulo(beats, 4) + 1;
              ctx.fillText(
                `${barString}.${beatString}`,
                x + 5,
                RULER_HEIGHT - 4
              );
            }

            ctx.fillRect(
              x,
              RULER_HEIGHT - TALL_LINE_HEIGHT,
              1,
              TALL_LINE_HEIGHT
            );
          } else if (beats % (beatsPerLine * 4) === 0) {
            ctx.fillRect(
              x,
              RULER_HEIGHT - TALL_LINE_HEIGHT,
              1,
              TALL_LINE_HEIGHT
            );
          } else {
            ctx.fillRect(
              x,
              RULER_HEIGHT - SHORT_LINE_HEIGHT,
              1,
              SHORT_LINE_HEIGHT
            );
          }
        });
        ctx.fillStyle = '#3336';
        ctx.fillRect(0, RULER_HEIGHT, timelineWrapperRect.width, 1);
      },
    },
    lines: {
      render: (ctx: CanvasRenderingContext2D) => {
        ctx.font = '12px "PP Neue Montreal", sans-serif';
        barXs.forEach(([x]) => {
          ctx.fillStyle = '#3336';
          ctx.fillRect(
            x,
            RULER_HEIGHT,
            1,
            timelineWrapperRect.height - RULER_HEIGHT
          );
        });

        Object.entries(
          studioContext.timelineController.trackHeadersRef.current
        ).forEach(([_, header], index, list) => {
          const rect = getCanvasRelativeRect(
            header.getBoundingClientRect(),
            studioContext.timelineController.getWrapperRect()
          );

          if (index === list.length - 1) {
            return;
          }

          const nextIsSelectableTrack =
            studioContext.state.tracks.find(
              (t) => t.id === list[index + 1][0]
            ) || list[index + 1][0] === AFTER_LAST_TRACK;
          ctx.fillStyle = '#3336';
          ctx.fillRect(
            0,
            rect.bottom,
            timelineWrapperRect.width,
            nextIsSelectableTrack ? 2 : 1
          );
        });
      },
    },
    shadedRegions: {
      render: (ctx: CanvasRenderingContext2D) => {
        ctx.fillStyle = 'rgba(255,255,255,0.01)';
        ctx.fillRect(0, 0, timelineWrapperRect.width, RULER_HEIGHT);

        ctx.fillStyle = 'rgba(255,255,255,0.01)';
        shadedRegionBounds.forEach(([start, end]) => {
          ctx.fillRect(
            start,
            RULER_HEIGHT,
            end - start,
            timelineWrapperRect.height - RULER_HEIGHT
          );
        });

        const firstDisplayBeatPx = beatsToCanvasPx(timelineStartBeats);
        const zeroPx = beatsToCanvasPx(0);
        const width = zeroPx - firstDisplayBeatPx;
        if (width > 0) {
          ctx.fillStyle = 'rgba(255,255,255,0.025)';
          ctx.fillRect(
            firstDisplayBeatPx,
            RULER_HEIGHT,
            width,
            timelineWrapperRect.height
          );
          ctx.fillStyle = 'rgba(255,255,255,0.01)';
          ctx.fillRect(firstDisplayBeatPx, 0, width, RULER_HEIGHT);
        }

        Object.entries(
          studioContext.timelineController.trackHeadersRef.current
        ).forEach(([trackId, header]) => {
          const rect = getCanvasRelativeRect(
            header.getBoundingClientRect(),
            studioContext.timelineController.getWrapperRect()
          );
          const parentTrack = takeLaneParentsByTrackId[trackId];
          if (!parentTrack) return;
          if (
            selectionConnectedTrackAndTakelaneIds.find((tid) => tid === trackId)
          ) {
            ctx.fillStyle = `${parentTrack.color}${trackId === parentTrack?.id ? '11' : '22'}`;
            ctx.fillRect(0, rect.top, timelineWrapperRect.width, rect.height);
          } else if (trackId !== parentTrack?.id) {
            ctx.fillStyle = '#3333';
            ctx.fillRect(0, rect.top, timelineWrapperRect.width, rect.height);
          }
        });
      },
    },
  };
}
