# shellcheck shell=dash

# claw should have a name and timezone.

# x cron add --ns "x-claw/default" ""

___x_cmd_claw_run___mq___dispatch_heartbeat(){
    if [ -n "$___X_CMD_CLAW_BOT_RECENT_STDOUT_LOG_DIR" ]; then
        local logfp="$___X_CMD_CLAW_BOT_RECENT_STDOUT_LOG_DIR/heartbeat.log"
        ___x_cmd ensurefp "$logfp"
        exec >>"$logfp" 2>&1
    fi

    local current_ts="$1"
    case "$current_ts" in
        ''|*[!0-9]*)
            claw:error "[HB] Invalid heartbeat timestamp: '$current_ts'. Expected a Unix timestamp."
            return 1
            ;;
    esac

    local heartbeat_tsfile="$___X_CMD_CLAW_BOT_HEARTBEAT/lastheartbeat_ts"
    local lastheartbeat_ts=0;
    [ ! -f "$heartbeat_tsfile" ] || read -r lastheartbeat_ts < "$heartbeat_tsfile"
    [ "$(( current_ts - lastheartbeat_ts ))" -ge 60 ] || return 0
    ___x_cmd ensurefp "$heartbeat_tsfile"
    printf "%s\n" "$current_ts" > "$heartbeat_tsfile"

    ___X_CMD_CLAW_BOT_RECENT_STDOUT_LOG_DIR="" ___x_cmd cron runonce --ns "x-claw-default"

    ___x_cmd_claw_run___heartbeat___weixin_session_check

    # Skip if previous heartbeat worker is still running
    if ___x_cmd worker isalive --ns x-claw-default-worker --name heartbeat-orchestrator; then
        claw:debug "[HB] Global heartbeat worker (heartbeat-orchestrator) still running — skipping this cycle"
        return 0
    fi

    ___x_cmd worker run --ns x-claw-default-worker --name heartbeat-orchestrator -- ___x_cmd_claw_run___heartbeat___tick
}

___x_cmd_claw_run___heartbeat___tick(){
    if [ -n "$___X_CMD_CLAW_BOT_RECENT_STDOUT_LOG_DIR" ]; then
        local logfp="$___X_CMD_CLAW_BOT_RECENT_STDOUT_LOG_DIR/heartbeat.log"
        ___x_cmd ensurefp "$logfp"
        exec >>"$logfp" 2>&1
    fi

    local active_im=""; local active_chatid=""
    local imdir="$___X_CMD_CLAW_BOT_CONNECT_DIR"
    [ -d "$imdir" ] || {
        claw:warn "[HB] No connect directory found at $imdir"
        return 0
    }

    # Probe every active chat for external activity. Maintain a single global
    # idle counter: it increments by one per heartbeat tick only when NONE of
    # the chats had activity. If any chat had activity, reset it to 0.
    local had_activity=0
    local i j
    for i in "$imdir"/*; do
        [ -d "$i" ] || continue
        active_im="${i##*/}"

        for j in "$i"/*; do
            [ -f "$j" ] || continue
            active_chatid="${j##*/}"

            # Keep IM services/subscriptions alive even if the chat is currently
            # in an agent failure cooldown; only skip the actual unread/agent
            # work. This avoids the noisy "activating / unread detected / skipped"
            # sequence on every heartbeat tick without letting subscriptions lapse.
            ___x_cmd_claw_run___ensure_im_service "$active_im" "$active_chatid" || {
                claw:error "[HB] Failed to ensure IM platform $active_im:$active_chatid"
                continue
            }

            if ! ___x_cmd_claw_run___state___check_unavailable "$active_im" "$active_chatid" "msg"; then
                continue
            fi

            if ___x_cmd_claw_run___heartbeat___probe "$active_im" "$active_chatid"; then
                had_activity=1
            fi
        done
    done

    local idle_file="$___X_CMD_CLAW_BOT_HEARTBEAT/idle_count"
    if [ "$had_activity" -eq 1 ]; then
        printf "%s\n" "0" > "$idle_file"
    else
        local idle_count=0
        [ ! -f "$idle_file" ] || read -r idle_count < "$idle_file"
        idle_count=$(( idle_count + 1 ))
        printf "%s\n" "$idle_count" > "$idle_file"
        claw:debug "[HB] No activity on any chat — global idle counter = $idle_count"
    fi

    local heartbeat_ok_file="$___X_CMD_CLAW_BOT_WS/HEARTBEAT_OK"
    # Skip heartbeat agent if already confirmed idle by agent itself
    if [ -f "$heartbeat_ok_file" ]; then
        ___x_cmd_claw_run___heartbeat___heartbeat_ok_check_ "$heartbeat_ok_file" && return 0
    fi

    # Skip heartbeat agent during initial observation period [3, ∞)
    # Idle count >= 3 means at least 3 minutes of complete inactivity.
    # No upper bound — as long as the system remains idle, heartbeat keeps checking
    # (unless HEARTBEAT_OK marker silences it).
    local idle_count=0
    [ ! -f "$idle_file" ] || read -r idle_count < "$idle_file"
    [ "$idle_count" -ge 3 ] || return 0
    claw:debug "[HB] Global idle counter = $idle_count — launching global heartbeat agent"
    ___x_cmd_claw_run___agent___exec_heartbeat
}

