// MidiMappingPanel — MIDI control surface mapping configuration
//
// Manages MIDI learn mode, displays current CC→mixer parameter mappings,
// and allows manual mapping creation/deletion.
// Communicates with the pipedald backend via MIDI WebSocket commands
// (mixerGetMidiMappings, mixerSetMidiLearnMode, mixerCommitMidiLearn, etc.)

import { useState, useEffect, useCallback, useRef } from "react";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import TextField from "@mui/material/TextField";
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import Tooltip from "@mui/material/Tooltip";
import Chip from "@mui/material/Chip";
import Alert from "@mui/material/Alert";
import Switch from "@mui/material/Switch";
import FormControlLabel from "@mui/material/FormControlLabel";
import Delete from "@mui/icons-material/Delete";
import Add from "@mui/icons-material/Add";
import MusicNote from "@mui/icons-material/MusicNote";
import Devices from "@mui/icons-material/Devices";
import type { MixerWsHandle } from "./useMixerWS";

// ── Types ──────────────────────────────────────────────────────────

export interface MidiMappingEntry {
  midiChannel: number;
  ccNumber: number;
  targetType: string;
  targetId: number;
  minValue: number;
  maxValue: number;
}

export interface MidiMappingPanelProps {
  ws: MixerWsHandle;
  /** Fired when learn mode state changes (so parent can prop drill). */
  onLearnModeChange?: (enabled: boolean) => void;
  disabled?: boolean;
}

// ── Target type labels ─────────────────────────────────────────────

const TARGET_TYPE_OPTIONS: { value: string; label: string }[] = [
  { value: "channelVolume", label: "Ch Volume" },
  { value: "channelPan", label: "Ch Pan" },
  { value: "channelMute", label: "Ch Mute" },
  { value: "channelSolo", label: "Ch Solo" },
  { value: "busVolume", label: "Bus Volume" },
  { value: "busMute", label: "Bus Mute" },
  { value: "masterVolume", label: "Master Volume" },
  { value: "masterMute", label: "Master Mute" },
];

function targetTypeLabel(type: string): string {
  return TARGET_TYPE_OPTIONS.find((o) => o.value === type)?.label ?? type;
}

// ── Learn feedback poll interval ───────────────────────────────────

const LEARN_POLL_MS = 400;

// ── Component ──────────────────────────────────────────────────────

