#!/usr/bin/env tclsh
# PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
# Copyright 2025 TimeHexOn & foxhop & russell@unturf
# https://www.permacomputer.com

# UncloseAI - Tcl client for OpenAI-compatible APIs with streaming support

package require TclOO
package require json

# UncloseAI class using TclOO
oo::class create UncloseAI {
    variable models tts_endpoints api_key timeout debug

    constructor {{endpoints {}} {tts_eps {}} {key ""} {tm 30} {dbg false}} {
        set api_key $key
        set timeout $tm
        set debug $dbg

        # Discover endpoints from environment if not provided
        if {[llength $endpoints] == 0} {
            set endpoints [my DiscoverEnvEndpoints "MODEL_ENDPOINT"]
        }

        if {[llength $tts_eps] == 0} {
            set tts_endpoints [my DiscoverEnvEndpoints "TTS_ENDPOINT"]
        } else {
            set tts_endpoints $tts_eps
        }

        if {$debug} {
            puts "\[DEBUG\] Initialized with [llength $endpoints] endpoint(s)"
        }

        set models [my DiscoverModels $endpoints]
    }

    method DiscoverEnvEndpoints {prefix} {
        set eps [list]
        for {set i 1} {$i < 10000} {incr i} {
            set env_var "${prefix}_$i"
            if {![info exists ::env($env_var)]} {
                break
            }
            lappend eps $::env($env_var)
        }
        return $eps
    }

    method DiscoverModels {endpoints} {
        set model_list [list]

        foreach endpoint $endpoints {
            if {$debug} {
                puts "\[DEBUG\] Discovering from: $endpoint"
            }

            if {[catch {
                set response [exec curl -s "$endpoint/models" --max-time 10]
                set data [json::json2dict $response]
                set model_data [dict get $data data]

                foreach model $model_data {
                    set model_id [dict get $model id]
                    # Skip permission entries
                    if {![string match "modelperm-*" $model_id] && ![string match "chatcmpl-*" $model_id]} {
                        set max_tokens 8192
                        if {[dict exists $model max_model_len]} {
                            set max_tokens [dict get $model max_model_len]
                        }
                        lappend model_list [list $model_id $endpoint $max_tokens]

                        if {$debug} {
                            puts "\[DEBUG\] Discovered: $model_id"
                        }
                    }
                }
            } error]} {
                if {$debug} {
                    puts "\[DEBUG\] Error: $error"
                }
            }
        }

        return $model_list
    }

    method ListModels {} {
        return $models
    }

    method ResolveModel {model_id} {
        if {[llength $models] == 0} {
            error "No models available"
        }

        if {$model_id eq ""} {
            return [lindex $models 0]
        }

        foreach model $models {
            if {[lindex $model 0] eq $model_id} {
                return $model
            }
        }

        error "Model '$model_id' not found"
    }

    method Chat {messages {model_id ""} {max_tokens 100} {temperature 0.7}} {
        set model_info [my ResolveModel $model_id]
        set mid [lindex $model_info 0]
        set endpoint [lindex $model_info 1]

        # Build JSON request
        set msg_json "\["
        set first 1
        foreach msg $messages {
            if {!$first} {
                append msg_json ","
            }
            set first 0
            append msg_json "{\"role\":\"[lindex $msg 0]\",\"content\":\"[lindex $msg 1]\"}"
        }
        append msg_json "\]"

        set request_json "{\"model\":\"$mid\",\"messages\":$msg_json,\"max_tokens\":$max_tokens,\"temperature\":$temperature,\"stream\":false}"

        set curl_cmd [list curl -s -X POST "$endpoint/chat/completions" \
            -H "Content-Type: application/json" \
            -d $request_json \
            --max-time $timeout]

        if {$api_key ne ""} {
            lappend curl_cmd -H "Authorization: Bearer $api_key"
        }

        if {[catch {
            set response [exec {*}$curl_cmd]
            return $response
        } error]} {
            error "Chat request failed: $error"
        }
    }

    method ChatStream {messages callback {model_id ""} {max_tokens 500} {temperature 0.7}} {
        set model_info [my ResolveModel $model_id]
        set mid [lindex $model_info 0]
        set endpoint [lindex $model_info 1]

        # Build JSON request
        set msg_json "\["
        set first 1
        foreach msg $messages {
            if {!$first} {
                append msg_json ","
            }
            set first 0
            append msg_json "{\"role\":\"[lindex $msg 0]\",\"content\":\"[lindex $msg 1]\"}"
        }
        append msg_json "\]"

        set request_json "{\"model\":\"$mid\",\"messages\":$msg_json,\"max_tokens\":$max_tokens,\"temperature\":$temperature,\"stream\":true}"

        set curl_cmd [list curl -s -N -X POST "$endpoint/chat/completions" \
            -H "Content-Type: application/json" \
            -d $request_json \
            --max-time $timeout]

        if {$api_key ne ""} {
            lappend curl_cmd -H "Authorization: Bearer $api_key"
        }

        if {[catch {
            set fd [open "|$curl_cmd" r]
            fconfigure $fd -buffering line

            while {[gets $fd line] >= 0} {
                if {[string match "data: *" $line]} {
                    set data [string range $line 6 end]
                    set data [string trim $data]

                    if {$data eq "\[DONE\]"} {
                        break
                    }

                    if {[catch {
                        set chunk [json::json2dict $data]
                        if {[dict exists $chunk choices]} {
                            set choices [dict get $chunk choices]
                            if {[llength $choices] > 0} {
                                set choice [lindex $choices 0]
                                if {[dict exists $choice delta]} {
                                    set delta [dict get $choice delta]
                                    if {[dict exists $delta content]} {
                                        set content [dict get $delta content]
                                        if {$content ne ""} {
                                            $callback $content
                                        }
                                    }
                                }
                            }
                        }
                    } parse_error]} {
                        # Ignore parse errors
                    }
                }
            }

            close $fd
        } error]} {
            if {$debug} {
                puts "\[DEBUG\] Stream error: $error"
            }
        }
    }

    method Tts {text {voice "alloy"} {model "tts-1"} {format "mp3"}} {
        if {[llength $tts_endpoints] == 0} {
            error "No TTS endpoints available"
        }

        set endpoint [lindex $tts_endpoints 0]

        set request_json "{\"model\":\"$model\",\"voice\":\"$voice\",\"input\":\"$text\",\"response_format\":\"$format\"}"

        set curl_cmd [list curl -s -X POST "$endpoint/audio/speech" \
            -H "Content-Type: application/json" \
            -d $request_json \
            --max-time $timeout]

        if {$api_key ne ""} {
            lappend curl_cmd -H "Authorization: Bearer $api_key"
        }

        if {[catch {
            set audio_data [exec {*}$curl_cmd]
            return $audio_data
        } error]} {
            error "TTS request failed: $error"
        }
    }
}

