#!/bin/bash

# Build dashy's conf.yml from the same source of truth the WebUI uses
# for its URL buttons: apps-services.json. Each row in that file is a
# single (service, link) pair; the user picks which URLs to surface via
# CFG_DASHY_SHORTCUTS (CSV of "<service.name>:<link_index>" IDs, set by
# the Manage Shortcuts tool). Items are grouped by their app's category
# (read from the app's `# Category :` header).
appDashyUpdateConf()
{
    local conf_file="${containers_dir}dashy/etc/conf.yml"
    local services_json="${containers_dir}libreportal/frontend/data/apps/generated/apps-services.json"
    local icons_src_dir="${containers_dir}libreportal/frontend/icons/apps"

    # Don't check the apps DB here — appUpdateSpecifics calls us at
    # install step 6, BEFORE databaseInstallApp (step 8) writes the
    # row, so dockerCheckAppInstalled would say not_installed and bail.
    # Look at the actual docker container instead — if the container
    # exists or the install dir is present, generate the conf.
    if ! runFileOp docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qE '^(dashy|dashy-service)$' \
       && [[ ! -d "${containers_dir}dashy" ]]; then
        return 0
    fi

    isHeader "Dashy Config Generation"

    # Always seed a minimal conf so dashy never starts against an
    # empty user-data dir (which would 500 on / with "Configuration
    # could not be loaded"). Both branches below — the no-services
    # bootstrap and the full render — will overwrite this.
    _dashyWriteSkeleton() {
        local install_name="${CFG_INSTALL_NAME:-LibrePortal}"
        runFileOp mkdir -p "$(dirname "$conf_file")"
        runFileWrite "$conf_file" <<EOF
---
pageInfo:
  title: Dashy - LibrePortal - ${install_name}
  description: Welcome to your LibrePortal Dashy dashboard!
  navLinks:
  - title: Dashy GitHub
    path: http://github.com/Lissy93/dashy
  - title: Dashy Documentation
    path: http://dashy.to/docs

appConfig:
  theme: Nord-Frost

sections: []
EOF
        runFileOp chown "$docker_install_user:$docker_install_user" "$conf_file" 2>/dev/null || true
    }

    if ! command -v jq >/dev/null 2>&1; then
        isError "jq is required for dashy config generation but is not installed."
        # Still seed a skeleton so dashy can boot.
        [[ -f "$conf_file" ]] || _dashyWriteSkeleton
        return 1
    fi
    if [[ ! -f "$services_json" ]]; then
        isNotice "$services_json not found yet — emitting minimal dashy conf so the container can boot. Run the Manage Shortcuts tool to populate it."
        _dashyWriteSkeleton
        return 0
    fi

    local original_md5=""
    [[ -f "$conf_file" ]] && original_md5=$(runFileOp md5sum "$conf_file" 2>/dev/null | awk '{print $1}')

    runFileOp mkdir -p "$(dirname "$conf_file")"

    # Build the selected-id set (empty CFG = include every URL).
    local _selected_set=""
    if [[ -n "${CFG_DASHY_SHORTCUTS:-}" ]]; then
        _selected_set="|${CFG_DASHY_SHORTCUTS//,/|}|"
    fi
    _isSelected() {
        [[ -z "$_selected_set" ]] && return 0
        [[ "$_selected_set" == *"|$1|"* ]]
    }

    # Resolve an app's category from its install script header, with a
    # cache so we don't re-read the same file once per URL.
    declare -A _cat_cache=()
    _appCategory() {
        local app="$1"
        if [[ -n "${_cat_cache[$app]+x}" ]]; then
            printf '%s' "${_cat_cache[$app]}"
            return
        fi
        local cat=""
        local sh="${install_containers_dir}/${app}/${app}.sh"
        if [[ -f "$sh" ]]; then
            cat=$(awk -F ': ' '/# Category :/{print $2; exit}' "$sh" \
                | tr -d '\r' | sed 's/[[:space:]]*$//')
        fi
        [[ -z "$cat" ]] && cat="Other"
        _cat_cache[$app]="$cat"
        printf '%s' "$cat"
    }

    # Resolve display title + description from the app's .config so the
    # dashboard tile reads "Jellyfin / Media server" instead of the raw
    # service slug.
    declare -A _title_cache=() _desc_cache=()
    _appMeta() {
        local app="$1"
        if [[ -z "${_title_cache[$app]+x}" ]]; then
            local cfg="${install_containers_dir}/${app}/${app}.config"
            local upper="${app^^}"
            local t="" d=""
            if [[ -f "$cfg" ]]; then
                t=$(grep -E "^CFG_${upper}_TITLE=" "$cfg" | cut -d= -f2- | sed 's/^"//; s/"$//')
                d=$(grep -E "^CFG_${upper}_DESCRIPTION=" "$cfg" | cut -d= -f2- | sed 's/^"//; s/"$//')
            fi
            [[ -z "$t" ]] && t="$app"
            _title_cache[$app]="$t"
            _desc_cache[$app]="$d"
        fi
    }

    # Stream every (service, link) pair as TSV:
    #   id <tab> app <tab> label <tab> url <tab> traefik <tab> login
    # Mirrors window.expandServiceLinks(): use links[] when present;
    # otherwise fall back to the service-level externalURL (or build
    # one from serverIP+externalPort).
    local rows
    rows=$(jq -r '
        .services
        | map(select(.buttonEnabled != false))
        | map(
            . as $s
            | (
                if (.links | type) == "array" and (.links | length) > 0
                then .links | to_entries | map(
                    {
                        id: ($s.name + ":" + (.key|tostring)),
                        app: $s.app,
                        label: (.value.label // $s.buttonText // $s.name),
                        url: (.value.externalURL // .value.internalURL // ""),
                        traefik: ($s.traefikManaged == true),
                        login: ($s.loginRequired == true)
                    })
                else [{
                    id: ($s.name + ":0"),
                    app: $s.app,
                    label: ($s.buttonText // $s.name),
                    url: ($s.externalURL // ("http://" + ($s.serverIP // "localhost") + ":" + ($s.externalPort|tostring))),
                    traefik: ($s.traefikManaged == true),
                    login: ($s.loginRequired == true)
                }]
                end
            )
        )
        | flatten
        | map(select(.url != "" and .url != null))
        | .[]
        | [.id, .app, .label, .url, (.traefik|tostring), (.login|tostring)] | @tsv
    ' "$services_json")

    # Group selected rows by category in a stable order.
    declare -A _cat_buckets=()
    declare -a _cat_order=()
    local _id _app _label _url _traefik _login
    local IFS_BAK="$IFS"
    while IFS=$'\t' read -r _id _app _label _url _traefik _login; do
        [[ -z "$_id" ]] && continue
        _isSelected "$_id" || continue
        # Skip dashy URLs themselves to avoid a recursive shortcut.
        [[ "$_app" == "dashy" ]] && continue
        local cat
        cat=$(_appCategory "$_app")
        if [[ -z "${_cat_buckets[$cat]+x}" ]]; then
            _cat_buckets[$cat]=""
            _cat_order+=("$cat")
        fi
        # Prefix items under their parent app with an icon name dashy
        # understands (`hl-<app>`). YAML-escape via printf %q-style: we
        # only need to handle quotes in label/desc; URLs are URI-safe.
        local _label_safe="${_label//\"/\\\"}"
        _cat_buckets[$cat]+=$'\t'"${_app}"$'\x01'"${_label_safe}"$'\x01'"${_url}"$'\x01'"${_traefik}"$'\x01'"${_login}"$'\n'
    done <<< "$rows"
    IFS="$IFS_BAK"

    # Header.
    local install_name="${CFG_INSTALL_NAME:-LibrePortal}"
    local theme="${CFG_DASHY_THEME:-Nord-Frost}"
    local page_title="${CFG_DASHY_PAGE_TITLE:-}"
    local page_desc="${CFG_DASHY_PAGE_DESCRIPTION:-}"
    local layout="${CFG_DASHY_LAYOUT:-auto}"
    local icon_size="${CFG_DASHY_ICON_SIZE:-medium}"
    local open_target="${CFG_DASHY_OPEN_TARGET:-newtab}"
    local status_check="${CFG_DASHY_STATUS_CHECK:-false}"
    [[ -z "$page_title" ]] && page_title="Dashy - LibrePortal - ${install_name}"
    [[ -z "$page_desc"  ]] && page_desc="Welcome to your LibrePortal Dashy dashboard!"

    runFileWrite "$conf_file" <<EOF
---
pageInfo:
  title: ${page_title}
  description: ${page_desc}
  navLinks:
  - title: Dashy GitHub
    path: http://github.com/Lissy93/dashy
  - title: Dashy Documentation
    path: http://dashy.to/docs

appConfig:
  theme: ${theme}
  layout: ${layout}
  iconSize: ${icon_size}
  defaultOpeningMethod: ${open_target}
  statusCheck: ${status_check}

sections:
EOF

    # Reference icons via absolute URLs back to the LibrePortal WebUI's
    # public icon server (the same URL that renders these icons inside
    # the WebUI itself). This sidesteps two problems with the previous
    # local-mirror approach:
    #   1. Dashy's Vue icon resolver doesn't treat bare `/path.svg`
    #      as a fetchable image — it routes through its own icon
    #      pipeline which expects `item-icons/foo.svg` or `hl-foo`.
    #   2. The `./etc:/app/user-data` mount only serves files via
    #      dashy's express middleware, not via its icon pipeline.
    # Pulling ed_base from apps-services.json keeps it in sync with
    # whatever IP/port LibrePortal is currently on.
    local ed_base
    ed_base=$(jq -r '.services[] | select(.app == "libreportal") | .externalURL' "$services_json" 2>/dev/null | head -1)
    [[ -z "$ed_base" || "$ed_base" == "null" ]] && ed_base="http://localhost:3719"

    local _total_items=0
    local cat
    for cat in "${_cat_order[@]}"; do
        printf -- "- name: %s\n  icon: fas fa-cube\n  items:\n" "$cat" \
            | runFileWrite -a "$conf_file"
        local IFS_BAK="$IFS"
        local entry
        while IFS= read -r entry; do
            entry="${entry#$'\t'}"
            [[ -z "$entry" ]] && continue
            local app label url traefik login
            IFS=$'\x01' read -r app label url traefik login <<< "$entry"
            [[ -z "$url" ]] && continue
            _appMeta "$app"
            local title="${_title_cache[$app]}"
            local desc="${_desc_cache[$app]}"
            # If the app exposes multiple URLs, prefix the per-URL
            # label so users can tell them apart on the tile.
            local tile_title="$title"
            if [[ -n "$label" && "$label" != "$title" ]]; then
                tile_title="${title} — ${label}"
            fi
            local tile_desc="$desc"
            [[ -z "$tile_desc" ]] && tile_desc="$label"
            # Pick an icon: prefer the synced local SVG (always works
            # for our installed apps), fall back to dashy's homelab
            # bundle for anything we couldn't sync.
            # Absolute URL to the LibrePortal WebUI's icon endpoint.
            # Falls back to dashy's bundled `hl-<app>` set if the SVG
            # isn't present (covers third-party apps not shipped with
            # an LibrePortal icon).
            local icon_ref
            if [[ -f "${icons_src_dir}/${app}.svg" ]]; then
                icon_ref="${ed_base}/icons/apps/${app}.svg"
            else
                icon_ref="hl-${app}"
            fi
            printf -- "  - title: %s\n    description: %s\n    icon: %s\n    url: %s\n    statusCheck: %s\n    target: %s\n" \
                "$tile_title" "$tile_desc" "$icon_ref" "$url" "$status_check" "$open_target" \
                | runFileWrite -a "$conf_file"
            _total_items=$((_total_items + 1))
        done <<< "${_cat_buckets[$cat]}"
        IFS="$IFS_BAK"
    done

    runFileOp chown "$docker_install_user:$docker_install_user" "$conf_file" 2>/dev/null || true

    local updated_md5=$(runFileOp md5sum "$conf_file" 2>/dev/null | awk '{print $1}')
    if [[ "$original_md5" != "$updated_md5" ]]; then
        isNotice "Dashy config changed — restarting container..."
        runFileOp docker restart dashy-service >/dev/null 2>&1 || runFileOp docker restart dashy >/dev/null 2>&1 || true
        local _cat_label="categories"
        [[ ${#_cat_order[@]} -eq 1 ]] && _cat_label="category"
        isSuccessful "Restarted dashy (${#_cat_order[@]} ${_cat_label}, ${_total_items} URL(s))."
    else
        isSuccessful "No changes to dashy config (${_total_items} URL(s) selected)."
    fi
}
