import React, { useEffect, useRef, useState } from 'react';
import { KGTrack, TrackType } from '../../core/track/KGTrack';
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
import TrackGridItem from './TrackGridItem';
import { Playhead, FileImportModal } from '../common';
import SelectionBox from '../piano-roll/SelectionBox';
import type { RegionClickOptions, RegionUI } from '../interfaces';
import { DEBUG_MODE, PIANO_ROLL_CONSTANTS, REGION_CONSTANTS } from '../../constants';
import { KGMainContentState } from '../../core/state/KGMainContentState';
import { isModifierKeyPressed } from '../../util/osUtil';
import { CreateRegionCommand, ResizeRegionCommand, MoveRegionCommand, MoveMultipleRegionsCommand, ResizeMultipleRegionsCommand, ImportAudioCommand, ImportMidiClipCommand } from '../../core/commands';
import { KGCore } from '../../core/KGCore';
import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface';
import { KGAudioRegion } from '../../core/region/KGAudioRegion';
import { generateNewRegionName } from '../../util/miscUtil';
import { KGAudioFileStorage } from '../../core/io/KGAudioFileStorage';
import { showAlert } from '../../util/dialogUtil';
import { parseMidiFirstTrackNotes } from '../../util/midiUtil';
import * as Tone from 'tone';
import { useProjectStore } from '../../stores/projectStore';

interface TrackGridPanelProps {
  tracks: KGTrack[];
  regions: RegionUI[];
  maxBars: number;
  timeSignature: { numerator: number; denominator: number };
  draggedTrackIndex: number | null;
  dragOverTrackIndex: number | null;
  selectedRegionId: string | null;
  projectName: string;
  onRegionCreated: (trackIndex: number, region: RegionUI, midiRegion: KGMidiRegion) => void;
  onRegionUpdated?: (regionId: string, updates: Partial<RegionUI>, expectedModelUpdates?: { startBeat: number, length: number }) => void;
  onRegionClick?: (regionId: string, options: RegionClickOptions) => void;
  onRegionLassoSelection?: (regionIds: string[], options: RegionClickOptions) => void;
  onRegionLassoCommit?: () => void;
  onOpenPianoRoll?: (regionId: string) => void;
  onOpenSpectrogram?: (regionId: string) => void;
  showHybridButtonForAudio?: boolean;
  showHybridButtonForMidi?: boolean;
  onOpenHybrid?: (regionId: string) => void;
  onExternalDropComplete?: (trackIndex: number, regionUI: RegionUI) => void;
}

