#!/bin/bash

# Fixed field mappings generator for LibrePortal
# Creates apps-field-mappings.json file with PORT_1 through PORT_20

webuiCreateAppFieldMappings() {
    local output_dir="$1"
    
    if [[ -z "$output_dir" ]]; then
        isError " Output directory not specified"
    fi
    
    echo "Creating apps-field-mappings.json..."
    
    # Create output directory if it doesn't exist
    runFileOp mkdir -p "$output_dir"
    
    # Create temp file first, then atomic move
    local temp_file="$(mktemp)"
    local final_file="${output_dir}/apps-field-mappings.json"
    
    # Generate the complete JSON in one go
    cat > "$temp_file" << 'JSONEOF'
{
  "fields": {
    "APP_NAME": {
      "category": "advanced",
      "label": "Application Name",
      "type": "text",
      "tooltip": "The internal name for this application (advanced: only change if you know what you're doing)",
      "required": true,
      "advanced": true
    },
    "TITLE": {
      "category": "display",
      "label": "Display Title",
      "type": "text",
      "tooltip": "The display name shown in the interface",
      "required": true,
      "readonly": true,
      "nonEditable": true,
      "displayInfo": true
    },
    "DESCRIPTION": {
      "category": "display",
      "label": "Description",
      "type": "textarea",
      "tooltip": "A brief description of what this application does",
      "rows": 3,
      "readonly": true,
      "nonEditable": true,
      "displayInfo": true
    },
    "LONG_DESCRIPTION": {
      "category": "display",
      "label": "Detailed Description",
      "type": "textarea",
      "tooltip": "A detailed description of this application",
      "rows": 5,
      "readonly": true,
      "nonEditable": true,
      "displayInfo": true
    },
    "CATEGORY": {
      "category": "display",
      "label": "Category",
      "type": "text",
      "tooltip": "The category this application belongs to",
      "readonly": true,
      "nonEditable": true,
      "displayInfo": true
    },
    "URL": {
      "category": "display",
      "label": "Project URL",
      "type": "text",
      "tooltip": "URL to the project's website or repository",
      "readonly": true,
      "nonEditable": true,
      "displayInfo": true
    },
    "DOMAIN": {
      "category": "network",
      "label": "Domain",
      "type": "number",
      "tooltip": "The domain number for multi-domain setups",
      "min": 1,
      "default": 1
    },
JSONEOF

    # Add PORT_1 through PORT_20 dynamically
    for i in {1..20}; do
        cat >> "$temp_file" << PORTEOF
    "PORT_${i}": {
      "category": "ports",
      "label": "Port ${i}",
      "type": "text",
      "tooltip": "Port ${i} for this application"
    },
PORTEOF
    done

    # Add the remaining fields
    cat >> "$temp_file" << 'RESTEOF'
    "PORTS": {
      "category": "network",
      "label": "Port Configuration",
      "type": "text",
      "tooltip": "Port configuration for the application",
      "placeholder": "8080:80"
    },
    "WHITELIST": {
      "category": "access",
      "label": "Enable Whitelist",
      "type": "checkbox",
      "tooltip": "Enable IP whitelist for access control (requires Traefik to be installed)",
      "default": false,
      "requiresService": "traefik",
      "disabledReason": "Traefik needs to be installed."
    },
    "NETWORK": {
      "category": "network",
      "label": "Network Mode",
      "type": "select",
      "tooltip": "Route this app's outbound traffic through Gluetun (VPN)",
      "default": "default",
      "options": [
        {"value": "default", "label": "Default network"},
        {"value": "gluetun", "label": "Through Gluetun (VPN)"}
      ],
      "requiresService": "gluetun",
      "disabledReason": "Gluetun needs to be installed.",
      "categoryAllowlist": ["media", "downloads", "torrent", "usenet", "arr"],
      "requirementOverride": "GLUETUN_FOR_ALL"
    },
    "AUTHELIA": {
      "category": "access",
      "label": "Authelia Integration",
      "type": "checkbox",
      "tooltip": "Enable Authelia authentication (requires Authelia to be installed)",
      "default": false,
      "requiresService": "authelia",
      "disabledReason": "Authelia needs to be installed"
    },
    "HEADSCALE": {
      "category": "access",
      "label": "Headscale Integration",
      "type": "checkbox",
      "tooltip": "Enable Headscale integration (requires Headscale to be installed)",
      "default": false,
      "requiresService": "headscale",
      "disabledReason": "Headscale needs to be installed"
    },
    "BACKUP": {
      "category": "general",
      "label": "Enable Backups",
      "type": "checkbox",
      "tooltip": "Enable automatic backups for this application",
      "default": false
    },
    "BACKUP_STRATEGY": {
      "category": "advanced",
      "label": "Backup Strategy",
      "type": "select",
      "tooltip": "How this app is quiesced before its backup snapshot. Automatic picks the safest zero-downtime method available and falls back to stopping the app if a live dump ever fails. The Live option appears only for apps that can be backed up consistently without stopping (a database we can dump, or a static app marked live-safe).",
      "advanced": true,
      "options": [
        {"value": "auto", "label": "Automatic (recommended)"},
        {"value": "stop-snapshot-start", "label": "Stop → snapshot → start"},
        {"value": "pause-snapshot-unpause", "label": "Pause → snapshot → unpause"},
        {"value": "live", "label": "Live — no downtime"}
      ],
      "default": "auto"
    },
    "MONITORING": {
      "category": "features",
      "label": "Export metrics to Grafana",
      "type": "checkbox",
      "tooltip": "Scrape this app's metrics into Prometheus and provision its Grafana dashboards. Requires Prometheus and Grafana to be installed.",
      "default": false,
      "requiresServices": ["prometheus", "grafana"],
      "disabledReason": "Prometheus and Grafana need to be installed"
    },
    "HEALTHCHECK": {
      "category": "advanced",
      "label": "Health Check",
      "type": "checkbox",
      "tooltip": "Enable container health monitoring (advanced: affects container restart behavior)",
      "default": true,
      "advanced": true
    },
    "LOGGING": {
      "category": "features",
      "label": "Enable Logging",
      "type": "checkbox",
      "tooltip": "Enable detailed logging for this application",
      "default": true
    },
    "VERSION": {
      "category": "features",
      "label": "Application Version",
      "type": "text",
      "tooltip": "Specific version of the application to deploy"
    },
    "TIMEOUT": {
      "category": "features",
      "label": "Timeout (seconds)",
      "type": "number",
      "tooltip": "Timeout for application startup/shutdown",
      "min": 10,
      "default": 60
    },
    "THEME": {
      "category": "features",
      "label": "Theme",
      "type": "select",
      "tooltip": "Visual theme for the application",
      "options": [
        {"value": "light", "label": "Light"},
        {"value": "dark", "label": "Dark"},
        {"value": "auto", "label": "Auto"}
      ],
      "default": "auto"
    },
    "COMPOSE_FILE": {
      "category": "advanced",
      "label": "Compose File",
      "type": "select",
      "tooltip": "Docker Compose file to use (advanced: incorrect selection may break the application)",
      "advanced": true,
      "options": [
        {"value": "default", "label": "Default"},
        {"value": "custom", "label": "Custom"}
      ],
      "default": "default"
    },
    "404_SITE": {
      "category": "general",
      "label": "404 Redirect Site",
      "type": "text",
      "tooltip": "URL to redirect users to when they hit a 404"
    },
    "ABUSEIPDB_APIKEY": {
      "category": "general",
      "label": "AbuseIPDB API Key",
      "type": "password",
      "tooltip": "API key for AbuseIPDB integration"
    },
    "PIHOLE_ADMIN_PASSWORD": {
      "category": "general",
      "label": "Pi-hole Admin Password",
      "type": "password",
      "tooltip": "Password for accessing the Pi-hole admin interface"
    },
    "WIREGUARD_PASSWORD": {
      "category": "general",
      "label": "WireGuard Admin Password",
      "type": "password",
      "tooltip": "Password for the wg-easy admin interface."
    },
    "PIHOLE_WEB_THEME": {
      "category": "general",
      "label": "Pi-hole Web Theme",
      "type": "select",
      "tooltip": "Visual theme for the Pi-hole web interface",
      "options": [
        {"value": "default-dark", "label": "Default Dark"},
        {"value": "default-light", "label": "Default Light"},
        {"value": "default-auto", "label": "Default Auto"},
        {"value": "high-contrast", "label": "High Contrast"},
        {"value": "high-contrast-dark", "label": "High Contrast Dark"},
        {"value": "high-contrast-light", "label": "High Contrast Light"},
        {"value": "high-contrast-auto", "label": "High Contrast Auto"},
        {"value": "lcars", "label": "LCARS"},
        {"value": "lcars-color", "label": "LCARS Color"}
      ],
      "default": "default-dark"
    },
    "SPEEDTEST_PASSWORD_ENABLED": {
      "category": "general",
      "label": "Speedtest Password Protection",
      "type": "checkbox",
      "tooltip": "Enable password protection for the speedtest interface",
      "default": false
    },
    "SPEEDTEST_PASSWORD": {
      "category": "general",
      "label": "Speedtest Password",
      "type": "password",
      "tooltip": "Password for accessing the speedtest interface (when enabled)"
    },
    "OWNCLOUD_ADMIN_USERNAME": {
      "category": "general",
      "label": "OwnCloud Admin Username",
      "type": "text",
      "tooltip": "Username for the OwnCloud administrator account"
    },
    "OWNCLOUD_ADMIN_PASSWORD": {
      "category": "general",
      "label": "OwnCloud Admin Password",
      "type": "password",
      "tooltip": "Password for the OwnCloud administrator account"
    },
    "NEXTCLOUD_ADMIN_USER": {
      "category": "general",
      "label": "Nextcloud Admin Username",
      "type": "text",
      "tooltip": "Username seeded into Nextcloud by the auto-install on first boot",
      "default": "admin"
    },
    "NEXTCLOUD_ADMIN_PASSWORD": {
      "category": "general",
      "label": "Nextcloud Admin Password",
      "type": "password",
      "tooltip": "Password seeded into Nextcloud by the auto-install on first boot (auto-generated; persists across reinstalls)"
    },
    "NEXTCLOUD_DB_NAME": {
      "category": "advanced",
      "label": "Database Name",
      "type": "text",
      "tooltip": "MariaDB schema Nextcloud uses (internal to the docker network)",
      "advanced": true,
      "default": "nextcloud"
    },
    "NEXTCLOUD_DB_USER": {
      "category": "advanced",
      "label": "Database User",
      "type": "text",
      "tooltip": "MariaDB account Nextcloud connects with (internal to the docker network)",
      "advanced": true,
      "default": "nextcloud"
    },
    "NEXTCLOUD_DB_PASSWORD": {
      "category": "advanced",
      "label": "Database Password",
      "type": "password",
      "tooltip": "MariaDB password for the Nextcloud user (auto-generated; persists across reinstalls)",
      "advanced": true
    },
    "NEXTCLOUD_DB_ROOT_PASSWORD": {
      "category": "advanced",
      "label": "Database Root Password",
      "type": "password",
      "tooltip": "MariaDB root password (auto-generated; kept separate from the app user password so root can be left alone if you rotate the app account)",
      "advanced": true
    },
    "VAULTWARDEN_ADMIN_TOKEN": {
      "category": "general",
      "label": "Vaultwarden Admin Token",
      "type": "password",
      "tooltip": "Admin token for accessing the Vaultwarden admin interface"
    },
    "VAULTWARDEN_SIGNUPS_ALLOWED": {
      "category": "general",
      "label": "Vaultwarden Allow Signups",
      "type": "checkbox",
      "tooltip": "Allow new user registrations for Vaultwarden",
      "default": false
    },
    "ADGUARD_USER": {
      "category": "general",
      "label": "AdGuard Admin Username",
      "type": "text",
      "tooltip": "Username for the AdGuardHome admin web UI",
      "default": "admin"
    },
    "ADGUARD_PASSWORD": {
      "category": "general",
      "label": "AdGuard Admin Password",
      "type": "password",
      "tooltip": "Password for the AdGuardHome admin web UI"
    },
    "BOOKSTACK_ADMIN_EMAIL": {
      "category": "general",
      "label": "Bookstack Admin Email",
      "type": "email",
      "tooltip": "Email used for the Bookstack admin account",
      "default": "admin@admin.com"
    },
    "BOOKSTACK_ADMIN_PASSWORD": {
      "category": "general",
      "label": "Bookstack Admin Password",
      "type": "password",
      "tooltip": "Password for the Bookstack admin account"
    },
    "AUTHELIA_ADMIN_USERNAME": {
      "category": "general",
      "label": "Authelia Admin Username",
      "type": "text",
      "tooltip": "Username for the seeded Authelia admin account",
      "default": "admin"
    },
    "AUTHELIA_ADMIN_PASSWORD": {
      "category": "general",
      "label": "Authelia Admin Password",
      "type": "password",
      "tooltip": "Password for the seeded Authelia admin account"
    },
    "GLUETUN_CONTROL_SERVER_API_KEY": {
      "category": "general",
      "label": "Control Server API Key",
      "type": "password",
      "tooltip": "API key for the gluetun HTTP control server (blank disables auth)"
    },
    "GLUETUN_VPN_SERVICE_PROVIDER": {
      "category": "general",
      "label": "VPN Provider",
      "type": "select",
      "tooltip": "VPN provider to route traffic through",
      "default": "mullvad"
    },
    "GLUETUN_VPN_TYPE": {
      "category": "general",
      "label": "VPN Protocol",
      "type": "select",
      "tooltip": "VPN protocol supported by the selected provider",
      "default": "wireguard"
    },
    "GLUETUN_VPN_COUNTRIES": {
      "category": "general",
      "label": "VPN Countries",
      "type": "text",
      "tooltip": "Comma-separated countries to allow",
      "placeholder": "Switzerland,Sweden"
    },
    "GLUETUN_OPENVPN_USER": {
      "category": "general",
      "label": "OpenVPN Username",
      "type": "text",
      "tooltip": "OpenVPN account username",
      "hideByDefault": true
    },
    "GLUETUN_OPENVPN_PASSWORD": {
      "category": "general",
      "label": "OpenVPN Password",
      "type": "password",
      "tooltip": "OpenVPN account password",
      "hideByDefault": true
    },
    "GLUETUN_WIREGUARD_PRIVATE_KEY": {
      "category": "general",
      "label": "WireGuard Private Key",
      "type": "password",
      "tooltip": "WireGuard private key",
      "hideByDefault": true
    },
    "GLUETUN_WIREGUARD_ADDRESSES": {
      "category": "general",
      "label": "WireGuard Address",
      "type": "text",
      "tooltip": "WireGuard interface address",
      "placeholder": "10.64.0.2/32",
      "hideByDefault": true
    },
    "GLUETUN_HEALTH_TARGETS": {
      "category": "network",
      "label": "Health Check Sites",
      "type": "text",
      "tooltip": "Sites Gluetun checks to confirm the VPN is working. Comma-separated host:port.",
      "placeholder": "mullvad.net:443,eff.org:443",
      "default": "mullvad.net:443,eff.org:443"
    },
    "GLUETUN_HEALTH_ICMP_IPS": {
      "category": "network",
      "label": "Health Check IPs",
      "type": "text",
      "tooltip": "IPs Gluetun pings to confirm the VPN is working. Comma-separated.",
      "placeholder": "9.9.9.9",
      "default": "9.9.9.9"
    },

    "DASHY_THEME": {
      "category": "general",
      "label": "Dashboard Theme",
      "type": "select",
      "tooltip": "Built-in Dashy theme applied to the dashboard.",
      "options": [
        {"value": "Nord-Frost",  "label": "Nord Frost (default)"},
        {"value": "Nord",        "label": "Nord"},
        {"value": "Dracula",     "label": "Dracula"},
        {"value": "Material",    "label": "Material"},
        {"value": "Material-Dark", "label": "Material Dark"},
        {"value": "Cyberpunk",   "label": "Cyberpunk"},
        {"value": "Hacker-Terminal", "label": "Hacker Terminal"},
        {"value": "High-Contrast-Light", "label": "High Contrast Light"},
        {"value": "High-Contrast-Dark",  "label": "High Contrast Dark"},
        {"value": "Minimal-Light", "label": "Minimal Light"},
        {"value": "Minimal-Dark",  "label": "Minimal Dark"},
        {"value": "Adventure",   "label": "Adventure"},
        {"value": "Thebe",       "label": "Thebe"},
        {"value": "Tiger",       "label": "Tiger"},
        {"value": "Vaporwave",   "label": "Vaporwave"}
      ],
      "default": "Nord-Frost"
    },
    "DASHY_PAGE_TITLE": {
      "category": "general",
      "label": "Dashboard Title",
      "type": "text",
      "tooltip": "Page header title. Leave blank to use the default shown in the input below.",
      "placeholder": "Dashy - LibrePortal - __INSTALL_NAME__"
    },
    "DASHY_PAGE_DESCRIPTION": {
      "category": "general",
      "label": "Dashboard Description",
      "type": "text",
      "tooltip": "Subtitle shown under the dashboard title. Blank uses the default.",
      "placeholder": "Welcome to your LibrePortal Dashy dashboard!"
    },
    "DASHY_LAYOUT": {
      "category": "general",
      "label": "Item Layout",
      "type": "select",
      "tooltip": "How tiles are arranged within sections.",
      "options": [
        {"value": "auto",       "label": "Auto"},
        {"value": "horizontal", "label": "Horizontal"},
        {"value": "vertical",   "label": "Vertical"}
      ],
      "default": "auto"
    },
    "DASHY_ICON_SIZE": {
      "category": "general",
      "label": "Icon Size",
      "type": "select",
      "tooltip": "Tile icon size on the dashboard.",
      "options": [
        {"value": "small",  "label": "Small"},
        {"value": "medium", "label": "Medium"},
        {"value": "large",  "label": "Large"}
      ],
      "default": "medium"
    },
    "DASHY_OPEN_TARGET": {
      "category": "general",
      "label": "Tile Click Behaviour",
      "type": "select",
      "tooltip": "Where a tile opens when clicked.",
      "options": [
        {"value": "newtab",    "label": "New tab"},
        {"value": "sametab",   "label": "Same tab"},
        {"value": "modal",     "label": "Modal preview"},
        {"value": "workspace", "label": "Workspace view"}
      ],
      "default": "newtab"
    },
    "DASHY_STATUS_CHECK": {
      "category": "features",
      "label": "Status Checks",
      "type": "checkbox",
      "tooltip": "Ping each tile periodically and show an up/down indicator. Off by default to avoid hammering apps.",
      "default": false
    },
    "DASHY_SHORTCUTS": {
      "category": "advanced",
      "label": "Dashboard Shortcuts (raw)",
      "type": "text",
      "tooltip": "Comma-separated list of <service>:<link_index> IDs to surface as shortcuts. Managed via the Tools tab → Manage Shortcuts; editing here is advanced.",
      "advanced": true
    },

    "NOTIFY_EMAIL": {
      "category": "notifications",
      "label": "📧 Email",
      "type": "checkbox",
      "tooltip": "Send notifications via SMTP email.",
      "default": false,
      "requiresGlobalMail": true,
      "disabledReason": "Configure mail in General settings (CFG_MAIL_ENABLED=true) before enabling email notifications."
    },
    "NOTIFY_EMAIL_TO": {
      "category": "notifications",
      "label": "Email To",
      "type": "text",
      "placeholder": "you@example.com",
      "tooltip": "Recipient address. Sender (CFG_MAIL_FROM) and SMTP credentials come from General settings.",
      "showWhen": { "NOTIFY_EMAIL": "true" }
    },

    "NOTIFY_DISCORD": {
      "category": "notifications",
      "label": "💬 Discord",
      "type": "checkbox",
      "tooltip": "Send notifications to a Discord channel via webhook.",
      "default": false
    },
    "NOTIFY_DISCORD_WEBHOOK": {
      "category": "notifications",
      "label": "Discord Webhook URL",
      "type": "text",
      "placeholder": "https://discord.com/api/webhooks/<id>/<token>",
      "showWhen": { "NOTIFY_DISCORD": "true" }
    },

    "NOTIFY_SLACK": {
      "category": "notifications",
      "label": "💬 Slack",
      "type": "checkbox",
      "tooltip": "Send notifications to a Slack channel via incoming webhook.",
      "default": false
    },
    "NOTIFY_SLACK_WEBHOOK": {
      "category": "notifications",
      "label": "Slack Webhook URL",
      "type": "text",
      "placeholder": "https://hooks.slack.com/services/...",
      "showWhen": { "NOTIFY_SLACK": "true" }
    },

    "NOTIFY_TELEGRAM": {
      "category": "notifications",
      "label": "📱 Telegram",
      "type": "checkbox",
      "tooltip": "Send notifications via a Telegram bot.",
      "default": false
    },
    "NOTIFY_TELEGRAM_TOKEN": {
      "category": "notifications",
      "label": "Telegram Bot Token",
      "type": "password",
      "placeholder": "123456:ABC-DEF...",
      "showWhen": { "NOTIFY_TELEGRAM": "true" }
    },
    "NOTIFY_TELEGRAM_CHAT_ID": {
      "category": "notifications",
      "label": "Telegram Chat ID",
      "type": "text",
      "placeholder": "12345678",
      "showWhen": { "NOTIFY_TELEGRAM": "true" }
    },

    "NOTIFY_GOTIFY": {
      "category": "notifications",
      "label": "🔔 Gotify",
      "type": "checkbox",
      "tooltip": "Send notifications to a self-hosted Gotify server.",
      "default": false
    },
    "NOTIFY_GOTIFY_URL": {
      "category": "notifications",
      "label": "Gotify Server URL",
      "type": "text",
      "placeholder": "https://gotify.example.com",
      "showWhen": { "NOTIFY_GOTIFY": "true" }
    },
    "NOTIFY_GOTIFY_TOKEN": {
      "category": "notifications",
      "label": "Gotify App Token",
      "type": "password",
      "showWhen": { "NOTIFY_GOTIFY": "true" }
    },

    "NOTIFY_PUSHOVER": {
      "category": "notifications",
      "label": "🔔 Pushover",
      "type": "checkbox",
      "tooltip": "Send notifications via Pushover.",
      "default": false
    },
    "NOTIFY_PUSHOVER_TOKEN": {
      "category": "notifications",
      "label": "Pushover App Token",
      "type": "password",
      "showWhen": { "NOTIFY_PUSHOVER": "true" }
    },
    "NOTIFY_PUSHOVER_USER": {
      "category": "notifications",
      "label": "Pushover User Key",
      "type": "password",
      "showWhen": { "NOTIFY_PUSHOVER": "true" }
    },

    "NOTIFY_NTFY": {
      "category": "notifications",
      "label": "🔔 Ntfy",
      "type": "checkbox",
      "tooltip": "Send notifications to an ntfy.sh topic.",
      "default": false
    },
    "NOTIFY_NTFY_URL": {
      "category": "notifications",
      "label": "Ntfy Server URL",
      "type": "text",
      "placeholder": "https://ntfy.sh",
      "showWhen": { "NOTIFY_NTFY": "true" }
    },
    "NOTIFY_NTFY_TOPIC": {
      "category": "notifications",
      "label": "Ntfy Topic",
      "type": "text",
      "placeholder": "my-topic",
      "showWhen": { "NOTIFY_NTFY": "true" }
    },

    "AUTO_UPDATE": {
      "category": "general",
      "label": "Auto-update hub content",
      "type": "checkbox",
      "tooltip": "Pull parser/scenario updates from hub.crowdsec.net."
    },
    "COMMUNITY_BLOCKLIST": {
      "category": "general",
      "label": "Community blocklist (CAPI)",
      "type": "checkbox",
      "tooltip": "Subscribe + send anonymous attack signals."
    },
    "CONSOLE_ENROLL": {
      "category": "general",
      "label": "Enroll with SaaS Console",
      "type": "checkbox",
      "tooltip": "Connect agent to cloud app.crowdsec.net."
    },
    "CONSOLE_TOKEN": {
      "category": "general",
      "label": "SaaS Console Token",
      "type": "password",
      "tooltip": "Enrollment token from app.crowdsec.net.",
      "placeholder": "paste token",
      "showWhen": { "CONSOLE_ENROLL": "true" }
    },
    "BOUNCER": {
      "category": "general",
      "label": "Traefik bouncer middleware",
      "type": "checkbox",
      "tooltip": "Attach bouncer to every Traefik public route."
    },
    "ADMIN_EMAIL": {
      "category": "general",
      "label": "Admin Email",
      "type": "text",
      "tooltip": "Login email for the app's admin account."
    },
    "ADMIN_PASSWORD": {
      "category": "general",
      "label": "Admin Password",
      "type": "password",
      "tooltip": "Randomized on first install. Changing it requires a reinstall."
    },

    "HOST_INSTALL": {
      "category": "advanced",
      "label": "Host install",
      "type": "checkbox",
      "tooltip": "Runs on host (apt + systemd), not Docker.",
      "readonly": true
    },
    "HOST_PACKAGE": {
      "category": "advanced",
      "label": "Host package",
      "type": "text",
      "tooltip": "dpkg package name.",
      "readonly": true
    },
    "HOST_SERVICE": {
      "category": "advanced",
      "label": "Primary unit",
      "type": "text",
      "tooltip": "systemd unit for stop/restart.",
      "readonly": true
    },
    "HOST_SERVICES": {
      "category": "advanced",
      "label": "All units",
      "type": "text",
      "tooltip": "Feeds Services + Logs tabs.",
      "readonly": true
    },
    "HOST_LOG_FILES": {
      "category": "advanced",
      "label": "Log files",
      "type": "textarea",
      "rows": 2,
      "tooltip": "<unit>|<path>,... mapping."
    },
    "LAPI_HOST": {
      "category": "advanced",
      "label": "LAPI listen",
      "type": "text",
      "tooltip": "Local API bind address."
    },
    "BOUNCER_NAME_TRAEFIK": {
      "category": "advanced",
      "label": "Traefik bouncer name",
      "type": "text",
      "tooltip": "Registered via cscli bouncers add."
    },
    "TRAEFIK_LAPI_KEY": {
      "category": "advanced",
      "label": "Traefik bouncer key",
      "type": "password",
      "tooltip": "Auto-generated. Use rotate Tools action to change.",
      "readonly": true
    }
  }
}
RESTEOF

    # Substitute live-runtime placeholders the heredoc can't compute
    # (single-quoted heredoc → no $var expansion). Currently:
    #   __INSTALL_NAME__  → CFG_INSTALL_NAME
    if [ $? -eq 0 ]; then
        local install_name_safe
        install_name_safe=$(printf '%s' "${CFG_INSTALL_NAME:-LibrePortal}" \
            | sed -e 's/[\/&]/\\&/g' -e 's/"/\\"/g')
        sed -i "s|__INSTALL_NAME__|${install_name_safe}|g" "$temp_file"
        runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
    else
        rm -f "$temp_file" 2>/dev/null
    fi
}

# Call the function if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    webuiCreateAppFieldMappings "$1"
fi
