import React, { useCallback, useState, useEffect, useRef } from 'react';
import { getModuleDef } from '../engine/moduleRegistry.js';
import { state, removeModule, updateModuleParam, isPortConnected, emit } from '../engine/state.js';
import { updateParam } from '../engine/audioEngine.js';
import Knob from './Knob.jsx';
import ScopeDisplay from './ScopeDisplay.jsx';
import KeyboardWidget from './KeyboardWidget.jsx';
import DrumPadWidget from './DrumPadWidget.jsx';
import SequencerWidget from './SequencerWidget.jsx';
import PianoRollWidget from './PianoRollWidget.jsx';

// Dynamic module widths for sequencer/pianoroll based on step/bar count
function getModuleWidth(mod, type) {
  if (type === 'sequencer') {
    const numSteps = parseInt(mod?.params?.steps || '16');
    return Math.max(200, numSteps * 18 + 20); // CELL_W=18 + padding
  }
  if (type === 'pianoroll') {
    const bars = parseInt(mod?.params?.bars || '4');
    const totalBeats = bars * 4;
    return 24 + totalBeats * 30 + 20; // KEY_W + beats*BEAT_PX + padding
  }
  return undefined;
}

// Map input port names → the param name they modulate (for visual feedback)
const PORT_TO_PARAM = {
  filter:     { cutoff: 'frequency' },
  oscillator: { freq: 'frequency', detune: 'detune' },
  vca:        { cv: 'gain' },
};

// Compute a simulated LFO waveform value at time t (seconds)
function simulateLFO(waveform, phase) {
  switch (waveform) {
    case 'sine': return Math.sin(2 * Math.PI * phase);
    case 'triangle': return 1 - 4 * Math.abs((phase % 1) - 0.5);
    case 'sawtooth': return 2 * (phase % 1) - 1;
    case 'square': return (phase % 1) < 0.5 ? 1 : -1;
    default: return Math.sin(2 * Math.PI * phase);
  }
}

