#!/usr/bin/env bash

shopt -s nullglob

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_DIR="$HOME/.config/wireplumber/wireplumber.conf.d"

declare -a DEVICES
declare -a DEVICE_STREAMS
declare -a DEVICE_CARDS
declare -a DEVICE_FORMATS
declare -a DEVICE_RATES

SELECTED_CUSTOM_NAME=""

cleanup() {
    clear
}

trap cleanup EXIT

die() {
    dialog \
        --title "Error" \
        --msgbox "$1" \
        10 70

    exit 1
}

# ------------------------------------------------------------
# Format sample rate for display
# ------------------------------------------------------------

rate_display() {
    case "$1" in
        44100)  echo "44.1kHz" ;;
        48000)  echo "48kHz" ;;
        88200)  echo "88.2kHz" ;;
        96000)  echo "96kHz" ;;
        176400) echo "176.4kHz" ;;
        192000) echo "192kHz" ;;
        352800) echo "352.8kHz" ;;
        384000) echo "384kHz" ;;
        *)       echo "${1}Hz" ;;
    esac
}

# ------------------------------------------------------------
# Generate a clean filename from the device name
# ------------------------------------------------------------

make_config_filename() {
    local name="$1"

    name="$(
        printf '%s' "$name" |
        sed -E 's/[[:space:]]*:[[:space:]]*USB Audio.*$//' |
        sed -E 's/[[:space:]]+at usb-.*$//' |
        sed -E 's/^[^[:space:]]+[[:space:]]+//' |
        tr '[:upper:]' '[:lower:]' |
        sed -E 's/[^a-z0-9]+/-/g' |
        sed -E 's/^-+//; s/-+$//'
    )"

    printf '%s/%s.conf\n' "$CONFIG_DIR" "$name"
}

# ------------------------------------------------------------
# Create WirePlumber configuration directory
# ------------------------------------------------------------

create_config_directory() {
    if ! mkdir -p "$CONFIG_DIR"; then
        die "Could not create the WirePlumber configuration directory:

$CONFIG_DIR"
    fi
}

# ------------------------------------------------------------
# Discover ALSA PCM playback devices
# ------------------------------------------------------------

discover_devices() {
    local stream
    local card
    local name
    local playback
    local formats
    local format
    local rates
    local rate
    local display_format

    DEVICES=()
    DEVICE_STREAMS=()
    DEVICE_CARDS=()
    DEVICE_FORMATS=()
    DEVICE_RATES=()

    for stream in /proc/asound/card*/stream*; do
        [[ -f "$stream" ]] || continue

        # Only playback devices.
        grep -q '^Playback:' "$stream" || continue

        card="${stream#/proc/asound/card}"
        card="${card%%/*}"

        name="$(head -n1 "$stream")"

        # Extract Playback section.
        playback=$(
            awk '
                /^Playback:/ {
                    in_playback=1
                    next
                }

                /^Capture:/ {
                    in_playback=0
                }

                in_playback {
                    print
                }
            ' "$stream"
        )

        # Supported PCM formats.
        formats=$(
            printf '%s\n' "$playback" |
            grep -E '^[[:space:]]+Format:' |
            awk '{print $2}' |
            grep -E '^(S16_LE|S24_3LE|S24_LE|S32_LE)$' |
            sort -u
        )

        [[ -n "$formats" ]] || continue

        DEVICES+=("$name")
        DEVICE_STREAMS+=("$stream")
        DEVICE_CARDS+=("$card")

        local format_list=""
        local rate_list=""

        while read -r format; do
            [[ -n "$format" ]] || continue

            case "$format" in
                S16_LE)
                    display_format="S16LE"
                    ;;
                S24_3LE|S24_LE)
                    display_format="S24LE"
                    ;;
                S32_LE)
                    display_format="S32LE"
                    ;;
                *)
                    continue
                    ;;
            esac

            format_list+="$display_format"$'\n'

            # Get rates for this specific format.
            rates=$(
                printf '%s\n' "$playback" |
                awk -v fmt="$format" '
                    $1 == "Format:" && $2 == fmt {
                        found=1
                        next
                    }

                    found && $1 == "Rates:" {
                        $1=""
                        sub(/^[[:space:]]+/, "")
                        print
                        found=0
                    }
                ' |
                grep -oE '[0-9]+' |
                sort -nu
            )

            while read -r rate; do
                [[ -n "$rate" ]] || continue
                rate_list+="$rate"$'\n'
            done <<< "$rates"

        done <<< "$formats"

        DEVICE_FORMATS+=("$format_list")
        DEVICE_RATES+=("$rate_list")
    done
}

