import React, { useState, useEffect, useRef, useCallback } from 'react';
import * as Tone from 'tone';
import { state, updateModuleParam, emit } from '../engine/state.js';
import { setSequencerSignals } from '../engine/audioEngine.js';
import { parseMidi } from '../utils/midiParser.js';

const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
const BLACK_KEYS = [1, 3, 6, 8, 10];

function midiToFreq(midi) { return 440 * Math.pow(2, (midi - 69) / 12); }
function isBlack(midi) { return BLACK_KEYS.includes(midi % 12); }

// Super Mario Bros - Overworld Theme (NES, 1985)
// BPM ~200, swing 8ths feel. Each beat = 1 quarter note.
// s = eighth note unit (0.5 beats)
const s = 0.5;
const MARIO_MELODY = [
  // Bar 1: E5 E5 . E5 . C5 E5 .
  { note: 76, start: 0,     duration: s },     // E5
  { note: 76, start: 1*s,   duration: s },     // E5
  // rest
  { note: 76, start: 3*s,   duration: s },     // E5
  // rest
  { note: 72, start: 5*s,   duration: s },     // C5
  { note: 76, start: 6*s,   duration: 2*s },   // E5

  // Bar 2: G5 . . . G4 . . .
  { note: 79, start: 8*s,   duration: 2*s },   // G5
  { note: 67, start: 12*s,  duration: 2*s },   // G4

  // Bar 3: C5 . . G4 . . E4 . .
  { note: 72, start: 16*s,  duration: s },     // C5
  { note: 67, start: 18*s,  duration: s },     // G4
  { note: 64, start: 20*s,  duration: s },     // E4

  // Bar 4: . A4 . B4 . Bb4 A4 .
  { note: 69, start: 22*s,  duration: s },     // A4
  { note: 71, start: 24*s,  duration: s },     // B4
  { note: 70, start: 25*s,  duration: s },     // Bb4
  { note: 69, start: 26*s,  duration: s },     // A4

  // Bar 5: G4 E5 G5 A5 . F5 G5 .
  { note: 67, start: 28*s,  duration: 0.75 },  // G4 (triplet feel)
  { note: 76, start: 30*s,  duration: 0.75 },  // E5
  { note: 79, start: 32*s,  duration: s },     // G5
  { note: 81, start: 33*s,  duration: s },     // A5
  { note: 77, start: 35*s,  duration: s },     // F5
  { note: 79, start: 36*s,  duration: s },     // G5

  // Bar 6: . E5 . C5 D5 B4 . .
  { note: 76, start: 38*s,  duration: s },     // E5
  { note: 72, start: 40*s,  duration: s },     // C5
  { note: 74, start: 41*s,  duration: s },     // D5
  { note: 71, start: 42*s,  duration: s },     // B4

  // Bar 7: . . C5 . . G4 . . E4
  { note: 72, start: 44*s,  duration: s },     // C5
  { note: 67, start: 46*s,  duration: s },     // G4
  { note: 64, start: 48*s,  duration: s },     // E4

  // Bar 8: . A4 . B4 . Bb4 A4 .
  { note: 69, start: 50*s,  duration: s },     // A4
  { note: 71, start: 52*s,  duration: s },     // B4
  { note: 70, start: 53*s,  duration: s },     // Bb4
  { note: 69, start: 54*s,  duration: s },     // A4

  // Bar 9: G4 E5 G5 A5 . F5 G5 .
  { note: 67, start: 56*s,  duration: 0.75 },  // G4
  { note: 76, start: 58*s,  duration: 0.75 },  // E5
  { note: 79, start: 60*s,  duration: s },     // G5
  { note: 81, start: 61*s,  duration: s },     // A5
  { note: 77, start: 63*s,  duration: s },     // F5
  { note: 79, start: 64*s,  duration: s },     // G5

  // Bar 10: . E5 . C5 D5 B4
  { note: 76, start: 66*s,  duration: s },     // E5
  { note: 72, start: 68*s,  duration: s },     // C5
  { note: 74, start: 69*s,  duration: s },     // D5
  { note: 71, start: 70*s,  duration: 2*s },   // B4
];

const BEAT_PX = 30; // pixels per beat — constant density regardless of bar count
const ROLL_H = 200;
const KEY_W = 24;
const MIN_NOTE = 48; // C3
const MAX_NOTE = 84; // C6
const NOTE_RANGE = MAX_NOTE - MIN_NOTE;
const ROW_H = ROLL_H / NOTE_RANGE;

