import { CanvasRegion } from '@/components/edit2025/canvasRenderer/CanvasRenderer';
import { StudioContextType } from '@/components/studio/StudioContext';
import { combineActions } from '@/components/studio/actions/combineActions';
import focusTimeline from '@/components/studio/actions/focusTimeline';
import setGapSize from '@/components/studio/actions/setGapSize';
import { StudioClip, StudioTrack } from '@/components/studio/types';
import logWebUserEvent from '@/logging/logWebUserEvent';

import { ClipBoundaryContextMenu } from '../ClipBoundaryContextMenu';
import assignMetadata from '../actions/assignMetadata';
import updateSelection from '../actions/updateSelection';
import getCanvasRelativeRect from '../getCanvasRelativeRect';
import { OverlapType, getOverlapType } from '../getOverlapType';
import {
  getFirstClipAfterSelection,
  getLastClipBeforeSelection,
  getSelectionStartBeats,
  getSelectionStartSeconds,
} from '../selectors';

const EPSILON = 0.000001; // ~0.1 samples at 120bpm

function makeSingleAddSectionButtonRegions(
  x: number,
  y: number,
  radius: number,
  hoverGroup: string,
  trackHeight: number,
  hasActiveContextMenu: boolean,
  studioContext: StudioContextType
): CanvasRegion[] {
  // Get the clip ID from the hover group (format: add-section-button-{clipId})
  const clipId = hoverGroup.replace('add-section-button-', '');

  // Find the track and clip that contains this button
  const track = studioContext.state.tracks.find((track: StudioTrack) =>
    track.clips.some((clip: StudioClip) => clip.id === clipId)
  );

  if (!track) return [];

  // Get the clip that this button belongs to
  const sourceClip = track.clips.find((clip: StudioClip) => clip.id === clipId);
  if (!sourceClip) return [];

  const nextClip = track.clips.find(
    (clip: StudioClip) => clip.startBeats > sourceClip.startBeats
  );

  // Create an empty selection at the end of the clip
  const gapStartBeats = sourceClip.endBeats;
  const gapEndBeats = nextClip?.startBeats ?? gapStartBeats;

  const horizontalTouchSize = 50;
  const verticalTouchSize = 100;

  // Don't allow clicking if any clip is being dragged
  if (studioContext.inFlightDrags?.isAnyDragging ?? false) return [];

  const isGap = gapStartBeats < gapEndBeats - EPSILON;
  const canRemoveGap = !track.clipCreationIntents.some(
    (c) =>
      getOverlapType(
        gapStartBeats,
        gapEndBeats,
        c.startBeats ?? -Infinity,
        c.endBeats ?? Infinity
      ) !== OverlapType.NoOverlap
  );

  // First region: large touch target for visibility
  const visibilityRegion: CanvasRegion = {
    touchTarget: {
      bounds: {
        top: y - verticalTouchSize,
        left: x - horizontalTouchSize,
        right: x + horizontalTouchSize,
        bottom: y + trackHeight + verticalTouchSize,
      },
      hoverGroup,
      passthrough: true,
    },
    render: (ctx: CanvasRenderingContext2D, hovered: boolean) => {
      if (!hovered && !hasActiveContextMenu) return;

      // Don't render if any clip is being dragged
      if (studioContext.inFlightDrags?.isAnyDragging ?? false) return;
      if (isGap && !canRemoveGap) return;

      // Draw glass effect background
      ctx.save();
      ctx.beginPath();
      ctx.arc(x, y, radius, 0, Math.PI * 2);
      ctx.fillStyle = '#272728';
      ctx.fill();

      if (!isGap) {
        // Draw plus icon
        const plusSize = radius * 0.3;
        const plusThickness = 1.5;
        ctx.beginPath();
        // Vertical line
        ctx.moveTo(x, y - plusSize);
        ctx.lineTo(x, y + plusSize);
        // Horizontal line
        ctx.moveTo(x - plusSize, y);
        ctx.lineTo(x + plusSize, y);
        ctx.strokeStyle = '#F9F8F6';
        ctx.lineWidth = plusThickness;
        ctx.lineCap = 'round';
        ctx.stroke();

        // Draw white arrow below
        const arrowY = y + radius + 4; // Position arrow below the button with doubled gap
        ctx.beginPath();
        ctx.moveTo(x - 3, arrowY);
        ctx.lineTo(x, arrowY + 3);
        ctx.moveTo(x + 3, arrowY);
        ctx.lineTo(x, arrowY + 3);
        ctx.strokeStyle = '#F9F8F6';
        ctx.lineWidth = 1.5;
        ctx.lineCap = 'round';
        ctx.stroke();
      } else if (canRemoveGap) {
        // Draw X icon
        const xSize = radius * 0.3;
        const xThickness = 1.5;
        ctx.beginPath();
        ctx.moveTo(x - xSize, y - xSize);
        ctx.lineTo(x + xSize, y + xSize);
        ctx.moveTo(x + xSize, y - xSize);
        ctx.lineTo(x - xSize, y + xSize);
        ctx.strokeStyle = '#F9F8F6';
        ctx.lineWidth = xThickness;
        ctx.lineCap = 'round';
        ctx.stroke();
      }

      ctx.restore();
    },
  };

  // Second region: small touch target for highlighting
  const highlightRegion: CanvasRegion = {
    touchTarget: {
      bounds: {
        top: y - radius,
        left: x - radius,
        right: x + radius,
        bottom: y + radius,
      },
      hoverCursor: isGap && !canRemoveGap ? 'default' : 'pointer',
      hoverGroup: `${hoverGroup}-highlight`,
      passthrough: false,
      onMouseDown: () => {
        if (isGap && !canRemoveGap) return;
        if (!isGap) {
          // First focus the timeline, set selection, and set gap size in a single state update
          studioContext.setState(
            combineActions(
              // 1. Focus timeline and set selection at the clip's end
              focusTimeline,
              updateSelection((prev) => ({
                ...prev,
                anchorBeats: gapStartBeats,
                focusBeats: gapEndBeats,
                focusedTrackId: track.id,
                trackIds: [track.id],
              })),
              // 2. Apply the gap size of at least 4 beats
              (state) => {
                // First create a selection at the end point
                const stateWithSelection = {
                  ...state,
                  selection: {
                    ...state.selection,
                    anchorBeats: gapStartBeats,
                    focusBeats: gapEndBeats,
                    focusedTrackId: track.id,
                    trackIds: [track.id],
                  },
                };

                // Then apply the setGapSize action with a minimum of 4 beats
                // Calculate the current gap size manually by finding the clips before and after selection
                const lastClipBeforeSelection =
                  getLastClipBeforeSelection(stateWithSelection);
                const firstClipAfterSelection =
                  getFirstClipAfterSelection(stateWithSelection);
                const currentGapSize =
                  lastClipBeforeSelection && firstClipAfterSelection
                    ? firstClipAfterSelection.startBeats -
                      lastClipBeforeSelection.endBeats
                    : 0;
                const targetGapSize = Math.max(currentGapSize, 32);

                logWebUserEvent({
                  actionName: 'EditV3CreateSectionStarted',
                  context: {
                    editSessionId: studioContext.editSessionId,
                    editingClipId:
                      studioContext.state.editClipId || 'MISSING_EDIT_CLIP_ID',
                    beats: getSelectionStartBeats(studioContext.state),
                    seconds: getSelectionStartSeconds(studioContext.state),
                    trigger: 'section-boundary-button',
                  },
                });
                return setGapSize(targetGapSize)(stateWithSelection);
              },
              // 3. Record that the feature was used
              assignMetadata({ usedCreateSection: true })
            )
          );
        } else {
          studioContext.setState(
            combineActions(
              focusTimeline,
              updateSelection((prev) => ({
                ...prev,
                anchorBeats: gapStartBeats,
                focusBeats: gapEndBeats,
                focusedTrackId: track.id,
                trackIds: [track.id],
              })),
              setGapSize(0)
            )
          );
        }
      },
    },
    render: (ctx: CanvasRenderingContext2D, hovered: boolean) => {
      if (!hovered) return;

      // Don't render if any clip is being dragged
      if (studioContext.inFlightDrags?.isAnyDragging ?? false) return;
      if (isGap && !canRemoveGap) return;

      // Draw glass effect background
      ctx.save();
      ctx.beginPath();
      ctx.arc(x, y, radius, 0, Math.PI * 2);
      ctx.fillStyle = '#313131';
      ctx.fill();

      if (!isGap) {
        // Draw plus icon
        const plusSize = radius * 0.3;
        const plusThickness = 1.5;
        ctx.beginPath();
        // Vertical line
        ctx.moveTo(x, y - plusSize);
        ctx.lineTo(x, y + plusSize);
        // Horizontal line
        ctx.moveTo(x - plusSize, y);
        ctx.lineTo(x + plusSize, y);
        ctx.strokeStyle = '#F9F8F6';
        ctx.lineWidth = plusThickness;
        ctx.lineCap = 'round';
        ctx.stroke();

        // Draw white arrow below
        const arrowY = y + radius + 4; // Position arrow below the button with doubled gap
        ctx.beginPath();
        ctx.moveTo(x - 3, arrowY);
        ctx.lineTo(x, arrowY + 3);
        ctx.moveTo(x + 3, arrowY);
        ctx.lineTo(x, arrowY + 3);
        ctx.strokeStyle = '#F9F8F6';
        ctx.lineWidth = 1.5;
        ctx.lineCap = 'round';
        ctx.stroke();
      } else if (canRemoveGap) {
        // Draw X icon
        const xSize = radius * 0.3;
        const xThickness = 1.5;
        ctx.beginPath();
        ctx.moveTo(x - xSize, y - xSize);
        ctx.lineTo(x + xSize, y + xSize);
        ctx.moveTo(x + xSize, y - xSize);
        ctx.lineTo(x - xSize, y + xSize);
        ctx.strokeStyle = '#F9F8F6';
        ctx.lineWidth = xThickness;
        ctx.lineCap = 'round';
        ctx.stroke();
      }

      ctx.restore();
    },
  };

  return [visibilityRegion, highlightRegion];
}

