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

import { StudioContextType } from '../StudioContext';
import dragSelect from '../actions/dragSelect';
import updateSelection from '../actions/updateSelection';
import { CLIP_CORNER_RADIUS } from '../constants';
import getCanvasRelativeRect from '../getCanvasRelativeRect';
import { inFlightDragKeys } from '../hooks/useInFlightDrags';
import {
  getClipCreationIntentsByTrackOrTakeLaneId,
  getFocusedClipCreationIntent,
  getSelectableTrackIds,
  getSelectionEndBeats,
  getSelectionEndSeconds,
  getSelectionStartBeats,
  getSelectionStartSeconds,
  getSongEndBeats,
  getStudioClipsByTrackOrTakeLaneId,
} from '../selectors';
import { clampToSongBounds } from './makeStudioBaseRegion';
import { RULER_HEIGHT } from './makeStudioBeatGridRegion';

export const EDGE_TOUCH_TARGET_WIDTH = 16;
const SELECTION_CORNER_RADIUS = CLIP_CORNER_RADIUS + 0.5;

const GRAB_HANDLE_HEIGHT = 64;
const HALF_GRAB_HANDLE_HEIGHT = GRAB_HANDLE_HEIGHT / 2;

export default function makeStudioSelectionRegions(
  studioContext: StudioContextType
): {
  aboveRulerRegions: CanvasRegion[];
  belowRulerRegions: CanvasRegion[];
} {
  const effectiveSelection = studioContext.getEffectiveSelection();
  const contextWindow = {
    startBeats:
      effectiveSelection.startBeats -
      (studioContext.contextBeforeBeats +
        studioContext.inFlightDrags.get(inFlightDragKeys.contextWindow())),
    endBeats:
      effectiveSelection.endBeats +
      (studioContext.contextAfterBeats +
        studioContext.inFlightDrags.get(inFlightDragKeys.contextWindow())),
  };
  const trackIds = effectiveSelection.trackIds;
  const firstSelectedTrackId = trackIds[0];
  const lastSelectedTrackId = trackIds[trackIds.length - 1];
  const isExtend = studioContext.generateMode === 'extend';

  const anyClipLifted = studioContext.inFlightDrags.hasMatching(
    (key) => key.startsWith('clip-') || key.startsWith('clipEdge-')
  );

  const belowRulerRegions: CanvasRegion[] = trackIds
    .map((trackId) => {
      const selectableTrackIds = getSelectableTrackIds(studioContext.state);
      if (!selectableTrackIds.includes(trackId)) return null;

      const element =
        studioContext.timelineController.trackHeadersRef.current[trackId];
      if (!element) return null;

      const rawBounds = getCanvasRelativeRect(
        element.getBoundingClientRect(),
        studioContext.timelineController.getWrapperRect()
      );

      const bounds = new DOMRect(
        rawBounds.x,
        rawBounds.y - 0.5 - (anyClipLifted ? 5 : 0),
        rawBounds.width,
        rawBounds.height + 1
      );

      let anchorX = studioContext.beatsToCanvasX(
        effectiveSelection.anchorBeats
      );
      let focusX = studioContext.beatsToCanvasX(effectiveSelection.focusBeats);
      let startX = anchorX;
      let endX = focusX;
      if (startX > endX) {
        [startX, endX] = [endX, startX];
      }

      if (
        isExtend &&
        !studioContext.inFlightDrags.has(
          inFlightDragKeys.selectionEdge('focus')
        ) &&
        !studioContext.inFlightDrags.has(
          inFlightDragKeys.selectionEdge('anchor')
        )
      ) {
        const wasFocus = endX === focusX;
        endX = Math.max(
          endX,
          studioContext.beatsToCanvasX(getSongEndBeats(studioContext.state)) +
            80
        );
        if (wasFocus) {
          focusX = endX;
        } else {
          anchorX = endX;
        }
      }

      startX += 0.5;
      endX += 0.5;

      const centerY = bounds.top + bounds.height / 2;
      const centerX = startX + (endX - startX) / 2;

      const renderOutline = (ctx: CanvasRenderingContext2D) => {
        ctx.strokeStyle =
          studioContext.state.selection.focusedArea === 'timeline'
            ? `rgba(255, 255, 255, ${
                trackId === effectiveSelection.focusedTrackId ? 1 : 0.5
              })`
            : 'transparent';

        ctx.lineWidth = 1;

        ctx.beginPath();

        // Draw main selection rectangle as custom path
        const maxRadius = Math.min(
          Math.max(0, (endX - startX) / 2), // half width
          bounds.height / 2 // half height
        );

        const radius0 = Math.min(radii[0], maxRadius);
        const radius1 = Math.min(radii[1], maxRadius);
        const radius2 = Math.min(radii[2], maxRadius);
        const radius3 = Math.min(radii[3], maxRadius);

        // Start from top-left corner
        ctx.moveTo(startX + radius0, bounds.top);

        // Top line
        ctx.lineTo(endX - radius1, bounds.top);
        // Top-right corner
        ctx.arcTo(endX, bounds.top, endX, bounds.top + radius1, radius1);

        // Right line
        ctx.lineTo(endX, bounds.top + bounds.height - radius2); // Second segment from bottom of grabber

        // Bottom-right corner
        ctx.arcTo(
          endX,
          bounds.top + bounds.height,
          endX - radius2,
          bounds.top + bounds.height,
          radius2
        );

        // Bottom line
        ctx.lineTo(startX + radius3, bounds.top + bounds.height);
        // Bottom-left corner
        ctx.arcTo(
          startX,
          bounds.top + bounds.height,
          startX,
          bounds.top + bounds.height - radius3,
          radius3
        );

        // Left line
        ctx.lineTo(startX, bounds.top + radius0); // Second segment from top of grabber

        // Top-left corner
        ctx.arcTo(startX, bounds.top, startX + radius0, bounds.top, radius0);

        ctx.closePath();

        ctx.stroke();
      };

      const selectionInFlight =
        studioContext.inFlightDrags.has(
          inFlightDragKeys.selectionEdge('focus')
        ) ||
        studioContext.inFlightDrags.has(
          inFlightDragKeys.selectionEdge('anchor')
        ) ||
        studioContext.inFlightDrags.has(inFlightDragKeys.selection());

      const closeCircleCenterY = bounds.top - 30;

      const trackClips =
        getStudioClipsByTrackOrTakeLaneId(studioContext.state)[trackId] || [];
      const trackClipCreationIntents =
        getClipCreationIntentsByTrackOrTakeLaneId(studioContext.state)[
          trackId
        ] || [];
      const clipsAndCCIs = [...trackClips, ...trackClipCreationIntents];

      const alignsWithLeftEdge = clipsAndCCIs.some(
        (clipOrCCI) =>
          effectiveSelection.startBeats !== effectiveSelection.endBeats &&
          clipOrCCI.startBeats === effectiveSelection.startBeats
      );
      const alignsWithRightEdge = clipsAndCCIs.some(
        (clipOrCCI) =>
          effectiveSelection.startBeats !== effectiveSelection.endBeats &&
          clipOrCCI.endBeats === effectiveSelection.endBeats
      );

      const radii = [0, 0, 0, 0];
      if (anyClipLifted || alignsWithLeftEdge) {
        radii[0] = SELECTION_CORNER_RADIUS;
        radii[3] = SELECTION_CORNER_RADIUS;
      }
      if (anyClipLifted || alignsWithRightEdge) {
        radii[1] = SELECTION_CORNER_RADIUS;
        radii[2] = SELECTION_CORNER_RADIUS;
      }

      const result = [
        studioContext.lyricsEditController.replacingLyrics
          ? {
              touchTarget: {
                bounds: {
                  top: closeCircleCenterY - 15,
                  left: centerX - 15,
                  right: centerX + 15,
                  bottom: closeCircleCenterY + 15,
                },
                hoverCursor: 'pointer',
                onMouseDown: () => {
                  if (!studioContext.previewController.previewingOnTimeline)
                    studioContext.stopReplacingLyrics();
                },
              },
              renderTop: (ctx: CanvasRenderingContext2D) => {
                ctx.fillStyle = 'rgba(0,0,0,0.5)';
                ctx.fillRect(0, 0, startX, ctx.canvas.height);
                ctx.fillRect(
                  endX,
                  0,
                  ctx.canvas.width - endX,
                  ctx.canvas.height
                );
                ctx.fillRect(startX, 0, endX - startX, bounds.top);
                ctx.fillRect(
                  startX,
                  bounds.bottom,
                  endX - startX,
                  ctx.canvas.height - bounds.bottom
                );

                if (!studioContext.previewController.previewingOnTimeline) {
                  ctx.fillStyle = '#333';
                  ctx.beginPath();
                  ctx.arc(centerX, closeCircleCenterY, 15, 0, Math.PI * 2);
                  ctx.fill();
                  ctx.beginPath();
                  ctx.strokeStyle = '#fff';
                  ctx.moveTo(centerX + 5, closeCircleCenterY + 5);
                  ctx.lineTo(centerX - 5, closeCircleCenterY - 5);
                  ctx.moveTo(centerX - 5, closeCircleCenterY + 5);
                  ctx.lineTo(centerX + 5, closeCircleCenterY - 5);
                  ctx.stroke();
                }
              },
            }
          : {},
        {
          renderTop: (ctx: CanvasRenderingContext2D) => {
            ctx.beginPath();
            ctx.fillStyle = selectionInFlight
              ? 'rgba(255,255,255,0.2)'
              : 'rgba(255,255,255,0.1)';

            ctx.roundRect(
              startX,
              bounds.top + 1,
              Math.max(1, endX - startX),
              bounds.height - 1,
              radii
            );
            ctx.closePath();
            ctx.fill();

            if (
              studioContext.lyricsEditController.replacingLyrics &&
              !getFocusedClipCreationIntent(studioContext.state)
            ) {
              ctx.save();
              ctx.fillStyle = '#fff';
              ctx.font = '10px "Input Sans", monospace';
              ctx.textAlign = 'center';
              ctx.textBaseline = 'middle';
              ctx.fillText(
                'New lyrics inserted here'.toUpperCase(),
                centerX,
                bounds.top + bounds.height / 2
              );
              ctx.restore();
            }
            if (
              studioContext.showContextWindow &&
              studioContext.mode === 'edit' &&
              effectiveSelection.endBeats > effectiveSelection.startBeats
            ) {
              ctx.save();
              ctx.beginPath();
              ctx.strokeStyle = 'rgba(255,255,255,0.5)';
              const contextWindowStartX = studioContext.beatsToCanvasX(
                contextWindow.startBeats
              );
              const contextWindowEndX = studioContext.beatsToCanvasX(
                contextWindow.endBeats
              );
              ctx.roundRect(
                contextWindowStartX,
                bounds.top - 31,
                Math.max(1, contextWindowEndX - contextWindowStartX),
                bounds.height + 62,
                radii
              );
              ctx.font = '11px "Input Sans", monospace';
              ctx.textAlign = 'center';
              ctx.textBaseline = 'bottom';
              ctx.fillStyle = 'rgba(255,255,255,0.5)';
              ctx.fillText(
                'Context Window'.toUpperCase(),
                (contextWindowEndX + contextWindowStartX) / 2,
                bounds.top - 40
              );
              ctx.closePath();
              ctx.stroke();
              ctx.restore();
            }

            if (selectionInFlight) return;

            renderOutline(ctx);
          },
        },
        studioContext.mode === 'edit'
          ? {
              touchTarget: {
                bounds: {
                  top: centerY - HALF_GRAB_HANDLE_HEIGHT,
                  left: anchorX - EDGE_TOUCH_TARGET_WIDTH / 2,
                  right: anchorX + EDGE_TOUCH_TARGET_WIDTH / 2,
                  bottom: centerY + HALF_GRAB_HANDLE_HEIGHT,
                },
                hoverCursor: 'ew-resize',
                dragCursor: 'ew-resize',

                onMouseDown: studioContext.handleClickDrag(() => {
                  const originalFocusBeats =
                    studioContext.state.selection.focusBeats;
                  const originalAnchorBeats =
                    studioContext.state.selection.anchorBeats;
                  studioContext.setState(
                    updateSelection((prev) => ({
                      ...prev,
                      focusBeats: originalFocusBeats,
                      anchorBeats: originalAnchorBeats,
                      focusedTrackId: trackId,
                    }))
                  );
                  return {
                    onMouseMove: ({ moveBeats, moveEvent }) => {
                      const snappedMoveBeats = studioContext
                        .lyricsEditController.replacingLyrics
                        ? moveBeats
                        : snapWithEvent(
                            moveEvent,
                            moveBeats,
                            studioContext.gridSizeRef.current,
                            null,
                            studioContext.getMaxShift()
                          );
                      const constrainedMoveBeats = clampToSongBounds(
                        studioContext,
                        snappedMoveBeats
                      );
                      const delta = constrainedMoveBeats - originalAnchorBeats;
                      studioContext.inFlightDrags.update(
                        inFlightDragKeys.selectionEdge('anchor'),
                        delta
                      );
                    },
                    onMouseUp: ({ upBeats, upEvent }) => {
                      const snappedUpBeats = studioContext.lyricsEditController
                        .replacingLyrics
                        ? upBeats
                        : snapWithEvent(
                            upEvent,
                            upBeats,
                            studioContext.gridSizeRef.current,
                            null,
                            studioContext.getMaxShift()
                          );
                      const constrainedUpBeats = clampToSongBounds(
                        studioContext,
                        snappedUpBeats
                      );
                      studioContext.setState(
                        dragSelect(
                          originalFocusBeats,
                          constrainedUpBeats,
                          firstSelectedTrackId,
                          lastSelectedTrackId,
                          trackId
                        )
                      );
                      studioContext.inFlightDrags.finish(
                        inFlightDragKeys.selectionEdge('anchor')
                      );
                    },
                  };
                }),
              },
              renderTop: (ctx: CanvasRenderingContext2D, hovered: boolean) => {
                if (selectionInFlight) {
                  return;
                }

                if (
                  hovered ||
                  studioContext.lyricsEditController.replacingLyrics
                ) {
                  ctx.beginPath();

                  ctx.roundRect(
                    anchorX - 3,
                    centerY - HALF_GRAB_HANDLE_HEIGHT,
                    6,
                    GRAB_HANDLE_HEIGHT,
                    6
                  );

                  ctx.closePath();
                  ctx.fillStyle = `rgba(255, 255, 255, 1)`;
                  ctx.fill();
                }
              },
            }
          : {},
        studioContext.mode === 'edit'
          ? {
              touchTarget: {
                bounds: {
                  top: centerY - HALF_GRAB_HANDLE_HEIGHT,
                  left: focusX - EDGE_TOUCH_TARGET_WIDTH / 2,
                  right: focusX + EDGE_TOUCH_TARGET_WIDTH / 2,
                  bottom: centerY + HALF_GRAB_HANDLE_HEIGHT,
                },
                hoverCursor: 'ew-resize',
                dragCursor: 'ew-resize',

                onMouseDown: studioContext.handleClickDrag(() => {
                  const originalAnchorBeats =
                    studioContext.state.selection.anchorBeats;
                  const originalFocusBeats =
                    studioContext.state.selection.focusBeats;
                  studioContext.setState(
                    updateSelection((prev) => ({
                      ...prev,
                      anchorBeats: originalAnchorBeats,
                      focusBeats: originalFocusBeats,
                      focusedTrackId: trackId,
                    }))
                  );
                  return {
                    onMouseMove: ({ moveBeats, moveEvent }) => {
                      const snappedMoveBeats = studioContext
                        .lyricsEditController.replacingLyrics
                        ? moveBeats
                        : snapWithEvent(
                            moveEvent,
                            moveBeats,
                            studioContext.gridSizeRef.current,
                            null,
                            studioContext.getMaxShift()
                          );
                      const constrainedMoveBeats = clampToSongBounds(
                        studioContext,
                        snappedMoveBeats
                      );
                      const delta = constrainedMoveBeats - originalFocusBeats;
                      studioContext.inFlightDrags.update(
                        inFlightDragKeys.selectionEdge('focus'),
                        delta
                      );
                    },
                    onMouseUp: ({ upBeats, upEvent }) => {
                      const snappedUpBeats = studioContext.lyricsEditController
                        .replacingLyrics
                        ? upBeats
                        : snapWithEvent(
                            upEvent,
                            upBeats,
                            studioContext.gridSizeRef.current,
                            null,
                            studioContext.getMaxShift()
                          );
                      const constrainedUpBeats = clampToSongBounds(
                        studioContext,
                        snappedUpBeats
                      );
                      studioContext.setState(
                        dragSelect(
                          originalAnchorBeats,
                          constrainedUpBeats,
                          firstSelectedTrackId,
                          lastSelectedTrackId,
                          trackId
                        )
                      );

                      studioContext.inFlightDrags.finish(
                        inFlightDragKeys.selectionEdge('focus')
                      );

                      logWebUserEvent({
                        actionName: 'EditV3Selection',
                        context: {
                          editSessionId: studioContext.editSessionId,
                          editingClipId:
                            studioContext.state.editClipId ||
                            'MISSING_EDIT_CLIP_ID',
                          startBeats: getSelectionStartBeats(
                            studioContext.stateRef.current
                          ),
                          startSeconds: getSelectionStartSeconds(
                            studioContext.stateRef.current
                          ),
                          endBeats: getSelectionEndBeats(
                            studioContext.stateRef.current
                          ),
                          endSeconds: getSelectionEndSeconds(
                            studioContext.stateRef.current
                          ),
                          trigger: 'selection-handle',
                        },
                      });
                    },
                  };
                }),
              },

              renderTop: (ctx: CanvasRenderingContext2D, hovered: boolean) => {
                if (selectionInFlight) {
                  return;
                }

                if (
                  hovered ||
                  studioContext.lyricsEditController.replacingLyrics
                ) {
                  ctx.beginPath();

                  ctx.roundRect(
                    focusX - 3,
                    centerY - HALF_GRAB_HANDLE_HEIGHT,
                    6,
                    GRAB_HANDLE_HEIGHT,
                    6
                  );

                  ctx.closePath();
                  ctx.fillStyle = `rgba(255, 255, 255, 1)`;
                  ctx.fill();
                }
              },
            }
          : {},
      ];

      if (
        isExtend &&
        !studioContext.inFlightDrags.hasMatching((key) =>
          key.startsWith('selection')
        )
      ) {
        // selection is an extend, draw the extension hint
        result.push({
          renderTop: (ctx: CanvasRenderingContext2D) => {
            ctx.beginPath();
            ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
            ctx.roundRect(
              startX - 3,
              centerY - HALF_GRAB_HANDLE_HEIGHT,
              6,
              GRAB_HANDLE_HEIGHT,
              10
            );
            ctx.moveTo(startX + 3, centerY - 8);
            ctx.lineTo(startX + 14, centerY);
            ctx.lineTo(startX + 3, centerY + 8);
            ctx.fill();
          },
        });
      }

      return result;
    })
    .flat()
    .filter(Boolean) as CanvasRegion[];

  const aboveRulerRegions: CanvasRegion[] =
    effectiveSelection.startBeats !== effectiveSelection.endBeats
      ? [
          {
            // draw a triangle for each edge of the selection
            renderTop: (ctx: CanvasRenderingContext2D) => {
              ctx.beginPath();
              ctx.fillStyle = '#1F8BFF';
              ctx.strokeStyle = '#1F8BFF';
              ctx.lineWidth = 1;
              ctx.lineJoin = 'round';
              const startX =
                studioContext.beatsToCanvasX(effectiveSelection.startBeats) +
                0.5;
              const endX =
                studioContext.beatsToCanvasX(effectiveSelection.endBeats) + 0.5;
              const bottomY = RULER_HEIGHT;

              const topY = bottomY - 10;
              const startLeftX = startX - 10;
              const startRightX = startX;
              const endLeftX = endX;
              const endRightX = endX + 10;

              ctx.moveTo(startLeftX + 2, topY + 1);
              ctx.lineTo(startRightX, topY + 1);
              ctx.lineTo(startRightX, bottomY - 1);
              ctx.lineTo(startLeftX + 2, topY + 1);

              ctx.moveTo(endRightX - 2, topY + 1);
              ctx.lineTo(endLeftX, topY + 1);
              ctx.lineTo(endLeftX, bottomY - 1);
              ctx.lineTo(endRightX - 2, topY + 1);

              ctx.fill();
              ctx.stroke();
            },
          },
        ]
      : [
          {
            // draw one triangle pointing down
            renderTop: (ctx: CanvasRenderingContext2D) => {
              ctx.beginPath();
              ctx.fillStyle = '#1F8BFF';
              ctx.strokeStyle = '#1F8BFF';
              ctx.lineWidth = 1;
              ctx.lineJoin = 'round';
              const x =
                studioContext.beatsToCanvasX(effectiveSelection.startBeats) +
                0.5;
              const bottomY = RULER_HEIGHT;

              const topY = bottomY - 10;
              const leftX = x - 10;
              const rightX = x + 10;

              ctx.moveTo(x, bottomY - 1);
              ctx.lineTo(leftX + 2, topY + 1);
              ctx.lineTo(rightX - 2, topY + 1);
              ctx.lineTo(x, bottomY - 1);

              ctx.closePath();
              ctx.fill();
              ctx.stroke();
            },
          },
        ];

  return {
    aboveRulerRegions,
    belowRulerRegions,
  };
}
