import { useState, useRef } from 'react';
import { DEBUG_MODE, PIANO_ROLL_CONSTANTS } from '../constants';
import { KGMidiRegion } from '../core/region/KGMidiRegion';
import { beatsToBar, pitchToNoteNameString, pianoRollIndexToPitch, pitchToNoteName } from '../util/midiUtil';
import { isModifierKeyPressed } from '../util/osUtil';
import { KGTrack } from '../core/track/KGTrack';
import { KGMidiNote } from '../core/midi/KGMidiNote';
import type { MutableRefObject } from 'react';
import { KGCore } from '../core/KGCore';
import { KGPianoRollState } from '../core/state/KGPianoRollState';
import { KGAudioInterface } from '../core/audio-interface/KGAudioInterface';
import { CreateNoteCommand, DeleteNotesCommand, ResizeNotesCommand, MoveNotesCommand } from '../core/commands';
import { CreateNotesCommand } from '../core/commands/note/CreateNotesCommand';
import { DeleteMidiEventsCommand } from '../core/commands/note/DeleteMidiEventsCommand';
import { KGMidiPitchBend } from '../core/midi/KGMidiPitchBend';
import { KGMidiControllerEvent } from '../core/midi/KGMidiControllerEvent';
import { getSnappedBeatPosition, getSnappedLength } from '../components/piano-roll/pianoRollSnap';
import { useProjectStore } from '../stores/projectStore';

interface UseNoteOperationsProps {
  activeRegion: KGMidiRegion | null;
  timeSignature: { numerator: number; denominator: number };
  updateTrack: (track: KGTrack) => void;
  tracks: KGTrack[];
  pianoGridRef: MutableRefObject<HTMLDivElement | null>;
}

interface ResizePreviewBaseline {
  noteId: string;
  originalStartBeat: number;
  originalEndBeat: number;
  originalLeft: number;
  originalWidth: number;
}

interface DragPreviewBaseline {
  noteId: string;
  originalStartBeat: number;
  originalEndBeat: number;
  originalPitch: number;
  originalLeft: number;
  originalTop: number;
  originalWidth: number;
  originalHeight: number;
}

