/**
 * Audio control card.
 *
 *   ┌──────────────────────────────────────────────────────┐
 *   │  ┌──────────────────┐    ┌──────────────────┐        │
 *   │  │ ▌▎▍▎▍▌▎▍▎▌▎▍▎▌▎ │    │ ▌▎▍▎▍▌▎▍▎▌▎▍▎▌▎ │        │
 *   │  └──────────────────┘    └──────────────────┘        │
 *   │  ┌──────────────────┐    ┌──────────────────┐        │
 *   │  │  🎤  Mic on      │    │  🔊  Sound on    │        │
 *   │  └──────────────────┘    └──────────────────┘        │
 *   │  🎙 Mic gain   80%       🔊 Speaker   100%            │
 *   │  ──●─────────────        ───────●───────             │
 *   └──────────────────────────────────────────────────────┘
 *
 * Two parallel columns - one per direction of audio. Each column
 * is self-contained top-to-bottom:
 *
 *   - LEFT  ("Mic"):     spectrum viz of your mic + toggle + mic-gain slider
 *   - RIGHT ("Sound"):   spectrum viz of robot audio + toggle + speaker slider
 *
 * Stacking the viz on top of the toggle (rather than a single
 * bidirectional header) makes ownership obvious: every visual
 * element ABOVE a column is driven by that column's stream.
 */
import {
  Alert,
  Box,
  Paper,
  Slider,
  Stack,
  ToggleButton,
  Typography,
  useTheme,
} from '@mui/material';
import type { ReactNode } from 'react';
import MicIcon from '@mui/icons-material/Mic';
import MicOffIcon from '@mui/icons-material/MicOff';
import VolumeUpIcon from '@mui/icons-material/VolumeUp';
import VolumeOffIcon from '@mui/icons-material/VolumeOff';
import GraphicEqIcon from '@mui/icons-material/GraphicEq';
import { useEffect, useState } from 'react';

import type { ReachyMiniInstance } from '@/sdk-types';
import ChannelSpectrum from './ChannelSpectrum';

interface AudioControlCardProps {
  robot: ReachyMiniInstance;
  micMuted: boolean;
  audioMuted: boolean;
  /** Local mic MediaStream (user → robot). Powers the top spectrum half. */
  micStream: MediaStream | null;
  /** Remote MediaStream containing the robot's audio track
   *  (robot → user). Powers the bottom spectrum half. */
  robotStream: MediaStream | null;
  onSetMicMuted(muted: boolean): void;
  onSetAudioMuted(muted: boolean): void;
}