export default function PianoRollWidget({ moduleId }) {
  const mod = state.modules.find(m => m.id === moduleId);
  const canvasRef = useRef(null);
  const partRef = useRef(null);
  const [playPos, setPlayPos] = useState(-1);
  const [tool, setTool] = useState('draw'); // 'draw' | 'erase'
  const drawingRef = useRef(null);
  const rafRef = useRef(null);
  const midiInputRef = useRef(null);

  const bpm = mod?.params?.bpm ?? 140;
  const bars = parseInt(mod?.params?.bars || '4');
  const loop = mod?.params?.loop !== 'off';
  const totalBeats = bars * 4;

  // Init notes
  if (!mod?.params?._notes) {
    if (mod) mod.params._notes = [...MARIO_MELODY];
  }
  const notes = mod?.params?._notes || MARIO_MELODY;
  const notesRef = useRef(notes);
  notesRef.current = notes;

  const rollW = KEY_W + totalBeats * BEAT_PX;
  const beatW = BEAT_PX;

  // Draw the piano roll
  const draw = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const w = canvas.width;
    const h = canvas.height;

    ctx.fillStyle = '#06060e';
    ctx.fillRect(0, 0, w, h);

    // Piano keys
    for (let i = 0; i < NOTE_RANGE; i++) {
      const midi = MAX_NOTE - 1 - i;
      const y = i * ROW_H;
      const black = isBlack(midi);

      // Row background
      ctx.fillStyle = black ? '#0a0a16' : '#0e0e1a';
      ctx.fillRect(KEY_W, y, w - KEY_W, ROW_H);

      // Grid lines
      ctx.strokeStyle = '#151525';
      ctx.lineWidth = 0.5;
      ctx.beginPath();
      ctx.moveTo(KEY_W, y); ctx.lineTo(w, y);
      ctx.stroke();

      // Key label
      ctx.fillStyle = black ? '#1a1a30' : '#222240';
      ctx.fillRect(0, y, KEY_W, ROW_H);
      ctx.strokeStyle = '#0a0a14';
      ctx.strokeRect(0, y, KEY_W, ROW_H);

      if (midi % 12 === 0) { // C notes
        ctx.fillStyle = '#4466aa';
        ctx.font = '7px monospace';
        ctx.textAlign = 'center';
        ctx.fillText(`C${Math.floor(midi / 12) - 1}`, KEY_W / 2, y + ROW_H / 2 + 2.5);
      }
    }

    // Beat grid lines
    for (let b = 0; b <= totalBeats; b++) {
      const x = KEY_W + b * beatW;
      ctx.strokeStyle = b % 4 === 0 ? '#2a2a50' : b % 1 === 0 ? '#151525' : '#101020';
      ctx.lineWidth = b % 4 === 0 ? 1 : 0.5;
      ctx.beginPath();
      ctx.moveTo(x, 0); ctx.lineTo(x, h);
      ctx.stroke();
    }

    // Draw notes
    const currentNotes = notesRef.current;
    for (const n of currentNotes) {
      if (n.note < MIN_NOTE || n.note >= MAX_NOTE) continue;
      const row = MAX_NOTE - 1 - n.note;
      const x = KEY_W + n.start * beatW;
      const nw = n.duration * beatW;
      const y = row * ROW_H;

      // Note body
      const gradient = ctx.createLinearGradient(x, y, x, y + ROW_H);
      gradient.addColorStop(0, '#00ccff');
      gradient.addColorStop(1, '#0066aa');
      ctx.fillStyle = gradient;
      ctx.fillRect(x + 0.5, y + 0.5, Math.max(nw - 1, 2), ROW_H - 1);

      // Note border
      ctx.strokeStyle = '#00e5ff';
      ctx.lineWidth = 0.5;
      ctx.strokeRect(x + 0.5, y + 0.5, Math.max(nw - 1, 2), ROW_H - 1);

      // Note label for wider notes
      if (nw > 15) {
        ctx.fillStyle = '#fff';
        ctx.font = '6px monospace';
        ctx.textAlign = 'left';
        ctx.fillText(NOTE_NAMES[n.note % 12], x + 2, y + ROW_H / 2 + 2);
      }
    }

    // Playhead
    if (playPos >= 0 && playPos < totalBeats) {
      const px = KEY_W + playPos * beatW;
      ctx.strokeStyle = '#ff6644';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(px, 0); ctx.lineTo(px, h);
      ctx.stroke();

      // Glow
      ctx.strokeStyle = 'rgba(255,102,68,0.2)';
      ctx.lineWidth = 6;
      ctx.beginPath();
      ctx.moveTo(px, 0); ctx.lineTo(px, h);
      ctx.stroke();
    }

    // Currently drawing note preview
    if (drawingRef.current) {
      const d = drawingRef.current;
      const row = MAX_NOTE - 1 - d.note;
      const x = KEY_W + d.start * beatW;
      const nw = d.duration * beatW;
      ctx.fillStyle = 'rgba(0,229,255,0.3)';
      ctx.fillRect(x, row * ROW_H, Math.max(nw, 2), ROW_H);
    }
  }, [totalBeats, beatW, playPos, rollW]);

  // Animation loop
  useEffect(() => {
    const animate = () => {
      draw();
      rafRef.current = requestAnimationFrame(animate);
    };
    animate();
    return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); };
  }, [draw]);

  // Playback — uses independent Tone.Clock so multiple pianorolls/sequencers
  // don't interfere with each other via the global Transport
  useEffect(() => {
    if (!state.isRunning) {
      if (partRef.current) {
        try { partRef.current.stop(); } catch {}
        try { partRef.current.dispose(); } catch {}
        partRef.current = null;
      }
      setPlayPos(-1);
      return;
    }

    const sixteenthRate = (bpm * 4) / 60; // Hz: sixteenth notes per second
    const sixteenthDur = 1 / sixteenthRate; // seconds per sixteenth note
    let tickCount = 0;
    let currentNote = null; // track currently sounding note for on/off transitions

    const clock = new Tone.Clock(() => {
      const rawPos = tickCount * 0.25; // position in beats (each tick = 1 sixteenth = 0.25 beats)
      const pos = loop ? rawPos % totalBeats : rawPos;
      const prevRawPos = (tickCount - 1) * 0.25;
      const prevPos = loop ? prevRawPos % totalBeats : prevRawPos;
      tickCount++;

      // Detect loop wrap (position jumped backwards)
      const looped = tickCount > 1 && pos < prevPos;

      // Stop at end if not looping
      if (!loop && rawPos >= totalBeats) {
        if (currentNote) {
          setSequencerSignals(moduleId, 0, false);
          currentNote = null;
        }
        setPlayPos(-1);
        return;
      }

      setPlayPos(pos);

      // Force note-off on loop boundary for clean retrigger
      if (looped && currentNote) {
        setSequencerSignals(moduleId, 0, false);
        currentNote = null;
      }

      // Find the note active at this position
      const allNotes = notesRef.current;
      const activeNote = allNotes.find(n => pos >= n.start && pos < n.start + n.duration);

      if (activeNote) {
        // New note or different note → trigger
        if (!currentNote || currentNote.note !== activeNote.note || currentNote.start !== activeNote.start) {
          setSequencerSignals(moduleId, midiToFreq(activeNote.note), true);
          currentNote = activeNote;
        }
        // Same note sustaining → do nothing
      } else {
        // No note at this position → gate off
        if (currentNote) {
          setSequencerSignals(moduleId, 0, false);
          currentNote = null;
        }
      }
    }, sixteenthRate);

    clock.start();
    partRef.current = clock;

    return () => {
      if (partRef.current) {
        try { partRef.current.stop(); } catch {}
        try { partRef.current.dispose(); } catch {}
        partRef.current = null;
      }
    };
  }, [state.isRunning, moduleId, bpm, bars, loop]);

  // Mouse interaction for drawing/erasing notes
  const handleMouseDown = useCallback((e) => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const rect = canvas.getBoundingClientRect();
    // Account for CSS transform scale (zoom) — rect is visual size, canvas is logical size
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    const mx = (e.clientX - rect.left) * scaleX;
    const my = (e.clientY - rect.top) * scaleY;

    if (mx < KEY_W) return; // Clicked on piano keys

    const beat = (mx - KEY_W) / beatW;
    const noteIdx = MAX_NOTE - 1 - Math.floor(my / ROW_H);
    if (noteIdx < MIN_NOTE || noteIdx >= MAX_NOTE) return;

    const snappedBeat = Math.floor(beat * 4) / 4; // Snap to 16th

    if (tool === 'erase') {
      // Remove any note at this position
      const filtered = notes.filter(n =>
        !(n.note === noteIdx && snappedBeat >= n.start && snappedBeat < n.start + n.duration)
      );
      if (filtered.length !== notes.length) {
        mod.params._notes = filtered;
        notesRef.current = filtered;
        emit();
      }
    } else {
      // Check if clicking on existing note — remove it
      const existing = notes.findIndex(n =>
        n.note === noteIdx && snappedBeat >= n.start && snappedBeat < n.start + n.duration
      );
      if (existing >= 0) {
        const filtered = [...notes];
        filtered.splice(existing, 1);
        mod.params._notes = filtered;
        notesRef.current = filtered;
        emit();
        return;
      }

      // Start drawing new note
      drawingRef.current = { note: noteIdx, start: snappedBeat, duration: 0.25 };

      const handleMove = (me) => {
        if (!drawingRef.current) return;
        const mmx = (me.clientX - rect.left) * scaleX;
        const endBeat = Math.max(drawingRef.current.start + 0.25,
          Math.ceil(((mmx - KEY_W) / beatW) * 4) / 4);
        drawingRef.current.duration = endBeat - drawingRef.current.start;
      };

      const handleUp = () => {
        if (drawingRef.current) {
          const newNote = { ...drawingRef.current };
          newNote.duration = Math.max(0.25, Math.min(newNote.duration, totalBeats - newNote.start));
          const updated = [...notes, newNote];
          mod.params._notes = updated;
          notesRef.current = updated;
          drawingRef.current = null;
          emit();
        }
        window.removeEventListener('pointermove', handleMove);
        window.removeEventListener('pointerup', handleUp);
      };

      window.addEventListener('pointermove', handleMove);
      window.addEventListener('pointerup', handleUp);
    }
  }, [tool, notes, beatW, totalBeats, mod]);

  const handleMidiImport = useCallback(async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    try {
      const arrayBuffer = await file.arrayBuffer();
      const midi = parseMidi(arrayBuffer);
      if (midi.notes.length === 0) return;

      // Update BPM if detected
      if (midi.bpm && mod) {
        mod.params.bpm = midi.bpm;
      }

      // Auto-fit bars to cover all notes
      const maxBeat = Math.max(...midi.notes.map(n => n.start + n.duration));
      const neededBars = Math.ceil(maxBeat / 4);
      const fitBars = [1, 2, 4, 8].find(b => b >= neededBars) || 8;
      if (mod) mod.params.bars = String(fitBars);

      // Set notes
      if (mod) {
        mod.params._notes = midi.notes;
        notesRef.current = midi.notes;
        emit();
      }
    } catch (err) {
      console.error('[PianoRoll] MIDI import failed:', err);
    }
    e.target.value = '';
  }, [mod]);

  return (
    <div style={{ width: rollW }}>
      {/* Mini toolbar */}
      <div style={{ display: 'flex', gap: 4, marginBottom: 3 }}>
        <button
          style={{
            padding: '1px 6px', fontSize: 9, border: '1px solid',
            borderColor: tool === 'draw' ? '#00e5ff' : '#333',
            background: tool === 'draw' ? '#00e5ff' : '#111',
            color: tool === 'draw' ? '#000' : '#888',
            borderRadius: 3, cursor: 'pointer', fontFamily: 'inherit',
          }}
          onClick={() => setTool('draw')}
        >✏ Draw</button>
        <button
          style={{
            padding: '1px 6px', fontSize: 9, border: '1px solid',
            borderColor: tool === 'erase' ? '#ff4466' : '#333',
            background: tool === 'erase' ? '#ff4466' : '#111',
            color: tool === 'erase' ? '#000' : '#888',
            borderRadius: 3, cursor: 'pointer', fontFamily: 'inherit',
          }}
          onClick={() => setTool('erase')}
        >✕ Erase</button>
        <button
          style={{
            padding: '1px 6px', fontSize: 9, border: '1px solid #333',
            background: '#111', color: '#888',
            borderRadius: 3, cursor: 'pointer', fontFamily: 'inherit',
          }}
          onClick={() => midiInputRef.current?.click()}
        >🎵 MIDI</button>
        <input ref={midiInputRef} type="file" accept=".mid,.midi" style={{ display: 'none' }} onChange={handleMidiImport} />
        <span style={{ fontSize: 8, color: '#555', marginLeft: 'auto', alignSelf: 'center' }}>
          {notes.length} notes · {bars} bars
        </span>
      </div>
      <canvas
        ref={canvasRef}
        width={rollW}
        height={ROLL_H}
        style={{ width: rollW, height: ROLL_H, borderRadius: 4, cursor: tool === 'draw' ? 'crosshair' : 'pointer' }}
        onPointerDown={handleMouseDown}
      />
    </div>
  );
}