export function makeSectionBoundaryRegions(
  studioContext: StudioContextType
): CanvasRegion[] {
  if (studioContext.lyricsEditController.replacingLyrics) return [];

  const { x: mouseX, y: mouseY } =
    studioContext.timelineController.canvasRelativeMousePositionRef.current;
  const contextMenuConfig = studioContext.timelineController.contextMenuConfig;

  const addSectionButtons = studioContext.state.tracks.flatMap((track) => {
    return track.clips
      .map((studioClip, index) => {
        // Skip if this is the last clip in the track
        if (index === track.clips.length - 1) return null;

        const nextClip = track.clips[index + 1];

        // Skip if the clip is not visible in the viewport
        const timelineStartBeats =
          studioContext.timelineController.getTimelineStartBeats();
        const timelineEndBeats =
          studioContext.timelineController.getTimelineEndBeats();

        // We only need to render if the clip's end edge is within the viewport
        if (
          studioClip.endBeats < timelineStartBeats ||
          studioClip.endBeats > timelineEndBeats
        ) {
          return null;
        }

        const trackElement =
          studioContext.timelineController.trackHeadersRef.current[track.id];
        if (!trackElement) return null;
        const timelineRect = studioContext.timelineController.getWrapperRect();
        const trackRect = getCanvasRelativeRect(
          trackElement.getBoundingClientRect(),
          timelineRect
        );

        const sectionMiddleX = studioContext.beatsToCanvasX(
          (studioClip.endBeats + nextClip.startBeats) / 2
        );
        const buttonRadius = 12;
        const buttonX = sectionMiddleX;
        const buttonY = trackRect.top - buttonRadius - 20;

        return {
          x: buttonX,
          y: buttonY,
          hasActiveContextMenu:
            contextMenuConfig?.ChildComponent === ClipBoundaryContextMenu &&
            contextMenuConfig.otherProps.beats === studioClip.endBeats,
          beats: studioClip.endBeats,
          radius: buttonRadius,
          trackRect,
          clipId: studioClip.id,
          studioContext,
        };
      })
      .filter(
        (config): config is NonNullable<typeof config> => config !== null
      );
  });
  if (addSectionButtons.length === 0) return [];

  // Find the button closest to the mouse
  const closestButton = addSectionButtons.reduce((closest, current) => {
    if (current.hasActiveContextMenu) {
      return current;
    }
    if (closest.hasActiveContextMenu) {
      return closest;
    }
    const closestDistance = Math.sqrt(
      Math.pow(closest.x - mouseX, 2) + Math.pow(closest.y - mouseY, 2)
    );
    const currentDistance = Math.sqrt(
      Math.pow(current.x - mouseX, 2) + Math.pow(current.y - mouseY, 2)
    );
    return currentDistance < closestDistance ? current : closest;
  });
  // Only create regions for the closest button
  const hoverGroup = `add-section-button-${closestButton.clipId}`;
  return [
    addSectionButtons.map((button) => ({
      render: (ctx: CanvasRenderingContext2D) => {
        ctx.beginPath();
        ctx.arc(button.x, button.y, button.radius / 3, 0, Math.PI * 2);
        ctx.fillStyle = '#272728';
        ctx.fill();
      },
    })),
    makeSingleAddSectionButtonRegions(
      closestButton.x,
      closestButton.y,
      closestButton.radius,
      hoverGroup,
      closestButton.trackRect.height,
      closestButton.hasActiveContextMenu,
      closestButton.studioContext
    ),
  ].flat();
}