export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }) {
  const def = getModuleDef(mod.type);
  if (!def) return null;

  const isSelected = state.selectedModuleId === mod.id;

  // Merge default params
  const params = { ...Object.fromEntries(Object.entries(def.params).map(([k, v]) => [k, v.default])), ...mod.params };

  // Find which params are being modulated (have an incoming connection on their corresponding port)
  const modulatedParams = new Set();
  const portMap = PORT_TO_PARAM[mod.type] || {};
  for (const conn of state.connections) {
    if (conn.to.moduleId === mod.id && portMap[conn.to.port]) {
      modulatedParams.add(portMap[conn.to.port]);
    }
  }

  // ==================== Live LFO modulation visualization ====================
  const [liveValues, setLiveValues] = useState({});
  const rafRef = useRef(null);
  const startTimeRef = useRef(performance.now() / 1000);

  useEffect(() => {
    if (modulatedParams.size === 0) {
      setLiveValues({});
      return;
    }

    const tick = () => {
      const t = performance.now() / 1000 - startTimeRef.current;
      const newValues = {};

      for (const conn of state.connections) {
        if (conn.to.moduleId !== mod.id) continue;
        const paramName = portMap[conn.to.port];
        if (!paramName) continue;

        const srcMod = state.modules.find(m => m.id === conn.from.moduleId);
        if (!srcMod || srcMod.type !== 'lfo') continue;

        // Read LFO params from state
        const lfoDef = getModuleDef('lfo');
        const lfoP = { ...Object.fromEntries(Object.entries(lfoDef.params).map(([k, v]) => [k, v.default])), ...srcMod.params };
        const freq = lfoP.frequency;
        const amp = lfoP.amplitude;
        const waveform = lfoP.waveform;
        const phase = (t * freq) % 1;
        const lfoVal = simulateLFO(waveform, phase) * amp;

        // Compute modulated value (same scaling as audioEngine)
        const baseValue = params[paramName];
        let scale;
        if (mod.type === 'oscillator' && paramName === 'frequency') scale = baseValue * 0.5;
        else if (mod.type === 'filter' && paramName === 'frequency') scale = baseValue;
        else if (mod.type === 'vca' && paramName === 'gain') scale = 1;
        else scale = baseValue || 1;

        newValues[paramName] = baseValue + lfoVal * scale;
      }

      setLiveValues(newValues);
      rafRef.current = requestAnimationFrame(tick);
    };

    rafRef.current = requestAnimationFrame(tick);
    return () => {
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
    };
  }, [mod.id, mod.type, modulatedParams.size]);

  const handleParamChange = useCallback((name, value) => {
    updateModuleParam(mod.id, name, value);
    updateParam(mod.id, name, value);
  }, [mod.id]);

  const handleHeaderDown = useCallback((e) => {
    if (e.button !== 0) return;
    e.stopPropagation();
    state.selectedModuleId = mod.id;
    state.dragging = {
      moduleId: mod.id,
      offsetX: e.clientX / zoom - mod.x,
      offsetY: e.clientY / zoom - mod.y,
    };
    emit();
  }, [mod, zoom]);

  const handleDelete = useCallback((e) => {
    e.stopPropagation();
    removeModule(mod.id);
  }, [mod.id]);

  const handlePortMouseDown = useCallback((e, portName, direction) => {
    e.stopPropagation(); e.preventDefault();
    const portDef = direction === 'output'
      ? def.outputs.find(p => p.name === portName)
      : def.inputs.find(p => p.name === portName);
    if (!portDef) return;

    const rect = e.currentTarget.getBoundingClientRect();
    onStartConnect({
      moduleId: mod.id,
      port: portName,
      portType: portDef.type,
      direction,
      startX: rect.left + rect.width / 2,
      startY: rect.top + rect.height / 2,
    });
  }, [mod.id, def, onStartConnect]);

  // Report port positions for wire rendering
  const portRef = useCallback((el, portName, direction) => {
    if (el) {
      onPortPosition(mod.id, portName, direction, el);
    }
  }, [mod.id, onPortPosition]);

  return (
    <div
      className={`module ${isSelected ? 'selected' : ''}`}
      style={{
        left: mod.x * zoom, top: mod.y * zoom,
        transform: `scale(${zoom})`, transformOrigin: 'top left',
        ...(mod.type === 'pianoroll' ? { width: getModuleWidth(mod, 'pianoroll') } : mod.type === 'sequencer' ? { width: getModuleWidth(mod, 'sequencer') } : {}),
      }}
      data-module-id={mod.id}
      onPointerDown={(e) => {
        // Don't deselect when clicking inside a module
        e.stopPropagation();
        state.selectedModuleId = mod.id; emit();
      }}
    >
      <div className="module-header" onPointerDown={handleHeaderDown}>
        <span className="type-icon">{def.icon}</span>
        <span className="type-name">{def.name}</span>
        <button className="close-btn" onClick={handleDelete}>✕</button>
      </div>

      <div className="module-body">
        {/* Input ports */}
        {def.inputs.map(port => (
          <div key={port.name} className="port-row input">
            <div
              className={`port-dot ${port.type} ${isPortConnected(mod.id, port.name, 'input') ? 'connected' : ''}`}
              ref={el => portRef(el, port.name, 'input')}
              data-module-id={mod.id}
              data-port-name={port.name}
              data-port-direction="input"
              data-port-type={port.type}
              onPointerDown={e => handlePortMouseDown(e, port.name, 'input')}
            />
            <span className="port-label">{port.label}</span>
          </div>
        ))}

        {/* Parameters */}
        {Object.entries(def.params).map(([name, paramDef]) => {
          if (paramDef.type === 'knob') {
            const color = paramDef.unit === 'Hz' ? 'var(--accent)' :
              paramDef.unit === 'dB' ? 'var(--green)' :
              paramDef.unit === 's' ? 'var(--purple)' : 'var(--accent)';
            return (
              <div key={name} className="param-row">
                <span className="param-label">{paramDef.label}</span>
                <Knob
                  value={params[name]}
                  min={paramDef.min}
                  max={paramDef.max}
                  onChange={v => handleParamChange(name, v)}
                  color={color}
                  modulated={modulatedParams.has(name)}
                  liveValue={liveValues[name]}
                />
                <span className={`param-value ${liveValues[name] !== undefined ? 'param-value-live' : ''}`}>
                  {(() => {
                    const v = liveValues[name] !== undefined ? liveValues[name] : params[name];
                    const s = v >= 1000 ? `${(v / 1000).toFixed(1)}k` :
                      v >= 100 ? Math.round(v) :
                      v >= 1 ? Number(v).toFixed(1) :
                      Number(v).toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
                    return s;
                  })()}
                  {paramDef.unit ? ` ${paramDef.unit}` : ''}
                </span>
              </div>
            );
          }
          if (paramDef.type === 'select') {
            return (
              <div key={name} className="param-row">
                <span className="param-label">{paramDef.label}</span>
                <select className="param-select" value={params[name]}
                  onChange={e => handleParamChange(name, e.target.value)}>
                  {paramDef.options.map(opt => <option key={opt} value={opt}>{opt}</option>)}
                </select>
              </div>
            );
          }
          return null;
        })}

        {/* Scope display */}
        {mod.type === 'scope' && <ScopeDisplay moduleId={mod.id} />}

        {/* Keyboard widget */}
        {mod.type === 'keyboard' && <KeyboardWidget moduleId={mod.id} />}

        {/* Drum Pad widget */}
        {mod.type === 'drumpad' && <DrumPadWidget moduleId={mod.id} />}

        {/* Sequencer widget */}
        {mod.type === 'sequencer' && <SequencerWidget moduleId={mod.id} />}

        {/* Piano Roll widget */}
        {mod.type === 'pianoroll' && <PianoRollWidget moduleId={mod.id} />}

        {/* Output ports */}
        {def.outputs.map(port => (
          <div key={port.name} className="port-row output">
            <div
              className={`port-dot ${port.type} ${isPortConnected(mod.id, port.name, 'output') ? 'connected' : ''}`}
              ref={el => portRef(el, port.name, 'output')}
              data-module-id={mod.id}
              data-port-name={port.name}
              data-port-direction="output"
              data-port-type={port.type}
              onPointerDown={e => handlePortMouseDown(e, port.name, 'output')}
            />
            <span className="port-label">{port.label}</span>
          </div>
        ))}
      </div>
    </div>
  );
}
