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

import { truncateText } from '@/utils/utils';

import { stemColors } from '../StemsContext';
import { songSectionColors } from '../getSections';
import { CanvasRegion } from './CanvasRenderer';
import { EditCanvasRenderContext } from './EditCanvasRenderContext';
import crop from './crop';
import makeRoundedRectRegion from './makeRoundedRectRegion';
import makeWaveformRegion from './makeWaveformRegion';

const CLIP_TITLE_CHAR_WIDTH = 6.5;
const EDGE_TOUCH_TARGET_WIDTH = 10;

const edgeDrags: Record<string, number> = {};
const getStartEdgeDragId = (trackId: string, originalStartSeconds: number) =>
  `${trackId}-${originalStartSeconds}-start`;
const getEndEdgeDragId = (trackId: string, originalEndSeconds: number) =>
  `${trackId}-${originalEndSeconds}-end`;

export default function makeStemRegions({
  pxPerBeat,
  scrollX,
  timelineStartBeats,
  timelineEndBeats,
  timing,
  frameCountRef,
  stemsContext,
  stemRowAlignments,
  waveformY,
  waveformHeight,
  sectionDividerBottom,
  handleClickDrag,
  timelineWrapperRect,
}: EditCanvasRenderContext): CanvasRegion[] {
  const tracksAndAlignments = stemsContext.stemTracks.map((track, index) => {
    const alignment = stemRowAlignments[index];
    return {
      track,
      alignment,
    };
  });

  tracksAndAlignments.unshift({
    track: stemsContext.fullSongTrack,
    alignment: [waveformY, waveformHeight],
  });

  const fullSongRegions: CanvasRegion[] = [];
  const stemRegions: CanvasRegion[] = [];

  tracksAndAlignments.forEach(({ track, alignment }) => {
    const destinationArray =
      track.id === stemsContext.fullSongTrack.id
        ? fullSongRegions
        : stemRegions;
    const selected = stemsContext.selectedTrackId === track.id;
    destinationArray.push(
      ...track.arrangedClips
        .map((originalClip) => {
          const startEdgeDragId = getStartEdgeDragId(
            track.id,
            originalClip.startSeconds
          );
          const endEdgeDragId = getEndEdgeDragId(
            track.id,
            originalClip.endSeconds
          );
          const clip = {
            ...originalClip,
            startSeconds:
              originalClip.startSeconds + (edgeDrags[startEdgeDragId] || 0),
            readStartSeconds:
              originalClip.readStartSeconds + (edgeDrags[startEdgeDragId] || 0),
            endSeconds:
              originalClip.endSeconds + (edgeDrags[endEdgeDragId] || 0),
          };
          const color = '#000000';
          const x =
            getBeatsFromZero(clip.startSeconds, timing) * pxPerBeat - scrollX;
          const width =
            getBeatsFromZero(clip.endSeconds, timing) * pxPerBeat - scrollX - x;

          const availableChars = width / CLIP_TITLE_CHAR_WIDTH;
          const clipTitle = truncateText(
            clip.title || 'Untitled Clip',
            availableChars
          );
          const clipTitleBlockWidth =
            20 + clipTitle.length * CLIP_TITLE_CHAR_WIDTH;

          const clipTitleBlockX = Math.max(x, 0);

          return [
            makeRoundedRectRegion({
              startBeats: getBeatsFromZero(clip.startSeconds, timing),
              endBeats: getBeatsFromZero(clip.endSeconds, timing),
              pxPerBeat,
              scrollX,
              y: alignment[0] + 1,
              height: alignment[1] - 1,
              color:
                track.stemType && stemColors[track.stemType]
                  ? stemColors[track.stemType] + '88'
                  : songSectionColors.chorus + '88',
            }),
            makeWaveformRegion({
              arrangedClip: clip,
              color,
              pxPerBeat,
              scrollX,
              y: alignment[0],
              height: alignment[1],
              timelineStartBeats,
              timelineEndBeats,
              timing,
              frameCountRef,
              collapseToMono: true,
            }),
            {
              touchTarget: {
                bounds: {
                  top: alignment[0],
                  left: x,
                  bottom: alignment[0] + alignment[1],
                  right: x + EDGE_TOUCH_TARGET_WIDTH,
                },
                hoverCursor: 'ew-resize',
                onMouseDown: handleClickDrag(({ downSeconds }) => {
                  const originalClips = track.arrangedClips;
                  edgeDrags[startEdgeDragId] = 0;
                  return {
                    onMouseMove: ({ moveSeconds }) => {
                      const delta = moveSeconds - downSeconds;
                      const resultingStart = Math.max(
                        0,
                        Math.min(
                          clip.endSeconds - 0.01,
                          clip.startSeconds + delta
                        )
                      );
                      const actualDelta = resultingStart - clip.startSeconds;
                      edgeDrags[startEdgeDragId] = actualDelta;
                      frameCountRef.current++;
                    },
                    onMouseUp: () => {
                      stemsContext.updateTrack(track.id, (prev) => ({
                        ...prev,
                        arrangedClips: originalClips.map((clip) => ({
                          ...clip,
                          readStartSeconds:
                            clip.readStartSeconds +
                            (edgeDrags[startEdgeDragId] || 0),
                          startSeconds:
                            clip.startSeconds +
                            (edgeDrags[startEdgeDragId] || 0),
                        })),
                      }));
                      edgeDrags[startEdgeDragId] = 0;
                    },
                  };
                }),
              },
            },
            {
              touchTarget: {
                bounds: {
                  top: alignment[0],
                  left: x + width - EDGE_TOUCH_TARGET_WIDTH,
                  bottom: alignment[0] + alignment[1],
                  right: x + width,
                },
                hoverCursor: 'ew-resize',
                onMouseDown: handleClickDrag(({ downSeconds }) => {
                  const originalClips = track.arrangedClips;
                  return {
                    onMouseMove: ({ moveSeconds }) => {
                      const delta = moveSeconds - downSeconds;
                      const resultingEnd = Math.max(
                        clip.startSeconds + 0.01,
                        Math.min(clip.contentSeconds, clip.endSeconds + delta)
                      );
                      const actualDelta = resultingEnd - clip.endSeconds;
                      edgeDrags[endEdgeDragId] = actualDelta;
                      frameCountRef.current++;
                    },
                    onMouseUp: () => {
                      stemsContext.updateTrack(track.id, (prev) => ({
                        ...prev,
                        arrangedClips: originalClips.map((clip) => ({
                          ...clip,
                          endSeconds:
                            clip.endSeconds + (edgeDrags[endEdgeDragId] || 0),
                        })),
                      }));
                      edgeDrags[endEdgeDragId] = 0;
                    },
                  };
                }),
              },
            },
            {
              touchTarget: {
                bounds: {
                  top: alignment[0] + 4,
                  left: clipTitleBlockX,
                  right: clipTitleBlockX + clipTitleBlockWidth,
                  bottom: alignment[0] + 28,
                },
                hoverCursor: 'pointer',
                onMouseDown: () => {
                  stemsContext.setSelectedTrackId(track.id);
                },
              },
              render: (ctx: CanvasRenderingContext2D, hovered: boolean) => {
                if (selected) {
                  ctx.strokeStyle = '#fff';
                  ctx.lineWidth = 2;
                  ctx.beginPath();
                  ctx.roundRect(
                    clipTitleBlockX,
                    alignment[0] + 1,
                    width,
                    alignment[1] - 1,
                    6
                  );
                  ctx.closePath();
                  ctx.stroke();
                }

                if (selected || hovered) {
                  ctx.fillStyle = '#ffffff22';
                  ctx.beginPath();
                  ctx.roundRect(
                    clipTitleBlockX + 4,
                    alignment[0] + 4,
                    clipTitleBlockWidth,
                    24,
                    4
                  );
                  ctx.closePath();
                  ctx.fill();
                }

                ctx.fillStyle = '#fff';
                ctx.font = '9px "Input Sans", monospace';
                ctx.textAlign = 'left';
                ctx.textBaseline = 'middle';
                ctx.fillText(
                  clipTitle.toUpperCase(),
                  clipTitleBlockX + 14,
                  alignment[0] + 16
                );
              },
            },
          ];
        })
        .flat()
    );
  });

  return [
    ...fullSongRegions,
    ...crop(stemRegions, {
      top: sectionDividerBottom,
      left: 0,
      right: timelineWrapperRect.width,
      bottom: timelineWrapperRect.height,
    }),
  ];
}
