import { useEffect } from 'react';
import { useSetAtom } from 'jotai';
import { CallEmbed, useCallControlState } from '../plugins/call';
import { useSetting } from '../state/hooks/settings';
import { settingsAtom } from '../state/settings';
import { toastQueueAtom } from '../state/toast';
import { useMatrixClient } from './useMatrixClient';

const CHECK_INTERVAL_MS = 500;

/**
 * Monitors microphone activity while in a call. If the mic stays unmuted but
 * the user is not speaking for longer than the configured timeout, the mic is
 * muted and a toast is shown.
 *
 * [C-H2] Activity is read from the EC fork's `io.lotus.call_state` stream
 * (getLotusParticipants) — i.e. the VAD state of the user's ACTUAL published
 * track on their SELECTED input device. The previous implementation opened its
 * own `getUserMedia({ audio: true })`, which captured the browser DEFAULT mic
 * (not necessarily the device EC publishes from): it could measure silence
 * while the user spoke on a different device (auto-muting an active speaker) and
 * lit a second OS microphone indicator. Sourcing from the fork removes both
 * problems and needs no extra capture.
 *
 * If the fork hasn't reported call-state yet (getLotusParticipants() === null —
 * e.g. plain EC, or immediately after join), we cannot tell whether the user is
 * publishing, so we fail SAFE and never auto-mute during that window.
 */
export function useAfkAutoMute(callEmbed: CallEmbed | undefined): void {
  const mx = useMatrixClient();
  const [enabled] = useSetting(settingsAtom, 'afkAutoMute');
  const [timeoutMinutes] = useSetting(settingsAtom, 'afkTimeoutMinutes');
  const setToast = useSetAtom(toastQueueAtom);
  const { microphone } = useCallControlState(callEmbed?.control);

  useEffect(() => {
    // Only monitor while in a call, enabled, AND unmuted — there is nothing to
    // auto-mute once you are already muted.
    if (!callEmbed || !enabled || !microphone) return undefined;

    const localUserId = mx.getSafeUserId();
    const timeoutMs = timeoutMinutes * 60 * 1000;
    let silenceStart: number | null = null;
    let active = true;

    // undefined = fork hasn't reported call-state yet (can't tell — fail safe).
    const isLocalSpeaking = (): boolean | undefined => {
      const participants = callEmbed.getLotusParticipants();
      // null = fork not reported; [] = malformed/spurious payload (CallEmbed
      // stores [] for a non-array). You are ALWAYS present in your own joined
      // call, so an empty list means "no usable data", NOT "silent" — matching
      // useCallSpeakers / useRemoteAllMuted. Treating [] as silent would let the
      // timer mute an active speaker. Fail safe on both.
      if (participants === null || participants.length === 0) return undefined;
      return participants.some((p) => p.userId === localUserId && p.audioEnabled && p.speaking);
    };

    const intervalId = setInterval(() => {
      if (!active) return;
      const speaking = isLocalSpeaking();

      if (speaking === undefined) {
        // No usable signal — don't risk muting an active speaker.
        silenceStart = null;
      } else if (speaking) {
        // Voice detected on the published track — reset the silence timer.
        silenceStart = null;
      } else if (silenceStart === null) {
        // Mic is unmuted (effect only runs while unmuted) but silent — start the timer.
        silenceStart = Date.now();
      } else if (Date.now() - silenceStart >= timeoutMs) {
        callEmbed.control.setMicrophone(false);
        setToast({
          id: `afk-mute-${Date.now()}`,
          displayName: 'Lotus Chat',
          body: 'Your microphone was muted after inactivity.',
          roomName: 'Voice call',
          roomId: callEmbed.roomId,
        });
        silenceStart = null;
      }
    }, CHECK_INTERVAL_MS);

    return () => {
      active = false;
      clearInterval(intervalId);
    };
  }, [callEmbed, enabled, timeoutMinutes, setToast, microphone, mx]);
}
