#!/usr/bin/env bash

# Generates Panama's hyprlock configuration into the state directory. The
# gitignored fallback config (rendered from the tracked .template at link
# time) is never rewritten by this script and remains the fallback if
# generation fails, so a malformed preference can never leave the session
# without a working locker.

set -euo pipefail

config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
state_home="${XDG_STATE_HOME:-$HOME/.local/state}"
settings="$config_home/panama/settings.json"
state_dir="$state_home/panama"
generated="$state_dir/hyprlock.conf"
status_file="$state_dir/hyprlock-status.json"
fallback="$config_home/hypr/hyprlock.conf"
temporary="$generated.tmp.$$"
status_temporary="$status_file.tmp.$$"
shipped_wallpaper="$HOME/Pictures/Wallpapers/faroe_islands.jpg"

settings_valid=false
wallpaper_warning=""

cleanup() {
    rm -f "$temporary" "$status_temporary" 2>/dev/null || true
}
trap cleanup EXIT

read_string() {
    local key="$1" default="$2" value
    if [[ "$settings_valid" != true ]]; then
        printf '%s' "$default"
        return
    fi
    value="$(jq -er --arg key "$key" \
        'if has($key) and (.[$key] | type) == "string" then .[$key] else empty end' \
        "$settings" 2>/dev/null)" || value="$default"
    printf '%s' "$value"
}

read_bool() {
    local key="$1" default="$2" value
    if [[ "$settings_valid" != true ]]; then
        printf '%s' "$default"
        return
    fi
    value="$(jq -er --arg key "$key" \
        'if has($key) and (.[$key] | type) == "boolean" then (.[$key] | tostring) else empty end' \
        "$settings" 2>/dev/null)" || value="$default"
    printf '%s' "$value"
}

read_int() {
    local key="$1" default="$2" low="$3" high="$4" value
    if [[ "$settings_valid" != true ]]; then
        printf '%s' "$default"
        return
    fi
    value="$(jq -er --arg key "$key" \
        'if has($key) and (.[$key] | type) == "number" and (.[$key] | floor) == .[$key]
         then (.[$key] | tostring) else empty end' "$settings" 2>/dev/null)" || value="$default"
    if [[ ! "$value" =~ ^-?[0-9]+$ ]] || (( value < low || value > high )); then
        value="$default"
    fi
    printf '%s' "$value"
}

read_object() {
    local key="$1"
    if [[ "$settings_valid" != true ]]; then
        printf '{}'
        return
    fi
    jq -c --arg key "$key" \
        'if has($key) and (.[$key] | type) == "object" then .[$key] else {} end' \
        "$settings" 2>/dev/null || printf '{}'
}

