import React, { useRef, useEffect, useState } from "react";
import { Play, Pause, Square, Music, Volume2, VolumeX, Maximize2, ZoomIn, ZoomOut } from "lucide-react";
import { NoteEvent } from "../utils/midiWriter";

interface MidiVisualizerProps {
  notes: NoteEvent[];
  currentTime: number;
  duration: number;
  onScrub: (seconds: number) => void;
  isPlaying: boolean;
  onPlayToggle: () => void;
}

export default function MidiVisualizer({
  notes,
  currentTime,
  duration,
  onScrub,
  isPlaying,
  onPlayToggle,
}: MidiVisualizerProps) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const containerRef = useRef<HTMLDivElement | null>(null);
  
  const [zoom, setZoom] = useState<number>(60); // pixels per second
  const [synthEnabled, setSynthEnabled] = useState<boolean>(true);
  const [activeNotes, setActiveNotes] = useState<Set<number>>(new Set());

  // Web Audio Context for real-time MIDI synthesizers
  const audioCtxRef = useRef<AudioContext | null>(null);
  const activeSynthsRef = useRef<Map<number, { osc: OscillatorNode; gain: GainNode }>>(new Map());

  // Set up synth Web Audio context
  const getAudioContext = () => {
    if (!audioCtxRef.current) {
      audioCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)();
    }
    if (audioCtxRef.current.state === "suspended") {
      audioCtxRef.current.resume();
    }
    return audioCtxRef.current;
  };

  // Convert MIDI note to Frequency
  const mtoF = (note: number) => {
    return 440 * Math.pow(2, (note - 69) / 12);
  };

  // Trigger synth note on
  const playSynthNote = (noteNumber: number, velocity: number) => {
    if (!synthEnabled) return;
    try {
      const ctx = getAudioContext();
      // If note already playing, stop it first
      stopSynthNote(noteNumber);

      const osc = ctx.createOscillator();
      const gain = ctx.createGain();

      osc.type = "triangle"; // Nice warm, quasi-piano/organ sound
      osc.frequency.value = mtoF(noteNumber);

      // Simple ADSR envelope with fast decay to simulate piano keys
      const now = ctx.currentTime;
      const vol = (velocity / 127) * 0.25; // Scale down safely to avoid shearing
      gain.gain.setValueAtTime(0, now);
      gain.gain.linearRampToValueAtTime(vol, now + 0.02); // attack
      gain.gain.exponentialRampToValueAtTime(vol * 0.3, now + 0.4); // decay to sustain

      osc.connect(gain);
      gain.connect(ctx.destination);
      osc.start(now);

      activeSynthsRef.current.set(noteNumber, { osc, gain });
    } catch (e) {
      console.warn("Synth voice start error", e);
    }
  };

  // Stop synth voice
  const stopSynthNote = (noteNumber: number) => {
    try {
      const voice = activeSynthsRef.current.get(noteNumber);
      if (voice) {
        const ctx = getAudioContext();
        const now = ctx.currentTime;
        // Fast release to avoid click
        voice.gain.gain.cancelScheduledValues(now);
        voice.gain.gain.setValueAtTime(voice.gain.gain.value, now);
        voice.gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.15);
        
        setTimeout(() => {
          try {
            voice.osc.stop();
            voice.osc.disconnect();
            voice.gain.disconnect();
          } catch (err) {}
        }, 200);

        activeSynthsRef.current.delete(noteNumber);
      }
    } catch (e) {
      console.warn("Synth voice release error", e);
    }
  };

  // Mute synth if disabled
  useEffect(() => {
    if (!synthEnabled) {
      // stop all playing notes immediately
      activeSynthsRef.current.forEach((_, note) => stopSynthNote(note));
      activeSynthsRef.current.clear();
    }
  }, [synthEnabled]);

  // Track and play keyboard synths based on real-time playhead
  useEffect(() => {
    if (!isPlaying) {
      // stop all synthesisers when paused
      activeSynthsRef.current.forEach((_, note) => stopSynthNote(note));
      activeSynthsRef.current.clear();
      setActiveNotes(new Set());
      return;
    }

    const currentPlayingNotes = new Set<number>();
    notes.forEach((note) => {
      const isNoteActive = currentTime >= note.onset_time && currentTime <= note.offset_time;
      if (isNoteActive) {
        currentPlayingNotes.add(note.midi_note);
      }
    });

    // For any notes starting now
    currentPlayingNotes.forEach((noteNum) => {
      if (!activeNotes.has(noteNum)) {
        // find note object for velocity
        const noteObj = notes.find((n) => n.midi_note === noteNum && currentTime >= n.onset_time && currentTime <= n.offset_time);
        playSynthNote(noteNum, noteObj ? noteObj.velocity : 80);
      }
    });

    // For any notes that just ended
    activeNotes.forEach((noteNum) => {
      if (!currentPlayingNotes.has(noteNum)) {
        stopSynthNote(noteNum);
      }
    });

    setActiveNotes(currentPlayingNotes);
  }, [currentTime, isPlaying, notes]);

  // Clean up synthesisers on unmount
  useEffect(() => {
    return () => {
      activeSynthsRef.current.forEach(({ osc, gain }) => {
        try {
          osc.stop();
          osc.disconnect();
          gain.disconnect();
        } catch (e) {}
      });
      activeSynthsRef.current.clear();
    };
  }, []);

  // Determine key range for drawing boundaries
  const pitchRange = (() => {
    if (notes.length === 0) return { min: 48, max: 72 }; // default 2 octaves
    const pitches = notes.map((n) => n.midi_note);
    return {
      min: Math.max(0, Math.min(...pitches) - 2),
      max: Math.min(127, Math.max(...pitches) + 2),
    };
  })();

  const heightPerNote = 18; // Height in pixels for each piano key row
  const totalNotes = pitchRange.max - pitchRange.min + 1;
  const canvasHeight = totalNotes * heightPerNote;

  // Track resize
  const [canvasWidth, setCanvasWidth] = useState<number>(800);
  useEffect(() => {
    if (!containerRef.current) return;
    const observer = new ResizeObserver((entries) => {
      if (entries[0]) {
        setCanvasWidth(entries[0].contentRect.width);
      }
    });
    observer.observe(containerRef.current);
    return () => observer.disconnect();
  }, []);

  // Canvas Drawing
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    // Clear background
    ctx.fillStyle = "#0f172a"; // Deep Slate base
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Dynamic horizontal scale
    const horizontalScale = zoom; // pixels per second
    const visibleTimeStart = 0; // standard container left coordinate has offset

    // Draw grid lines
    ctx.strokeStyle = "#1e293b";
    ctx.lineWidth = 1;
    const gridDuration = duration || 30;

    // Draw horizontal row dividing lines
    for (let i = 0; i <= totalNotes; i++) {
      const y = canvasHeight - i * heightPerNote;
      ctx.beginPath();
      ctx.moveTo(0, y);
      ctx.lineTo(canvas.width, y);
      ctx.stroke();

      // Check if this row is a black key on a piano
      const currentPitch = pitchRange.min + i;
      const keyInOctave = currentPitch % 12;
      const isBlackKey = [1, 3, 6, 8, 10].includes(keyInOctave);

      if (isBlackKey && i < totalNotes) {
        ctx.fillStyle = "rgba(15, 23, 42, 0.45)";
        ctx.fillRect(0, canvasHeight - (i + 1) * heightPerNote, canvas.width, heightPerNote);
      }
    }

    // Draw vertical beat separators (grid ticks)
    const timeStep = 0.5; // lines every 0.5s
    for (let t = 0; t <= gridDuration; t += timeStep) {
      const x = t * horizontalScale;
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, canvas.height);
      // emphasize beats vs subdivisions
      ctx.strokeStyle = t % 1 === 0 ? "#334155" : "#1e293b";
      ctx.stroke();
    }

    // Draw transcribed MIDI Note Blocks
    notes.forEach((note) => {
      const noteX = note.onset_time * horizontalScale;
      const noteW = (note.offset_time - note.onset_time) * horizontalScale;
      
      const pitchIndex = note.midi_note - pitchRange.min;
      const noteY = canvasHeight - (pitchIndex + 1) * heightPerNote;

      const isVoiceActive = currentTime >= note.onset_time && currentTime <= note.offset_time;

      // Color based on velocity + active state
      if (isVoiceActive) {
        // Highlighting active sounding notes
        ctx.fillStyle = "#34d399"; // glowing emerald
        ctx.strokeStyle = "#ffffff";
        ctx.lineWidth = 1.5;
      } else {
        // Tone color based on velocity
        const percentage = Math.min(100, Math.max(20, (note.velocity / 127) * 100));
        ctx.fillStyle = `hsla(199, 89%, ${percentage * 0.6 + 25}%, 0.85)`; // nice sky blue notes
        ctx.strokeStyle = "rgba(56, 189, 248, 0.4)";
        ctx.lineWidth = 1;
      }

      // Draw rounded rectangle for note block
      ctx.beginPath();
      ctx.roundRect(noteX + 1, noteY + 1, Math.max(2, noteW - 2), heightPerNote - 2, 3);
      ctx.fill();
      ctx.stroke();
    });

    // Draw running playback head
    const playheadX = currentTime * horizontalScale;
    ctx.strokeStyle = "#ef4444"; // bright red head
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(playheadX, 0);
    ctx.lineTo(playheadX, canvas.height);
    ctx.stroke();

    // Small glowing circle at top of playhead
    ctx.fillStyle = "#ef4444";
    ctx.beginPath();
    ctx.arc(playheadX, 4, 4, 0, Math.PI * 2);
    ctx.fill();

  }, [notes, currentTime, zoom, duration, pitchRange, canvasHeight, totalNotes]);

  // Click on canvas to scrub timeline
  const handleCanvasClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const rect = canvas.getBoundingClientRect();
    const clickX = e.clientX - rect.left;
    const clickedSeconds = clickX / zoom;
    onScrub(Math.min(duration || 0, Math.max(0, clickedSeconds)));
  };

  // Convert MIDI note number to human pitch string (C4, D#2, etc)
  const getNoteName = (midiNum: number) => {
    const notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
    const octave = Math.floor(midiNum / 12) - 1;
    const noteName = notes[midiNum % 12];
    return `${noteName}${octave}`;
  };

  return (
    <div className="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl transition-all duration-300">
      
      {/* Header controls toolbar */}
      <div className="flex flex-wrap items-center justify-between border-b border-slate-800 bg-slate-950 px-4 py-3 gap-3">
        <div className="flex items-center gap-2">
          <Music className="w-5 h-5 text-emerald-400" id="visualizer-header-icon" />
          <h3 className="text-sm font-semibold tracking-wide text-slate-100 font-sans">
            TRANSCRIPTION PIANO ROLL
          </h3>
          <span className="bg-emerald-950 text-emerald-400 text-xs px-2 py-0.5 rounded-full border border-emerald-800 font-mono">
            {notes.length} notes
          </span>
        </div>

        <div className="flex items-center gap-4">
          {/* Zoom controls */}
          <div className="flex items-center bg-slate-900 border border-slate-800 rounded-lg p-1 text-slate-400">
            <button
              onClick={() => setZoom(Math.max(20, zoom - 10))}
              className="p-1 hover:text-slate-200 hover:bg-slate-800 rounded transition-colors"
              title="Zoom Out"
            >
              <ZoomOut className="w-4 h-4" />
            </button>
            <span className="text-xs px-2 font-mono">{zoom}px/s</span>
            <button
              onClick={() => setZoom(Math.min(150, zoom + 10))}
              className="p-1 hover:text-slate-200 hover:bg-slate-800 rounded transition-colors"
              title="Zoom In"
            >
              <ZoomIn className="w-4 h-4" />
            </button>
          </div>

          {/* Synthesizer audio monitoring tool */}
          <button
            onClick={() => setSynthEnabled(!synthEnabled)}
            className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-semibold tracking-normal border transition-all ${
              synthEnabled
                ? "bg-emerald-500/10 text-emerald-400 border-emerald-500/30 hover:bg-emerald-500/20"
                : "bg-slate-900 text-slate-400 border-slate-800 hover:bg-slate-800 hover:text-slate-300"
            }`}
          >
            {synthEnabled ? <Volume2 className="w-3.5 h-3.5" /> : <VolumeX className="w-3.5 h-3.5" />}
            INTERNAL SYNTH: {synthEnabled ? "ON" : "OFF"}
          </button>
        </div>
      </div>

      {/* Main Split Piano Keys + Scrollable Canvas Grid */}
      <div className="flex relative h-80 select-none bg-slate-950">
        
        {/* Left Vertical Axis: Piano Keys Guide */}
        <div 
          className="w-16 flex-shrink-0 bg-slate-900 border-r border-slate-800 overflow-hidden relative z-10 select-none"
          style={{ height: "100%" }}
        >
          <div 
            className="absolute left-0 w-full flex flex-col pt-0 transition-transform duration-75 select-none"
            style={{ 
              height: `${canvasHeight}px`,
              bottom: 0 // Align keys to bottom grid matching midi pitch logic
            }}
          >
            {Array.from({ length: totalNotes }).map((_, idx) => {
              const currentPitch = pitchRange.min + idx;
              const keyInOctave = currentPitch % 12;
              const isBlackKey = [1, 3, 6, 8, 10].includes(keyInOctave);
              
              const isVoiceSounding = activeNotes.has(currentPitch);

              return (
                <div
                  key={currentPitch}
                  className={`flex items-center justify-between border-b border-slate-800/50 pr-2 font-mono text-[9px] select-none ${
                    isBlackKey 
                      ? "bg-slate-950 text-sky-400 font-semibold" 
                      : "bg-white text-slate-900"
                  } ${isVoiceSounding ? "bg-amber-400 text-slate-950 border-amber-600 font-bold" : ""}`}
                  style={{ 
                    height: `${heightPerNote}px`,
                    // Reverse the vertical index to go top-to-bottom
                    order: totalNotes - idx
                  }}
                >
                  <div className={`w-3.5 h-2 rounded-r select-none ${isBlackKey ? "bg-slate-800" : "bg-slate-200 border border-slate-300"}`} />
                  <span className="select-none">{getNoteName(currentPitch)}</span>
                </div>
              );
            })}
          </div>
        </div>

        {/* Scrollable Canvas for notes */}
        <div
          ref={containerRef}
          className="flex-grow overflow-x-auto overflow-y-hidden custom-scrollbar relative bg-slate-950"
          id="piano-roll-scroll-container"
        >
          {/* Virtual space corresponding to horizontal zoom */}
          <div 
            style={{ 
              width: `${Math.max(canvasWidth, (duration || 30) * zoom)}px`,
              height: "100%"
            }}
            className="relative"
          >
            <canvas
              ref={canvasRef}
              width={Math.max(canvasWidth, (duration || 30) * zoom)}
              height={canvasHeight}
              onClick={handleCanvasClick}
              className="absolute left-0 bottom-0 cursor-crosshair h-full"
              style={{ maxHeight: "100%" }}
            />
          </div>
        </div>
      </div>

      {/* Control bar / timing guide */}
      <div className="bg-slate-950 border-t border-slate-800 px-4 py-2 flex flex-wrap items-center justify-between text-xs text-slate-400 gap-2">
        <div className="flex items-center gap-4 font-mono">
          <div>
            TIME: <span className="text-slate-200">{currentTime.toFixed(2)}s</span> / <span className="text-slate-400">{(duration || 0).toFixed(2)}s</span>
          </div>
          <div className="hidden sm:block">
            BEATS: <span className="text-slate-200">{Math.floor((currentTime / 60) * 120)}</span>
          </div>
        </div>

        <div className="flex items-center gap-2 bg-slate-900 px-3 py-1 rounded border border-slate-800 font-sans">
          <span className="inline-block w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
          <span>Interactive Playback Active: Click on grid to seek</span>
        </div>
      </div>
    </div>
  );
}
