#!/usr/bin/env nu
# Hot-deploy content to running pod without restart (within hot-reload caveats below).
#
# Hot-reload caveats:
#   Markdown / images / CSS / static:  hot-reload works (filesystem-served per request)
#   FTL translation changes (any):     requires restart — registry loaded at startup
#   New routes or new pages:           requires full rebuild (build.nu) + deploy.nu update
#
# Usage:
#   nu provisioning/content.nu pack   [--since <git-ref>] [--all]
#   nu provisioning/content.nu deploy [--pod <name>] [--namespace <ns>]
#   nu provisioning/content.nu sync   [--since <git-ref>] [--all] [--pod <name>] [--namespace <ns>]

# Hot-updatable paths come from the single distro manifest (provisioning/distro.ncl),
# the same source the Dockerfiles and `just distro` read — no divergent lists.
def content-dirs [project_root: string]: nothing -> list<string> {
    ^nickel export $"($project_root)/provisioning/distro.ncl" --field content_hot --format json
        | from json
}

const CONTENT_EXCLUDES = [
    ".draft"
    ".DS_Store"
    "/_archive/"
]

const PVC_MOUNT = "/var/www"
const NAMESPACE = "example-pro"
const APP_LABEL = "app.kubernetes.io/name=website"

def find-pod [ns: string]: nothing -> string {
    let r = (do { ^kubectl get pods -n $ns -l $APP_LABEL --no-headers -o jsonpath="{.items[0].metadata.name}" } | complete)
    if $r.exit_code != 0 or ($r.stdout | str trim | is-empty) {
        error make { msg: $"no running pod found for ($APP_LABEL) in ($ns)" }
    }
    $r.stdout | str trim
}

def changed-files [since: string]: nothing -> list<string> {
    let r = (do { ^git diff --name-only $since HEAD } | complete)
    if $r.exit_code != 0 { error make { msg: "git diff failed" } }
    $r.stdout | lines | where { |f| $f | is-not-empty }
}

def excluded? [f: string]: nothing -> bool {
    CONTENT_EXCLUDES | any { |pat| $f | str contains $pat }
}

def content-files [project_root: string, since: string, all: bool]: nothing -> list<string> {
    let content_dirs = (content-dirs $project_root)
    let candidates = if $all {
        $content_dirs | each { |d|
            do { ^find $"($project_root)/($d)" -type f } | complete
                | if $in.exit_code == 0 { $in.stdout | lines | where { |l| $l | is-not-empty } } else { [] }
        } | flatten
    } else {
        let changed = (changed-files $since)
        $changed
            | where { |f| $content_dirs | any { |d| $f | str starts-with $d } }
            | each { |f| $"($project_root)/($f)" }
    }
    $candidates
        | where { |f| $f | path exists }
        | where { |f| not (excluded? $f) }
}

def pick-tar []: nothing -> string {
    let gtar = (do { ^which gtar } | complete)
    if $gtar.exit_code == 0 { ($gtar.stdout | str trim) } else { "tar" }
}

def do-pack [project_root: string, since: string, all: bool]: nothing -> string {
    let timestamp = (date now | format date "%Y%m%dT%H%M%S")
    let git_sha   = (do { ^git rev-parse --short HEAD } | complete | if $in.exit_code == 0 { $in.stdout | str trim } else { "unknown" })
    let pack_dir  = $"($project_root)/provisioning/.content-packs"
    let outfile   = $"($pack_dir)/($timestamp)-content.tgz"

    mkdir $pack_dir

    let files = (content-files $project_root $since $all)
    if ($files | is-empty) { print "no content files to pack"; return "" }

    let manifest_path  = $"($pack_dir)/($timestamp)-manifest.json"
    let file_list_path = $"($pack_dir)/($timestamp)-files.txt"

    { version: $timestamp, git_sha: $git_sha, file_count: ($files | length), files: $files }
        | to json --raw
        | save -f $manifest_path

    let rel_files = ($files | each { |f| $f | str replace $"($project_root)/" "" })
    $rel_files | str join "\n" | save -f $file_list_path

    let tar_bin = (pick-tar)
    do { cd $project_root; ^$tar_bin -czf $outfile -T $file_list_path } | complete
        | if $in.exit_code != 0 { error make { msg: "tar failed" } }

    print $"packed ($files | length) files → ($outfile)"
    $outfile
}

def do-deploy [pack_path: string, pod: string, ns: string]: nothing -> nothing {
    let remote_tmp = $"/tmp/($pack_path | path basename)"
    print $"[deploy] ($pack_path) → ($pod):($remote_tmp)"
    ^kubectl cp $pack_path $"($ns)/($pod):($remote_tmp)"
    ^kubectl exec -n $ns $pod -- tar -xzf $remote_tmp -C $PVC_MOUNT
    ^kubectl exec -n $ns $pod -- rm -f $remote_tmp
    print "[deploy] done — no restart required (markdown/CSS/static)"
    print "[deploy] note: FTL changes require: nu provisioning/deploy.nu restart"
}

def main [
    subcmd: string = "sync"
    --since: string = "HEAD~1"
    --all
    --pod: string = ""
    --namespace: string = $NAMESPACE
]: nothing -> nothing {
    let project_root = ($env.FILE_PWD | path dirname)

    match $subcmd {
        "pack" => { do-pack $project_root $since $all | ignore }
        "deploy" => {
            let pack_dir  = $"($project_root)/provisioning/.content-packs"
            let tgz_files = (glob $"($pack_dir)/*.tgz")
            if ($tgz_files | is-empty) {
                error make { msg: "no packs found — run pack first" }
            }
            let latest = ($tgz_files | each { |p| { name: $p, mtime: (ls $p | first | get modified) } } | sort-by mtime | last | get name)
            let resolved_pod = if ($pod | is-not-empty) { $pod } else { find-pod $namespace }
            do-deploy $latest $resolved_pod $namespace
        }
        "sync" => {
            let pack_path = (do-pack $project_root $since $all)
            if ($pack_path | is-not-empty) {
                let resolved_pod = if ($pod | is-not-empty) { $pod } else { find-pod $namespace }
                do-deploy $pack_path $resolved_pod $namespace
            }
        }
        _ => {
            print "Usage: content.nu <pack|deploy|sync> [--since <ref>] [--all] [--pod <name>] [--namespace <ns>]"
        }
    }
}