valid_path() {
    [[ "$1" == /* && "$1" != *","* && "$1" != *$'\n'* && -f "$1" && -r "$1" ]]
}

resolve_wallpaper_path() {
    local candidate="$1"
    if valid_path "$candidate"; then
        resolved_wallpaper="$candidate"
        return
    fi
    if [[ -n "$candidate" ]]; then
        wallpaper_warning="One or more lock-screen wallpapers were unavailable; a safe fallback is in use."
    fi
    if valid_path "$shipped_wallpaper"; then
        resolved_wallpaper="$shipped_wallpaper"
    else
        resolved_wallpaper="screenshot"
    fi
}

# Same 8 named accents as config/Theme.qml's `accents` map (kept in sync by
# hand -- there is no shared source between QML and a shell script), primary
# hue per scheme only: the lock screen shows a flat focus ring, not the
# two-stop gradient the window border does. Falls back to blue for an unknown
# name, matching Theme.accentPair's own fallback.
accent_hex() {
    local name="$1" scheme="$2"
    case "$name" in
        orchid) [[ "$scheme" == light ]] && printf '7847bd' || printf 'c099ff' ;;
        teal)   [[ "$scheme" == light ]] && printf '007197' || printf '86e1fc' ;;
        green)  [[ "$scheme" == light ]] && printf '587539' || printf 'c3e88d' ;;
        amber)  [[ "$scheme" == light ]] && printf '8c6c3e' || printf 'ffc777' ;;
        orange) [[ "$scheme" == light ]] && printf 'b15c00' || printf 'ff966c' ;;
        rose)   [[ "$scheme" == light ]] && printf 'f52a65' || printf 'ff757f' ;;
        slate)  [[ "$scheme" == light ]] && printf '6172b0' || printf '828bb8' ;;
        *)      [[ "$scheme" == light ]] && printf '2e7de9' || printf '82aaff' ;;
    esac
}

# hyprlock wants "R, G, B" decimal, not hex.
hex_to_rgb() {
    local hex="$1"
    printf '%d, %d, %d' "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
}

load_preferences() {
    if [[ -r "$settings" ]] && jq -e 'type == "object"' "$settings" >/dev/null 2>&1; then
        settings_valid=true
    else
        settings_valid=false
    fi

    background_mode="$(read_string lockBackgroundMode screenshot)"
    [[ "$background_mode" == screenshot || "$background_mode" == wallpaper || "$background_mode" == solid ]] \
        || background_mode=screenshot

    blur_level="$(read_int lockBlurLevel 3 0 5)"
    show_clock="$(read_bool lockShowClock true)"
    show_date="$(read_bool lockShowDate true)"
    show_user="$(read_bool lockShowUser true)"
    fade_on_empty="$(read_bool lockFadeOnEmpty false)"
    use_24_hour="$(read_bool use24Hour false)"

    color_scheme="$(read_string colorScheme dark)"
    [[ "$color_scheme" == dark || "$color_scheme" == light ]] || color_scheme=dark

    accent_name="$(read_string accentName blue)"
    case "$accent_name" in
        blue|orchid|teal|green|amber|orange|rose|slate) ;;
        *) accent_name=blue ;;
    esac

    wallpaper_mode="$(read_string wallpaperMode single)"
    [[ "$wallpaper_mode" == single || "$wallpaper_mode" == slideshow || "$wallpaper_mode" == per-monitor ]] \
        || wallpaper_mode=single
    wallpaper_warning=""
    resolve_wallpaper_path "$(read_string wallpaperPath '')"
    wallpaper_path="$resolved_wallpaper"
    wallpaper_assignments="$(read_object wallpaperPerMonitor)"

    case "$blur_level" in
        0) blur_passes=0; blur_size=1 ;;
        1) blur_passes=1; blur_size=3 ;;
        2) blur_passes=2; blur_size=5 ;;
        3) blur_passes=3; blur_size=8 ;;
        4) blur_passes=4; blur_size=10 ;;
        5) blur_passes=5; blur_size=12 ;;
    esac

    if [[ "$color_scheme" == light ]]; then
        background_color='rgba(225, 226, 231, 1.0)'
        foreground_color='rgba(55, 96, 191, 1.0)'
        dim_color='rgba(97, 114, 176, 1.0)'
        error_color='rgba(245, 42, 101, 1.0)'
        field_color='rgba(208, 213, 227, 0.85)'
        dim_hex='6172b0'
        error_hex='f52a65'
    else
        background_color='rgba(34, 36, 54, 1.0)'
        foreground_color='rgba(200, 211, 245, 1.0)'
        dim_color='rgba(130, 139, 184, 1.0)'
        error_color='rgba(255, 117, 127, 1.0)'
        field_color='rgba(46, 47, 61, 0.85)'
        dim_hex='828bb8'
        error_hex='ff757f'
    fi

    # The focus ring is the accent role, not a scheme-relative one: it follows
    # accentName the same way ColorScheme.qml's active_border does, and needs
    # both the accent and the scheme since each accent carries a separate pair.
    accent_rgb="$(hex_to_rgb "$(accent_hex "$accent_name" "$color_scheme")")"
    accent_color="rgba($accent_rgb, 1.0)"
    accent_ring_color="rgba($accent_rgb, 0.9)"
}

monitor_names() {
    hyprctl -j monitors 2>/dev/null \
        | jq -r '.[]? | .name | select(type == "string") | select(test("^[A-Za-z0-9_.-]+$"))' \
        2>/dev/null || true
}

emit_background() {
    local monitor="$1" path="$2"
    printf 'background {\n'
    printf '    monitor = %s\n' "$monitor"
    if [[ -n "$path" ]]; then
        printf '    path = %s\n' "$path"
    fi
    printf '    blur_passes = %s\n' "$blur_passes"
    printf '    blur_size = %s\n' "$blur_size"
    printf '    noise = 0.0117\n'
    printf '    contrast = 0.9\n'
    printf '    brightness = 0.8\n'
    printf '    vibrancy = 0.17\n'
    printf '    vibrancy_darkness = 0.05\n'
    printf '    color = %s\n' "$background_color"
    printf '    zindex = -1\n'
    printf '}\n\n'
}

emit_backgrounds() {
    local output assigned saw_output=false
    case "$background_mode" in
        screenshot)
            emit_background '' screenshot
            ;;
        solid)
            emit_background '' ''
            ;;
        wallpaper)
            while IFS= read -r output; do
                [[ -n "$output" ]] || continue
                saw_output=true
                assigned="$wallpaper_path"
                if [[ "$wallpaper_mode" == per-monitor ]]; then
                    candidate="$(jq -r --arg output "$output" \
                        'if has($output) and (.[$output] | type) == "string" then .[$output] else "" end' \
                        <<<"$wallpaper_assignments" 2>/dev/null || true)"
                    if [[ -n "$candidate" ]]; then
                        resolve_wallpaper_path "$candidate"
                        assigned="$resolved_wallpaper"
                    fi
                fi
                emit_background "$output" "$assigned"
            done < <(monitor_names)
            if [[ "$saw_output" != true ]]; then
                emit_background '' "$wallpaper_path"
            fi
            ;;
    esac
}

emit_config() {
    printf '# Generated by Panama. Do not edit.\n\n'
    printf 'general {\n'
    printf '    hide_cursor = true\n'
    printf '    fractional_scaling = 2\n'
    printf '    screencopy_mode = 0\n'
    printf '    fail_timeout = 2000\n'
    printf '}\n\n'
    printf 'auth {\n'
    printf '    pam:enabled = true\n'
    printf '    pam:module = hyprlock\n'
    printf '}\n\n'

    emit_backgrounds

    if [[ "$show_clock" == true ]]; then
        printf 'label {\n'
        printf '    monitor =\n'
        if [[ "$use_24_hour" == true ]]; then
            printf '    text = cmd[update:1000] date +"%%H:%%M"\n'
        else
            printf '    text = cmd[update:1000] date +"%%-I:%%M"\n'
        fi
        printf '    color = %s\n' "$foreground_color"
        printf '    font_size = 120\n'
        printf '    font_family = Adwaita Sans Light\n'
        printf '    position = 0, 260\n'
        printf '    halign = center\n'
        printf '    valign = center\n'
        printf '}\n\n'
    fi

    if [[ "$show_date" == true ]]; then
        printf 'label {\n'
        printf '    monitor =\n'
        printf '    text = cmd[update:60000] date +"%%A, %%B %%-d"\n'
        printf '    color = %s\n' "$dim_color"
        printf '    font_size = 24\n'
        printf '    font_family = Adwaita Sans\n'
        printf '    position = 0, 160\n'
        printf '    halign = center\n'
        printf '    valign = center\n'
        printf '}\n\n'
    fi

    printf 'input-field {\n'
    printf '    monitor =\n'
    printf '    size = 340, 52\n'
    printf '    position = 0, -40\n'
    printf '    halign = center\n'
    printf '    valign = center\n'
    printf '    outline_thickness = 2\n'
    printf '    rounding = 26\n'
    printf '    outer_color = %s\n' "$accent_ring_color"
    printf '    inner_color = %s\n' "$field_color"
    printf '    font_color = %s\n' "$foreground_color"
    printf '    check_color = %s\n' "$accent_color"
    printf '    fail_color = %s\n' "$error_color"
    printf '    dots_size = 0.25\n'
    printf '    dots_spacing = 0.3\n'
    printf '    dots_center = true\n'
    printf '    placeholder_text = <span foreground="##%s"><i>Password</i></span>\n' "$dim_hex"
    printf '    fail_text = <span foreground="##%s"><i>$FAIL ($ATTEMPTS)</i></span>\n' "$error_hex"
    printf '    fade_on_empty = %s\n' "$fade_on_empty"
    printf '    hide_input = false\n'
    printf '}\n\n'

    if [[ "$show_user" == true ]]; then
        printf 'label {\n'
        printf '    monitor =\n'
        printf '    text = $USER\n'
        printf '    color = %s\n' "$foreground_color"
        printf '    font_size = 16\n'
        printf '    font_family = Adwaita Sans\n'
        printf '    position = 0, -110\n'
        printf '    halign = center\n'
        printf '    valign = center\n'
        printf '}\n'
    fi
}

write_status() {
    local generated_value="$1" path_value="$2" fallback_value="$3" error_value="$4"
    mkdir -p "$state_dir" 2>/dev/null || return 0
    [[ -w "$state_dir" ]] || return 0
    jq -nc --argjson generated "$generated_value" --arg path "$path_value" \
        --argjson fallback "$fallback_value" --arg error "$error_value" \
        '{generated:$generated,path:$path,fallback:$fallback,error:$error}' \
        >"$status_temporary" 2>/dev/null || return 0
    mv "$status_temporary" "$status_file" 2>/dev/null || true
}

generate() {
    load_preferences
    mkdir -p "$state_dir" || return 1
    if ! emit_config >"$temporary"; then
        rm -f "$temporary" 2>/dev/null || true
        write_status false "$fallback" true "The lock-screen configuration could not be generated."
        return 1
    fi
    if [[ ! -s "$temporary" ]] \
        || ! rg -q '^auth \{' "$temporary" \
        || ! rg -q '^background \{' "$temporary" \
        || ! rg -q '^input-field \{' "$temporary"; then
        rm -f "$temporary"
        write_status false "$fallback" true "The lock-screen configuration could not be generated."
        return 1
    fi
    if ! mv "$temporary" "$generated"; then
        rm -f "$temporary" 2>/dev/null || true
        write_status false "$fallback" true "The lock-screen configuration could not be generated."
        return 1
    fi
    write_status true "$generated" false "$wallpaper_warning"
}

status() {
    if [[ -r "$status_file" ]] \
        && jq -e '.generated | type == "boolean"' "$status_file" >/dev/null 2>&1 \
        && jq -e '.path | type == "string"' "$status_file" >/dev/null 2>&1; then
        jq -c '{generated,path,fallback:(.fallback == true),error:(.error // "")}' "$status_file"
    elif [[ -s "$generated" ]]; then
        jq -nc --arg path "$generated" '{generated:true,path:$path,fallback:false,error:""}'
    else
        jq -nc --arg path "$fallback" \
            '{generated:false,path:$path,fallback:true,error:"The generated lock-screen configuration is unavailable."}'
    fi
}

case "${1:-status}" in
    generate)
        generate
        ;;
    status)
        status
        ;;
    run)
        if generate >/dev/null 2>&1; then
            exec hyprlock -c "$generated"
        fi
        write_status false "$fallback" true "The lock-screen configuration could not be generated."
        exec hyprlock -c "$fallback"
        ;;
    *)
        printf 'usage: panama-lock [generate|status|run]\n' >&2
        exit 2
        ;;
esac