export const useNoteOperations = ({
  activeRegion,
  timeSignature,
  updateTrack,
  tracks,
  pianoGridRef
}: UseNoteOperationsProps) => {
  // State for note resizing
  const [resizingNoteId, setResizingNoteId] = useState<string | null>(null);
  const [tempNoteStyles, setTempNoteStyles] = useState<Record<string, React.CSSProperties>>({});
  
  // State for note dragging
  const [draggingNoteId, setDraggingNoteId] = useState<string | null>(null);
  
  // Refs for resize operations
  const currentResizeWidth = useRef<number | null>(null);
  const currentResizeLeft = useRef<number | null>(null);
  const initialStartBeatRef = useRef<number | null>(null);
  const initialEndBeatRef = useRef<number | null>(null);
  const resizePreviewBaselinesRef = useRef<ResizePreviewBaseline[]>([]);
  const resizePreviewNoteIdsRef = useRef<string[]>([]);
  
  // Refs for drag operations
  const initialDragLeft = useRef<number | null>(null);
  const initialDragTop = useRef<number | null>(null);
  const currentDragLeft = useRef<number | null>(null);
  const currentDragTop = useRef<number | null>(null);
  const initialPitchRef = useRef<number | null>(null);
  const dragPreviewBaselinesRef = useRef<DragPreviewBaseline[]>([]);
  const dragPreviewNoteIdsRef = useRef<string[]>([]);
  
  // Counter to trigger re-renders when notes are updated
  const [noteUpdateCounter, setNoteUpdateCounter] = useState(0);

  // Get KGCore instance for accessing selected items
  const core = KGCore.instance();

  const clearTempNoteStyles = (noteIds?: string[]) => {
    if (!noteIds || noteIds.length === 0) {
      setTempNoteStyles({});
      return;
    }

    setTempNoteStyles(prev => {
      const updated = { ...prev };
      noteIds.forEach(id => {
        delete updated[id];
      });
      return updated;
    });
  };
  
  // Utility function to delete selected notes from the active region using commands
  const deleteSelectedNotes = () => {
    if (!activeRegion) return false;
    
    // Get all selected notes
    const selectedItems = core.getSelectedItems();
    const selectedNotes = selectedItems.filter(item => 
      item instanceof KGMidiNote && 
      activeRegion.getNotes().some(note => note.getId() === item.getId())
    ) as KGMidiNote[];
    const selectedPitchBends = selectedItems.filter(item =>
      item instanceof KGMidiPitchBend &&
      activeRegion.getPitchBends().some(pitchBend => pitchBend.getId() === item.getId())
    ) as KGMidiPitchBend[];
    const selectedControllerEvents = selectedItems.filter(item =>
      item instanceof KGMidiControllerEvent &&
      activeRegion.getAllControllerEventsFlattened().some(({ event }) => event.getId() === item.getId())
    ) as KGMidiControllerEvent[];
    
    if (selectedNotes.length === 0 && selectedPitchBends.length === 0 && selectedControllerEvents.length === 0) {
      if (DEBUG_MODE.PIANO_ROLL) {
        console.log('No MIDI events selected for deletion');
      }
      return false;
    }
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`Deleting MIDI events: notes=${selectedNotes.length}, pitchBends=${selectedPitchBends.length}, controllers=${selectedControllerEvents.length}`);
    }
    
    const noteIds = selectedNotes.map(note => note.getId());
    const pitchBendIds = selectedPitchBends.map(pitchBend => pitchBend.getId());
    const controllerEventIds = selectedControllerEvents.map(controllerEvent => controllerEvent.getId());
    const command = pitchBendIds.length > 0 || controllerEventIds.length > 0
      ? new DeleteMidiEventsCommand(noteIds, pitchBendIds, controllerEventIds)
      : new DeleteNotesCommand(noteIds);
    core.executeCommand(command);
    if (pitchBendIds.length > 0 || controllerEventIds.length > 0) {
      useProjectStore.getState().bumpAutomationRedrawVersion();
    }
    
    // Find the track that contains this region and update it for UI sync
    const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId());
    if (track) {
      updateTrack(track);
    }
    
    // Trigger a re-render by incrementing the note update counter
    setNoteUpdateCounter(prev => prev + 1);
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log('Deleted selected MIDI events using command');
    }
    
    return true;
  };

  // Utility function to add a note to the active region using commands
  const addNoteToActiveRegion = (mouseX: number, mouseY: number) => {
    if (!pianoGridRef.current || !activeRegion) return;
    
    // Get the grid element's bounding rectangle
    const rect = pianoGridRef.current.getBoundingClientRect();
    
    // Calculate relative position within the grid
    const x = mouseX - rect.left;
    const y = mouseY - rect.top + PIANO_ROLL_CONSTANTS.PIANO_KEY_CLICK_Y_OFFSET;
    
    // Get the CSS variables for beat width and note height
    const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
    const noteHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-height')) || 20;
    
    // Calculate the beat number (0-indexed) and apply floor snapping for note creation
    const rawBeatNumber = x / beatWidth;
    const currentSnap = KGPianoRollState.instance().getCurrentSnap();
    const beatNumber = currentSnap === 'NO SNAP' 
      ? Math.floor(rawBeatNumber)  // Snap to 1-beat grid when no snapping is selected
      : getSnappedBeatPosition(rawBeatNumber, currentSnap, true); // Use floor snapping for note creation
    
    // Calculate the pitch (MIDI note number)
    // The piano roll is drawn from bottom to top, with higher notes at the top
    const pitch = pianoRollIndexToPitch(Math.floor(y / noteHeight));
    
    // Get the note name for logging
    const { note, octave } = pitchToNoteName(pitch);
    const { bar, beatInBar } = beatsToBar(beatNumber, timeSignature);
    
    // Log the information if debug mode is on
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`Creating note at position: x=${x}, y=${y}`);
      console.log(`Beat: ${beatNumber} (${bar + 1}:${beatInBar + 1}), Pitch: ${pitch} (${note}${octave})`);
    }

    // Calculate note timing relative to the region
    const regionStartBeat = activeRegion.getStartFromBeat();
    const noteStartBeat = beatNumber - regionStartBeat; // relative beat position
    const lastEditedLength = KGPianoRollState.instance().getLastEditedNoteLength();
    const noteEndBeat = noteStartBeat + lastEditedLength; // Use last edited note length
    const velocity = 127; // Maximum velocity
    const pianoRollState = KGPianoRollState.instance();
    const matchingChordPitches = pianoRollState.getCurrentMatchingChords();
    const selectedChordIndex = pianoRollState.getCurrentSelectedChordIndex();
    const cursorChordPitch = pianoRollState.getCurrentChordCursorPitch();
    const chordIndex = matchingChordPitches.length > 0
      ? ((selectedChordIndex % matchingChordPitches.length) + matchingChordPitches.length) % matchingChordPitches.length
      : 0;

    let chordNotePitches: number[] = [];

    if (
      matchingChordPitches.length > 0 &&
      cursorChordPitch !== null &&
      Math.abs(cursorChordPitch - pitch) <= 1
    ) {
      const cursorOctave = Math.floor(cursorChordPitch / 12);
      const pitchClasses = matchingChordPitches[chordIndex] || [];
      chordNotePitches = pitchClasses
        .map(actualPitchClass => {
          const actualPitch = cursorOctave * 12 + actualPitchClass;
          return actualPitch >= 0 && actualPitch <= 127 ? actualPitch : null;
        })
        .filter((value): value is number => value !== null);

      chordNotePitches = Array.from(new Set(chordNotePitches));
    }

    const notePitchesToCreate = chordNotePitches.length > 0 ? chordNotePitches : [pitch];
    const isChordCreation = notePitchesToCreate.length > 1;

    let createdNotes: KGMidiNote[] = [];

    if (isChordCreation) {
      const chordCommand = new CreateNotesCommand(
        notePitchesToCreate.map(notePitch => ({
          regionId: activeRegion.getId(),
          startBeat: noteStartBeat,
          endBeat: noteEndBeat,
          pitch: notePitch,
          velocity
        }))
      );
      core.executeCommand(chordCommand);
      createdNotes = chordCommand.getCreatedNotes().map(({ note }) => note);
    } else {
      const singleNoteCommand = new CreateNoteCommand(
        activeRegion.getId(),
        noteStartBeat,
        noteEndBeat,
        notePitchesToCreate[0],
        velocity
      );
      core.executeCommand(singleNoteCommand);
      const createdNote = singleNoteCommand.getCreatedNote();
      if (createdNote) {
        createdNotes = [createdNote];
      }
    }

    // Increment the note update counter to trigger a re-render
    setNoteUpdateCounter((prev: number) => prev + 1);

    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`Created note using command: startBeat=${noteStartBeat}, endBeat=${noteEndBeat}, pitch=${pitch}`);
    }

    // Find the track that contains this region and update it for UI sync
    const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId());
    if (track) {
      updateTrack(track);
      
      // Play note preview if audio interface is ready and note was created
      if (createdNotes.length > 0) {
        const audioInterface = KGAudioInterface.instance();
        if (audioInterface.getIsInitialized()) {
          if (!audioInterface.getIsAudioContextStarted()) {
            audioInterface.startAudioContext().catch(() => {
              // Silently fail if still not allowed - browser policy
            });
          }

          if (audioInterface.getIsAudioContextStarted()) {
            createdNotes.forEach(note => {
              audioInterface.triggerNote(track.getId().toString(), note);
            });
          }
        }
      }
    }
  };

  // Handle double click on the piano grid
  const handleGridDoubleClick = (e: React.MouseEvent) => {
    addNoteToActiveRegion(e.clientX, e.clientY);
  };

  // Handle single click on the piano grid for pencil mode or modifier+click
  const handleGridClick = (e: React.MouseEvent) => {
    // Create notes on single click in pencil mode OR when modifier key is pressed
    if (KGPianoRollState.instance().getActiveTool() === 'pencil' || isModifierKeyPressed(e)) {
      addNoteToActiveRegion(e.clientX, e.clientY);
    }
  };

  // Handle note resize start
  const handleNoteResizeStart = (noteId: string, resizeEdge: 'start' | 'end', initialX: number) => {
    if (!activeRegion) return;
    
    // Disable resizing in pencil mode
    if (KGPianoRollState.instance().getActiveTool() === 'pencil') {
      return;
    }
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`NOTE RESIZE START: noteId=${noteId}, edge=${resizeEdge}`);
    }
    
    setResizingNoteId(noteId);
    
    // Find the note being resized
    const note = activeRegion.getNotes().find(n => n.getId() === noteId);
    if (!note) return;

    const selectedNotesInRegion = core.getSelectedItems().filter(item =>
      item instanceof KGMidiNote &&
      activeRegion.getNotes().some(regionNote => regionNote.getId() === item.getId())
    ) as KGMidiNote[];
    const isResizedNoteSelected = selectedNotesInRegion.some(selectedNote => selectedNote.getId() === noteId);

    if (!isResizedNoteSelected) {
      selectedNotesInRegion.forEach(selectedNote => {
        selectedNote.deselect();
      });
      useProjectStore.getState().clearAllSelections();

      note.select();
      core.addSelectedItem(note);
      KGPianoRollState.instance().setLastEditedNoteLength(note.getEndBeat() - note.getStartBeat());

      const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId());
      if (track) {
        updateTrack(track);
      }
    }

    const resizeTargetNotes = isResizedNoteSelected
      ? selectedNotesInRegion
      : [note];
    
    // Store the initial start and end beats
    initialStartBeatRef.current = note.getStartBeat();
    initialEndBeatRef.current = note.getEndBeat();
    
    // Get the beat width
    const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
    
    // Calculate the region's start beat (for absolute positioning)
    const regionStartBeat = activeRegion.getStartFromBeat();
    
    // Calculate the absolute start and end beats
    const absStartBeat = note.getStartBeat() + regionStartBeat;
    const absEndBeat = note.getEndBeat() + regionStartBeat;
    
    // Calculate the note's position and dimensions
    const left = absStartBeat * beatWidth;
    const width = (absEndBeat - absStartBeat) * beatWidth;
    
    // Store the initial width and left position
    currentResizeWidth.current = width;
    currentResizeLeft.current = left;
    
    resizePreviewBaselinesRef.current = resizeTargetNotes.map(targetNote => {
      const targetAbsStartBeat = targetNote.getStartBeat() + regionStartBeat;
      const targetAbsEndBeat = targetNote.getEndBeat() + regionStartBeat;
      return {
        noteId: targetNote.getId(),
        originalStartBeat: targetNote.getStartBeat(),
        originalEndBeat: targetNote.getEndBeat(),
        originalLeft: targetAbsStartBeat * beatWidth,
        originalWidth: (targetAbsEndBeat - targetAbsStartBeat) * beatWidth,
      };
    });
    resizePreviewNoteIdsRef.current = resizeTargetNotes.map(targetNote => targetNote.getId());

    setTempNoteStyles(prev => ({
      ...prev,
      ...Object.fromEntries(resizePreviewBaselinesRef.current.map(baseline => [
        baseline.noteId,
        {
          left: `${baseline.originalLeft}px`,
          width: `${baseline.originalWidth}px`,
        },
      ])),
    }));
  };

  // Handle note resize
  const handleNoteResize = (noteId: string, resizeEdge: 'start' | 'end', deltaX: number) => {
    if (!activeRegion) return;
    
    // Find the note being resized
    const note = activeRegion.getNotes().find(n => n.getId() === noteId);
    if (!note) return;
    
    // Get the beat width
    const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
    
    // Calculate the region's start beat (for absolute positioning)
    const regionStartBeat = activeRegion.getStartFromBeat();
    
    // Get initial values
    if (initialStartBeatRef.current === null || initialEndBeatRef.current === null ||
        currentResizeWidth.current === null || currentResizeLeft.current === null) {
      return;
    }
    
    // Calculate the absolute start and end beats
    const absStartBeat = initialStartBeatRef.current + regionStartBeat;
    const absEndBeat = initialEndBeatRef.current + regionStartBeat;
    
    // Calculate the original position and dimensions
    const originalLeft = absStartBeat * beatWidth;
    const originalWidth = (absEndBeat - absStartBeat) * beatWidth;
    
    // Calculate minimum width in pixels
    const minWidth = beatWidth * PIANO_ROLL_CONSTANTS.MIN_NOTE_LENGTH;
    
    let newLeft = originalLeft;
    let newWidth = originalWidth;
    
    if (resizeEdge === 'end') {
      // End resize: adjust width only
      newWidth = Math.max(minWidth, originalWidth + deltaX);
    } else if (resizeEdge === 'start') {
      // Start resize: adjust both left position and width
      // Calculate maximum delta to prevent negative width
      const maxDelta = originalWidth - minWidth;
      const clampedDeltaX = Math.min(maxDelta, deltaX);
      
      // Adjust left position and width
      newLeft = originalLeft + clampedDeltaX;
      newWidth = originalWidth - clampedDeltaX;
    }
    
    // Apply length snapping to the visual feedback
    const newLengthInBeats = newWidth / beatWidth;
    const snappedLengthInBeats = getSnappedLength(
      newLengthInBeats,
      KGPianoRollState.instance().getCurrentSnap(),
      PIANO_ROLL_CONSTANTS.MIN_NOTE_LENGTH,
    );
    const snappedWidth = snappedLengthInBeats * beatWidth;
    
    // Adjust position if necessary for start resize to maintain snapped length
    if (resizeEdge === 'start') {
      newLeft = originalLeft + originalWidth - snappedWidth;
    }
    
    // Store the current values in refs (use snapped values)
    currentResizeWidth.current = snappedWidth;
    currentResizeLeft.current = newLeft;
    
    const startBeatDelta = resizeEdge === 'start'
      ? (newLeft - originalLeft) / beatWidth
      : 0;
    const endBeatDelta = resizeEdge === 'end'
      ? (snappedWidth - originalWidth) / beatWidth
      : 0;

    const previewBaselines = resizePreviewBaselinesRef.current.length > 0
      ? resizePreviewBaselinesRef.current
      : [{
        noteId,
        originalStartBeat: note.getStartBeat(),
        originalEndBeat: note.getEndBeat(),
        originalLeft: originalLeft,
        originalWidth: originalWidth,
      }];

    setTempNoteStyles(prev => ({
      ...prev,
      ...Object.fromEntries(previewBaselines.map(baseline => {
        const previewLeft = resizeEdge === 'start'
          ? baseline.originalLeft + (startBeatDelta * beatWidth)
          : baseline.originalLeft;
        const previewWidth = resizeEdge === 'end'
          ? baseline.originalWidth + (endBeatDelta * beatWidth)
          : baseline.originalWidth - (startBeatDelta * beatWidth);

        return [
          baseline.noteId,
          {
            left: `${previewLeft}px`,
            width: `${previewWidth}px`,
          },
        ];
      })),
    }));
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`NOTE RESIZE: noteId=${noteId}, edge=${resizeEdge}, deltaX=${deltaX}, newLeft=${newLeft}, newWidth=${newWidth}`);
    }
  };

  // Handle note resize end
  const handleNoteResizeEnd = (noteId: string, resizeEdge: 'start' | 'end') => {
    if (!activeRegion) return;
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`NOTE RESIZE END: noteId=${noteId}, edge=${resizeEdge}`);
    }
    
    // Find the note being resized
    const note = activeRegion.getNotes().find(n => n.getId() === noteId);
    if (!note) return;
    
    // Get the beat width
    const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
    
    // Calculate the region's start beat (for absolute positioning)
    const regionStartBeat = activeRegion.getStartFromBeat();
    
    if (currentResizeWidth.current === null || currentResizeLeft.current === null ||
        initialStartBeatRef.current === null || initialEndBeatRef.current === null) {
      // Reset resizing state
      setResizingNoteId(null);
      clearTempNoteStyles(resizePreviewNoteIdsRef.current);
      resizePreviewBaselinesRef.current = [];
      resizePreviewNoteIdsRef.current = [];
      return;
    }
    
    // Calculate the new start and end beats based on the current position and width
    let newStartBeat, newEndBeat;
    
    if (resizeEdge === 'end') {
      // End resize: only the end beat changes
      newStartBeat = note.getStartBeat(); // Keep the original start beat
      newEndBeat = newStartBeat + (currentResizeWidth.current / beatWidth); // Calculate new end beat
      
      // Ensure minimum note length
      if (newEndBeat - newStartBeat < PIANO_ROLL_CONSTANTS.MIN_NOTE_LENGTH) {
        newEndBeat = newStartBeat + PIANO_ROLL_CONSTANTS.MIN_NOTE_LENGTH;
      }
    } else if (resizeEdge === 'start') {
      // Start resize: both start and end beats change
      // Calculate the new start beat (relative to region)
      newStartBeat = (currentResizeLeft.current / beatWidth) - regionStartBeat;
      // Keep the original end beat
      newEndBeat = initialEndBeatRef.current;
      
      // Ensure minimum note length
      if (newEndBeat - newStartBeat < PIANO_ROLL_CONSTANTS.MIN_NOTE_LENGTH) {
        newStartBeat = newEndBeat - PIANO_ROLL_CONSTANTS.MIN_NOTE_LENGTH;
      }
    } else {
      // This shouldn't happen, but just in case
      newStartBeat = note.getStartBeat();
      newEndBeat = note.getEndBeat();
    }
    
    // Apply length snapping to the final resize commit
    const originalLength = newEndBeat - newStartBeat;
    const snappedLength = getSnappedLength(
      originalLength,
      KGPianoRollState.instance().getCurrentSnap(),
      PIANO_ROLL_CONSTANTS.MIN_NOTE_LENGTH,
    );
    
    // Adjust the note bounds to use the snapped length
    if (resizeEdge === 'end') {
      // For end resize, keep start beat and adjust end beat
      newEndBeat = newStartBeat + snappedLength;
    } else if (resizeEdge === 'start') {
      // For start resize, keep end beat and adjust start beat
      newStartBeat = newEndBeat - snappedLength;
    }
    
    // Note: Delta calculations are now handled inside ResizeNotesCommand
    
    // Determine which notes to resize (capture selection at command creation time)
    const selectedItems = core.getSelectedItems();
    const selectedNotes = selectedItems.filter(item => 
      item instanceof KGMidiNote && 
      activeRegion.getNotes().some(n => n.getId() === item.getId())
    ) as KGMidiNote[];

    // If no notes are selected, just resize the primary note
    // If notes are selected, resize all selected notes (including the primary)
    const noteIdsToResize = selectedNotes.length > 0 
      ? selectedNotes.map(n => n.getId())
      : [noteId];

    // Use command pattern to resize notes with undo support
    try {
      const command = ResizeNotesCommand.fromNoteResize(
        noteId,
        resizeEdge,
        initialStartBeatRef.current,
        initialEndBeatRef.current,
        newStartBeat,
        newEndBeat,
        activeRegion.getId(),
        noteIdsToResize
      );
      
      KGCore.instance().executeCommand(command);
      
      // Update last edited note length
      const noteLength = newEndBeat - newStartBeat;
      KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
      
      if (DEBUG_MODE.PIANO_ROLL) {
        console.log(`Executed ResizeNotesCommand: resized notes using command pattern`);
      }
    } catch (error) {
      console.error('Error resizing notes:', error);
      // Reset resizing state and return early on error
      setResizingNoteId(null);
      clearTempNoteStyles(resizePreviewNoteIdsRef.current);
      currentResizeWidth.current = null;
      currentResizeLeft.current = null;
      initialStartBeatRef.current = null;
      initialEndBeatRef.current = null;
      resizePreviewBaselinesRef.current = [];
      resizePreviewNoteIdsRef.current = [];
      return;
    }

    const { bar: startBar, beatInBar: startBeatInBar } = beatsToBar(newStartBeat + regionStartBeat, timeSignature);
    const { bar: endBar, beatInBar: endBeatInBar } = beatsToBar(newEndBeat + regionStartBeat, timeSignature);
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`Updated note: 
        noteId=${noteId}, resizeEdge=${resizeEdge},
        startBeat=${newStartBeat}, endBeat=${newEndBeat}
        absolute startBeat=${newStartBeat + regionStartBeat}, absolute endBeat=${newEndBeat + regionStartBeat}
        absolute startBar=${startBar + 1}:${(startBeatInBar + 1).toFixed(3)}, absolute endBar=${endBar + 1}:${(endBeatInBar + 1).toFixed(3)}`);
    }
    
    // Reset resizing state
    setResizingNoteId(null);
    clearTempNoteStyles(resizePreviewNoteIdsRef.current);
    currentResizeWidth.current = null;
    currentResizeLeft.current = null;
    initialStartBeatRef.current = null;
    initialEndBeatRef.current = null;
    resizePreviewBaselinesRef.current = [];
    resizePreviewNoteIdsRef.current = [];
    
    // Increment the note update counter to trigger a re-render
    setNoteUpdateCounter(prev => prev + 1);
    
    // Find the track that contains this region and update it
    const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId());
    if (track) {
      updateTrack(track);
    }
  };
  
  // Handle note drag start
  const handleNoteDragStart = (noteId: string, initialX: number, initialY: number) => {
    if (!activeRegion) return;
    
    // Disable dragging in pencil mode
    if (KGPianoRollState.instance().getActiveTool() === 'pencil') {
      return;
    }
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`NOTE DRAG START: noteId=${noteId}`);
    }
    
    setDraggingNoteId(noteId);
    
    // Find the note being dragged
    const note = activeRegion.getNotes().find(n => n.getId() === noteId);
    if (!note) return;

    const selectedNotesInRegion = core.getSelectedItems().filter(item =>
      item instanceof KGMidiNote &&
      activeRegion.getNotes().some(regionNote => regionNote.getId() === item.getId())
    ) as KGMidiNote[];
    const isDraggedNoteSelected = selectedNotesInRegion.some(selectedNote => selectedNote.getId() === noteId);
    const dragTargetNotes = isDraggedNoteSelected
      ? selectedNotesInRegion
      : [note];
    
    // Store the initial pitch
    initialPitchRef.current = note.getPitch();
    
    // Get the beat width and note height
    const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
    const noteHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-height')) || 20;
    
    // Calculate the region's start beat (for absolute positioning)
    const regionStartBeat = activeRegion.getStartFromBeat();
    
    // Calculate the absolute start and end beats
    const absStartBeat = note.getStartBeat() + regionStartBeat;
    
    // Calculate the note's position
    const left = absStartBeat * beatWidth;
    const width = (note.getEndBeat() - note.getStartBeat()) * beatWidth;
    
    // Convert pitch to y position (higher notes have lower y values)
    const pitchIndex = 107 - note.getPitch(); // Reverse the pitch to get the index (B7 is 107)
    const top = pitchIndex * noteHeight;
    
    // Store the initial position
    initialDragLeft.current = left;
    initialDragTop.current = top;
    currentDragLeft.current = left;
    currentDragTop.current = top;

    dragPreviewBaselinesRef.current = dragTargetNotes.map(targetNote => {
      const targetAbsStartBeat = targetNote.getStartBeat() + regionStartBeat;
      const targetWidth = (targetNote.getEndBeat() - targetNote.getStartBeat()) * beatWidth;
      const targetTop = (107 - targetNote.getPitch()) * noteHeight;

      return {
        noteId: targetNote.getId(),
        originalStartBeat: targetNote.getStartBeat(),
        originalEndBeat: targetNote.getEndBeat(),
        originalPitch: targetNote.getPitch(),
        originalLeft: targetAbsStartBeat * beatWidth,
        originalTop: targetTop,
        originalWidth: targetWidth,
        originalHeight: noteHeight,
      };
    });
    dragPreviewNoteIdsRef.current = dragTargetNotes.map(targetNote => targetNote.getId());

    setTempNoteStyles(prev => ({
      ...prev,
      ...Object.fromEntries(dragPreviewBaselinesRef.current.map(baseline => [
        baseline.noteId,
        {
          left: `${baseline.originalLeft}px`,
          top: `${baseline.originalTop}px`,
          width: `${baseline.originalWidth}px`,
          height: `${baseline.originalHeight}px`,
          zIndex: 100,
        },
      ])),
    }));
  };
  
  // Handle note drag
  const handleNoteDrag = (noteId: string, deltaX: number, deltaY: number) => {
    if (!activeRegion) return;
    
    // Find the note being dragged
    const note = activeRegion.getNotes().find(n => n.getId() === noteId);
    if (!note) return;
    
    // Get the beat width and note height
    const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
    const noteHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-height')) || 20;
    
    // Get the initial left and top positions
    if (initialDragLeft.current === null || initialDragTop.current === null) return;
    if (currentDragLeft.current === null || currentDragTop.current === null) return;
    
    // Calculate new position
    const originalLeft = initialDragLeft.current;
    const originalTop = initialDragTop.current;

    // Calculate the raw new horizontal position
    const rawNewLeft = originalLeft + deltaX;
    
    // Apply horizontal snapping based on current snap setting
    const regionStartBeat = activeRegion.getStartFromBeat();
    const rawBeatPosition = (rawNewLeft / beatWidth) - regionStartBeat;
    const snappedBeatPosition = getSnappedBeatPosition(
      rawBeatPosition,
      KGPianoRollState.instance().getCurrentSnap(),
    );
    const snappedLeft = (snappedBeatPosition + regionStartBeat) * beatWidth;
    
    // Use snapped horizontal position, but keep raw vertical position
    const newLeft = snappedLeft;
    const newTop = originalTop + deltaY;

    // Update the current position refs with the new position
    currentDragLeft.current = newLeft;
    currentDragTop.current = newTop;
    
    const previewBaselines = dragPreviewBaselinesRef.current.length > 0
      ? dragPreviewBaselinesRef.current
      : [{
        noteId,
        originalStartBeat: note.getStartBeat(),
        originalEndBeat: note.getEndBeat(),
        originalPitch: note.getPitch(),
        originalLeft,
        originalTop,
        originalWidth: (note.getEndBeat() - note.getStartBeat()) * beatWidth,
        originalHeight: noteHeight,
      }];
    const leftDelta = newLeft - originalLeft;
    const topDelta = newTop - originalTop;

    setTempNoteStyles(prev => ({
      ...prev,
      ...Object.fromEntries(previewBaselines.map(baseline => [
        baseline.noteId,
        {
          left: `${baseline.originalLeft + leftDelta}px`,
          top: `${baseline.originalTop + topDelta}px`,
          width: `${baseline.originalWidth}px`,
          height: `${baseline.originalHeight}px`,
          zIndex: 100,
        },
      ])),
    }));
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`NOTE DRAG: noteId=${noteId}, originalLeft=${originalLeft}, originalTop=${originalTop}, deltaX=${deltaX}, deltaY=${deltaY}, newLeft=${newLeft}, newTop=${newTop}`);
    }
  };
  
  // Handle note drag end
  const handleNoteDragEnd = (noteId: string) => {
    if (!activeRegion) return;
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`NOTE DRAG END: noteId=${noteId}`);
    }
    
    // Find the note being dragged
    const note = activeRegion.getNotes().find(n => n.getId() === noteId);
    if (!note) return;
    
    // Get the beat width and note height
    const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
    const noteHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-height')) || 20;
    
    // Calculate the region's start beat (for absolute positioning)
    const regionStartBeat = activeRegion.getStartFromBeat();
    
    if (currentDragLeft.current === null || currentDragTop.current === null || 
        initialDragLeft.current === null || initialDragTop.current === null || 
        initialPitchRef.current === null) {
      // Reset dragging state
      setDraggingNoteId(null);
      clearTempNoteStyles(dragPreviewNoteIdsRef.current);
      dragPreviewBaselinesRef.current = [];
      dragPreviewNoteIdsRef.current = [];
      return;
    }

    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`NOTE DRAG DONE: noteId=${noteId}, finalLeft=${currentDragLeft.current}, finalTop=${currentDragTop.current}`);
    }
    
    // Calculate the new start beat based on the current position (no quantization for horizontal)
    const newStartBeat = (currentDragLeft.current / beatWidth) - regionStartBeat;
    
    // Calculate the new end beat (maintain the same duration)
    const duration = note.getEndBeat() - note.getStartBeat();
    const newEndBeat = newStartBeat + duration;
    
    // Calculate the new pitch based on the current position (quantize to nearest pitch)
    // The piano roll is drawn from bottom to top, with higher notes at the top
    // We need to convert the y position to a pitch
    const pitchIndex = Math.round(currentDragTop.current / noteHeight);
    const newPitch = 107 - pitchIndex; // Convert index back to pitch (B7 is 107)
    
    // Calculate the original start beat for the command
    const originalStartBeat = (initialDragLeft.current / beatWidth) - regionStartBeat;

    // Determine which notes to move (capture selection at command creation time)
    const selectedItems = core.getSelectedItems();
    const selectedNotes = selectedItems.filter(item => 
      item instanceof KGMidiNote && 
      activeRegion.getNotes().some(n => n.getId() === item.getId())
    ) as KGMidiNote[];

    // If no notes are selected, just move the primary note
    // If notes are selected, move all selected notes (including the primary)
    const noteIdsToMove = selectedNotes.some(n => n.getId() === noteId)
      ? selectedNotes.map(n => n.getId())
      : [noteId];

    // Use command pattern to move notes with undo support
    try {
      const command = MoveNotesCommand.fromNoteDrag(
        noteId,
        originalStartBeat,
        initialPitchRef.current,
        newStartBeat,
        newPitch,
        activeRegion.getId(),
        noteIdsToMove
      );
      
      KGCore.instance().executeCommand(command);
      
      // Update last edited note length
      const noteLength = newEndBeat - newStartBeat;
      KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
      
      if (DEBUG_MODE.PIANO_ROLL) {
        console.log(`Executed MoveNotesCommand: moved notes using command pattern`);
      }
    } catch (error) {
      console.error('Error moving notes:', error);
      // Reset dragging state and return early on error
      setDraggingNoteId(null);
      clearTempNoteStyles(dragPreviewNoteIdsRef.current);
      currentDragLeft.current = null;
      currentDragTop.current = null;
      initialDragLeft.current = null;
      initialDragTop.current = null;
      initialPitchRef.current = null;
      dragPreviewBaselinesRef.current = [];
      dragPreviewNoteIdsRef.current = [];
      return;
    }
    
    const { bar: startBar, beatInBar: startBeatInBar } = beatsToBar(newStartBeat + regionStartBeat, timeSignature);
    const { bar: endBar, beatInBar: endBeatInBar } = beatsToBar(newEndBeat + regionStartBeat, timeSignature);
    
    if (DEBUG_MODE.PIANO_ROLL) {
      console.log(`Updated note position: 
        noteId=${noteId},
        startBeat=${newStartBeat}, endBeat=${newEndBeat}, pitch=${newPitch}
        absolute startBeat=${newStartBeat + regionStartBeat}, absolute endBeat=${newEndBeat + regionStartBeat}
        absolute startBar=${startBar + 1}:${(startBeatInBar + 1).toFixed(3)}, absolute endBar=${endBar + 1}:${(endBeatInBar + 1).toFixed(3)}
        original pitch=${initialPitchRef.current} (${pitchToNoteNameString(initialPitchRef.current)}), new pitch=${newPitch} (${pitchToNoteNameString(newPitch)})`);
    }
    
    // Reset dragging state
    setDraggingNoteId(null);
    clearTempNoteStyles(dragPreviewNoteIdsRef.current);
    currentDragLeft.current = null;
    currentDragTop.current = null;
    initialDragLeft.current = null;
    initialDragTop.current = null;
    initialPitchRef.current = null;
    dragPreviewBaselinesRef.current = [];
    dragPreviewNoteIdsRef.current = [];
    
    // Increment the note update counter to trigger a re-render
    setNoteUpdateCounter(prev => prev + 1);
    
    // Find the track that contains this region and update it
    const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId());
    if (track) {
      updateTrack(track);
      
      // Play note preview if audio interface is ready
      const audioInterface = KGAudioInterface.instance();
      if (audioInterface.getIsInitialized()) {
        // Try to start audio context if not started yet (user interaction will allow this)
        if (!audioInterface.getIsAudioContextStarted()) {
          audioInterface.startAudioContext().catch(() => {
            // Silently fail if still not allowed - browser policy
          });
        }
        
        // Trigger note if audio context is now started
        if (audioInterface.getIsAudioContextStarted()) {
          audioInterface.triggerNote(track.getId().toString(), note);
        }
      }
    }
  };
  
  return {
    resizingNoteId,
    draggingNoteId,
    tempNoteStyles,
    noteUpdateCounter,
    setNoteUpdateCounter,
    handleGridDoubleClick,
    handleGridClick,
    handleNoteResizeStart,
    handleNoteResize,
    handleNoteResizeEnd,
    handleNoteDragStart,
    handleNoteDrag,
    handleNoteDragEnd,
    deleteSelectedNotes
  };
}; 
