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

! UncloseAI Fortran Library
! OpenAI-compatible API client with streaming support
! Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
!
! Note: Fortran lacks native HTTP support, so this uses curl via shell commands

module uncloseai_lib
    implicit none
    private
    public :: uncloseai_client, uncloseai_init, uncloseai_chat, uncloseai_chat_stream, uncloseai_tts

    type :: model_info
        character(len=512) :: id
        character(len=512) :: endpoint
        integer :: max_tokens
    end type model_info

    type :: uncloseai_client
        type(model_info), dimension(:), allocatable :: models
        character(len=512), dimension(:), allocatable :: tts_endpoints
        integer :: model_count
        integer :: tts_count
        integer :: timeout
    end type uncloseai_client

contains

    ! Initialize client with auto-discovery
    subroutine uncloseai_init(client)
        type(uncloseai_client), intent(out) :: client
        integer :: i, ios
        character(len=512) :: var_name, endpoint
        character(len=1024) :: cmd
        logical :: file_exists

        write(*,*) 'Initializing UncloseAI client...'

        client%model_count = 0
        client%tts_count = 0
        client%timeout = 30

        allocate(client%models(100))
        allocate(client%tts_endpoints(10))

        ! Discover chat/code models
        do i = 1, 9999
            write(var_name, '(A,I0)') 'MODEL_ENDPOINT_', i
            call get_environment_variable(trim(var_name), endpoint)
            if (len_trim(endpoint) == 0) exit

            write(*,'(A,I0,A,A)') 'Endpoint ', i, ': ', trim(endpoint)

            ! Use curl to get models, filter modelperm-*, save model ID
            write(cmd, '(A,A,A)') &
                'curl -s "', trim(endpoint), '/models" | ' // &
                'jq -r ''.data[]|select(.id|test("^modelperm-")|not)|.id'' | ' // &
                'head -1 > /tmp/fortran_model.txt 2>/dev/null || echo "" > /tmp/fortran_model.txt'
            call execute_command_line(trim(cmd), exitstat=ios)

            ! Read discovered model
            inquire(file='/tmp/fortran_model.txt', exist=file_exists)
            if (file_exists) then
                open(unit=10, file='/tmp/fortran_model.txt', status='old', action='read', iostat=ios)
                if (ios == 0) then
                    read(10, '(A)', iostat=ios) client%models(client%model_count + 1)%id
                    close(10)
                    if (ios == 0 .and. len_trim(client%models(client%model_count + 1)%id) > 0) then
                        client%models(client%model_count + 1)%endpoint = trim(endpoint)
                        client%models(client%model_count + 1)%max_tokens = 8192
                        client%model_count = client%model_count + 1
                    end if
                else
                    close(10)
                end if
            end if
        end do

        ! Discover TTS endpoints
        do i = 1, 9999
            write(var_name, '(A,I0)') 'TTS_ENDPOINT_', i
            call get_environment_variable(trim(var_name), endpoint)
            if (len_trim(endpoint) == 0) exit

            client%tts_endpoints(client%tts_count + 1) = trim(endpoint)
            client%tts_count = client%tts_count + 1
        end do

        write(*,'(A,I0,A,I0,A)') 'Discovered ', client%model_count, &
                                  ' models, ', client%tts_count, ' TTS endpoints'
        write(*,*)

    end subroutine uncloseai_init

    ! Non-streaming chat completion
    subroutine uncloseai_chat(client, messages, model_idx, response, status)
        type(uncloseai_client), intent(in) :: client
        character(len=*), intent(in) :: messages
        integer, intent(in), optional :: model_idx
        character(len=4096), intent(out) :: response
        integer, intent(out) :: status
        integer :: idx, ios
        character(len=4096) :: cmd
        logical :: file_exists

        idx = 0
        if (present(model_idx)) idx = model_idx

        if (idx >= client%model_count) then
            response = 'ERROR: Invalid model index'
            status = -1
            return
        end if

        ! Build curl command for non-streaming
        write(cmd, '(A,A,A,A,A)') &
            'curl -s -X POST "', trim(client%models(idx + 1)%endpoint), '/chat/completions" ', &
            '-H "Content-Type: application/json" ', &
            '-d ''{"model":"', trim(client%models(idx + 1)%id), &
            '","messages":', trim(messages), &
            ',"stream":false,"max_tokens":100}'' | ' // &
            'jq -r ''.choices[0].message.content'' > /tmp/fortran_response.txt 2>/dev/null'

        call execute_command_line(trim(cmd), exitstat=ios)

        if (ios == 0) then
            inquire(file='/tmp/fortran_response.txt', exist=file_exists)
            if (file_exists) then
                open(unit=10, file='/tmp/fortran_response.txt', status='old', action='read', iostat=ios)
                if (ios == 0) then
                    read(10, '(A)', iostat=ios) response
                    close(10)
                    status = 0
                else
                    close(10)
                    response = 'ERROR: Failed to read response'
                    status = -1
                end if
            else
                response = 'ERROR: No response file'
                status = -1
            end if
        else
            response = 'ERROR: HTTP request failed'
            status = -1
        end if

        call execute_command_line('rm -f /tmp/fortran_response.txt', exitstat=ios)

    end subroutine uncloseai_chat

    ! Streaming chat completion
    subroutine uncloseai_chat_stream(client, messages, model_idx, status)
        type(uncloseai_client), intent(in) :: client
        character(len=*), intent(in) :: messages
        integer, intent(in), optional :: model_idx
        integer, intent(out) :: status
        integer :: idx, ios
        character(len=4096) :: cmd

        idx = 0
        if (present(model_idx)) idx = model_idx

        if (idx >= client%model_count) then
            write(*,*) 'ERROR: Invalid model index'
            status = -1
            return
        end if

        ! Build curl command for streaming with SSE parsing
        write(cmd, '(A,A,A,A,A)') &
            'curl -s --no-buffer -X POST "', trim(client%models(idx + 1)%endpoint), &
            '/chat/completions" ', &
            '-H "Content-Type: application/json" ', &
            '-d ''{"model":"', trim(client%models(idx + 1)%id), &
            '","messages":', trim(messages), &
            ',"stream":true,"max_tokens":500}'' | ' // &
            'while IFS= read -r line; do ' // &
            '[[ "$line" =~ ^data:\ (.+)$ ]] || continue; ' // &
            'data="${BASH_REMATCH[1]}"; ' // &
            '[ "$data" = "[DONE]" ] && break; ' // &
            'printf "%s" "$(echo "$data"|jq -r ''.choices[0].delta.content//empty'')"; ' // &
            'done'

        call execute_command_line(trim(cmd), exitstat=ios)

        status = ios

    end subroutine uncloseai_chat_stream

    ! Text-to-speech generation
    subroutine uncloseai_tts(client, text, voice, output_file, status)
        type(uncloseai_client), intent(in) :: client
        character(len=*), intent(in) :: text
        character(len=*), intent(in), optional :: voice
        character(len=*), intent(in), optional :: output_file
        integer, intent(out) :: status
        character(len=256) :: voice_str, file_str
        character(len=2048) :: cmd
        integer :: ios

        voice_str = 'alloy'
        if (present(voice)) voice_str = trim(voice)

        file_str = '/tmp/speech.mp3'
        if (present(output_file)) file_str = trim(output_file)

        if (client%tts_count == 0) then
            status = -1
            return
        end if

        ! Build curl command for TTS
        write(cmd, '(A,A,A,A,A,A,A)') &
            'curl -s -X POST "', trim(client%tts_endpoints(1)), '/audio/speech" ', &
            '-H "Content-Type: application/json" ', &
            '-d ''{"model":"tts-1","voice":"', trim(voice_str), &
            '","input":"', trim(text), '"}'' ', &
            '-o ', trim(file_str)

        call execute_command_line(trim(cmd), exitstat=ios)

        status = ios

    end subroutine uncloseai_tts

