#!/usr/bin/env wish
# =============================================================================
# secrets-gui — windowed front-end for `secrets`. Stores on the left, details
# on the right; every action delegates to the `secrets` command itself, so the
# GUI reuses exactly the same logic as the CLI.
#
# Tcl/Tk (wish) — no Python. THE PASSPHRASE IS NEVER SEEN BY THIS PROGRAM:
# anything needing it is handed to gpg, whose own pinentry prompts you, and
# anything needing an editor opens a terminal. This window only ever sees
# ciphertext filenames and exit codes.
#
# Usage: secrets-gui          (just launch it)
#        secrets-gui --version
# =============================================================================

set VERSION 1.0.0
if {[llength $argv] > 0} {
    switch -- [lindex $argv 0] {
        --version - -V { puts "secrets-gui $VERSION"; exit 0 }
        --help - -h {
            puts "secrets-gui — windowed front-end for `secrets`."
            puts "Usage: secrets-gui \[--help|--version\]"
            exit 0
        }
    }
}

package require Tk
wm title . "secrets $VERSION"
wm minsize . 760 440

set SECRETS [expr {[info exists env(SECRETS_BIN)] ? $env(SECRETS_BIN) : "secrets"}]

# ── helpers ─────────────────────────────────────────────────────────────────
proc runCapture {args} {
    # Run `secrets ...` and capture output. Used only for read-only listings
    # that never need a passphrase.
    global SECRETS
    if {[catch {exec {*}[linsert $args 0 $SECRETS] 2>@1} out]} { return $out }
    return $out
}

proc pickTerminal {} {
    foreach t {x-terminal-emulator gnome-terminal konsole xfce4-terminal mate-terminal xterm} {
        if {[auto_execok $t] ne ""} { return $t }
    }
    return ""
}

proc runInTerminal {title args} {
    # Anything that needs the passphrase or an editor gets a REAL terminal:
    # gpg's pinentry and $EDITOR both want a tty, and this window must never
    # stand between you and the passphrase prompt.
    global SECRETS
    set term [pickTerminal]
    set cmd [linsert $args 0 $SECRETS]
    set sh "printf '\\033\[1;33m>>> %s\\033\[0m\\n\\n' [list $cmd]; [join $cmd { }]; \
             printf '\\n--- exit \$? --- press Enter ---'; read _"
    if {$term eq ""} {
        tk_messageBox -icon error -title "No terminal" -message \
"No terminal emulator found.\n\nRun this yourself:\n\n  $cmd"
        return
    }
    switch -glob -- $term {
        gnome-terminal* - mate-terminal* - xfce4-terminal* {
            exec $term --title=$title -- sh -c $sh &
        }
        konsole { exec $term -p tabtitle=$title -e sh -c $sh & }
        default { exec $term -T $title -e sh -c $sh & }
    }
}

proc setStatus {msg} { .status configure -text $msg }

# ── layout ──────────────────────────────────────────────────────────────────
frame .top
label .top.l -text "Stores" -anchor w -font {-weight bold}
listbox .top.lb -width 24 -exportselection 0
scrollbar .top.sb -command {.top.lb yview}
.top.lb configure -yscrollcommand {.top.sb set}
grid .top.l  -row 0 -column 0 -columnspan 2 -sticky w -padx 4 -pady {4 0}
grid .top.lb -row 1 -column 0 -sticky nsew -padx {4 0} -pady 4
grid .top.sb -row 1 -column 1 -sticky ns -pady 4
grid rowconfigure .top 1 -weight 1

frame .right
label .right.l -text "Details" -anchor w -font {-weight bold}
text .right.t -wrap none -height 20 -width 74 -state disabled
scrollbar .right.sy -command {.right.t yview}
.right.t configure -yscrollcommand {.right.sy set}
grid .right.l  -row 0 -column 0 -columnspan 2 -sticky w -pady {4 0}
grid .right.t  -row 1 -column 0 -sticky nsew -pady 4
grid .right.sy -row 1 -column 1 -sticky ns -pady 4
grid rowconfigure .right 1 -weight 1
grid columnconfigure .right 0 -weight 1

frame .btn
label .status -text "Ready." -anchor w -relief sunken -bd 1

