#!/bin/bash
# o — unified CLI for the ollie 9P namespace.
#
# Context is determined by the first argument:
#   o <cmd>                     Root level
#   o <session> <cmd>           Session level
#   o <session>/<agent> <cmd>   Agent level
#
# The slash in session/agent disambiguates context level.
#
# Subcommands:
#   read   <path>         Read a file (-l to loop)
#   write  <path> [data]  Write to a file (args or stdin)
#   rdwr   <path> [data]  Write then read (request-response files)
#   ls     [path]         List namespace entries
#   new    [cwd]         Create session (if needed) and agent (agent-level)
#   ctl    <command>      Send a control command
#   prompt                Interactive REPL for submitting prompts (agent-level)
#   tui                   Launch tmux TUI (agent-level)
#   generate <prompt>     One-shot generation (root-level only)
#   env                   Print export statements for context
#
# Examples:
#   o ls                        List root (sessions, models, etc.)
#   o myproj ls                 List session contents
#   o myproj/coding new         Create session+agent (cwd=$PWD)
#   o myproj/coding prompt      Interactive prompt
#   o myproj/coding read chat   Read chat output

set -euo pipefail

# ── Terminal capability ──────────────────────────────────────────
USE_COLOR=true
if [[ "${TERM:-dumb}" == "dumb" ]] || [[ ! -t 1 ]]; then
    USE_COLOR=false
fi

# ── Globals set by dispatch ─────────────────────────────────────
PREFIX=""        # Path prefix based on context
CTX_SESSION=""   # Session name if in session/agent context
CTX_AGENT=""     # Agent name if in agent context

# ── Known commands (to distinguish from context) ─────────────────
COMMANDS="read write rdwr ls new ctl prompt log tui env generate overview help"

is_command() {
    local arg="$1"
    for cmd in $COMMANDS; do
        [[ "$arg" == "$cmd" ]] && return 0
    done
    [[ "$arg" == "-h" || "$arg" == "--help" ]] && return 0
    return 1
}