end module uncloseai_lib

! Demo program showing library usage
program main
    use uncloseai_lib
    implicit none
    type(uncloseai_client) :: client
    character(len=4096) :: response
    integer :: status, model_idx
    logical :: file_exists

    write(*,*) '=== UncloseAI Fortran Client (with Streaming) ==='
    write(*,*)

    ! Initialize client
    call uncloseai_init(client)

    if (client%model_count == 0) then
        write(*,*) 'ERROR: No models discovered'
        stop 1
    end if

    ! Non-streaming chat example
    write(*,*) '=== Non-Streaming Chat ==='
    write(*,'(A,A)') 'Model: ', trim(client%models(1)%id)

    call uncloseai_chat(client, &
        '[{"role":"user","content":"Explain quantum computing in one sentence"}]', &
        0, response, status)

    if (status == 0) then
        write(*,'(A,A)') 'Response: ', trim(response)
    else
        write(*,*) trim(response)
    end if
    write(*,*)

    ! Streaming chat example
    model_idx = 0
    if (client%model_count >= 2) model_idx = 1

    write(*,*) '=== Streaming Chat ==='
    write(*,'(A,A)') 'Model: ', trim(client%models(model_idx + 1)%id)
    write(*,'(A)', advance='no') 'Response: '

    call uncloseai_chat_stream(client, &
        '[{"role":"user","content":"Write a hello world program in Fortran"}]', &
        model_idx, status)
    write(*,*)
    write(*,*)

    ! TTS example
    if (client%tts_count > 0) then
        write(*,*) '=== TTS Speech Generation ==='
        write(*,*) 'Model: tts-1'

        call uncloseai_tts(client, &
            'Hello from UncloseAI Fortran client!', &
            'alloy', '/tmp/speech.mp3', status)

        if (status == 0) then
            write(*,*) 'Audio saved to /tmp/speech.mp3'
        else
            write(*,*) 'TTS failed'
        end if
    end if

    write(*,*)
    write(*,*) '=== Examples Complete ==='

end program main