grid .top   -row 0 -column 0 -sticky nsew
grid .right -row 0 -column 1 -sticky nsew
grid .btn   -row 1 -column 0 -columnspan 2 -sticky ew -padx 4 -pady 4
grid .status -row 2 -column 0 -columnspan 2 -sticky ew
grid rowconfigure . 0 -weight 1
grid columnconfigure . 1 -weight 1

proc showText {s} {
    .right.t configure -state normal
    .right.t delete 1.0 end
    .right.t insert end $s
    .right.t configure -state disabled
}

proc selectedStore {} {
    set i [.top.lb curselection]
    if {$i eq ""} { return "" }
    return [.top.lb get $i]
}

proc refreshStores {} {
    .top.lb delete 0 end
    set out [runCapture list]
    set n 0
    foreach line [split $out "\n"] {
        set line [string trim $line]
        if {$line eq "" || [string match "(*" $line]} continue
        .top.lb insert end [lindex $line 0]
        incr n
    }
    setStatus "$n store(s). Actions needing your passphrase open a terminal."
    if {$n == 0} { showText "No stores yet.\n\nUse  New...  to create your first one." }
}

proc showMirrors {} {
    set s [selectedStore]
    if {$s eq ""} { setStatus "Select a store first."; return }
    showText [runCapture mirrors $s]
    setStatus "Mirrors for $s — each copy tagged with the passphrase it needs."
}

proc showTemplates {} {
    showText [runCapture templates]
    setStatus "Templates hold no secrets."
}

proc showScan {} {
    setStatus "Scanning..."
    update idletasks
    showText [runCapture scan]
    setStatus "Loose credential files. Contents were never read."
}

proc doView {}   { set s [selectedStore]; if {$s ne ""} { runInTerminal "secrets view $s"   view   $s } }
proc doEdit {}   { set s [selectedStore]; if {$s ne ""} { runInTerminal "secrets edit $s"   edit   $s } }
proc doPasswd {} { set s [selectedStore]; if {$s ne ""} { runInTerminal "secrets passwd $s" passwd $s } }
proc doRekey {}  { set s [selectedStore]; if {$s ne ""} { runInTerminal "secrets rekey $s"  rekey  $s } }

proc doNew {} {
    set name [askString "New store" "Name for the new store:"]
    if {$name eq ""} return
    set tpl [askString "Template" "Template to start from (blank for none):"]
    if {$tpl eq ""} { runInTerminal "secrets init $name" init $name } \
    else            { runInTerminal "secrets init $name $tpl" init $name $tpl }
}

proc askString {title prompt {initial ""}} {
    set w .ask
    catch {destroy $w}
    toplevel $w; wm title $w $title; wm transient $w .
    label $w.l -text $prompt -anchor w
    entry $w.e -width 44
    $w.e insert 0 $initial
    frame $w.b
    set ::askResult ""
    button $w.b.ok -text OK -width 9 -command {set ::askDone 1}
    button $w.b.no -text Cancel -width 9 -command {set ::askDone 0}
    pack $w.l -padx 10 -pady {10 2} -fill x
    pack $w.e -padx 10 -pady 2 -fill x
    pack $w.b.ok $w.b.no -side left -padx 4
    pack $w.b -pady 10
    bind $w.e <Return> {set ::askDone 1}
    bind $w <Escape>   {set ::askDone 0}
    array set c [theme_palette $::theme]
    $w configure -background $c(bg)
    $w.l configure -background $c(bg) -foreground $c(fg)
    $w.e configure -background $c(field) -foreground $c(fg) -insertbackground $c(fg) \
                   -highlightthickness 1 -highlightbackground $c(line) -relief flat
    $w.b configure -background $c(bg)
    foreach bb [list $w.b.ok $w.b.no] {
        $bb configure -background $c(btn) -foreground $c(btnfg) \
            -activebackground $c(sel) -activeforeground $c(selfg) -relief flat -borderwidth 0
    }
    focus $w.e; grab $w
    vwait ::askDone
    if {$::askDone} { set ::askResult [$w.e get] }
    catch {grab release $w}; destroy $w
    return $::askResult
}