# ------------------------------------------------------------
# Get actual PipeWire sink IDs
# ------------------------------------------------------------

get_sink_ids() {
    wpctl status 2>/dev/null |
    awk '
        /Sinks:/ {
            in_sinks=1
            next
        }

        /Sources:/ {
            in_sinks=0
        }

        in_sinks {
            if (match($0, /[0-9]+\./)) {
                id = substr($0, RSTART, RLENGTH - 1)
                print id
            }
        }
    ' |
    sort -nu
}

# ------------------------------------------------------------
# Find PipeWire sink by ALSA card number
# ------------------------------------------------------------

find_node_for_device() {
    local card_number="$1"

    local id
    local info
    local node_name

    while read -r id; do
        [[ "$id" =~ ^[0-9]+$ ]] || continue

        info="$(wpctl inspect "$id" 2>/dev/null)" || continue

        # Make absolutely sure this is an audio sink.
        grep -q 'media.class = "Audio/Sink"' <<< "$info" || continue

        # Match the exact ALSA card number.
        if grep -q "alsa.card = \"$card_number\"" <<< "$info"; then

            node_name="$(
                grep -m1 'node.name = ' <<< "$info" |
                sed -E 's/.*node.name = "(.*)"/\1/'
            )"

            if [[ -n "$node_name" ]]; then
                printf '%s\n' "$node_name"
                return 0
            fi
        fi

    done < <(get_sink_ids)

    return 1
}

# ------------------------------------------------------------
# Select device
# ------------------------------------------------------------

select_device() {
    local args=()
    local i

    for i in "${!DEVICES[@]}"; do
        args+=(
            "$i"
            "${DEVICES[$i]}"
        )
    done

    SELECTED_DEVICE="$(
        dialog \
            --title "PipeWire Audio Configurator" \
            --menu \
            "Select an audio output device:" \
            20 100 10 \
            "${args[@]}" \
            2>&1 >/dev/tty
    )" || exit 0
}

# ------------------------------------------------------------
# Select format
# ------------------------------------------------------------

select_format() {
    local index="$1"
    local args=()
    local format
    local i=0

    while read -r format; do
        [[ -n "$format" ]] || continue

        args+=(
            "$i"
            "$format"
        )

        ((i++))
    done <<< "${DEVICE_FORMATS[$index]}"

    local choice

    choice="$(
        dialog \
            --title "Select PCM Format" \
            --menu \
            "Device:

${DEVICES[$index]}

Select format:" \
            16 70 6 \
            "${args[@]}" \
            2>&1 >/dev/tty
    )" || exit 0

    SELECTED_FORMAT="$(
        printf '%s\n' "${DEVICE_FORMATS[$index]}" |
        sed -n "$((choice + 1))p"
    )"
}

# ------------------------------------------------------------
# Select sample rate
# ------------------------------------------------------------

select_rate() {
    local index="$1"
    local args=()
    local rate

    while read -r rate; do
        [[ -n "$rate" ]] || continue

        args+=(
            "$rate"
            "$(rate_display "$rate")"
        )

    done < <(
        printf '%s\n' "${DEVICE_RATES[$index]}" |
        sort -nu
    )

    SELECTED_RATE="$(
        dialog \
            --title "Select Sample Rate" \
            --menu \
            "Format: $SELECTED_FORMAT

Select sample rate:" \
            20 70 10 \
            "${args[@]}" \
            2>&1 >/dev/tty
    )" || exit 0
}

# ------------------------------------------------------------
# Select custom device name
# ------------------------------------------------------------

select_custom_name() {
    local choice

    choice="$(
        dialog \
            --title "Device Name" \
            --menu \
            "Current device:

${DEVICES[$SELECTED_DEVICE]}

Would you like to give this device a custom name?" \
            14 80 2 \
            "1" "Keep default name" \
            "2" "Set custom name" \
            2>&1 >/dev/tty
    )" || exit 0

    case "$choice" in
        1)
            SELECTED_CUSTOM_NAME=""
            ;;

        2)
            SELECTED_CUSTOM_NAME="$(
                dialog \
                    --title "Custom Device Name" \
                    --inputbox \
"Enter the name that PipeWire should display for this device:

Current device:
${DEVICES[$SELECTED_DEVICE]}" \
                    12 80 \
                    2>&1 >/dev/tty
            )" || exit 0

            if [[ -z "$SELECTED_CUSTOM_NAME" ]]; then
                dialog \
                    --title "Invalid Name" \
                    --msgbox \
                    "The custom device name cannot be empty.