const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
  tracks,
  regions,
  maxBars,
  timeSignature,
  draggedTrackIndex,
  dragOverTrackIndex,
  selectedRegionId,
  projectName,
  onRegionCreated,
  onRegionUpdated,
  onRegionClick,
  onRegionLassoSelection,
  onRegionLassoCommit,
  onOpenPianoRoll,
  onOpenSpectrogram,
  showHybridButtonForAudio,
  showHybridButtonForMidi,
  onOpenHybrid,
  onExternalDropComplete,
}) => {
  const selectedRegionIds = useProjectStore(state => state.selectedRegionIds);
  const refreshProjectState = useProjectStore(state => state.refreshProjectState);
  const gridContainerRef = useRef<HTMLDivElement>(null);
  const [showAudioImportModal, setShowAudioImportModal] = useState(false);
  const [previewRegionStyles, setPreviewRegionStyles] = useState<Record<string, React.CSSProperties>>({});
  const pendingAudioImportRef = useRef<{ barNumber: number; trackIndex: number } | null>(null);
  const isLassoSelectingRef = useRef(false);
  const isLassoShiftPressedRef = useRef(false);
  const lassoBoxRef = useRef({ startX: 0, startY: 0, endX: 0, endY: 0 });
  const [lassoRenderTick, setLassoRenderTick] = useState(0);

  const getBulkSelectedRegionIds = (primaryRegionId: string) => (
    selectedRegionIds.length > 1 && selectedRegionIds.includes(primaryRegionId)
      ? selectedRegionIds
      : [primaryRegionId]
  );

  const startLassoSelection = (e: React.MouseEvent<HTMLDivElement>) => {
    if (e.button !== 0) return;
    if (KGMainContentState.instance().getActiveTool() !== 'pointer') return;
    if (isModifierKeyPressed(e)) return;
    if (!(e.target instanceof HTMLElement)) return;
    if (!e.target.closest('.track-grid')) return;
    if (e.target.closest('.track-region')) return;

    const rect = gridContainerRef.current?.getBoundingClientRect();
    if (!rect) return;

    const startX = e.clientX - rect.left;
    const startY = e.clientY - rect.top;

    isLassoSelectingRef.current = true;
    isLassoShiftPressedRef.current = e.shiftKey;
    lassoBoxRef.current = { startX, startY, endX: startX, endY: startY };
    setLassoRenderTick(prev => prev + 1);

    document.addEventListener('mousemove', handleLassoMouseMove);
    document.addEventListener('mouseup', handleLassoMouseUp);
  };

  const handleLassoMouseMove = (e: MouseEvent) => {
    if (!isLassoSelectingRef.current || !gridContainerRef.current) return;

    const rect = gridContainerRef.current.getBoundingClientRect();
    lassoBoxRef.current = {
      ...lassoBoxRef.current,
      endX: e.clientX - rect.left,
      endY: e.clientY - rect.top,
    };
    setLassoRenderTick(prev => prev + 1);
  };

  const handleLassoMouseUp = () => {
    if (!isLassoSelectingRef.current || !gridContainerRef.current) return;

    const { startX, startY, endX, endY } = lassoBoxRef.current;
    const left = Math.min(startX, endX);
    const top = Math.min(startY, endY);
    const right = Math.max(startX, endX);
    const bottom = Math.max(startY, endY);
    const isClick = (right - left < PIANO_ROLL_CONSTANTS.DRAG_THRESHOLD)
      && (bottom - top < PIANO_ROLL_CONSTANTS.DRAG_THRESHOLD);

    if (isClick) {
      if (!isLassoShiftPressedRef.current) {
        onRegionLassoSelection?.([], { shiftKey: false });
      }
    } else {
      const containerWidth = gridContainerRef.current.clientWidth;
      const barWidth = containerWidth > 0 ? containerWidth / maxBars : 0;
      const releasePointX = endX;
      const releasePointY = endY;
      const intersectedRegions = barWidth > 0
        ? regions.filter(region => {
            const regionLeft = (region.barNumber - 1) * barWidth;
            const regionRight = regionLeft + (region.length * barWidth);
            const regionTop = region.trackIndex * 120;
            const regionBottom = regionTop + 120;

            return !(
              regionRight < left ||
              regionLeft > right ||
              regionBottom < top ||
              regionTop > bottom
            );
          })
        : [];

      const releaseRegionIndex = intersectedRegions.findIndex(region => {
        const regionLeft = (region.barNumber - 1) * barWidth;
        const regionRight = regionLeft + (region.length * barWidth);
        const regionTop = region.trackIndex * 120;
        const regionBottom = regionTop + 120;

        return releasePointX >= regionLeft
          && releasePointX <= regionRight
          && releasePointY >= regionTop
          && releasePointY <= regionBottom;
      });

      const intersectedRegionIds = intersectedRegions.map(region => region.id);
      if (intersectedRegionIds.length > 0) {
        const primaryRegionIndex = releaseRegionIndex > -1
          ? releaseRegionIndex
          : intersectedRegions.reduce((closestIndex, region, index, allRegions) => {
              const regionLeft = (region.barNumber - 1) * barWidth;
              const regionRight = regionLeft + (region.length * barWidth);
              const regionTop = region.trackIndex * 120;
              const regionBottom = regionTop + 120;
              const regionCenterX = (regionLeft + regionRight) / 2;
              const regionCenterY = (regionTop + regionBottom) / 2;
              const regionDistance = Math.hypot(releasePointX - regionCenterX, releasePointY - regionCenterY);

              const closestRegion = allRegions[closestIndex];
              const closestLeft = (closestRegion.barNumber - 1) * barWidth;
              const closestRight = closestLeft + (closestRegion.length * barWidth);
              const closestTop = closestRegion.trackIndex * 120;
              const closestBottom = closestTop + 120;
              const closestCenterX = (closestLeft + closestRight) / 2;
              const closestCenterY = (closestTop + closestBottom) / 2;
              const closestDistance = Math.hypot(releasePointX - closestCenterX, releasePointY - closestCenterY);

              return regionDistance < closestDistance ? index : closestIndex;
            }, 0);

        const [primaryRegionId] = intersectedRegionIds.splice(primaryRegionIndex, 1);
        intersectedRegionIds.push(primaryRegionId);
      }

      onRegionLassoSelection?.(intersectedRegionIds, { shiftKey: isLassoShiftPressedRef.current });
      onRegionLassoCommit?.();
    }

    isLassoSelectingRef.current = false;
    setLassoRenderTick(prev => prev + 1);
    document.removeEventListener('mousemove', handleLassoMouseMove);
    document.removeEventListener('mouseup', handleLassoMouseUp);
  };

  useEffect(() => {
    return () => {
      document.removeEventListener('mousemove', handleLassoMouseMove);
      document.removeEventListener('mouseup', handleLassoMouseUp);
    };
  }, []);

  // Utility function to create a region at a specific position
  const createRegionAtPosition = async (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
    // Get the grid container element
    const gridContainer = e.currentTarget.closest('.grid-container');
    if (!gridContainer) return;
    
    // Get the grid container's bounding rectangle
    const gridRect = gridContainer.getBoundingClientRect();
    
    // Calculate the relative X position within the grid
    const relativeX = e.clientX - gridRect.left;
    
    // Calculate the width of each bar
    const barWidth = gridContainer.clientWidth / maxBars;
    
    // Calculate which bar was clicked (0-indexed)
    const barIndex = Math.floor(relativeX / barWidth);
    
    // Add 1 to convert to 1-indexed bar number
    const barNumber = barIndex + 1;
    
    // Get the track and its ID
    const track = tracks[trackIndex];
    const trackId = track.getId().toString();

    // For audio tracks, show the file import modal instead of creating a blank region
    if (track.getType() === TrackType.Wave) {
      const snap = KGMainContentState.instance().isSnappingEnabled();
      const rawBar = relativeX / barWidth + 1;
      const snappedBarNumber = Math.max(1, snap ? Math.round(rawBar) : rawBar);
      pendingAudioImportRef.current = { barNumber: snappedBarNumber, trackIndex };
      setShowAudioImportModal(true);
      return;
    }

    if (DEBUG_MODE.TRACK_GRID_PANEL) {
      console.log(`Creating region on track ${trackIndex + 1}, bar ${barNumber}`);
    }
    
    // Get beats per bar from the time signature
    const beatsPerBar = timeSignature.numerator;
    
    // Check for overlapping regions before creating a new one
    const newRegionStartBeat = (barNumber - 1) * beatsPerBar;
    const newRegionEndBeat = newRegionStartBeat + beatsPerBar - 1;
    
    const existingRegions = track.getRegions();
    const hasOverlap = existingRegions.some(region => {
      const existingStart = region.getStartFromBeat();
      const existingEnd = existingStart + region.getLength() - 1;
      return newRegionStartBeat <= existingEnd && newRegionEndBeat >= existingStart;
    });
    
    if (hasOverlap) {
      if (DEBUG_MODE.TRACK_GRID_PANEL) {
        console.log(`Cannot create region at bar ${barNumber}: overlaps with existing region`);
      }
      await showAlert('Cannot create region: overlaps with existing region');
      return; // Don't create the region if it overlaps
    }
    
    // Create and execute the region creation command
    const command = CreateRegionCommand.fromBarCoordinates(
      trackId,
      trackIndex,
      barNumber,
      1, // Default to 1 bar length
      beatsPerBar,
      generateNewRegionName(trackId)
    );
    
    KGCore.instance().executeCommand(command);
    
    // Get the created region for the UI callback
    const createdRegion = command.getCreatedRegion();
    if (createdRegion) {
      // Create the region UI object for the parent component
      const newRegionUI: RegionUI = {
        id: createdRegion.getId(),
        trackId: trackId,
        trackIndex,
        barNumber,
        length: 1,
        name: createdRegion.getName()
      };
      
      // Notify parent about the new region (for UI state updates)
      onRegionCreated(trackIndex, newRegionUI, createdRegion);
    }
  };

  // Handle audio file import after the user picks a file from the modal
  const handleAudioFileImport = async (file: File) => {
    setShowAudioImportModal(false);
    const pending = pendingAudioImportRef.current;
    if (!pending) return;
    pendingAudioImportRef.current = null;

    const { barNumber, trackIndex } = pending;
    const track = tracks[trackIndex];
    if (!track) return;

    const beatsPerBar = timeSignature.numerator;
    const fileId = KGAudioFileStorage.generateAudioFileId(file.name);

    try {
      const arrayBuffer = await file.arrayBuffer();
      const toneBuffer = new Tone.ToneAudioBuffer();
      await new Promise<void>((resolve, reject) => {
        const audioContext = Tone.getContext().rawContext as AudioContext;
        audioContext.decodeAudioData(
          arrayBuffer.slice(0),
          (decoded) => { toneBuffer.set(decoded); resolve(); },
          (err) => reject(err)
        );
      });

      const audioDurationSeconds = toneBuffer.duration;
      await KGAudioFileStorage.storeAudioFile(projectName, fileId, file);
      KGAudioInterface.instance().loadAudioBufferForTrack(
        track.getId().toString(),
        fileId,
        toneBuffer
      );

      const bpm = KGCore.instance().getCurrentProject().getBpm();
      const durationInBeats = audioDurationSeconds * (bpm / 60);
      const insertBeat = (barNumber - 1) * beatsPerBar;
      const lengthInBars = Math.max(1, Math.ceil(durationInBeats / beatsPerBar));
      const prevMaxBars = maxBars;
      const newMaxBars = Math.max(maxBars, barNumber + lengthInBars - 1);

      const cmd = new ImportAudioCommand(
        track.getId() as unknown as number,
        trackIndex,
        fileId,
        file.name,
        audioDurationSeconds,
        insertBeat,
        durationInBeats,
        prevMaxBars,
        newMaxBars
      );
      KGCore.instance().executeCommand(cmd);

      const created = cmd.getCreatedRegion();
      if (created && onExternalDropComplete) {
        const regionUI: RegionUI = {
          id: created.getId(),
          trackId: track.getId().toString(),
          trackIndex,
          barNumber,
          length: lengthInBars,
          name: created.getName(),
        };
        onExternalDropComplete(trackIndex, regionUI);
      }

      if (DEBUG_MODE.TRACK_GRID_PANEL) {
        console.log(`[TrackGrid] Imported audio "${file.name}" to track ${trackIndex}, bar ${barNumber}, ${audioDurationSeconds.toFixed(2)}s`);
      }
    } catch (err) {
      console.error('[TrackGrid] Audio import from click failed:', err);
    }
  };

  // Handle double click on track grid to create region
  const handleTrackGridDoubleClick = (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
    // Only allow double-click creation in pointer mode
    if (KGMainContentState.instance().getActiveTool() === 'pencil') {
      return;
    }
    createRegionAtPosition(e, trackIndex);
  };

  // Handle single click on track grid for pencil mode or modifier+click
  const handleTrackGridClick = (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
    // Create region on single click in pencil mode OR when modifier key is pressed
    if (KGMainContentState.instance().getActiveTool() === 'pencil' || isModifierKeyPressed(e)) {
      createRegionAtPosition(e, trackIndex);
    }
  };

  // Handle region resize during drag
  const handleRegionResize = (regionId: string, newBarNumber: number, newLength: number) => {
    // This is just for live visual updates, we don't update the model yet
    if (DEBUG_MODE.TRACK_GRID_PANEL) {
      console.log(`Resizing region ${regionId} to barNumber ${newBarNumber}, length ${newLength}`);
    }
  };

  // Handle region resize end
  const handleRegionResizeEnd = async (regionId: string, finalBarNumber: number, finalLength: number) => {
    // Now we update the model with the final rounded values
    if (DEBUG_MODE.TRACK_GRID_PANEL) {
      console.log(`Finished resizing region ${regionId} to barNumber ${finalBarNumber}, length ${finalLength}`);
    }

    // Find the region
    const region = regions.find(r => r.id === regionId);
    if (!region) return;

    // Calculate new start and length in beats
    const beatsPerBar = timeSignature.numerator;
    let clampedBarNumber = finalBarNumber;
    let clampedLength = finalLength;

    // Find the track that contains this region
    const track = tracks.find(t => t.getId().toString() === region.trackId);
    if (!track) return;

    // Update the region in the track's model
    const trackRegions = track.getRegions();
    const coreRegion = trackRegions.find(r => r.getId() === regionId);

    if (coreRegion) {
      const oldStartBeat = coreRegion.getStartFromBeat();
      const oldBarNumber = region.barNumber;
      const bulkRegionIds = getBulkSelectedRegionIds(regionId);
      const isBulkEdit = bulkRegionIds.length > 1;

      if (DEBUG_MODE.TRACK_GRID_PANEL) {
        console.log(`Updating KGRegion model - Before: startBeat=${oldStartBeat}, length=${coreRegion.getLength()}`);
        console.log(`Bar numbers - old: ${oldBarNumber}, new: ${finalBarNumber}`);
      }

      // Clamp audio region resize to audio file boundaries for single-region editing only.
      let newClipStartOffsetSeconds: number | undefined;
      if (!isBulkEdit && coreRegion instanceof KGAudioRegion) {
        const bpm = KGCore.instance().getCurrentProject().getBpm();
        const secondsPerBeat = 60 / bpm;
        const clipOffset = coreRegion.getClipStartOffsetSeconds();
        const audioDuration = coreRegion.getAudioDurationSeconds();
        const snap = KGMainContentState.instance().isSnappingEnabled();

        // Left edge changed — calculate new clip offset
        if (clampedBarNumber !== oldBarNumber) {
          const newStartBeat = (clampedBarNumber - 1) * beatsPerBar;
          const beatDelta = newStartBeat - oldStartBeat;
          const secondsDelta = beatDelta * secondsPerBeat;
          const unclampedClipOffset = clipOffset + secondsDelta;

          if (unclampedClipOffset < 0) {
            // Dragged past audio start — snap to earliest allowed position
            const maxLeftExtensionBeats = clipOffset / secondsPerBeat;
            const minStartBeat = oldStartBeat - maxLeftExtensionBeats;
            clampedBarNumber = snap
              ? Math.ceil(minStartBeat / beatsPerBar) + 1
              : (minStartBeat / beatsPerBar) + 1;
            const oldEndBarNumber = oldBarNumber + (coreRegion.getLength() / beatsPerBar);
            clampedLength = oldEndBarNumber - clampedBarNumber;
            newClipStartOffsetSeconds = 0;
          } else {
            newClipStartOffsetSeconds = Math.min(unclampedClipOffset, audioDuration);
          }
        }

        // Right edge — clamp length so it doesn't exceed remaining audio
        const effectiveClipOffset = newClipStartOffsetSeconds ?? clipOffset;
        const maxDurationSeconds = audioDuration - effectiveClipOffset;
        const maxLengthBars = (maxDurationSeconds / secondsPerBeat) / beatsPerBar;
        if (clampedLength > maxLengthBars) {
          clampedLength = snap ? Math.floor(maxLengthBars) : maxLengthBars;
          if (clampedLength < REGION_CONSTANTS.MIN_REGION_LENGTH) {
            clampedLength = REGION_CONSTANTS.MIN_REGION_LENGTH;
          }
        }
      }

      const newStartBeat = (clampedBarNumber - 1) * beatsPerBar;
      const newLengthInBeats = clampedLength * beatsPerBar;

      // Use command pattern to update the region position and length (note adjustments handled inside command)
      try {
        if (isBulkEdit) {
          const command = new ResizeMultipleRegionsCommand(
            regionId,
            clampedBarNumber !== oldBarNumber ? 'start' : 'end',
            newStartBeat - oldStartBeat,
            newLengthInBeats - coreRegion.getLength(),
            bulkRegionIds
          );

          KGCore.instance().executeCommand(command, { rethrow: true });
          refreshProjectState();
          return;
        }

        const command = ResizeRegionCommand.fromBarCoordinates(
          regionId,
          clampedBarNumber,
          clampedLength,
          timeSignature,
          newClipStartOffsetSeconds
        );

        KGCore.instance().executeCommand(command, { rethrow: true });

        if (DEBUG_MODE.TRACK_GRID_PANEL) {
          console.log(`Executed ResizeRegionCommand: region ${regionId} resized using command pattern`);

          // Verify the command worked
          const updatedRegion = track.getRegions().find(r => r.getId() === regionId);
          console.log(`Verified region in track: ${updatedRegion ? 'found' : 'not found'}, startBeat=${updatedRegion?.getStartFromBeat()}, length=${updatedRegion?.getLength()}`);
        }
      } catch (error) {
        console.error('Error resizing region:', error);
        await showAlert(error instanceof Error ? error.message : 'Unable to resize the selected regions.');
        return;
      }

      // Update the region in the parent component with expected model values
      if (onRegionUpdated) {
        onRegionUpdated(
          regionId,
          { barNumber: clampedBarNumber, length: clampedLength },
          { startBeat: newStartBeat, length: newLengthInBeats }
        );
      }
    }
  };

  // Handle region drag during movement
  const handleRegionDrag = (regionId: string, newBarNumber: number, trackIndex: number) => {
    // This is just for live visual updates, we don't update the model yet
    if (DEBUG_MODE.TRACK_GRID_PANEL) {
      console.log(`Dragging region ${regionId} to barNumber ${newBarNumber}, trackIndex ${trackIndex}`);
    }
    
    // We don't need to update any temporary state in the parent component anymore
    // The region will follow the mouse directly using transform in the TrackGridItem component
  };

  // Handle region drag end
  const handleRegionDragEnd = async (regionId: string, finalBarNumber: number, finalTrackIndex: number) => {
    // Now we update the model with the final rounded values
    if (DEBUG_MODE.TRACK_GRID_PANEL) {
      console.log(`Finished dragging region ${regionId} to barNumber ${finalBarNumber}, trackIndex ${finalTrackIndex}`);
    }
    
    // Find the region
    const region = regions.find(r => r.id === regionId);
    if (!region) return;
    const bulkRegionIds = getBulkSelectedRegionIds(regionId);
    const isBulkEdit = bulkRegionIds.length > 1;
    const effectiveTrackIndex = isBulkEdit ? region.trackIndex : finalTrackIndex;

    if (finalBarNumber === region.barNumber && effectiveTrackIndex === region.trackIndex) {
      if (DEBUG_MODE.TRACK_GRID_PANEL) {
        console.log(`Skipping no-op move for region ${regionId}`);
      }
      return;
    }
    
    // Get the target track
    const targetTrack = tracks[effectiveTrackIndex];
    if (!targetTrack) return;

    // Block cross-type region moves (MIDI <-> Audio)
    const sourceTrack = tracks.find(t => {
      return t.getRegions().some(r => r.getId() === regionId);
    });
    if (sourceTrack && sourceTrack.getType() !== targetTrack.getType()) {
      // Snap back — don't execute the move
      if (DEBUG_MODE.TRACK_GRID_PANEL) {
        console.log(`Blocked cross-type move: ${sourceTrack.getType()} region cannot move to ${targetTrack.getType()} track`);
      }
      return;
    }

    // Use command pattern to move the region
    try {
      if (isBulkEdit) {
        const oldStartBeat = (region.barNumber - 1) * timeSignature.numerator;
        const newStartBeat = (finalBarNumber - 1) * timeSignature.numerator;
        const command = new MoveMultipleRegionsCommand(
          regionId,
          newStartBeat - oldStartBeat,
          bulkRegionIds
        );

        KGCore.instance().executeCommand(command, { rethrow: true });
        refreshProjectState();
        return;
      }

      const command = MoveRegionCommand.fromBarCoordinates(
        regionId,
        finalBarNumber,
        targetTrack.getId().toString(),
        effectiveTrackIndex,
        timeSignature
      );
      
      KGCore.instance().executeCommand(command, { rethrow: true });

      // Copy audio buffer to target track if this is a cross-track audio region move
      if (sourceTrack && targetTrack && sourceTrack.getId() !== targetTrack.getId()) {
        const coreRegion = targetTrack.getRegions().find(r => r.getId() === regionId);
        if (coreRegion?.getCurrentType() === 'KGAudioRegion') {
          const audioRegion = coreRegion as unknown as KGAudioRegion;
          KGAudioInterface.instance().copyAudioBufferBetweenTracks(
            sourceTrack.getId().toString(),
            targetTrack.getId().toString(),
            audioRegion.getAudioFileId()
          );
        }
      }

      if (DEBUG_MODE.TRACK_GRID_PANEL) {
        console.log(`Executed MoveRegionCommand: region ${regionId} moved using command pattern`);

        // Verify the command worked
        const movedRegion = command.getTargetRegion();
        console.log(`Verified region: ${movedRegion ? 'found' : 'not found'}, startBeat=${movedRegion?.getStartFromBeat()}, trackId=${movedRegion?.getTrackId()}`);
      }
    } catch (error) {
      console.error('Error moving region:', error);
      await showAlert(error instanceof Error ? error.message : 'Unable to move the selected regions.');
      return;
    }
    
    // Calculate new start in beats for UI update
    const beatsPerBar = timeSignature.numerator;
    const startBeat = (finalBarNumber - 1) * beatsPerBar;
    
    // Update the region in the parent component with expected model values
    if (onRegionUpdated) {
      // Find the updated region to get its length
      const updatedTrack = tracks[effectiveTrackIndex];
      const updatedRegions = updatedTrack.getRegions();
      const updatedRegion = updatedRegions.find(r => r.getId() === regionId);
      
      onRegionUpdated(
        regionId, 
        { 
          trackId: targetTrack.getId().toString(), 
          trackIndex: effectiveTrackIndex, 
          barNumber: finalBarNumber 
        },
        { 
          startBeat, 
          length: updatedRegion ? updatedRegion.getLength() : 0 
        }
      );
    }
  };

  // Handle fine-move end — execute MoveRegionCommand with float-precision beat position
  const handleRegionFineMoveEnd = async (regionId: string, deltaInBars: number) => {
    const region = regions.find(r => r.id === regionId);
    if (!region) return;
    const track = tracks.find(t => t.getId().toString() === region.trackId);
    if (!track) return;
    const coreRegion = track.getRegions().find(r => r.getId() === regionId);
    if (!coreRegion) return;
    const bulkRegionIds = getBulkSelectedRegionIds(regionId);
    const isBulkEdit = bulkRegionIds.length > 1;

    const beatsPerBar = timeSignature.numerator;
    const newStartFromBeat = Math.max(0, coreRegion.getStartFromBeat() + deltaInBars * beatsPerBar);
    if (newStartFromBeat === coreRegion.getStartFromBeat()) return;

    try {
      if (isBulkEdit) {
        const command = new MoveMultipleRegionsCommand(
          regionId,
          deltaInBars * beatsPerBar,
          bulkRegionIds
        );
        KGCore.instance().executeCommand(command, { rethrow: true });
        refreshProjectState();
        return;
      }

      // Use constructor directly (NOT fromBarCoordinates) to preserve float precision
      const command = new MoveRegionCommand(
        regionId,
        newStartFromBeat,
        track.getId().toString(),
        region.trackIndex
      );
      KGCore.instance().executeCommand(command, { rethrow: true });

      if (DEBUG_MODE.TRACK_GRID_PANEL) {
        console.log(`Fine-moved region ${regionId}: startFromBeat=${newStartFromBeat}`);
      }

      const newBarNumber = newStartFromBeat / beatsPerBar + 1;
      onRegionUpdated?.(
        regionId,
        { barNumber: newBarNumber, trackId: region.trackId, trackIndex: region.trackIndex },
        { startBeat: newStartFromBeat, length: coreRegion.getLength() }
      );
    } catch (error) {
      console.error('Error executing fine-move:', error);
      await showAlert(error instanceof Error ? error.message : 'Unable to move the selected regions.');
    }
  };

  // Handle region click
  const handleRegionClick = (regionId: string, options: RegionClickOptions) => {
    if (DEBUG_MODE.TRACK_GRID_PANEL) {
      console.log(`Region clicked in panel: ${regionId}`);
    }
    
    // Find the region
    const region = regions.find(r => r.id === regionId);
    if (!region) return;
    
    // Find the track that contains this region
    const track = tracks.find(t => t.getId().toString() === region.trackId);
    if (!track) return;
    
    // Find the region in the track's model
    const trackRegions = track.getRegions();
    const midiRegion = trackRegions.find(r => r.getId() === regionId) as KGMidiRegion | undefined;
    
    if (midiRegion && DEBUG_MODE.TRACK_GRID_PANEL) {
      console.log(`Found region in model: ${midiRegion.getId()}, trackId=${midiRegion.getTrackId()}, name=${midiRegion.getName()}`);
    }
    
    // Notify parent about the click
    if (onRegionClick) {
      onRegionClick(regionId, options);
    }
  };

  // Handle external K.G.One clip drop onto a track row
  const handleExternalDrop = async (e: React.DragEvent<HTMLDivElement>, trackIndex: number) => {
    const raw = e.dataTransfer.getData('application/kgone-clip');
    if (!raw) return;

    let dropData: { midiUrl?: string; audioUrl: string; audioDurationSeconds: number; audioFileName: string };
    try {
      dropData = JSON.parse(raw);
    } catch {
      console.error('[KGOne] Invalid drop data');
      return;
    }

    // Calculate drop bar position
    if (!gridContainerRef.current) return;
    const gridRect = gridContainerRef.current.getBoundingClientRect();
    const relativeX = e.clientX - gridRect.left;
    const barWidth = gridContainerRef.current.clientWidth / maxBars;
    const rawBar = relativeX / barWidth + 1;
    const snap = KGMainContentState.instance().isSnappingEnabled();
    const barNumber = Math.max(1, snap ? Math.round(rawBar) : Math.floor(rawBar));

    const track = tracks[trackIndex];
    if (!track) return;

    const beatsPerBar = timeSignature.numerator;

    try {
      if (track.getType() === TrackType.MIDI) {
        if (!dropData.midiUrl) {
          await showAlert(
            'This audio clip can only be imported into an audio track.\n' +
            'Please drag it onto an audio track instead.'
          );
          return;
        }
        // ── MIDI track: fetch MIDI file and create a KGMidiRegion ──────────
        const midiResp = await fetch(dropData.midiUrl);
        if (!midiResp.ok) throw new Error(`MIDI fetch failed (${midiResp.status})`);
        const buf = await midiResp.arrayBuffer();
        const { notes, totalBeats } = parseMidiFirstTrackNotes(new Uint8Array(buf));
        const lengthInBars = Math.max(1, Math.ceil(totalBeats / beatsPerBar));

        const cmd = ImportMidiClipCommand.fromBarCoordinates(
          track.getId().toString(),
          trackIndex,
          barNumber,
          lengthInBars,
          beatsPerBar,
          notes,
          'KGOne Clip'
        );
        KGCore.instance().executeCommand(cmd);

        const created = cmd.getCreatedRegion();
        if (created && onExternalDropComplete) {
          const regionUI: RegionUI = {
            id: created.getId(),
            trackId: track.getId().toString(),
            trackIndex,
            barNumber,
            length: lengthInBars,
            name: created.getName(),
          };
          onExternalDropComplete(trackIndex, regionUI);
        }

        if (DEBUG_MODE.TRACK_GRID_PANEL) {
          console.log(`[KGOne] Imported MIDI clip to track ${trackIndex}, bar ${barNumber}, ${notes.length} notes`);
        }

      } else if (track.getType() === TrackType.Wave) {
        // ── Audio track: save blob to OPFS and create a KGAudioRegion ──────
        const blob = await fetch(dropData.audioUrl).then(r => r.blob());
        const audioFile = new File([blob], dropData.audioFileName, { type: 'audio/wav' });
        const fileId = `kgone_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;

        // Decode audio to get accurate duration and load into player bus
        const arrayBuffer = await audioFile.arrayBuffer();
        const toneBuffer = new Tone.ToneAudioBuffer();
        await new Promise<void>((resolve, reject) => {
          const audioContext = Tone.getContext().rawContext as AudioContext;
          audioContext.decodeAudioData(
            arrayBuffer.slice(0),
            (decoded) => { toneBuffer.set(decoded); resolve(); },
            (err) => reject(err)
          );
        });

        const audioDurationSeconds = toneBuffer.duration;

        await KGAudioFileStorage.storeAudioFile(projectName, fileId, audioFile);
        KGAudioInterface.instance().loadAudioBufferForTrack(
          track.getId().toString(),
          fileId,
          toneBuffer
        );

        const bpm = KGCore.instance().getCurrentProject().getBpm();
        const durationInBeats = audioDurationSeconds * (bpm / 60);
        const insertBeat = (barNumber - 1) * beatsPerBar;
        const lengthInBars = Math.max(1, Math.ceil(durationInBeats / beatsPerBar));
        const prevMaxBars = maxBars;
        const newMaxBars = Math.max(maxBars, barNumber + lengthInBars - 1);

        const cmd = new ImportAudioCommand(
          track.getId() as unknown as number,
          trackIndex,
          fileId,
          dropData.audioFileName,
          audioDurationSeconds,
          insertBeat,
          durationInBeats,
          prevMaxBars,
          newMaxBars
        );
        KGCore.instance().executeCommand(cmd);

        const created = cmd.getCreatedRegion();
        if (created && onExternalDropComplete) {
          const regionUI: RegionUI = {
            id: created.getId(),
            trackId: track.getId().toString(),
            trackIndex,
            barNumber,
            length: lengthInBars,
            name: created.getName(),
          };
          onExternalDropComplete(trackIndex, regionUI);
        }

        if (DEBUG_MODE.TRACK_GRID_PANEL) {
          console.log(`[KGOne] Imported audio clip to track ${trackIndex}, bar ${barNumber}, ${audioDurationSeconds.toFixed(2)}s`);
        }
      }
    } catch (err) {
      console.error('[KGOne] Drop import failed:', err);
    }
  };

  return (
    <div className="grid-container" ref={gridContainerRef} onMouseDownCapture={startLassoSelection}>
      {/* Playhead */}
      <Playhead context="main-grid" />

      {/* Track grids */}
      {tracks.map((track, index) => (
        <TrackGridItem
          key={track.getId()}
          track={track}
          index={index}
          isDragging={draggedTrackIndex === index}
          isDragOver={dragOverTrackIndex === index}
          regions={regions}
          maxBars={maxBars}
          selectedRegionId={selectedRegionId}
          gridContainerRef={gridContainerRef}
          onDoubleClick={handleTrackGridDoubleClick}
          onClick={handleTrackGridClick}
          onRegionResize={handleRegionResize}
          onRegionResizeEnd={handleRegionResizeEnd}
          onRegionDrag={handleRegionDrag}
          onRegionDragEnd={handleRegionDragEnd}
          onRegionFineMoveEnd={handleRegionFineMoveEnd}
          onRegionClick={handleRegionClick}
          onOpenPianoRoll={onOpenPianoRoll}
          onOpenSpectrogram={onOpenSpectrogram}
          showHybridButtonForAudio={showHybridButtonForAudio}
          showHybridButtonForMidi={showHybridButtonForMidi}
          onOpenHybrid={onOpenHybrid}
          allTracks={tracks}
          onKGOneClipDrop={handleExternalDrop}
          previewRegionStyles={previewRegionStyles}
          setPreviewRegionStyles={setPreviewRegionStyles}
        />
      ))}

      <FileImportModal
        isVisible={showAudioImportModal}
        onClose={() => setShowAudioImportModal(false)}
        onFileImport={handleAudioFileImport}
        acceptedTypes={['.wav', '.mp3', '.ogg', '.flac', '.aac']}
        title="Import Audio"
        description="Drag and drop your audio file here"
      />
      <SelectionBox
        key={lassoRenderTick}
        isSelecting={isLassoSelectingRef.current}
        selectionBox={lassoBoxRef.current}
      />
    </div>
  );
};

export default TrackGridPanel; 
