/*
Copyright 2022-2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

import { type ChangeEvent, type FC, type ReactNode, useEffect, useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { type MatrixClient } from "matrix-js-sdk";
import { Button, Heading, Root as Form, Separator } from "@vector-im/compound-web";
import { type Room as LivekitRoom } from "livekit-client";

import { Modal } from "../Modal";
import styles from "./SettingsModal.module.css";
import { type Tab, TabContainer } from "../tabs/Tabs";
import { ProfileSettingsTab } from "./ProfileSettingsTab";
import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
import { iosDeviceMenu$ } from "../state/MediaDevices";
import { useMediaDevices } from "../MediaDevicesContext";
import { widget } from "../widget";
import {
  useSetting,
  soundEffectVolume as soundEffectVolumeSetting,
  backgroundBlur as backgroundBlurSetting,
  developerMode,
  noiseGateEnabled as noiseGateEnabledSetting,
  noiseGateThreshold as noiseGateThresholdSetting,
  noiseGateAttack as noiseGateAttackSetting,
  noiseGateHold as noiseGateHoldSetting,
  noiseGateRelease as noiseGateReleaseSetting,
  transientSuppressorEnabled as transientSuppressorEnabledSetting,
  transientThreshold as transientThresholdSetting,
  transientRelease as transientReleaseSetting,
} from "./settings";
import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
import { Slider } from "../Slider";
import { NoiseLevelSlider } from "./NoiseLevelSlider";
import { DeviceSelection } from "./DeviceSelection";
import { useTrackProcessor } from "../livekit/TrackProcessorContext";
import { DeveloperSettingsTab } from "./DeveloperSettingsTab";
import { FieldRow, InputField } from "../input/Input";
import { useSubmitRageshake } from "./submit-rageshake";
import { useUrlParams } from "../UrlParams";
import { useBehavior } from "../useBehavior";

type SettingsTab =
  | "audio"
  | "video"
  | "profile"
  | "preferences"
  | "feedback"
  | "more"
  | "developer";

interface Props {
  open: boolean;
  onDismiss: () => void;
  tab: SettingsTab;
  onTabChange: (tab: SettingsTab) => void;
  client: MatrixClient;
  roomId?: string;
  livekitRooms?: {
    room: LivekitRoom;
    url: string;
    isLocal?: boolean;
  }[];
}

export const defaultSettingsTab: SettingsTab = "audio";

export const SettingsModal: FC<Props> = ({
  open,
  onDismiss,
  tab,
  onTabChange,
  client,
  roomId,
  livekitRooms,
}) => {
  const { t } = useTranslation();

  // Generate a `Checkbox` input to turn blur on or off.
  const BlurCheckbox: React.FC = (): ReactNode => {
    const { supported } = useTrackProcessor();

    const [blurActive, setBlurActive] = useSetting(backgroundBlurSetting);

    return (
      <>
        <h4>{t("settings.background_blur_header")}</h4>

        <FieldRow>
          <InputField
            id="activateBackgroundBlur"
            label={t("settings.background_blur_label")}
            description={
              supported ? "" : t("settings.blur_not_supported_by_browser")
            }
            type="checkbox"
            checked={!!blurActive}
            onChange={(b): void => setBlurActive(b.target.checked)}
            disabled={!supported}
          />
        </FieldRow>
      </>
    );
  };

  const devices = useMediaDevices();
  useEffect(() => {
    if (open) devices.requestDeviceNames();
  }, [open, devices]);

  const [soundVolume, setSoundVolume] = useSetting(soundEffectVolumeSetting);
  const [soundVolumeRaw, setSoundVolumeRaw] = useState(soundVolume);
  const [showDeveloperSettingsTab] = useSetting(developerMode);

  // Noise gate settings
  const [noiseGateEnabled, setNoiseGateEnabled] = useSetting(noiseGateEnabledSetting);
  const [noiseGateThreshold, setNoiseGateThreshold] = useSetting(noiseGateThresholdSetting);
  const [noiseGateThresholdRaw, setNoiseGateThresholdRaw] = useState(noiseGateThreshold);
  const [noiseGateAttack, setNoiseGateAttack] = useSetting(noiseGateAttackSetting);
  const [noiseGateAttackRaw, setNoiseGateAttackRaw] = useState(noiseGateAttack);
  const [noiseGateHold, setNoiseGateHold] = useSetting(noiseGateHoldSetting);
  const [noiseGateHoldRaw, setNoiseGateHoldRaw] = useState(noiseGateHold);
  const [noiseGateRelease, setNoiseGateRelease] = useSetting(noiseGateReleaseSetting);
  const [noiseGateReleaseRaw, setNoiseGateReleaseRaw] = useState(noiseGateRelease);

  const [showAdvancedGate, setShowAdvancedGate] = useState(false);

  // Transient suppressor settings
  const [transientEnabled, setTransientEnabled] = useSetting(transientSuppressorEnabledSetting);
  const [transientThreshold, setTransientThreshold] = useSetting(transientThresholdSetting);
  const [transientThresholdRaw, setTransientThresholdRaw] = useState(transientThreshold);
  const [transientRelease, setTransientRelease] = useSetting(transientReleaseSetting);
  const [transientReleaseRaw, setTransientReleaseRaw] = useState(transientRelease);

  const resetGateDefaults = useCallback((): void => {
    const a = noiseGateAttackSetting.defaultValue;
    const h = noiseGateHoldSetting.defaultValue;
    const r = noiseGateReleaseSetting.defaultValue;
    setNoiseGateAttack(a); setNoiseGateAttackRaw(a);
    setNoiseGateHold(h);   setNoiseGateHoldRaw(h);
    setNoiseGateRelease(r); setNoiseGateReleaseRaw(r);
  }, [setNoiseGateAttack, setNoiseGateHold, setNoiseGateRelease]);

  const { available: isRageshakeAvailable } = useSubmitRageshake();

  // For controlled devices, we will not show the input section:
  // Controlled media devices are used on mobile platforms, where input and output are grouped into
  // a single device. These are called "headset" or "speaker" (or similar) but contain both input and output.
  // On EC, we decided that it is less confusing for the user if they see those options in the output section
  // rather than the input section.
  const { controlledAudioDevices } = useUrlParams();
  // If we are on iOS we will show a button to open the native audio device picker.
  const iosDeviceMenu = useBehavior(iosDeviceMenu$);

  const audioTab: Tab<SettingsTab> = {
    key: "audio",
    name: t("common.audio"),
    content: (
      <>
        <Form>
          {!controlledAudioDevices && (
            <DeviceSelection
              device={devices.audioInput}
              title={t("settings.devices.microphone")}
              numberedLabel={(n) =>
                t("settings.devices.microphone_numbered", { n })
              }
            />
          )}
          {iosDeviceMenu && controlledAudioDevices && (
            <Button
              onClick={(e): void => {
                e.preventDefault();
                window.controls.showNativeAudioDevicePicker?.();
                // call deprecated method for backwards compatibility.
                window.controls.showNativeOutputDevicePicker?.();
              }}
            >
              {t("settings.devices.change_device_button")}
            </Button>
          )}
          <DeviceSelection
            device={devices.audioOutput}
            title={t("settings.devices.speaker")}
            numberedLabel={(n) => t("settings.devices.speaker_numbered", { n })}
          />

          <div className={styles.volumeSlider}>
            <label>{t("settings.audio_tab.effect_volume_label")}</label>
            <p>{t("settings.audio_tab.effect_volume_description")}</p>
            <Slider
              label={t("video_tile.volume")}
              value={soundVolumeRaw}
              onValueChange={setSoundVolumeRaw}
              onValueCommit={setSoundVolume}
              min={0}
              max={1}
              step={0.01}
            />
          </div>
        </Form>
        <div className={styles.noiseGateSection}>
          <Heading
            type="body"
            weight="semibold"
            size="sm"
            as="h4"
            className={styles.noiseGateHeading}
          >
            Noise Gate
          </Heading>
          <Separator className={styles.noiseGateSeparator} />
        <FieldRow>
          <InputField
            id="noiseGateEnabled"
            type="checkbox"
            label="Enable noise gate"
            description="Suppress audio below a configurable threshold."
            checked={noiseGateEnabled}
            onChange={(e: ChangeEvent<HTMLInputElement>): void =>
              setNoiseGateEnabled(e.target.checked)
            }
          />
        </FieldRow>
        {noiseGateEnabled && (
          <>
            <div className={`${styles.volumeSlider} ${styles.thresholdSlider}`}>
              <span className={styles.sliderLabel}>Threshold</span>
              <p>Gate opens above this level, closes below it.</p>
              <NoiseLevelSlider
                label="Noise gate threshold"
                value={noiseGateThresholdRaw}
                onValueChange={setNoiseGateThresholdRaw}
                onValueCommit={setNoiseGateThreshold}
                min={-100}
                max={0}
                step={1}
              />
            </div>
            <div className={styles.advancedGate}>
              <button
                className={styles.advancedGateToggle}
                onClick={(): void => setShowAdvancedGate((v) => !v)}
              >
                {showAdvancedGate ? "▾" : "▸"} Advanced settings
              </button>
              {showAdvancedGate && (
                <>
                  <div className={styles.volumeSlider}>
                    <label>Attack: {noiseGateAttackRaw} ms</label>
                    <p>How quickly the gate opens when signal exceeds threshold.</p>
                    <Slider
                      label="Noise gate attack"
                      value={noiseGateAttackRaw}
                      onValueChange={setNoiseGateAttackRaw}
                      onValueCommit={setNoiseGateAttack}
                      min={1}
                      max={100}
                      step={1}
                      tooltip={false}
                    />
                  </div>
                  <div className={styles.volumeSlider}>
                    <label>Hold: {noiseGateHoldRaw} ms</label>
                    <p>How long the gate stays open after signal drops below threshold.</p>
                    <Slider
                      label="Noise gate hold"
                      value={noiseGateHoldRaw}
                      onValueChange={setNoiseGateHoldRaw}
                      onValueCommit={setNoiseGateHold}
                      min={0}
                      max={500}
                      step={10}
                      tooltip={false}
                    />
                  </div>
                  <div className={styles.volumeSlider}>
                    <label>Release: {noiseGateReleaseRaw} ms</label>
                    <p>How quickly the gate closes after hold expires.</p>
                    <Slider
                      label="Noise gate release"
                      value={noiseGateReleaseRaw}
                      onValueChange={setNoiseGateReleaseRaw}
                      onValueCommit={setNoiseGateRelease}
                      min={10}
                      max={500}
                      step={10}
                      tooltip={false}
                    />
                  </div>
                  <div className={styles.restoreDefaults}>
                    <Button kind="secondary" size="sm" onClick={resetGateDefaults}>
                      Restore defaults
                    </Button>
                  </div>
                </>
              )}
            </div>
          </>
        )}
        </div>
        <div className={styles.noiseGateSection}>
          <Heading
            type="body"
            weight="semibold"
            size="sm"
            as="h4"
            className={styles.noiseGateHeading}
          >
            Transient Suppressor
          </Heading>
          <Separator className={styles.noiseGateSeparator} />
          <FieldRow>
            <InputField
              id="transientEnabled"
              type="checkbox"
              label="Enable transient suppressor"
              description="Cut sudden loud impacts like desk hits or mic bumps."
              checked={transientEnabled}
              onChange={(e: ChangeEvent<HTMLInputElement>): void =>
                setTransientEnabled(e.target.checked)
              }
            />
          </FieldRow>
          {transientEnabled && (
            <>
              <div className={`${styles.volumeSlider} ${styles.thresholdSlider}`}>
                <span className={styles.sliderLabel}>Sensitivity: {transientThresholdRaw} dB above background</span>
                <p>Lower values catch more impacts; higher values only catch the loudest ones.</p>
                <Slider
                  label="Transient threshold"
                  value={transientThresholdRaw}
                  onValueChange={setTransientThresholdRaw}
                  onValueCommit={setTransientThreshold}
                  min={8}
                  max={30}
                  step={1}
                  tooltip={false}
                />
              </div>
              <div className={styles.volumeSlider}>
                <span className={styles.sliderLabel}>Release: {transientReleaseRaw} ms</span>
                <p>How quickly audio returns after suppression.</p>
                <Slider
                  label="Transient release"
                  value={transientReleaseRaw}
                  onValueChange={setTransientReleaseRaw}
                  onValueCommit={setTransientRelease}
                  min={20}
                  max={200}
                  step={10}
                  tooltip={false}
                />
              </div>
            </>
          )}
        </div>
      </>
    ),
  };

  const videoTab: Tab<SettingsTab> = {
    key: "video",
    name: t("common.video"),
    content: (
      <>
        <Form>
          <DeviceSelection
            device={devices.videoInput}
            title={t("settings.devices.camera")}
            numberedLabel={(n) => t("settings.devices.camera_numbered", { n })}
          />
        </Form>
        <Separator />
        <BlurCheckbox />
      </>
    ),
  };

  const preferencesTab: Tab<SettingsTab> = {
    key: "preferences",
    name: t("common.preferences"),
    content: <PreferencesSettingsTab />,
  };

  const profileTab: Tab<SettingsTab> = {
    key: "profile",
    name: t("common.profile"),
    content: <ProfileSettingsTab client={client} />,
  };

  const feedbackTab: Tab<SettingsTab> = {
    key: "feedback",
    name: t("settings.feedback_tab_title"),
    content: <FeedbackSettingsTab roomId={roomId} />,
  };

  const developerTab: Tab<SettingsTab> = {
    key: "developer",
    name: t("settings.developer_tab_title"),
    content: (
      <DeveloperSettingsTab
        env={import.meta.env}
        client={client}
        livekitRooms={livekitRooms}
        roomId={roomId}
      />
    ),
  };

  const tabs = [audioTab, videoTab];
  if (widget === null) tabs.push(profileTab);
  tabs.push(preferencesTab);
  if (isRageshakeAvailable || import.meta.env.VITE_PACKAGE === "full") {
    // for full package we want to show the analytics consent checkbox
    // even if rageshake is not available
    tabs.push(feedbackTab);
  }
  if (showDeveloperSettingsTab) tabs.push(developerTab);

  return (
    <Modal
      title={t("common.settings")}
      className={styles.settingsModal}
      open={open}
      onDismiss={onDismiss}
      tabbed
    >
      <TabContainer
        label={t("common.settings")}
        tab={tab}
        onTabChange={onTabChange}
        tabs={tabs}
      />
    </Modal>
  );
};