# Demo when run as script
if {[info script] eq $argv0} {
    puts "=== UncloseAI Tcl Client (with Streaming) ===\n"

    set client [UncloseAI new {} {} "" 30 true]

    if {[llength [$client ListModels]] == 0} {
        puts "ERROR: No models discovered. Set environment variables:"
        puts "  MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc."
        exit 1
    }

    puts "\nDiscovered [llength [$client ListModels]] model(s):"
    foreach model [$client ListModels] {
        puts "  - [lindex $model 0] (max_tokens: [lindex $model 2])"
    }
    puts ""

    # Non-streaming chat
    puts "=== Non-Streaming Chat ==="
    if {[catch {
        set response [$client Chat {{system "You are a helpful AI assistant."} {user "Explain quantum computing in one sentence."}}]
        set data [json::json2dict $response]
        set choices [dict get $data choices]
        set choice [lindex $choices 0]
        set message [dict get $choice message]
        set content [dict get $message content]
        puts "Response: $content\n"
    } error]} {
        puts "Error: $error\n"
    }

    # Streaming chat
    puts "=== Streaming Chat ==="
    set model_id ""
    if {[llength [$client ListModels]] > 1} {
        set model_id [lindex [lindex [$client ListModels] 1] 0]
    }
    set model_name $model_id
    if {$model_name eq ""} {
        set model_name [lindex [lindex [$client ListModels] 0] 0]
    }
    puts "Model: $model_name"
    puts "Response: "

    $client ChatStream {{system "You are a coding assistant."} {user "Write a Tcl function to check if a number is prime"}} \
        {apply {{content} {puts -nonewline $content}}} $model_id 200
    puts "\n"

    # TTS
    if {[llength [$client ListModels]] > 0} {
        puts "=== TTS Speech Generation ==="
        if {[catch {
            set audio_data [$client Tts "Hello from UncloseAI Tcl client! This demonstrates streaming support."]
            set fd [open "speech.mp3" wb]
            puts -nonewline $fd $audio_data
            close $fd
            puts "[OK] Speech file created: speech.mp3 ([string length $audio_data] bytes)\n"
        } error]} {
            puts "[ERROR] TTS Error: $error\n"
        }
    }

    puts "=== Examples Complete ==="
}