export default function AudioControlCard({
  robot,
  micMuted,
  audioMuted,
  micStream,
  robotStream,
  onSetMicMuted,
  onSetAudioMuted,
}: AudioControlCardProps) {
  const theme = useTheme();

  const [speakerVolume, setSpeakerVolume] = useState<number | null>(null);
  const [micVolume, setMicVolume] = useState<number | null>(null);
  const [pendingSpeaker, setPendingSpeaker] = useState(false);
  const [pendingMic, setPendingMic] = useState(false);

  // Pull the daemon's current volumes when the card mounts. The
  // round-trip can take a few hundred ms if the data channel is
  // freshly negotiated, so we render `null` (slider in indeterminate
  // state) until the values land.
  useEffect(() => {
    let cancelled = false;
    void (async () => {
      try {
        const [spk, mic] = await Promise.all([
          robot.getVolume(),
          robot.getMicrophoneVolume(),
        ]);
        if (cancelled) return;
        if (spk !== null) setSpeakerVolume(spk);
        if (mic !== null) setMicVolume(mic);
      } catch (err) {
        console.warn('[audio] could not read volumes:', err);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [robot]);

  const applySpeakerVolume = async (value: number) => {
    setSpeakerVolume(value);
    setPendingSpeaker(true);
    try {
      const acked = await robot.setVolume(value);
      if (acked !== null) setSpeakerVolume(acked);
    } catch (err) {
      console.warn('[audio] setVolume failed:', err);
    } finally {
      setPendingSpeaker(false);
    }
  };

  const applyMicVolume = async (value: number) => {
    setMicVolume(value);
    setPendingMic(true);
    try {
      const acked = await robot.setMicrophoneVolume(value);
      if (acked !== null) setMicVolume(acked);
    } catch (err) {
      console.warn('[audio] setMicrophoneVolume failed:', err);
    } finally {
      setPendingMic(false);
    }
  };

  const micSupported = robot.micSupported;

  return (
    <Paper
      elevation={0}
      sx={{
        p: 1.75,
        display: 'flex',
        flexDirection: 'column',
        gap: 1.5,
        // Match the mobile app's `RADIUS.lg` (12 px = shape.borderRadius).
        // Less pronounced than the previous `3` (36 px) which read as
        // "blob". Lighter cards = quieter UI.
        borderRadius: 1,
        // 1 px hairline border instead of a heavy shadow. Matches the
        // mobile app's "paper-on-canvas" aesthetic - we want the card
        // to read as a slight elevation, not a floating drawer.
        border: `1px solid ${theme.palette.divider}`,
        boxShadow: 'none',
        backgroundColor: theme.palette.background.paper,
      }}
    >
      {!micSupported && (
        <Alert
          severity="info"
          icon={false}
          sx={{
            py: 0.25,
            px: 1,
            fontSize: 11,
            lineHeight: 1.4,
            backgroundColor:
              theme.palette.mode === 'dark'
                ? 'rgba(255, 149, 0, 0.08)'
                : 'rgba(255, 149, 0, 0.12)',
            color: theme.palette.text.primary,
            border: 'none',
            borderRadius: 1,
          }}
        >
          The robot reports no microphone support. You can hear it but
          your voice won't be sent.
        </Alert>
      )}

      <Stack direction="row" spacing={1.5}>
        <ChannelColumn
          channelLabel="Mic"
          isOn={!micMuted && micSupported}
          onIcon={<MicIcon fontSize="small" />}
          offIcon={<MicOffIcon fontSize="small" />}
          disabled={!micSupported}
          onToggle={() => onSetMicMuted(!micMuted)}
          stream={micStream}
          vizActive={!micMuted && micSupported && micStream != null}
          sliderIcon={<GraphicEqIcon sx={{ fontSize: 14 }} />}
          sliderLabel="Mic gain"
          sliderValue={micVolume}
          sliderPending={pendingMic}
          onSliderChange={(v) => setMicVolume(v)}
          onSliderCommit={(v) => void applyMicVolume(v)}
        />
        <ChannelColumn
          channelLabel="Sound"
          isOn={!audioMuted}
          onIcon={<VolumeUpIcon fontSize="small" />}
          offIcon={<VolumeOffIcon fontSize="small" />}
          onToggle={() => onSetAudioMuted(!audioMuted)}
          stream={robotStream}
          vizActive={!audioMuted && robotStream != null}
          sliderIcon={<VolumeUpIcon sx={{ fontSize: 14 }} />}
          sliderLabel="Speaker"
          sliderValue={speakerVolume}
          sliderPending={pendingSpeaker}
          onSliderChange={(v) => setSpeakerVolume(v)}
          onSliderCommit={(v) => void applySpeakerVolume(v)}
        />
      </Stack>
    </Paper>
  );
}

interface ChannelColumnProps {
  /** Top toggle button label ("Mic" → "Mic on" / "Mic off"). */
  channelLabel: string;
  /** Toggle button state - is the channel currently active? */
  isOn: boolean;
  /** Disable the toggle. Used when daemon reports no mic support. */
  disabled?: boolean;
  /** Icon shown when `isOn` is true. */
  onIcon: ReactNode;
  /** Icon shown when `isOn` is false. */
  offIcon: ReactNode;
  onToggle(): void;
  /** MediaStream feeding the spectrum viz. */
  stream: MediaStream | null;
  /** When `false`, the viz dims out (channel is off). */
  vizActive: boolean;
  /** Slider row icon. */
  sliderIcon: ReactNode;
  /** Slider row label. */
  sliderLabel: string;
  /** Current slider value (null = round-tripping with the daemon). */
  sliderValue: number | null;
  /** Slider is awaiting a daemon ack (dims the % readout). */
  sliderPending: boolean;
  onSliderChange(value: number): void;
  onSliderCommit(value: number): void;
}

/**
 * One channel column. Three vertically-aligned elements:
 *
 *   ┌───────────────────────┐
 *   │  ▌ ▎ ▍ ▎ ▍ ▌ ▎ ▍ ▎     │  ← live spectrum viz
 *   ├───────────────────────┤
 *   │  [icon]  Mic on       │  ← toggle (primary tint when ON)
 *   ├───────────────────────┤
 *   │  🎙 Mic gain      80% │  ← slider header
 *   │  ──●────────────────  │  ← slider track
 *   └───────────────────────┘
 *
 * The viz lives INSIDE the column it represents (one viz per
 * channel, side-by-side), making the ownership obvious without
 * labelling: bars above the "Mic" toggle = mic; bars above
 * "Sound" toggle = robot speaker.
 */
function ChannelColumn({
  channelLabel,
  isOn,
  disabled = false,
  onIcon,
  offIcon,
  onToggle,
  stream,
  vizActive,
  sliderIcon,
  sliderLabel,
  sliderValue,
  sliderPending,
  onSliderChange,
  onSliderCommit,
}: ChannelColumnProps) {
  const theme = useTheme();
  return (
    <Box
      sx={{
        flex: 1,
        minWidth: 0,
        display: 'flex',
        flexDirection: 'column',
        gap: 1,
      }}
    >
      <ChannelSpectrum stream={stream} active={vizActive} />
      <ToggleButton
        value="channel"
        selected={isOn}
        onChange={onToggle}
        disabled={disabled}
        sx={{
          gap: 1,
          py: 1,
          px: 1.25,
          // Soft-cornered button - same radius as the card. Avoids
          // the "pill button inside a square card" mismatch.
          borderRadius: 1,
          border: 'none',
          textTransform: 'none',
          color: 'text.secondary',
          backgroundColor:
            theme.palette.mode === 'dark'
              ? 'rgba(255,255,255,0.04)'
              : 'rgba(0,0,0,0.035)',
          transition: theme.transitions.create(
            ['background-color', 'color'],
            { duration: theme.transitions.duration.shortest },
          ),
          '&:hover': {
            backgroundColor:
              theme.palette.mode === 'dark'
                ? 'rgba(255,255,255,0.07)'
                : 'rgba(0,0,0,0.06)',
          },
          '&.Mui-selected': {
            color: 'primary.main',
            backgroundColor:
              theme.palette.mode === 'dark'
                ? 'rgba(255, 149, 0, 0.14)'
                : 'rgba(255, 149, 0, 0.1)',
            '&:hover': {
              backgroundColor:
                theme.palette.mode === 'dark'
                  ? 'rgba(255, 149, 0, 0.2)'
                  : 'rgba(255, 149, 0, 0.16)',
            },
          },
        }}
      >
        {isOn ? onIcon : offIcon}
        <Typography
          variant="caption"
          sx={{ fontWeight: 600, fontSize: 12, letterSpacing: '0.15px' }}
        >
          {channelLabel} {isOn ? 'on' : 'off'}
        </Typography>
      </ToggleButton>
      <Box>
        <Stack
          direction="row"
          spacing={1}
          sx={{
            alignItems: "center",
            justifyContent: "space-between",
            mb: 0.25
          }}>
          <Stack direction="row" spacing={0.6} sx={{
            alignItems: "center"
          }}>
            <Box
              sx={{
                display: 'flex',
                alignItems: 'center',
                color: 'text.secondary',
              }}
            >
              {sliderIcon}
            </Box>
            <Typography
              variant="caption"
              sx={{ color: 'text.secondary', fontSize: 11.5 }}
            >
              {sliderLabel}
            </Typography>
          </Stack>
          <Typography
            variant="caption"
            sx={{
              color: 'text.secondary',
              fontFamily: 'monospace',
              fontSize: 11,
              opacity: sliderPending ? 0.5 : 1,
            }}
          >
            {sliderValue === null ? '— %' : `${sliderValue}%`}
          </Typography>
        </Stack>
        <Slider
          aria-label={sliderLabel}
          value={sliderValue ?? 0}
          min={0}
          max={100}
          disabled={sliderValue === null}
          onChange={(_, v) => {
            if (typeof v === 'number') onSliderChange(v);
          }}
          onChangeCommitted={(_, v) => {
            if (typeof v === 'number') onSliderCommit(v);
          }}
          size="small"
          sx={{
            '& .MuiSlider-thumb': {
              width: 12,
              height: 12,
              // Very subtle thumb shadow - we want it to read as
              // "indicator" not "physical knob".
              boxShadow: '0 1px 2px rgba(0,0,0,0.18)',
            },
            '& .MuiSlider-rail': { opacity: 0.35 },
          }}
        />
      </Box>
    </Box>
  );
}
