#!/bin/bash

# LibrePortal WebUI Config Generator
# Generates apps.json from container configuration files

# Single awk pass extracting SERVICE_TAG_1..SERVICE_TAG_9 from a compose
# file. Was 9 separate tagsManagerGetTagContent forks per app — across
# 31 apps that's 279 awk forks; now 1 per app.
# Output: tab-separated values for slots 1..9 (empty for missing).
_webuiReadServiceTags() {
    local file_path="$1"
    awk '
        BEGIN { for (i = 1; i <= 9; i++) v[i] = "" }
        {
            for (i = 1; i <= 9; i++) {
                if (v[i] != "") continue
                marker = "#LIBREPORTAL|SERVICE_TAG_" i "|"
                p = index($0, marker)
                if (p == 0) continue
                rest = substr($0, p + length(marker))
                n = match(rest, /[| \t\r]/)
                if (n > 0) rest = substr(rest, 1, n - 1)
                v[i] = rest
            }
        }
        END {
            for (i = 1; i <= 9; i++) printf "%s\t", v[i]
            print ""
        }
    ' "$file_path"
}

webuiGenerateLibrePortalConfig() {
    local testing_mode="$1"
    local specific_app="$2"
    local output_file="${containers_dir}libreportal/frontend/data/apps/generated/apps.json"
    local first_app=true
    local app_count=0

    isNotice "Generating LibrePortal apps.json from config files..."
    [[ -n "$specific_app" ]] && isNotice "Filtering to specific app: $specific_app"

    runFileOp mkdir -p "$(dirname "$output_file")"
    local temp_file="$(mktemp)"

    cat > "$temp_file" << 'EOF'
{
  "apps": [
    
EOF

    # Determine which apps to process
    local apps_to_process=()
    if [[ -n "$specific_app" ]]; then
        if [[ -d "$install_containers_dir/$specific_app" && "$specific_app" != "template" ]]; then
            apps_to_process=("$specific_app")
        else
            isError "App '$specific_app' not found or is template"
        fi
    else
        while IFS= read -r -d '' dir; do
            app_name="${dir##*/}"
            [[ "$app_name" != "template" ]] && apps_to_process+=("$app_name")
        done < <(find "$install_containers_dir" -maxdepth 1 -type d ! -path "$install_containers_dir" -print0 | sort -z)
    fi

    local app_name dir deployed_config_file config_file
    local title description long_description category url config_vars
    local host_install host_package
    local var_name var_value
    local APP_UPPER

    for app_name in "${apps_to_process[@]}"; do
        dir="$install_containers_dir/$app_name"
        deployed_config_file="$containers_dir/$app_name/$app_name.config"
        config_file="$dir/$app_name.config"
        [[ -f "$deployed_config_file" ]] && config_file="$deployed_config_file"
        [[ ! -f "$config_file" ]] && { isNotice "No config file for $app_name"; continue; }

        title=""; description=""; long_description=""; category=""; url=""; config_vars=""
        host_install=""; host_package=""
        APP_UPPER="${app_name^^}"

        # Single pass through the config file.
        while IFS='=' read -r var_name var_value || [[ -n "$var_name" ]]; do
            [[ -z "$var_name" || "$var_name" =~ ^[[:space:]]*# ]] && continue
            [[ "$var_name" == CFG_${APP_UPPER}_* ]] || continue

            # Strip surrounding quotes + \r, then trim whitespace.
            var_value="${var_value#\"}"
            var_value="${var_value%\"}"
            var_value="${var_value//$'\r'/}"
            var_value="${var_value#"${var_value%%[![:space:]]*}"}"
            var_value="${var_value%"${var_value##*[![:space:]]}"}"

            case "$var_name" in
                "CFG_${APP_UPPER}_TITLE")            title="$var_value" ;;
                "CFG_${APP_UPPER}_DESCRIPTION")      description="$var_value" ;;
                "CFG_${APP_UPPER}_LONG_DESCRIPTION") long_description="$var_value" ;;
                "CFG_${APP_UPPER}_CATEGORY")         category="$var_value" ;;
                "CFG_${APP_UPPER}_URL")
                    url="$var_value"
                    config_vars+="\"$var_name\": \"$var_value\","
                    ;;
                "CFG_${APP_UPPER}_HOST_INSTALL")
                    host_install="$var_value"
                    config_vars+="\"$var_name\": \"$var_value\","
                    ;;
                "CFG_${APP_UPPER}_HOST_PACKAGE")
                    host_package="$var_value"
                    config_vars+="\"$var_name\": \"$var_value\","
                    ;;
                *)
                    config_vars+="\"$var_name\": \"$var_value\","
                    ;;
            esac
        done < "$config_file"

        [[ -z "$title" || -z "$category" ]] && { isNotice "Skipping $app_name - missing essential data"; continue; }

        local is_installed="false"
        if [[ "$host_install" == "true" ]]; then
            if [[ -n "$host_package" ]] \
                && dpkg-query -W -f='${Status}' "$host_package" 2>/dev/null | grep -q "install ok installed"; then
                is_installed="true"
            fi
        else
            [[ -d "$containers_dir/$app_name" ]] && is_installed="true"
        fi

        # Categories: comma-list, lowercase + trimmed via bash param expansion
        # (was echo|tr|sed — 3 forks per category per app).
        local mapped_category="" mapped_categories_json=""
        if [[ -n "$category" ]]; then
            local _first_cat="" _cats_acc=""
            local _cat_arr _c
            IFS=',' read -ra _cat_arr <<< "$category"
            for _c in "${_cat_arr[@]}"; do
                _c="${_c,,}"
                _c="${_c#"${_c%%[![:space:]]*}"}"
                _c="${_c%"${_c##*[![:space:]]}"}"
                [[ -z "$_c" ]] && continue
                [[ -z "$_first_cat" ]] && _first_cat="$_c"
                [[ -n "$_cats_acc" ]] && _cats_acc+=","
                _cats_acc+="\"$_c\""
            done
            mapped_category="$_first_cat"
            mapped_categories_json="[$_cats_acc]"
        fi

        local full_name="$title"
        [[ -n "$description" ]] && full_name="$title - $description"
        full_name="${full_name//\\/\\\\}"
        full_name="${full_name//\"/\\\"}"
        long_description="${long_description//\\/\\\\}"
        long_description="${long_description//\"/\\\"}"

        local icon_file=""
        if [[ -f "$dir/$app_name.svg" ]]; then
            icon_file="$app_name.svg"
        elif [[ -f "$dir/$app_name.png" ]]; then
            icon_file="$app_name.png"
        fi
        if [[ -n "$icon_file" ]]; then
            local icons_apps_dir="${containers_dir}libreportal/frontend/icons/apps"
            runFileOp mkdir -p "$icons_apps_dir"
            cp "$dir/$icon_file" "$icons_apps_dir/$icon_file" 2>/dev/null
        fi

        # Service names — one awk pass instead of 9 grep|awk|head pipelines.
        local services_json=""
        local compose_file="$dir/docker-compose.yml"
        if [[ -f "$compose_file" ]]; then
            local _tags_line
            _tags_line=$(_webuiReadServiceTags "$compose_file")
            local _svc_arr _s
            IFS=$'\t' read -ra _svc_arr <<< "$_tags_line"
            for _s in "${_svc_arr[@]}"; do
                [[ -n "$_s" ]] && services_json+="\"$_s\","
            done
            services_json="${services_json%,}"
        fi

        # Live-backup capability: the app can be snapshotted live without
        # downtime only if it declares a dumpable database, or is explicitly
        # marked live-safe. The app config UI uses this to hide the "live"
        # strategy option where it would be unsafe.
        local backup_live_capable="false"
        if [[ -f "$compose_file" ]]; then
            if grep -qE '^[[:space:]]*libreportal\.backup\.(db|files)[[:space:]]*:' "$compose_file" 2>/dev/null \
               || grep -qE '^[[:space:]]*libreportal\.backup\.live[[:space:]]*:[[:space:]]*["'\'']?true' "$compose_file" 2>/dev/null; then
                backup_live_capable="true"
            fi
        fi

        if [[ "$first_app" == false ]]; then
            echo "    }," >> "$temp_file"
        fi
        first_app=false
        app_count=$((app_count + 1))

        # Build the app's field list, then emit with correct commas in one
        # pass. Eliminates the per-app `sed -i '$ s/,$//'` (31 forks across
        # the catalog) that was needed to undo a speculative trailing
        # comma when longDescription was absent.
        local fields=()
        fields+=("      \"name\": \"$full_name\"")
        fields+=("      \"command\": \"libreportal app install $app_name\"")
        fields+=("      \"category\": \"$mapped_category\"")
        [[ -n "$mapped_categories_json" ]] && fields+=("      \"categories\": $mapped_categories_json")
        fields+=("      \"installed\": $is_installed")
        fields+=("      \"backup_live_capable\": $backup_live_capable")
        [[ -n "$url" ]]            && fields+=("      \"url\": \"$url\"")
        [[ -n "$description" ]]    && fields+=("      \"description\": \"$description\"")
        [[ -n "$icon_file" ]]      && fields+=("      \"icon\": \"/icons/apps/$icon_file\"")
        [[ -n "$services_json" ]]  && fields+=("      \"services\": [$services_json]")
        if [[ -n "$config_vars" ]]; then
            fields+=("      \"config\": {"$'\n'"${config_vars%,}"$'\n'"      }")
        fi
        [[ -n "$long_description" ]] && fields+=("      \"longDescription\": \"$long_description\"")

        {
            echo "    {"
            local _n=${#fields[@]}
            local _i
            for _i in "${!fields[@]}"; do
                if [[ $_i -lt $((_n - 1)) ]]; then
                    echo "${fields[$_i]},"
                else
                    echo "${fields[$_i]}"
                fi
            done
        } >> "$temp_file"
    done

    echo "    }" >> "$temp_file"
    echo "  ]" >> "$temp_file"
    echo "}" >> "$temp_file"

    # Already root via start.sh — drop redundant sudo.
    if [ $? -eq 0 ]; then
        runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
    else
        rm -f "$temp_file" 2>/dev/null
    fi

    if [[ "$testing_mode" == "test" ]]; then
        isSuccessful "Generated apps.json (test mode, $app_count apps)"
    else
        isSuccessful "Generated apps.json ($app_count apps)"
    fi
}