The default device name will be used instead." \
                    10 70

                SELECTED_CUSTOM_NAME=""
            fi
            ;;
    esac
}

# ------------------------------------------------------------
# Generate configuration
# ------------------------------------------------------------

generate_config() {
    local node_name="$1"

    OUTPUT_FILE="$(make_config_filename "${DEVICES[$SELECTED_DEVICE]}")"

    cat > "$OUTPUT_FILE" <<EOF
# PipeWire Audio Configurator
# Generated configuration
#
# Device:
# ${DEVICES[$SELECTED_DEVICE]}
#
# ALSA card:
# ${DEVICE_CARDS[$SELECTED_DEVICE]}
#
# PipeWire node:
# $node_name
#
# Format:
# $SELECTED_FORMAT
#
# Sample rate:
# $SELECTED_RATE
EOF

    if [[ -n "$SELECTED_CUSTOM_NAME" ]]; then
        cat >> "$OUTPUT_FILE" <<EOF
#
# Custom device name:
# $SELECTED_CUSTOM_NAME
EOF
    fi

    cat >> "$OUTPUT_FILE" <<EOF

monitor.alsa.rules = [
  {
    matches = [
      {
        node.name = "$node_name"
      }
    ]
    actions = {
      update-props = {
EOF

    if [[ -n "$SELECTED_CUSTOM_NAME" ]]; then
        printf '        node.description = "%s"\n' "$SELECTED_CUSTOM_NAME" >> "$OUTPUT_FILE"
    fi

    cat >> "$OUTPUT_FILE" <<EOF
        audio.format = "$SELECTED_FORMAT"
        audio.rate = $SELECTED_RATE
      }
    }
  }
]
EOF
}

# ------------------------------------------------------------
# Ask whether to restart PipeWire/WirePlumber
# ------------------------------------------------------------

ask_restart() {
    if dialog \
        --title "Apply Configuration" \
        --yesno \
"Configuration has been installed:

$OUTPUT_FILE

Would you like to restart PipeWire and WirePlumber now?

This will immediately apply the new configuration." \
        12 75
    then

        if systemctl --user restart pipewire pipewire-pulse wireplumber; then

            dialog \
                --title "Configuration Applied" \
                --msgbox \
"PipeWire and WirePlumber were restarted successfully.

The new configuration is now active." \
                10 70

        else

            dialog \
                --title "Restart Failed" \
                --msgbox \
"Configuration was installed successfully, but the PipeWire/WirePlumber restart failed.

You can try manually with:

systemctl --user restart pipewire pipewire-pulse wireplumber" \
                14 80

            return 1
        fi

    else

        dialog \
            --title "Configuration Installed" \
            --msgbox \
"Configuration was installed successfully.

It has NOT been applied yet.

To apply it later, run:

systemctl --user restart pipewire pipewire-pulse wireplumber" \
            14 80
    fi
}

# ------------------------------------------------------------
# Main
# ------------------------------------------------------------

create_config_directory

discover_devices

if (( ${#DEVICES[@]} == 0 )); then
    die "No PCM playback devices were found."
fi

select_device

select_format "$SELECTED_DEVICE"

select_rate "$SELECTED_DEVICE"

select_custom_name

CARD_NUMBER="${DEVICE_CARDS[$SELECTED_DEVICE]}"

NODE_NAME="$(
    find_node_for_device "$CARD_NUMBER"
)"

if [[ -z "$NODE_NAME" ]]; then

    dialog \
        --title "PipeWire Node Not Found" \
        --msgbox \
"ALSA device was detected, but its PipeWire Audio/Sink node could not be mapped.

Device:
${DEVICES[$SELECTED_DEVICE]}

ALSA card:
$CARD_NUMBER

No configuration was written." \
        14 80

    exit 1
fi

generate_config "$NODE_NAME"

CUSTOM_NAME_TEXT="Default"

if [[ -n "$SELECTED_CUSTOM_NAME" ]]; then
    CUSTOM_NAME_TEXT="$SELECTED_CUSTOM_NAME"
fi

dialog \
    --title "Configuration Generated" \
    --msgbox \
"Configuration installed successfully.

Device:
${DEVICES[$SELECTED_DEVICE]}

ALSA card:
$CARD_NUMBER

Format:
$SELECTED_FORMAT

Sample rate:
$(rate_display "$SELECTED_RATE")

Device name:
$CUSTOM_NAME_TEXT

PipeWire node:
$NODE_NAME

Output:
$OUTPUT_FILE" \
    18 80

ask_restart

exit 0