# ── Path resolution helper ──────────────────────────────────────
# Resolves a path: if it starts with /, treat as root-level (strip /)
# Otherwise, prepend PREFIX.
resolve_path() {
    local p="$1"
    if [[ "$p" == /* ]]; then
        # Absolute path - strip leading / and use as root-relative
        echo "${p#/}"
    else
        # Relative path - prepend PREFIX
        echo "${PREFIX}${PREFIX:+/}$p"
    fi
}

# ── Subcommands ─────────────────────────────────────────────────

cmd_read() {
    local loop=false
    while [[ "${1:-}" == -* ]]; do
        case "$1" in
            -l|--loop) loop=true; shift ;;
            *) break ;;
        esac
    done
    if [[ $# -lt 1 ]]; then
        echo "usage: o read [-l] <path>" >&2
        exit 1
    fi
    local path
    path=$(resolve_path "$1")
    if $loop; then
        while true; do
            ollie-9p read "$path" || break
        done
    else
        ollie-9p read "$path"
    fi
}

cmd_write() {
    if [[ $# -lt 1 ]]; then
        echo "usage: o write <path> [data...]" >&2
        exit 1
    fi
    local file="$1"; shift
    local path
    path=$(resolve_path "$file")
    if [[ $# -gt 0 ]]; then
        printf '%s\n' "$*" | ollie-9p write "$path"
    else
        ollie-9p write "$path"
    fi
}

cmd_rdwr() {
    if [[ $# -lt 1 ]]; then
        echo "usage: o rdwr <path> [data...]" >&2
        exit 1
    fi
    local file="$1"; shift
    local path
    path=$(resolve_path "$file")
    if [[ $# -gt 0 ]]; then
        printf '%s\n' "$*" | ollie-9p rdwr "$path"
    else
        ollie-9p rdwr "$path"
    fi
}

cmd_ls() {
    local path="${PREFIX}"
    if [[ $# -ge 1 ]]; then
        path=$(resolve_path "$1")
    fi
    ollie-9p ls "$path"
}

cmd_generate() {
    # One-shot generation via /generate (root-level only)
    if [[ -n "$PREFIX" ]]; then
        echo "error: generate is a root-level command" >&2
        echo "  usage: o generate <prompt>" >&2
        exit 1
    fi
    local prompt=""
    if [[ $# -gt 0 ]]; then
        prompt="$*"
    else
        prompt=$(cat)
    fi
    [[ -z "$prompt" ]] && { echo "usage: o generate <prompt>" >&2; exit 1; }
    printf '%s' "$prompt" | ollie-9p rdwr generate
}

cmd_ctl() {
    if [[ $# -lt 1 ]]; then
        if [[ -z "$PREFIX" ]]; then
            echo "usage: o <session> ctl <cmd>   (or o <session>/<agent> ctl <cmd>)" >&2
            exit 1
        fi
        # The ctl file is self-describing: an empty request returns the
        # valid commands with descriptions.
        echo "usage: o ctl <command> [args...]" >&2
        printf '' | ollie-9p rdwr "${PREFIX}/ctl" >&2
        exit 1
    fi

    if [[ -z "$PREFIX" ]]; then
        echo "error: ctl requires session or agent context" >&2
        echo "  usage: o <session> ctl <cmd>" >&2
        echo "     or: o <session>/<agent> ctl <cmd>" >&2
        exit 1
    fi

    if [[ -n "$CTX_AGENT" ]]; then
        # Agent-level: rdwr to get response
        printf '%s' "$*" | ollie-9p rdwr "${PREFIX}/ctl"
    else
        # Session-level: write only
        echo "$*" | ollie-9p write "${PREFIX}/ctl"
    fi
}

cmd_prompt() {
    if [[ -z "$CTX_AGENT" ]]; then
        echo "error: prompt requires agent context" >&2
        echo "  usage: o <session>/<agent> prompt" >&2
        exit 1
    fi

    # If stdin is not a terminal, read it all and send as a single prompt
    if [[ ! -t 0 ]]; then
        cat | ollie-9p write "${PREFIX}/prompt"
        return
    fi

    # Interactive REPL mode
    echo "[$CTX_SESSION/$CTX_AGENT]"
    echo "Type . on a blank line to send, Ctrl+D to exit."
    echo "/ prefix runs ctl: /stop, /compact, /model X, /i X (type /help for all)"
    echo "!q exits the TUI."
    echo ""

    local buf="" line
    
    while true; do
        local prompt_str
        if [[ -z "$buf" ]]; then
            if $USE_COLOR; then
                prompt_str=$'\033[1m>\033[0m '
            else
                prompt_str='> '
            fi
        else
            prompt_str="  "
        fi
        printf '%s' "$prompt_str"
        if ! IFS= read -r line; then
            if [[ -n "$buf" ]]; then
                history -s "$buf"
                printf '%s\n' "$buf" | ollie-9p write "${PREFIX}/prompt"
            fi
            printf "\n"
            break
        fi
        
        # Drain any additional lines that arrived quickly (multi-line paste)
        while IFS= read -r -t 0.1 extra_line; do
            line="$line"$'\n'"$extra_line"
        done
        if [[ -z "$buf" && "$line" == /* ]]; then
            history -s "$line"
            cmd_ctl "${line#/}" || true
            continue
        fi
        if [[ -z "$buf" && "$line" == !* ]]; then
            history -s "$line"
            local bang="${line#!}"
            case "$bang" in
                q|quit) tmux kill-session -t "o-${CTX_SESSION}" 2>/dev/null; exit 0 ;;
            esac
            continue
        fi
        if [[ "$line" == "." ]]; then
            if [[ -n "$buf" ]]; then
                history -s "$buf"
                printf '%s\n' "$buf" | ollie-9p write "${PREFIX}/prompt"
                buf=""
            fi
            continue
        fi
        if [[ -z "$line" && -n "$buf" ]]; then
            buf="$buf"$'\n'
        elif [[ -z "$line" ]]; then
            continue
        else
            if [[ -n "$buf" ]]; then
                buf="$buf"$'\n'"$line"
            else
                buf="$line"
            fi
        fi
    done
}

cmd_log() {
    local lines=1000
    while [[ $# -gt 0 && "$1" == -* ]]; do
        case "$1" in
            -n) shift; lines="${1:-1000}"; shift ;;
            -n*) lines="${1#-n}"; shift ;;
            *) break ;;
        esac
    done

    if [[ -z "$CTX_AGENT" ]]; then
        echo "error: log requires agent context" >&2
        echo "  usage: o <session>/<agent> log [-n N]" >&2
        exit 1
    fi
    ollie-9p read "${PREFIX}/log" | tail -n "$lines"
}

cmd_new() {
    if [[ -z "$CTX_AGENT" ]]; then
        echo "usage: o <session>/<agent> new [cwd]" >&2
        exit 1
    fi
    local agent_cwd="${1:-$PWD}"
    # Create session if it doesn't exist
    printf 'name=%s\ncwd=%s\n' "$CTX_SESSION" "$agent_cwd" | ollie-9p write "session/new" 2>/dev/null || true
    # Create the agent
    printf 'name=%s\ncwd=%s\n' "$CTX_AGENT" "$agent_cwd" | ollie-9p write "session/$CTX_SESSION/agent/new"
    echo "created: $CTX_SESSION/$CTX_AGENT"
}

cmd_tui() {
    if [[ -z "$CTX_AGENT" ]]; then
        echo "error: tui requires agent context" >&2
        echo "  usage: o <session>/<agent> tui" >&2
        exit 1
    fi

    local s="$CTX_SESSION"
    local a="$CTX_AGENT"
    local tmux_session="o-${s}"

    # Check for subcommands
    if [[ "${1:-}" == "exit" ]]; then
        # Use = prefix for exact match (prevents dot interpretation as window.pane)
        tmux kill-session -t "=${tmux_session}" 2>/dev/null && echo "[killed ${tmux_session}]" || echo "no tui session: ${tmux_session}"
        return
    fi

    tmux kill-session -t "=${tmux_session}" 2>/dev/null || true

    # Layout: chat (top 80%), prompt (bottom 20%)
    tmux new-session -d -s "$tmux_session" -c "$HOME"
    tmux set-option -g mouse on
    tmux send-keys -t "=${tmux_session}" "o $s/$a log -n 1000 | grep -v '\\[\\[\\[.*\\]\\]\\]' | grep -v '^\`\`\`'; o $s/$a read chat" Enter

    tmux split-window -t "=${tmux_session}" -v -l 20%
    tmux send-keys -t "=${tmux_session}" "clear; o $s/$a prompt" Enter

    tmux set-option -t "=${tmux_session}" status-right "starting... | %H:%M"
    tmux set-option -t "=${tmux_session}" status-right-length 64

    while true; do
        ollie-9p read "${PREFIX}/statewait" 2>/dev/null | while read -r state; do
            bg=""
            case "$state" in
                idle)    bg="colour70" ;;
                calling*) bg="colour214" ;;
                thinking) bg="colour33" ;;
                paused)  bg="colour245" ;;
                *)       bg="default" ;;
            esac
            tmux set-option -t "=${tmux_session}" status-right-style "fg=default,bg=$bg" 2>/dev/null
            status=$(ollie-9p read "${PREFIX}/status" 2>/dev/null | head -n1)
            tmux set-option -t "=${tmux_session}" status-right "${status:-$state} | %H:%M" 2>/dev/null
        done
    done &
    local bg_pid=$!
    trap "kill $bg_pid 2>/dev/null" EXIT

    tmux select-pane -t "=${tmux_session}:0.1"
    exec tmux attach -t "=${tmux_session}"
}

cmd_env() {
    # Print export statements for current context
    [[ -n "$CTX_SESSION" ]] && echo "export OLLIE_SESSION=$CTX_SESSION"
    [[ -n "$CTX_AGENT" ]] && echo "export OLLIE_AGENT=$CTX_AGENT"
}

# cmd_overview shows all running sessions and their agents with a human-readable
# status, so you can see what's live without knowing any names in advance.
cmd_overview() {
    local idx
    idx=$(ollie-9p read "session/idx" 2>/dev/null || true)
    if [[ -z "$idx" ]]; then
        echo "no active sessions"
        echo "start one: o <session>/<agent> new [cwd]"
        return
    fi
    # session/idx: id \t name \t paused \t connected \t remote \t cwd
    while IFS=$'\t' read -r _id sname paused _conn remote cwd; do
        [[ -z "$sname" ]] && continue
        local tag=""
        [[ "$paused" == "true" ]] && tag=" (paused)"
        [[ -n "$remote" ]] && tag="$tag [remote: $remote]"
        printf '%s%s\t%s\n' "$sname" "$tag" "$cwd"
        # agent idx: sess-id \t agent-id \t agent-name \t parent-id \t depth \t state
        local aidx
        aidx=$(ollie-9p read "session/$sname/agent/idx" 2>/dev/null || true)
        while IFS=$'\t' read -r _sid _aid aname _pid _depth _state; do
            [[ -z "$aname" ]] && continue
            local status
            status=$(ollie-9p read "session/$sname/agent/$aname/status" 2>/dev/null | head -n1)
            printf '  %s/%s\t%s\n' "$sname" "$aname" "${status:-?}"
        done <<< "$aidx"
    done <<< "$idx"
}

cmd_help() {
    cat <<'EOF'
usage: o [context] <command> [args...]

Context is determined by the first argument:
  o <cmd>                     Root level
  o <session> <cmd>           Session level
  o <session>/<agent> <cmd>   Agent level

The slash in <session>/<agent> disambiguates context level.

Paths starting with / are root-relative (ignore context):
  o myproj ls /bypass         List root bypass/ even in session context

Commands (availability depends on context):

  Root level:
    overview              Show running sessions/agents and their status
    ls                    List sessions, models, etc.
    generate <prompt>     One-shot generation (no session needed)

  Session level (o <session> ...):
    ls                    List session contents (agents, config)
    ctl <cmd>             Session control (kill, pause, resume)
    read/write <path>     Access session files

  Agent level (o <session>/<agent> ...):
    new [cwd]             Create session (if needed) and agent
    ls                    List agent files
    ctl <cmd>             Agent control (stop, compact, model, etc.)
    prompt                Interactive multi-line prompt REPL
    log [-n N]            Show last N lines of agent log
    tui                   Launch tmux layout
    read/write <path>     Access agent files (chat, cfg, etc.)

  Common:
    read [-l] <path>      Read a file (-l: loop)
    write <path> [data]   Write to a file
    rdwr <path> [data]    Write then read (request-response)
    env                   Print export statements for context
    help                  Show this help

Examples:
  o ls                        # list root (sessions, models)
  o myproj/coding new         # create session+agent (cwd=$PWD)
  o myproj/coding new ~/src   # create with specific cwd
  o myproj ls                 # list session contents
  o myproj/coding prompt      # interactive prompt
  o myproj/coding read chat   # read chat stream
  o myproj/coding ls /bypass  # list root bypass/ from agent context
  o generate "explain monads" # one-shot, no session
EOF
}

# ── Dispatch ────────────────────────────────────────────────────

dispatch_cmd() {
    local cmd="$1"; shift
    case "$cmd" in
        read)     cmd_read "$@" ;;
        write)    cmd_write "$@" ;;
        rdwr)     cmd_rdwr "$@" ;;
        ls)       cmd_ls "$@" ;;
        generate) cmd_generate "$@" ;;
        ctl)      cmd_ctl "$@" ;;
        prompt)   cmd_prompt "$@" ;;
        log)      cmd_log "$@" ;;
        new)      cmd_new "$@" ;;
        tui)      cmd_tui "$@" ;;
        env)      cmd_env "$@" ;;
        overview) cmd_overview "$@" ;;
        help|-h|--help) cmd_help ;;
        *)
            echo "unknown command: $cmd" >&2
            echo "run 'o help' for usage" >&2
            exit 1
            ;;
    esac
}

# Parse context from first argument
parse_context() {
    local arg="$1"
    if [[ "$arg" == */* ]]; then
        # session/agent format
        CTX_SESSION="${arg%%/*}"
        CTX_AGENT="${arg#*/}"
        PREFIX="session/$CTX_SESSION/agent/$CTX_AGENT"
    else
        # session only
        CTX_SESSION="$arg"
        PREFIX="session/$CTX_SESSION"
    fi
}

# Re-split arguments: handles quoted invocations (e.g., acme passes
# the entire command as a single-quoted string).
set -- $*

# Main dispatch
if [[ $# -lt 1 ]]; then
    cmd_overview
    echo ""
    echo "run 'o help' for usage"
    exit 0
fi

# Check if first arg is a command (root level) or context
if is_command "$1"; then
    # Root level - no context
    dispatch_cmd "$@"
else
    # First arg is context (session or session/agent)
    parse_context "$1"
    shift
    if [[ $# -lt 1 ]]; then
        # No command after context - default to ls
        cmd_ls
    else
        dispatch_cmd "$@"
    fi
fi