export default function MidiMappingPanel({
  ws,
  onLearnModeChange,
  disabled,
}: MidiMappingPanelProps) {
  // ── Dialog open state ───────────────────────────────────────────
  const [open, setOpen] = useState(false);

  // ── Mapping list state ──────────────────────────────────────────
  const [mappings, setMappings] = useState<MidiMappingEntry[]>([]);
  const [mappingError, setMappingError] = useState<string | null>(null);
  const [loadingMappings, setLoadingMappings] = useState(false);

  // ── Learn mode state ────────────────────────────────────────────
  const [learnEnabled, setLearnEnabled] = useState(false);
  const [learnPending, setLearnPending] = useState(false);
  const [capturedChannel, setCapturedChannel] = useState<number | null>(null);
  const [capturedCc, setCapturedCc] = useState<number | null>(null);
  const learnPollRef = useRef<ReturnType<typeof setInterval> | null>(null);

  // ── Manual add state ────────────────────────────────────────────
  const [manualOpen, setManualOpen] = useState(false);
  const [manualTargetType, setManualTargetType] = useState("channelVolume");
  const [manualTargetId, setManualTargetId] = useState(0);
  const [manualCcNumber, setManualCcNumber] = useState(7);
  const [manualMidiChannel, setManualMidiChannel] = useState(-1);
  const [manualMinValue, setManualMinValue] = useState(-96);
  const [manualMaxValue, setManualMaxValue] = useState(12);

  // ── Fetch mappings ──────────────────────────────────────────────

  const fetchMappings = useCallback(async () => {
    if (ws.status !== "connected") return;
    setLoadingMappings(true);
    try {
      const raw = (await ws.send("mixerGetMidiMappings")) as string;
      const parsed = JSON.parse(raw) as MidiMappingEntry[];
      setMappings(Array.isArray(parsed) ? parsed : []);
      setMappingError(null);
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : String(err);
      setMappingError(msg);
    } finally {
      setLoadingMappings(false);
    }
  }, [ws]);

  // ── Learn mode ──────────────────────────────────────────────────

  const toggleLearn = useCallback(async () => {
    if (ws.status !== "connected") return;
    const newState = !learnEnabled;
    try {
      await ws.send("mixerSetMidiLearnMode", { enabled: newState });
      setLearnEnabled(newState);
      // Reset captured event when toggling
      if (!newState) {
        setCapturedChannel(null);
        setCapturedCc(null);
        setLearnPending(false);
      } else {
        setLearnPending(true);
      }
      onLearnModeChange?.(newState);
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : String(err);
      setMappingError(msg);
    }
  }, [ws, learnEnabled, onLearnModeChange]);

  // ── Poll for captured CC event during learn mode ────────────────

  const pollLearnedEvent = useCallback(async () => {
    if (ws.status !== "connected" || !learnEnabled) return;
    try {
      const raw = (await ws.send("mixerGetLastLearnedMidiEvent")) as string;
      // Response format: {"hasEvent":true,"midiChannel":0,"ccNumber":7} or similar
      const parsed = JSON.parse(raw) as {
        hasEvent: boolean;
        midiChannel?: number;
        ccNumber?: number;
      };
      if (parsed.hasEvent && parsed.midiChannel !== undefined && parsed.ccNumber !== undefined) {
        setCapturedChannel(parsed.midiChannel);
        setCapturedCc(parsed.ccNumber);
        setLearnPending(false);
      }
    } catch {
      // Poll errors are expected during reconnect — ignore silently
    }
  }, [ws, learnEnabled]);

  // Start/stop learn poll interval
  useEffect(() => {
    if (learnEnabled) {
      learnPollRef.current = setInterval(pollLearnedEvent, LEARN_POLL_MS);
    } else {
      if (learnPollRef.current !== null) {
        clearInterval(learnPollRef.current);
        learnPollRef.current = null;
      }
    }
    return () => {
      if (learnPollRef.current !== null) {
        clearInterval(learnPollRef.current);
        learnPollRef.current = null;
      }
    };
  }, [learnEnabled, pollLearnedEvent]);

  // ── Commit learn mapping ────────────────────────────────────────

  const handleCommit = useCallback(async () => {
    if (ws.status !== "connected") return;
    try {
      const success = (await ws.send("mixerCommitMidiLearn")) as boolean;
      if (success) {
        setCapturedChannel(null);
        setCapturedCc(null);
        setLearnPending(true);
        await fetchMappings();
      } else {
        setMappingError("Failed to commit mapping — no CC event captured");
      }
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : String(err);
      setMappingError(msg);
    }
  }, [ws, fetchMappings]);

  // ── Delete mapping ──────────────────────────────────────────────

  const handleDelete = useCallback(
    async (ch: number, cc: number) => {
      if (ws.status !== "connected") return;
      try {
        await ws.send("mixerRemoveMidiMapping", { midiChannel: ch, ccNumber: cc });
        await fetchMappings();
      } catch (err: unknown) {
        const msg = err instanceof Error ? err.message : String(err);
        setMappingError(msg);
      }
    },
    [ws, fetchMappings],
  );

  // ── Clear all mappings ──────────────────────────────────────────

  const handleClearAll = useCallback(async () => {
    if (ws.status !== "connected") return;
    try {
      await ws.send("mixerClearMidiMappings");
      await fetchMappings();
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : String(err);
      setMappingError(msg);
    }
  }, [ws, fetchMappings]);

  // ── Manual add mapping ──────────────────────────────────────────

  const handleManualAdd = useCallback(async () => {
    if (ws.status !== "connected") return;
    try {
      await ws.send("mixerAddMidiMapping", {
        midiChannel: manualMidiChannel,
        ccNumber: manualCcNumber,
        targetType: manualTargetType,
        targetId: manualTargetId,
        minValue: manualMinValue,
        maxValue: manualMaxValue,
      });
      setManualOpen(false);
      // Reset defaults
      setManualTargetType("channelVolume");
      setManualTargetId(0);
      setManualCcNumber(7);
      setManualMidiChannel(-1);
      setManualMinValue(-96);
      setManualMaxValue(12);
      await fetchMappings();
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : String(err);
      setMappingError(msg);
    }
  }, [ws, manualTargetType, manualTargetId, manualCcNumber, manualMidiChannel, manualMinValue, manualMaxValue, fetchMappings]);

  // ── Fetch mappings when dialog opens ────────────────────────────

  useEffect(() => {
    if (open && ws.status === "connected") {
      fetchMappings();
    }
  }, [open, ws.status, fetchMappings]);

  // ── Reset learn state on disconnect ─────────────────────────────

  useEffect(() => {
    if (ws.status !== "connected") {
      setLearnEnabled(false);
      setCapturedChannel(null);
      setCapturedCc(null);
      setLearnPending(false);
    }
  }, [ws.status]);

  // ── Render ─────────────────────────────────────────────────────

  const canLearn = ws.status === "connected" && !disabled;

  return (
    <>
      {/* ── MIDI button in toolbar ── */}
      <Tooltip title="MIDI control surface mapping">
        <span>
          <IconButton
            size="small"
            onClick={() => setOpen(true)}
            disabled={ws.status !== "connected" || disabled}
            sx={{
              p: 0.5,
              color: learnEnabled ? "#f44336" : undefined,
              animation: learnEnabled ? "pulse 1.2s ease-in-out infinite" : undefined,
              "@keyframes pulse": {
                "0%, 100%": { opacity: 1 },
                "50%": { opacity: 0.4 },
              },
            }}
          >
            <Devices sx={{ fontSize: 18 }} />
          </IconButton>
        </span>
      </Tooltip>

      {/* ── Main dialog ── */}
      <Dialog
        open={open}
        onClose={() => setOpen(false)}
        maxWidth="sm"
        fullWidth
        PaperProps={{ sx: { maxHeight: "80vh" } }}
      >
        <DialogTitle sx={{ display: "flex", alignItems: "center", gap: 1 }}>
          <Devices sx={{ fontSize: 20 }} />
          <span>MIDI Control Surface Mapping</span>
        </DialogTitle>

        <DialogContent dividers sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
          {/* ── Learn mode section ── */}
          <Box
            sx={{
              p: 1.5,
              borderRadius: 2,
              bgcolor: learnEnabled ? "rgba(244,67,54,0.08)" : "rgba(255,255,255,0.03)",
              border: "1px solid",
              borderColor: learnEnabled ? "rgba(244,67,54,0.3)" : "rgba(255,255,255,0.08)",
              transition: "all 0.2s",
            }}
          >
            <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", mb: 1 }}>
              <Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
                <MusicNote sx={{ fontSize: 18, color: learnEnabled ? "#f44336" : undefined }} />
                <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
                  MIDI Learn
                </Typography>
              </Box>
              <FormControlLabel
                control={
                  <Switch
                    checked={learnEnabled}
                    onChange={toggleLearn}
                    disabled={!canLearn}
                    color="error"
                    size="small"
                  />
                }
                label={
                  <Typography variant="caption" sx={{ fontWeight: 500 }}>
                    {learnEnabled ? "ON" : "OFF"}
                  </Typography>
                }
                labelPlacement="start"
                sx={{ m: 0 }}
              />
            </Box>

            {/* Learn workflow status */}
            {learnEnabled && (
              <Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
                {/* Step 1: touch a control */}
                <Typography
                  variant="caption"
                  sx={{
                    display: "flex",
                    alignItems: "center",
                    gap: 0.5,
                    opacity: 0.7,
                  }}
                >
                  <Box
                    component="span"
                    sx={{
                      width: 6,
                      height: 6,
                      borderRadius: "50%",
                      bgcolor: "#f44336",
                      display: "inline-block",
                      animation: "pulse 1.2s ease-in-out infinite",
                      "@keyframes pulse": {
                        "0%, 100%": { opacity: 1 },
                        "50%": { opacity: 0.3 },
                      },
                    }}
                  />
                  1. Touch a control in the mixer (fader, mute, solo)
                </Typography>

                {/* Step 2: move hardware fader */}
                <Typography
                  variant="caption"
                  sx={{
                    display: "flex",
                    alignItems: "center",
                    gap: 0.5,
                    opacity: capturedCc !== null ? 0.4 : 0.7,
                  }}
                >
                  <Box
                    component="span"
                    sx={{
                      width: 6,
                      height: 6,
                      borderRadius: "50%",
                      bgcolor: capturedCc !== null ? "#4caf50" : "#f44336",
                      display: "inline-block",
                      animation: capturedCc !== null ? "none" : "pulse 1.2s ease-in-out infinite",
                    }}
                  />
                  2. Move a hardware fader/knob
                </Typography>

                {/* Step 3: commit */}
                <Typography
                  variant="caption"
                  sx={{
                    display: "flex",
                    alignItems: "center",
                    gap: 0.5,
                    opacity: capturedCc !== null ? 1 : 0.4,
                  }}
                >
                  <Box
                    component="span"
                    sx={{
                      width: 6,
                      height: 6,
                      borderRadius: "50%",
                      bgcolor: capturedCc !== null ? "#f44336" : "#555",
                      display: "inline-block",
                    }}
                  />
                  3. Click "Commit" to save the mapping
                </Typography>

                {/* Captured event display */}
                {capturedCc !== null && capturedChannel !== null && (
                  <Box
                    sx={{
                      mt: 1,
                      p: 1,
                      borderRadius: 1,
                      bgcolor: "rgba(76,175,80,0.12)",
                      border: "1px solid rgba(76,175,80,0.3)",
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "space-between",
                    }}
                  >
                    <Box>
                      <Typography variant="caption" sx={{ fontWeight: 600, color: "#81c784" }}>
                        Captured: CC#{capturedCc} from Channel {capturedChannel + 1}
                      </Typography>
                    </Box>
                    <Button
                      size="small"
                      variant="contained"
                      color="error"
                      onClick={handleCommit}
                      sx={{ minWidth: 80, fontSize: 11 }}
                    >
                      Commit
                    </Button>
                  </Box>
                )}

                {capturedCc === null && (
                  <Typography
                    variant="caption"
                    sx={{ fontStyle: "italic", opacity: 0.5, mt: 0.5 }}
                  >
                    Waiting for MIDI CC event...
                  </Typography>
                )}
              </Box>
            )}
          </Box>

          {/* ── Error banner ── */}
          {mappingError && (
            <Alert
              severity="warning"
              sx={{ py: 0, px: 1, fontSize: 11 }}
              onClose={() => setMappingError(null)}
            >
              {mappingError}
            </Alert>
          )}

          {/* ── Manual add mapping ── */}
          <Box sx={{ display: "flex", justifyContent: "flex-end" }}>
            <Button
              size="small"
              variant="outlined"
              startIcon={<Add sx={{ fontSize: 14 }} />}
              onClick={() => setManualOpen(true)}
              disabled={!canLearn}
              sx={{ fontSize: 11, textTransform: "none" }}
            >
              Add Mapping Manually
            </Button>
          </Box>

          {/* ── Current mappings list ── */}
          <Box>
            <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", mb: 0.5 }}>
              <Typography
                variant="subtitle2"
                sx={{
                  fontWeight: 600,
                  fontSize: 12,
                  textTransform: "uppercase",
                  letterSpacing: 1,
                  opacity: 0.6,
                }}
              >
                Current Mappings ({mappings.length})
              </Typography>
              {mappings.length > 0 && (
                <Button
                  size="small"
                  color="error"
                  onClick={handleClearAll}
                  disabled={!canLearn}
                  sx={{ fontSize: 10, textTransform: "none", minWidth: 0, p: 0.5 }}
                >
                  Clear All
                </Button>
              )}
            </Box>

            {loadingMappings && (
              <Typography variant="caption" sx={{ opacity: 0.5 }}>
                Loading...
              </Typography>
            )}

            {!loadingMappings && mappings.length === 0 && (
              <Typography variant="caption" sx={{ opacity: 0.4, fontStyle: "italic" }}>
                No MIDI mappings configured. Use MIDI Learn or add mappings manually.
              </Typography>
            )}

            {mappings.length > 0 && (
              <Box sx={{ display: "flex", flexDirection: "column", gap: 0.5, mt: 0.5 }}>
                {mappings.map((entry, idx) => (
                  <Box
                    key={`${entry.midiChannel}-${entry.ccNumber}-${idx}`}
                    sx={{
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "space-between",
                      gap: 1,
                      p: 0.75,
                      borderRadius: 1,
                      bgcolor: "rgba(255,255,255,0.03)",
                      border: "1px solid rgba(255,255,255,0.06)",
                      "&:hover": { bgcolor: "rgba(255,255,255,0.06)" },
                    }}
                  >
                    <Box sx={{ display: "flex", alignItems: "center", gap: 1, minWidth: 0 }}>
                      {/* CC badge */}
                      <Chip
                        label={`CC#${entry.ccNumber}`}
                        size="small"
                        variant="outlined"
                        sx={{ fontSize: 10, height: 20 }}
                      />
                      {/* MIDI channel badge */}
                      <Chip
                        label={entry.midiChannel < 0 ? "Omni" : `Ch ${entry.midiChannel + 1}`}
                        size="small"
                        variant="outlined"
                        sx={{ fontSize: 10, height: 20 }}
                      />
                      {/* Arrow */}
                      <Typography variant="caption" sx={{ opacity: 0.4 }}>→</Typography>
                      {/* Target */}
                      <Typography
                        variant="caption"
                        sx={{
                          fontWeight: 600,
                          overflow: "hidden",
                          textOverflow: "ellipsis",
                          whiteSpace: "nowrap",
                        }}
                      >
                        {targetTypeLabel(entry.targetType)} #{entry.targetId}
                      </Typography>
                      {/* Range info */}
                      <Typography
                        variant="caption"
                        sx={{ opacity: 0.4, fontSize: 10, display: { xs: "none", sm: "inline" } }}
                      >
                        ({entry.minValue} to {entry.maxValue})
                      </Typography>
                    </Box>

                    <IconButton
                      size="small"
                      onClick={() => handleDelete(entry.midiChannel, entry.ccNumber)}
                      disabled={!canLearn}
                      sx={{ p: 0.3 }}
                    >
                      <Delete sx={{ fontSize: 14 }} />
                    </IconButton>
                  </Box>
                ))}
              </Box>
            )}
          </Box>
        </DialogContent>

        <DialogActions>
          <Button onClick={() => setOpen(false)}>Close</Button>
        </DialogActions>
      </Dialog>

      {/* ── Manual add dialog ── */}
      <Dialog
        open={manualOpen}
        onClose={() => setManualOpen(false)}
        maxWidth="xs"
        fullWidth
      >
        <DialogTitle>Add MIDI Mapping</DialogTitle>
        <DialogContent>
          <Box sx={{ display: "flex", flexDirection: "column", gap: 1.5, mt: 0.5 }}>
            {/* Target type */}
            <FormControl size="small" fullWidth>
              <InputLabel>Target Parameter</InputLabel>
              <Select
                value={manualTargetType}
                label="Target Parameter"
                onChange={(e) => setManualTargetType(e.target.value)}
              >
                {TARGET_TYPE_OPTIONS.map((opt) => (
                  <MenuItem key={opt.value} value={opt.value}>
                    {opt.label}
                  </MenuItem>
                ))}
              </Select>
            </FormControl>

            {/* Target ID */}
            <TextField
              label="Target ID (channel index or bus ID)"
              type="number"
              size="small"
              fullWidth
              value={manualTargetId}
              onChange={(e) => setManualTargetId(parseInt(e.target.value) || 0)}
            />

            {/* MIDI Channel */}
            <TextField
              label="MIDI Channel (-1 = Omni)"
              type="number"
              size="small"
              fullWidth
              value={manualMidiChannel}
              onChange={(e) => setManualMidiChannel(parseInt(e.target.value) || -1)}
              inputProps={{ min: -1, max: 15 }}
            />

            {/* CC Number */}
            <TextField
              label="CC Number (0-127)"
              type="number"
              size="small"
              fullWidth
              value={manualCcNumber}
              onChange={(e) => setManualCcNumber(Math.min(127, Math.max(0, parseInt(e.target.value) || 0)))}
              inputProps={{ min: 0, max: 127 }}
            />

            {/* Min/Max range */}
            <Box sx={{ display: "flex", gap: 1 }}>
              <TextField
                label="Min Value"
                type="number"
                size="small"
                fullWidth
                value={manualMinValue}
                onChange={(e) => setManualMinValue(parseFloat(e.target.value) || 0)}
              />
              <TextField
                label="Max Value"
                type="number"
                size="small"
                fullWidth
                value={manualMaxValue}
                onChange={(e) => setManualMaxValue(parseFloat(e.target.value) || 0)}
              />
            </Box>
          </Box>
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setManualOpen(false)} color="secondary">Cancel</Button>
          <Button
            onClick={handleManualAdd}
            variant="contained"
            disabled={ws.status !== "connected" || disabled}
          >
            Add
          </Button>
        </DialogActions>
      </Dialog>
    </>
  );
}
