#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 Marco Allegretti <mightymarco4@gmail.com>
# SPDX-License-Identifier: EUPL-1.2

set -euo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_dir"

theme_dir="icons/org.shift.icons"
required_icons="$(mktemp)"
missing_icons="$(mktemp)"
broken_icons="$(mktemp)"
plasma_files="$(mktemp)"
trap 'rm -f "$required_icons" "$missing_icons" "$broken_icons" "$plasma_files"' EXIT

icon_name_prefix_re='^(accessories|activities|applications?|application-|appointment|archive|arrow-|audio|battery|bluetooth|bookmarks|call|camera-|chronometer|color|configure|computer|device-|dialog-|document|drive|edit-|emblem-|entry|favorites|flashlight|folder|format|games|games-|get-hot-new-stuff|go-|help|high-brightness|input-|internet|kcm|keyboard|klipper|krunner|layer|list-|low-brightness|mail|media-|mobile-|network-|notifications|object|office|org\.kde\.|osd-|phone|plasma|preferences-|printer|process|redshift|rotation-|search|security|settings-|software|speedometer|starred|start-here|system-|tablet|tools|touchpad|trash-|unknown|user|utilities|video|view|view-|virtual-|weather|widget-|window|zoom)'

fail() {
    printf '%s\n' "$1" >&2
    exit 1
}

require_line() {
    local file="$1"
    local pattern="$2"
    local message="$3"

    grep -Eq "$pattern" "$file" || fail "$message"
}

icon_exists() {
    local icon_name="$1"

    [[ -e "$theme_dir/preferences/scalable/$icon_name.svg" ]] && return 0
    [[ -e "$theme_dir/actions/symbolic/$icon_name.svg" ]] && return 0
    [[ -e "$theme_dir/places/symbolic/$icon_name.svg" ]] && return 0
    [[ -e "$theme_dir/apps/scalable/$icon_name.svg" ]] && return 0
    return 1
}

require_directory_order() {
    local directories

    directories="$(awk -F= '$1 == "Directories" { print $2 }' "$theme_dir/index.theme")"
    [[ "$directories" == preferences/scalable,*actions/symbolic* ]] \
        || fail "org.shift.icons must list preferences/scalable before actions/symbolic so System Settings category icons do not resolve to symbolic action aliases"
}

check_systemsettings_category_icons() {
    [[ -d /usr/share/systemsettings/categories ]] || return 0

    local missing_preferences

    missing_preferences="$(grep -Rh '^Icon=' /usr/share/systemsettings/categories 2>/dev/null \
        | cut -d= -f2- \
        | sort -u \
        | while IFS= read -r icon_name; do
            case "$icon_name" in
                preferences-*)
                    [[ -e "$theme_dir/preferences/scalable/$icon_name.svg" ]] || printf '%s\n' "$icon_name"
                    ;;
            esac
        done)"

    [[ -z "$missing_preferences" ]] || {
        printf 'System Settings category icons must exist in preferences/scalable so they are not shadowed by symbolic action aliases:\n' >&2
        printf '%s\n' "$missing_preferences" | sed 's/^/  - /' >&2
        exit 1
    }
}

check_systemsettings_module_icons() {
    compgen -G '/usr/share/applications/kcm_*.desktop' >/dev/null || return 0

    local missing_preferences

    missing_preferences="$({ grep -Rh '^Icon=' /usr/share/applications/kcm_*.desktop 2>/dev/null || true; } \
        | cut -d= -f2- \
        | sort -u \
        | while IFS= read -r icon_name; do
            [[ -e "$theme_dir/preferences/scalable/$icon_name.svg" ]] || printf '%s\n' "$icon_name"
        done)"

    [[ -z "$missing_preferences" ]] || {
        printf 'System Settings module icons must exist in preferences/scalable so KCM list entries do not resolve to dark symbolic glyphs:\n' >&2
        printf '%s\n' "$missing_preferences" | sed 's/^/  - /' >&2
        exit 1
    }
}

hicolor_fallback_exists() {
    local icon_name="$1"

    case "$icon_name" in
        start-here-shift)
            [[ -e icons/sc-places-start-here-shift.svg ]]
            ;;
        *)
            return 1
            ;;
    esac
}

