import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import Quickshell.Widgets
import qs.Commons
import qs.Modules.MainScreen
import qs.Services.Media
import qs.Widgets

SmartPanel {
  id: root

  preferredWidth: Math.round(440 * Style.uiScaleRatio)
  preferredHeight: Math.round(420 * Style.uiScaleRatio)

  panelContent: Item {
    id: panelContent

    // Volume state (lazy-loaded with panelContent)
    property real localOutputVolume: AudioService.volume || 0
    property bool localOutputVolumeChanging: false
    property int lastSinkId: -1

    property real localInputVolume: AudioService.inputVolume || 0
    property bool localInputVolumeChanging: false
    property int lastSourceId: -1

    readonly property bool outputVolumeGuard: outputVolumeSlider.sliderActive || localOutputVolumeChanging
    readonly property bool inputVolumeGuard: inputVolumeSlider.sliderActive || localInputVolumeChanging

    // UI state (lazy-loaded with panelContent)
    property int currentTabIndex: 0

    Component.onCompleted: {
      var vol = AudioService.volume;
      localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
      var inputVol = AudioService.inputVolume;
      localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0;
      if (AudioService.sink) {
        lastSinkId = AudioService.sink.id;
      }
      if (AudioService.source) {
        lastSourceId = AudioService.source.id;
      }
    }

    // Reset local volume when device changes - use current device's volume
    Connections {
      target: AudioService
      function onSinkChanged() {
        if (AudioService.sink) {
          const newSinkId = AudioService.sink.id;
          if (newSinkId !== panelContent.lastSinkId) {
            panelContent.lastSinkId = newSinkId;
            // Immediately set local volume to current device's volume
            var vol = AudioService.volume;
            panelContent.localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
          }
        } else {
          panelContent.lastSinkId = -1;
          panelContent.localOutputVolume = 0;
        }
      }
    }

    Connections {
      target: AudioService
      function onSourceChanged() {
        if (AudioService.source) {
          const newSourceId = AudioService.source.id;
          if (newSourceId !== panelContent.lastSourceId) {
            panelContent.lastSourceId = newSourceId;
            // Immediately set local volume to current device's volume
            var vol = AudioService.inputVolume;
            panelContent.localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
          }
        } else {
          panelContent.lastSourceId = -1;
          panelContent.localInputVolume = 0;
        }
      }
    }

    // Connections to update local volumes when AudioService changes
    Connections {
      target: AudioService
      function onVolumeChanged() {
        if (!panelContent.outputVolumeGuard && !AudioService.isSettingOutputVolume && AudioService.sink && AudioService.sink.id === panelContent.lastSinkId) {
          var vol = AudioService.volume;
          panelContent.localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
        }
      }
    }

    Connections {
      target: AudioService
      function onInputVolumeChanged() {
        if (!panelContent.inputVolumeGuard && !AudioService.isSettingInputVolume && AudioService.source && AudioService.source.id === panelContent.lastSourceId) {
          var vol = AudioService.inputVolume;
          panelContent.localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
        }
      }
    }

    Connections {
      target: outputVolumeSlider
      function onSliderActiveChanged() {
        if (!outputVolumeSlider.sliderActive && AudioService.sink && AudioService.sink.id === panelContent.lastSinkId) {
          var vol = AudioService.volume;
          panelContent.localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
        }
      }
    }

    Connections {
      target: inputVolumeSlider
      function onSliderActiveChanged() {
        if (!inputVolumeSlider.sliderActive && AudioService.source && AudioService.source.id === panelContent.lastSourceId) {
          var vol = AudioService.inputVolume;
          panelContent.localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
        }
      }
    }

    // Timer to debounce volume changes
    // Only sync if the device hasn't changed (check by comparing IDs)
    Timer {
      interval: 100
      running: true
      repeat: true
      onTriggered: {
        // Only sync if sink hasn't changed
        if (AudioService.sink && AudioService.sink.id === panelContent.lastSinkId) {
          if (Math.abs(panelContent.localOutputVolume - AudioService.volume) >= 0.01) {
            AudioService.setVolume(panelContent.localOutputVolume);
          }
        }
        // Only sync if source hasn't changed
        if (AudioService.source && AudioService.source.id === panelContent.lastSourceId) {
          if (Math.abs(panelContent.localInputVolume - AudioService.inputVolume) >= 0.01) {
            AudioService.setInputVolume(panelContent.localInputVolume);
          }
        }
      }
    }

    // Find application streams that are actually playing audio (connected to default sink)
    // Use linkGroups to find nodes connected to the default audio sink
    // Note: We need to use link IDs since source/target properties require binding
    readonly property var appStreams: AudioService.appStreams

    // Use implicitHeight from content + margins to avoid binding loops
    property real contentPreferredHeight: mainColumn.implicitHeight + Style.margin2L

    ColumnLayout {
      id: mainColumn
      anchors.fill: parent
      anchors.margins: Style.marginL
      spacing: Style.marginM

      // HEADER
      NBox {
        Layout.fillWidth: true
        implicitHeight: header.implicitHeight + Style.margin2M

        ColumnLayout {
          id: header
          anchors.fill: parent
          anchors.margins: Style.marginM
          spacing: Style.marginM

          RowLayout {
            NIcon {
              icon: "settings-audio"
              pointSize: Style.fontSizeXXL
              color: Color.mPrimary
            }

            NText {
              text: I18n.tr("panels.audio.title")
              pointSize: Style.fontSizeL
              font.weight: Style.fontWeightBold
              color: Color.mOnSurface
              Layout.fillWidth: true
            }

            NIconButton {
              icon: "close"
              tooltipText: I18n.tr("common.close")
              baseSize: Style.baseWidgetSize * 0.8
              onClicked: {
                root.close();
              }
            }
          }

          NTabBar {
            id: tabBar
            Layout.fillWidth: true
            margins: Style.marginS
            currentIndex: panelContent.currentTabIndex
            distributeEvenly: true
            onCurrentIndexChanged: panelContent.currentTabIndex = currentIndex

            NTabButton {
              text: I18n.tr("common.volumes")
              tabIndex: 0
              checked: tabBar.currentIndex === 0
            }

            NTabButton {
              text: I18n.tr("common.devices")
              tabIndex: 1
              checked: tabBar.currentIndex === 1
            }
          }
        }
      }

      // Content Stack
      StackLayout {
        Layout.fillWidth: true
        Layout.fillHeight: true
        currentIndex: panelContent.currentTabIndex

        // Applications Tab (Volume)
        NScrollView {
          id: volumeScrollView
          horizontalPolicy: ScrollBar.AlwaysOff
          verticalPolicy: ScrollBar.AsNeeded
          contentWidth: availableWidth
          reserveScrollbarSpace: false
          gradientColor: Color.mSurface

          ColumnLayout {
            spacing: Style.marginM
            width: volumeScrollView.availableWidth

            // Output Volume
            NBox {
              Layout.fillWidth: true
              Layout.preferredHeight: outputVolumeColumn.implicitHeight + Style.margin2M

              ColumnLayout {
                id: outputVolumeColumn
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.margins: Style.marginM
                spacing: Style.marginM

                RowLayout {
                  Layout.fillWidth: true
                  spacing: Style.marginXS

                  NText {
                    text: I18n.tr("common.output")
                    pointSize: Style.fontSizeM
                    color: Color.mPrimary
                  }

                  NText {
                    text: AudioService.sink ? (" - " + (AudioService.sink.description || AudioService.sink.name || "")) : ""
                    pointSize: Style.fontSizeS
                    color: Color.mOnSurfaceVariant
                    elide: Text.ElideRight
                    Layout.fillWidth: true
                  }
                }

                RowLayout {
                  Layout.fillWidth: true
                  spacing: Style.marginM

                  NValueSlider {
                    id: outputVolumeSlider
                    Layout.fillWidth: true
                    from: 0
                    to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
                    value: localOutputVolume
                    stepSize: 0.01
                    heightRatio: 0.5
                    onMoved: function (value) {
                      localOutputVolume = value;
                    }
                    onPressedChanged: function (pressed) {
                      localOutputVolumeChanging = pressed;
                    }
                  }

                  NText {
                    text: Math.round((panelContent.outputVolumeGuard ? localOutputVolume : AudioService.volume) * 100) + "%"
                    pointSize: Style.fontSizeM
                    family: Settings.data.ui.fontFixed
                    color: Color.mOnSurface
                    opacity: enabled ? 1.0 : 0.6
                    Layout.alignment: Qt.AlignVCenter
                    Layout.preferredWidth: 45 * Style.uiScaleRatio
                    horizontalAlignment: Text.AlignRight
                  }

                  NIconButton {
                    icon: AudioService.getOutputIcon()
                    tooltipText: I18n.tr("tooltips.output-muted")
                    baseSize: Style.baseWidgetSize * 0.7
                    onClicked: {
                      AudioService.suppressOutputOSD();
                      AudioService.setOutputMuted(!AudioService.muted);
                    }
                  }
                }
              }
            }

            // Input Volume
            NBox {
              Layout.fillWidth: true
              Layout.preferredHeight: inputVolumeColumn.implicitHeight + Style.margin2M

              ColumnLayout {
                id: inputVolumeColumn
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.margins: Style.marginM
                spacing: Style.marginM

                RowLayout {
                  Layout.fillWidth: true
                  spacing: Style.marginXS

                  NText {
                    text: I18n.tr("common.input")
                    pointSize: Style.fontSizeM
                    color: Color.mPrimary
                  }

                  NText {
                    text: AudioService.source ? (" - " + (AudioService.source.description || AudioService.source.name || "")) : ""
                    pointSize: Style.fontSizeS
                    color: Color.mOnSurfaceVariant
                    elide: Text.ElideRight
                    Layout.fillWidth: true
                  }
                }

                RowLayout {
                  Layout.fillWidth: true
                  spacing: Style.marginM

                  NValueSlider {
                    id: inputVolumeSlider
                    Layout.fillWidth: true
                    from: 0
                    to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
                    value: localInputVolume
                    stepSize: 0.01
                    heightRatio: 0.5
                    onMoved: function (value) {
                      localInputVolume = value;
                    }
                    onPressedChanged: function (pressed) {
                      localInputVolumeChanging = pressed;
                    }
                  }

                  NText {
                    text: Math.round((panelContent.inputVolumeGuard ? localInputVolume : AudioService.inputVolume) * 100) + "%"
                    pointSize: Style.fontSizeM
                    family: Settings.data.ui.fontFixed
                    color: Color.mOnSurface
                    opacity: enabled ? 1.0 : 0.6
                    Layout.alignment: Qt.AlignVCenter
                    Layout.preferredWidth: 45 * Style.uiScaleRatio
                    horizontalAlignment: Text.AlignRight
                  }

                  NIconButton {
                    icon: AudioService.getInputIcon()
                    tooltipText: I18n.tr("tooltips.input-muted")
                    baseSize: Style.baseWidgetSize * 0.7
                    onClicked: {
                      AudioService.suppressInputOSD();
                      AudioService.setInputMuted(!AudioService.inputMuted);
                    }
                  }
                }
              }
            }

            // Bind all app stream nodes to access their audio properties
            PwObjectTracker {
              id: appStreamsTracker
              objects: panelContent.appStreams
            }

            Repeater {
              model: panelContent.appStreams

              NBox {
                id: appBox
                required property PwNode modelData
                Layout.fillWidth: true
                Layout.preferredHeight: appRow.implicitHeight + Style.margin2M
                visible: !isCaptureStream

                // Track individual node to ensure properties are bound
                PwObjectTracker {
                  objects: modelData ? [modelData] : []
                }

                property PwNodeAudio nodeAudio: (modelData && modelData.audio) ? modelData.audio : null
                property real appVolume: (nodeAudio && nodeAudio.volume !== undefined) ? nodeAudio.volume : 0.0
                property bool appMuted: (nodeAudio && nodeAudio.muted !== undefined) ? nodeAudio.muted : false

                // Check if this is a capture stream (after node is bound)
                readonly property bool isCaptureStream: {
                  if (!modelData || !modelData.properties)
                    return false;
                  const props = modelData.properties;
                  // Exclude capture streams - check for stream.capture.sink property
                  if (props["stream.capture.sink"] !== undefined) {
                    return true;
                  }
                  const mediaClass = props["media.class"] || "";
                  // Exclude Stream/Input (capture) but allow Stream/Output (playback)
                  if (mediaClass.includes("Capture") || mediaClass === "Stream/Input" || mediaClass === "Stream/Input/Audio") {
                    return true;
                  }
                  const mediaRole = props["media.role"] || "";
                  if (mediaRole === "Capture") {
                    return true;
                  }
                  return false;
                }

                // Helper function to validate if a fuzzy match is actually related
                function isValidMatch(searchTerm, entry) {
                  if (!entry)
                    return false;
                  var search = searchTerm.toLowerCase();
                  var id = (entry.id || "").toLowerCase();
                  var name = (entry.name || "").toLowerCase();
                  var icon = (entry.icon || "").toLowerCase();
                  // Match is valid if search term appears in entry or entry appears in search
                  return id.includes(search) || name.includes(search) || icon.includes(search) || search.includes(id.split('.').pop()) || search.includes(name.replace(/\s+/g, ''));
                }

                readonly property string appName: {
                  if (!modelData)
                    return "Unknown App";

                  var props = modelData.properties;
                  var desc = modelData.description || "";
                  var name = modelData.name || "";

                  if (!props) {
                    if (desc)
                      return desc;
                    if (name) {
                      var nameParts = name.split(/[-_]/);
                      if (nameParts.length > 0 && nameParts[0])
                        return nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1);
                      return name;
                    }
                    return "Unknown App";
                  }

                  var binaryName = props["application.process.binary"] || "";

                  // Try binary name first (fixes Electron apps like vesktop)
                  if (binaryName) {
                    var binParts = binaryName.split("/");
                    if (binParts.length > 0) {
                      var binName = binParts[binParts.length - 1].toLowerCase();
                      var entry = ThemeIcons.findAppEntry(binName);
                      // Only use entry if it's actually related to binary name
                      if (entry && entry.name && isValidMatch(binName, entry))
                        return entry.name;
                    }
                  }

                  var computedAppName = props["application.name"] || "";
                  var mediaName = props["media.name"] || "";
                  var mediaTitle = props["media.title"] || "";
                  var appId = props["application.id"] || "";

                  if (appId) {
                    var entry = ThemeIcons.findAppEntry(appId);
                    if (entry && entry.name && isValidMatch(appId, entry))
                      return entry.name;
                    if (!computedAppName) {
                      var parts = appId.split(".");
                      if (parts.length > 0 && parts[0])
                        computedAppName = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
                    }
                  }

                  if (!computedAppName && binaryName) {
                    var binParts = binaryName.split("/");
                    if (binParts.length > 0 && binParts[binParts.length - 1])
                      computedAppName = binParts[binParts.length - 1].charAt(0).toUpperCase() + binParts[binParts.length - 1].slice(1);
                  }

                  var result = computedAppName || mediaTitle || mediaName || binaryName || desc || name;

                  if (!result || result === "" || result === "Unknown App") {
                    if (name) {
                      var nameParts = name.split(/[-_]/);
                      if (nameParts.length > 0 && nameParts[0])
                        result = nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1);
                    }
                  }

                  return result || "Unknown App";
                }

                readonly property string appIcon: {
                  if (!modelData)
                    return ThemeIcons.iconFromName("application-x-executable", "application-x-executable");

                  var props = modelData.properties;

                  if (!props) {
                    var name = modelData.name || "";
                    if (name) {
                      var nameParts = name.split(/[-_]/);
                      if (nameParts.length > 0) {
                        var entry = ThemeIcons.findAppEntry(nameParts[0].toLowerCase());
                        if (entry && entry.icon && isValidMatch(nameParts[0].toLowerCase(), entry))
                          return ThemeIcons.iconFromName(entry.icon, "application-x-executable");
                      }
                    }
                    return ThemeIcons.iconFromName("application-x-executable", "application-x-executable");
                  }

                  var binaryName = props["application.process.binary"] || "";
                  if (binaryName) {
                    var binParts = binaryName.split("/");
                    if (binParts.length > 0) {
                      var binName = binParts[binParts.length - 1].toLowerCase();
                      var entry = ThemeIcons.findAppEntry(binName);
                      if (entry && entry.icon && isValidMatch(binName, entry))
                        return ThemeIcons.iconFromName(entry.icon, "");
                    }
                  }

                  var iconName = props["application.icon-name"] || "";
                  if (iconName && ThemeIcons.iconExists(iconName)) {
                    var iconPath = ThemeIcons.iconFromName(iconName, "");
                    if (iconPath && iconPath !== "")
                      return iconPath;
                  }

                  var appId = props["application.id"] || "";
                  if (appId) {
                    var entry = ThemeIcons.findAppEntry(appId);
                    if (entry && entry.icon && isValidMatch(appId, entry))
                      return ThemeIcons.iconFromName(entry.icon, "");
                  }

                  var appName = props["application.name"] || "";
                  if (appName) {
                    var entry = ThemeIcons.findAppEntry(appName.toLowerCase());
                    if (entry && entry.icon && isValidMatch(appName.toLowerCase(), entry))
                      return ThemeIcons.iconFromName(entry.icon, "");
                  }

                  var name = modelData.name || "";
                  if (name) {
                    var nameParts = name.split(/[-_]/);
                    if (nameParts.length > 0) {
                      var entry = ThemeIcons.findAppEntry(nameParts[0].toLowerCase());
                      if (entry && entry.icon && isValidMatch(nameParts[0].toLowerCase(), entry))
                        return ThemeIcons.iconFromName(entry.icon, "");
                    }
                  }

                  return ThemeIcons.iconFromName("application-x-executable", "application-x-executable");
                }

                RowLayout {
                  id: appRow
                  anchors.fill: parent
                  anchors.margins: Style.marginM
                  spacing: Style.marginM

                  // App Icon
                  IconImage {
                    id: appIconImage
                    Layout.preferredWidth: Style.baseWidgetSize
                    Layout.preferredHeight: Style.baseWidgetSize
                    source: appBox.appIcon
                    smooth: true
                    asynchronous: true

                    // Fallback icon if image fails to load
                    NIcon {
                      anchors.fill: parent
                      icon: "apps"
                      pointSize: Style.fontSizeXL
                      color: Color.mPrimary
                      visible: appIconImage.status === Image.Error || appIconImage.status === Image.Null || appBox.appIcon === ""
                    }
                  }

                  // App Name and Volume Slider
                  ColumnLayout {
                    Layout.fillWidth: true
                    spacing: Style.marginXS

                    NText {
                      text: appBox.appName || "Unknown App"
                      pointSize: Style.fontSizeM
                      color: Color.mOnSurface
                      elide: Text.ElideRight
                      Layout.fillWidth: true
                    }

                    RowLayout {
                      Layout.fillWidth: true
                      spacing: Style.marginM

                      NValueSlider {
                        Layout.fillWidth: true
                        from: 0
                        to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
                        value: (appBox.appVolume !== undefined) ? appBox.appVolume : 0.0
                        stepSize: 0.01
                        heightRatio: 0.5
                        enabled: !!(appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true)
                        onMoved: function (value) {
                          if (appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true) {
                            appBox.nodeAudio.volume = value;
                            var key = AudioService.getAppKey(appBox.modelData);
                            if (key)
                              AudioService.setAppStreamVolume(key, value);
                          }
                        }
                      }

                      NText {
                        text: Math.round((appBox.appVolume !== undefined ? appBox.appVolume : 0.0) * 100) + "%"
                        pointSize: Style.fontSizeM
                        family: Settings.data.ui.fontFixed
                        color: Color.mOnSurface
                        opacity: enabled ? 1.0 : 0.6
                        Layout.alignment: Qt.AlignVCenter
                        Layout.preferredWidth: 45 * Style.uiScaleRatio
                        horizontalAlignment: Text.AlignRight
                        enabled: !!(appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true)
                      }

                      // Mute Button
                      NIconButton {
                        icon: (appBox.appMuted === true) ? "volume-mute" : "volume-high"
                        tooltipText: (appBox.appMuted === true) ? I18n.tr("tooltips.unmute") : I18n.tr("tooltips.mute")
                        baseSize: Style.baseWidgetSize * 0.7
                        enabled: !!(appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true)
                        onClicked: {
                          if (appBox.nodeAudio && appBox.modelData && appBox.modelData.ready === true) {
                            var newMuted = !appBox.appMuted;
                            appBox.nodeAudio.muted = newMuted;
                            var key = AudioService.getAppKey(appBox.modelData);
                            if (key)
                              AudioService.setAppStreamMuted(key, newMuted);
                          }
                        }
                      }
                    }
                  }
                }
              }
            }

            // Empty state
            NText {
              visible: panelContent.appStreams.length === 0
              text: I18n.tr("panels.audio.panel-applications-empty")
              pointSize: Style.fontSizeM
              color: Color.mOnSurfaceVariant
              horizontalAlignment: Text.AlignHCenter
              Layout.fillWidth: true
              Layout.topMargin: Style.marginXL
            }
          }
        }

        // Devices Tab
        NScrollView {
          id: devicesScrollView
          horizontalPolicy: ScrollBar.AlwaysOff
          verticalPolicy: ScrollBar.AsNeeded
          contentWidth: availableWidth
          reserveScrollbarSpace: false
          gradientColor: Color.mSurface

          // AudioService Devices
          ColumnLayout {
            spacing: Style.marginM
            width: devicesScrollView.availableWidth

            // -------------------------------
            // Output Devices
            ButtonGroup {
              id: sinks
            }

            NBox {
              Layout.fillWidth: true
              Layout.preferredHeight: outputColumn.implicitHeight + Style.margin2M

              ColumnLayout {
                id: outputColumn
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.margins: Style.marginM
                spacing: Style.marginS

                NText {
                  text: I18n.tr("panels.audio.devices-output-device-label")
                  pointSize: Style.fontSizeL
                  color: Color.mPrimary
                }

                Repeater {
                  model: AudioService.sinks
                  NRadioButton {
                    ButtonGroup.group: sinks
                    required property PwNode modelData
                    pointSize: Style.fontSizeS
                    text: modelData.description
                    checked: AudioService.sink?.id === modelData.id
                    onClicked: {
                      AudioService.setAudioSink(modelData);
                      localOutputVolume = AudioService.volume;
                    }
                    Layout.fillWidth: true
                  }
                }
              }
            }

            // -------------------------------
            // Input Devices
            ButtonGroup {
              id: sources
            }

            NBox {
              Layout.fillWidth: true
              Layout.preferredHeight: inputColumn.implicitHeight + Style.margin2M

              ColumnLayout {
                id: inputColumn
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                anchors.margins: Style.marginM
                spacing: Style.marginS

                NText {
                  text: I18n.tr("panels.audio.devices-input-device-label")
                  pointSize: Style.fontSizeL
                  color: Color.mPrimary
                }

                Repeater {
                  model: AudioService.sources
                  NRadioButton {
                    ButtonGroup.group: sources
                    required property PwNode modelData
                    pointSize: Style.fontSizeS
                    text: modelData.description
                    checked: AudioService.source?.id === modelData.id
                    onClicked: AudioService.setAudioSource(modelData)
                    Layout.fillWidth: true
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