# ── Theming (dark / light) ───────────────────────────────────────────────────
# Same palette as ftps-gui, so the suite looks like one thing. Defaults to dark,
# with a toggle in the button bar; the choice is remembered between launches.
set ::theme dark
set ::themefile [file join [expr {[info exists env(SECRETS_DIR)] ? $env(SECRETS_DIR) : [file join $env(HOME) .secrets]}] .gui_theme]
catch {
    set fh [open $::themefile r]; set t [string trim [read $fh]]; close $fh
    if {$t eq "light" || $t eq "dark"} { set ::theme $t }
}

proc theme_palette {mode} {
    if {$mode eq "light"} {
        return [list bg #eef1f6 panel #ffffff fg #1a2233 dim #5b6675 \
                     sel #2f6fd6 selfg #ffffff line #d3dae6 \
                     btn #e2e8f2 btnfg #1a2233 field #ffffff]
    }
    return [list bg #171e2b panel #10161f fg #e6edf7 dim #8b98b0 \
                 sel #2f6fd6 selfg #ffffff line #232f42 \
                 btn #223049 btnfg #e6edf7 field #0e141d]
}

proc apply_theme {mode} {
    set ::theme $mode
    array set c [theme_palette $mode]
    . configure -background $c(bg)
    foreach f {.top .right .btn} { catch {$f configure -background $c(bg)} }
    foreach l {.top.l .right.l} {
        catch {$l configure -background $c(bg) -foreground $c(fg)}
    }
    catch {.top.lb configure -background $c(panel) -foreground $c(fg) \
             -selectbackground $c(sel) -selectforeground $c(selfg) \
             -highlightthickness 0 -borderwidth 0 -relief flat -activestyle none}
    catch {.right.t configure -background $c(panel) -foreground $c(fg) \
             -insertbackground $c(fg) -highlightthickness 0 -borderwidth 0 \
             -relief flat -selectbackground $c(sel) -selectforeground $c(selfg)}
    foreach sb {.top.sb .right.sy} {
        catch {$sb configure -background $c(btn) -troughcolor $c(panel) \
                 -activebackground $c(sel) -highlightthickness 0 -borderwidth 0}
    }
    foreach b [winfo children .btn] {
        catch {$b configure -background $c(btn) -foreground $c(btnfg) \
                 -activebackground $c(sel) -activeforeground $c(selfg) \
                 -relief flat -borderwidth 0 -highlightthickness 0}
    }
    catch {.status configure -background $c(panel) -foreground $c(dim) -relief flat}
    catch {.btn.theme configure -text [expr {$mode eq "dark" ? "\u2600 Light" : "\u263e Dark"}]}
    # remember it — a GUI that forgets your theme every launch is a small insult
    catch {
        set fh [open $::themefile w]; puts -nonewline $fh $mode; close $fh
        file attributes $::themefile -permissions 0600
    }
}
proc toggle_theme {} { apply_theme [expr {$::theme eq "dark" ? "light" : "dark"}] }

foreach {b txt cmd} {
    view     "View"       doView
    edit     "Edit"       doEdit
    new      "New..."     doNew
    passwd   "Passphrase" doPasswd
    mirrors  "Mirrors"    showMirrors
    rekey    "Rekey"      doRekey
    tpl      "Templates"  showTemplates
    scan     "Scan"       showScan
    refresh  "Refresh"    refreshStores
} {
    button .btn.$b -text $txt -command $cmd -width 11
    pack .btn.$b -side left -padx 2
}
button .btn.theme -text "\u2600 Light" -width 9 -command toggle_theme
pack .btn.theme -side right -padx 2
button .btn.quit -text "Quit" -width 8 -command {exit 0}
pack .btn.quit -side right -padx 2

bind .top.lb <<ListboxSelect>> {showMirrors}
bind .top.lb <Double-Button-1> {doEdit}

apply_theme $::theme
refreshStores
showText "secrets — encrypted credential sheets.\n\n\
Select a store on the left.\n\n\
  View / Edit / Passphrase   open a terminal, because gpg must prompt you\n\
                             for the passphrase itself. This window never\n\
                             sees it.\n\
  Mirrors                    which copies exist and which passphrase each needs\n\
  Rekey                      bring old copies up to the current passphrase\n\
  Scan                       find credential files loose on your disks\n"