filter_icon_names() {
    awk 'length > 0 && $0 !~ /[[:space:]]/ && $0 !~ /\// && $0 !~ /[][{}();:%>]/ && $0 !~ /^#/ && $0 !~ /\.(qml|png|jpg|jpeg|svg|desktop|service|json|xml|wav|ogg)$/ && $0 !~ /-$/ { print }' \
        | grep -E "$icon_name_prefix_re"
}

collect_installed_plasma_icon_names() {
    command -v rpm >/dev/null || return 0

    local package

    for package in \
        plasma6-desktop \
        plasma6-workspace \
        plasma6-mobile \
        plasma6-nm \
        plasma6-pa \
        powerdevil6 \
        kscreen6 \
        bluedevil6 \
        systemsettings6 \
        kde-cli-tools6 \
        kwin6 \
        kactivitymanagerd6; do
        rpm -ql "$package" 2>/dev/null || true
    done \
        | awk '/\.(qml|js|json|desktop|actions)$/ || /metadata\.json$/ { print }' \
        | { grep -v '^/usr/share/icons/' || true; } \
        | sort -u > "$plasma_files"

    [[ -s "$plasma_files" ]] || return 0

    xargs -r perl -ne 'if (/^(?:Icon|IconName|X-KDE-PluginInfo-Icon)=(.+)$/) { print "$1\n"; } while (/"Icon"\s*:\s*"([^"]+)"/g) { print "$1\n"; } while (/(?:\bicon\b|icon\.name|iconName|iconSource|fallbackIconName|compactRepresentationIcon|categoryIcon|source)\s*:\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; } while (/(?:setIconName|iconName)\s*\(\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; }' < "$plasma_files" \
        | filter_icon_names || true
}

collect_repo_icon_names() {
    find \
        components/mobileshell/qml \
        containments/homescreens/folio/qml \
        containments/homescreens/halcyon/qml \
        containments/taskpanel/qml \
        initialstart \
        kcms \
        kwin \
        lookandfeel/contents/logout \
        quicksettings \
        -type f \( -name '*.qml' -o -name '*.js' -o -name '*.json' -o -name '*.desktop' -o -name '*.actions' -o -name '*.notifyrc' \) -print0 \
        | xargs -0 perl -ne 'if (/^(?:Icon|IconName|X-KDE-PluginInfo-Icon)=(.+)$/) { print "$1\n"; } while (/"Icon"\s*:\s*"([^"]+)"/g) { print "$1\n"; } while (/(?:\bicon\b|icon\.name|iconName|iconSource|fallbackIconName|compactRepresentationIcon|categoryIcon|source)\s*:\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; } while (/(?:setIconName|iconName)\s*\(\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; }' \
        | filter_icon_names || true
}

[[ -d "$theme_dir" ]] || fail "Missing Shift icon theme directory: $theme_dir"

while IFS= read -r -d '' icon_path; do
    [[ -e "$icon_path" ]] || printf '%s\n' "$icon_path"
done < <(find "$theme_dir" -type l -print0) > "$broken_icons"
if [[ -s "$broken_icons" ]]; then
    printf 'Broken Shift icon aliases:\n' >&2
    sed 's/^/  - /' "$broken_icons" >&2
    exit 1
fi

require_line lookandfeel/contents/defaults '^Theme=org\.shift\.icons$' \
    "look-and-feel defaults must select org.shift.icons"
require_line "$theme_dir/index.theme" '^Inherits=hicolor$' \
    "org.shift.icons must inherit only hicolor"
require_line "$theme_dir/index.theme" '^FollowsColorScheme=true$' \
    "org.shift.icons must follow the active color scheme so symbolic Places icons stay legible in dark and light themes"
require_line "$theme_dir/index.theme" '^Directories=.*actions/symbolic.*places/symbolic.*apps/scalable' \
    "org.shift.icons index.theme must list actions, places, and apps directories"
require_directory_order
require_line "$theme_dir/index.theme" '^\[actions/symbolic\]$' \
    "org.shift.icons index.theme is missing [actions/symbolic]"
require_line "$theme_dir/index.theme" '^\[places/symbolic\]$' \
    "org.shift.icons index.theme is missing [places/symbolic]"
require_line "$theme_dir/index.theme" '^\[apps/scalable\]$' \
    "org.shift.icons index.theme is missing [apps/scalable]"
require_line "$theme_dir/index.theme" '^\[preferences/scalable\]$' \
    "org.shift.icons index.theme is missing [preferences/scalable]"