___x_cmd_claw_run___heartbeat___probe(){
    local current_im="$1"
    local current_chatid="$2"
    local heartbeat_ok_file="$___X_CMD_CLAW_BOT_WS/HEARTBEAT_OK"

    # Activity: unread message pending to be processed — launch agent if not already handling
    if ___x_cmd claw "$current_im" has-unread "$current_chatid" && ! ___x_cmd worker isalive --ns x-claw-default-worker --name "chat-${current_im}-${current_chatid}"; then
        ___x_cmd rmrf "$heartbeat_ok_file"
        claw:info "[HB] [$current_im:$current_chatid] Unread messages detected — launching message handler"
        ___x_cmd_claw_run___mq___dispatch_chat "${current_im}:${current_chatid}" "hb"
        return 0
    fi

    # Activity: message worker still running
    if ___x_cmd worker isalive --ns x-claw-default-worker --name "chat-${current_im}-${current_chatid}"; then
        claw:debug "[HB] [$current_im:$current_chatid] Message handler still active"
        ___x_cmd rmrf "$heartbeat_ok_file"
        return 0
    fi

    # No external activity detected for this chat.
    return 1
}

___x_cmd_claw_run___heartbeat___weixin_session_check(){
    local weixin_connect_dir="$___X_CMD_CLAW_BOT_CONNECT_DIR/weixin"
    [ -d "$weixin_connect_dir" ] || return 0

    local has_task=""
    has_task="$(___x_cmd cron ls --ns "x-claw-default" --tsv 2>/dev/null | ___x_cmd_cmds head -n 1)"
    [ -n "$has_task" ] || {
        # claw:debug "[HB] No cron tasks, skipping weixin session reminder"
        return 0
    }

    local remind_threshold=$(( ___X_CMD_CLAW_WEIXIN_SESSION_TIMEOUT - ___X_CMD_CLAW_WEIXIN_REMIND_BEFORE ))
    local reminder_text="⏰ 连接提醒：微信 24 小时互动窗口快到期了。超过 24h 没回复，ClawBot 的主动消息（如定时任务）会收不到。请回复任意内容刷新一下~"

    local chatid_file="" chatid="" last_ts="" elapsed="" x_="" marker=""
    for chatid_file in "$weixin_connect_dir"/*; do
        [ -f "$chatid_file" ] || continue
        chatid="${chatid_file##*/}"
        [ -n "$chatid" ] || continue

        marker="$___X_CMD_CLAW_WEIXIN_REMIND_MARKER/$chatid"
        [ ! -f "$marker" ] || continue

        last_ts=""
        read -r last_ts < "$chatid_file"
        [ -n "$last_ts" ] || continue
        case "$last_ts" in *[!0-9]*) continue ;; esac

        elapsed=""
        x_=""
        ___x_cmd epoch minus_ "$last_ts"
        elapsed="$x_"
        [ -n "$elapsed" ] || continue
        [ "$elapsed" -ge "$remind_threshold" ] || continue

        if [ "$elapsed" -ge "$___X_CMD_CLAW_WEIXIN_SESSION_TIMEOUT" ]; then
            claw:warn "[HB] [weixin:$chatid] Session already expired (elapsed=${elapsed}s, >=24h), skipping reminder because wechat active session is required to send messages"
            continue
        fi

        ___x_cmd touch "$marker"
        claw:info "[HB] [weixin:$chatid] Session expiring soon (elapsed=${elapsed}s, will reach 24h limit soon) and cron tasks exist, sending reminder"
        if ___x_cmd_claw_weixin_send --chatid "$chatid" --text "$reminder_text"; then
            claw:info "[HB] [weixin:$chatid] Session reminder sent and marked"
        else
            claw:warn "[HB] [weixin:$chatid] Failed to send session reminder, will retry next heartbeat"
        fi
    done
}

___x_cmd_claw_run___heartbeat___heartbeat_ok_check_(){
    local heartbeat_ok_file="$1"
    local ttl="${___X_CMD_CLAW_HEARTBEAT_OK_TTL:-4h}"

    # x fileage --in returns 0 if the file was modified within the given window.
    if ___x_cmd fileage --in "$ttl" "$heartbeat_ok_file" 2>/dev/null; then
        local remaining
        remaining="$(___x_cmd fileage -H "$heartbeat_ok_file" 2>/dev/null)"
        claw:debug "[HB] HEARTBEAT_OK active (age=${remaining}) — skipping heartbeat agent"
        return 0
    fi

    claw:info "[HB] HEARTBEAT_OK expired (older than $ttl) — resuming heartbeat checks"
    ___x_cmd rmrf "$heartbeat_ok_file"
    return 1
}

___x_cmd_claw_run___heartbeat_generator(){
    local mqdir="$1"
    local x_

    ___x_cmd date timestamp_
    ___x_cmd_cmds sleep $(( 60 - (x_ % 60) )) || {
        claw:error "[HB] Initial sleep interrupted or failed"
        return 130
    }

    while true; do
        # TODO: Check if the father process is alive. If not. exit.
        x_=; ___x_cmd date timestamp_
        ___x_cmd tailer send "$mqdir" "HEARTBEAT:$x_"
        ___x_cmd_cmds sleep $(( 60 - (x_ % 60) )) || {
            claw:error "[HB] Heartbeat sleep interrupted or failed"
            return 130
        }
    done
}