[[ "$(readlink "$theme_dir/actions/symbolic/delete.svg")" == "window-close.svg" ]] \
    || fail "delete must resolve to the X close glyph for KWin Overview's virtual desktop delete button"
[[ "$(readlink "$theme_dir/actions/symbolic/delete-symbolic.svg")" == "window-close-symbolic.svg" ]] \
    || fail "delete-symbolic must resolve to the symbolic X close glyph for KWin Overview's virtual desktop delete button"
[[ "$(readlink "$theme_dir/actions/symbolic/edit-delete.svg")" == "window-close.svg" ]] \
    || fail "edit-delete must resolve to the X close glyph for KWin Overview's virtual desktop delete button"
[[ "$(readlink "$theme_dir/actions/symbolic/edit-delete-symbolic.svg")" == "window-close-symbolic.svg" ]] \
    || fail "edit-delete-symbolic must resolve to the symbolic X close glyph for KWin Overview's virtual desktop delete button"
[[ "$(readlink "$theme_dir/actions/symbolic/edit-delete-remove.svg")" == "window-close.svg" ]] \
    || fail "edit-delete-remove must resolve to the X close glyph for KWin Overview's virtual desktop delete button"
[[ "$(readlink "$theme_dir/actions/symbolic/edit-delete-remove-symbolic.svg")" == "window-close-symbolic.svg" ]] \
    || fail "edit-delete-remove-symbolic must resolve to the symbolic X close glyph for KWin Overview's virtual desktop delete button"

check_systemsettings_category_icons
check_systemsettings_module_icons

{
    collect_repo_icon_names
    collect_installed_plasma_icon_names

    if [[ -d /usr/share/icons ]]; then
        find /usr/share/icons -name 'network-*.svg' -o -name 'network-*.png' 2>/dev/null \
            | sed 's#.*/##; s/\.[^.]*$//' \
            | awk 'length > 0 { print }'
    fi

    cat <<'ICONS'
applications-all
applications-development
applications-education
applications-games
applications-graphics
applications-internet
applications-multimedia
applications-office
applications-other
applications-science
applications-system
applications-utilities
battery-000
battery-000-charging
battery-010
battery-010-charging
battery-020
battery-020-charging
battery-030
battery-030-charging
battery-040
battery-040-charging
battery-050
battery-050-charging
battery-060
battery-060-charging
battery-070
battery-070-charging
battery-080
battery-080-charging
battery-090
battery-090-charging
battery-100
battery-100-charging
battery-missing
network-bluetooth
network-bluetooth-activated
network-mobile-0
network-mobile-20
network-mobile-40
network-mobile-60
network-mobile-80
network-mobile-100
preferences-system
systemsettings
ICONS
} | sort -u > "$required_icons"

while IFS= read -r icon_name; do
    [[ -n "$icon_name" ]] || continue

    if icon_exists "$icon_name" || hicolor_fallback_exists "$icon_name"; then
        continue
    fi

    printf '%s\n' "$icon_name" >> "$missing_icons"
done < "$required_icons"

if [[ -s "$missing_icons" ]]; then
    printf 'Missing required Shift icons:\n' >&2
    sed 's/^/  - /' "$missing_icons" >&2
    exit 1
fi

# Quick-setting tiles render with Kirigami.Icon { isMask: true }; if their icon
# name resolves to a colored preferences/scalable tile it collapses to a solid
# rounded square. Require an explicit -symbolic suffix for any quick-setting
# icon name that also exists as a colored tile.
masked_collisions="$(mktemp)"
trap 'rm -f "$required_icons" "$missing_icons" "$broken_icons" "$plasma_files" "$masked_collisions"' EXIT

while IFS= read -r -d '' qml; do
    perl -ne 'if (/^\s*icon:\s*"([^"]+)"\s*$/) { print "$1\n"; }' "$qml"
done < <(find quicksettings -name 'main.qml' -print0) \
    | sort -u \
    | while IFS= read -r icon_name; do
        case "$icon_name" in
            *-symbolic) continue ;;
        esac
        [[ -e "$theme_dir/preferences/scalable/$icon_name.svg" ]] || continue
        printf '%s\n' "$icon_name"
    done > "$masked_collisions"

if [[ -s "$masked_collisions" ]]; then
    printf 'Quick-setting icon names must use a -symbolic suffix when a colored preferences/scalable tile exists, otherwise the masked tile renders as a solid square:\n' >&2
    sed 's/^/  - /' "$masked_collisions" >&2
    exit 1
fi