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

// UncloseAI - V client for OpenAI-compatible APIs with streaming support

import net.http
import json
import os

pub struct ChatMessage {
pub:
	role    string
	content string
}

pub struct ModelInfo {
pub mut:
	id         string
	endpoint   string
	max_tokens int
}

struct ChatRequest {
	model       string
	messages    []ChatMessage
	max_tokens  int
	temperature f64
	stream      bool
}

struct ResponseMessage {
	content string
}

struct DeltaMessage {
	content string @[json: 'content']
}

struct Choice {
	message ResponseMessage
}

struct StreamChoice {
	delta DeltaMessage
}

struct ChatResponse {
	choices []Choice
}

struct ChatStreamChunk {
	choices []StreamChoice
}

struct TTSRequest {
	model string
	voice string
	input string
}

struct ModelData {
	id            string
	max_model_len int @[json: 'max_model_len']
}

struct ModelsResponse {
	data []ModelData
}

pub struct UncloseAI {
pub mut:
	models        []ModelInfo
	tts_endpoints []string
	api_key       string
	timeout       int
	debug         bool
}

pub fn uncloseai_new(endpoints []string, tts_eps []string, api_key string, timeout int, debug bool) !UncloseAI {
	mut client := UncloseAI{
		models: []ModelInfo{}
		tts_endpoints: tts_eps
		api_key: api_key
		timeout: if timeout > 0 { timeout } else { 30 }
		debug: debug
	}

	// Discover endpoints from environment if not provided
	model_ends := if endpoints.len == 0 {
		discover_env_endpoints('MODEL_ENDPOINT')
	} else {
		endpoints
	}

	tts_ends := if tts_eps.len == 0 {
		discover_env_endpoints('TTS_ENDPOINT')
	} else {
		tts_eps
	}

	if debug {
		println('[DEBUG] Initialized with ${model_ends.len} endpoint(s)')
	}

	client.models = discover_models(model_ends, debug)!
	client.tts_endpoints = tts_ends

	return client
}

fn discover_env_endpoints(prefix string) []string {
	mut endpoints := []string{}
	for i := 1; i < 10000; i++ {
		endpoint := os.getenv('${prefix}_${i}')
		if endpoint == '' {
			break
		}
		endpoints << endpoint
	}
	return endpoints
}

fn discover_models(endpoints []string, debug bool) ![]ModelInfo {
	mut models := []ModelInfo{}

	for endpoint in endpoints {
		if debug {
			println('[DEBUG] Discovering from: ${endpoint}')
		}

		req := http.get('${endpoint}/models') or {
			if debug {
				println('[DEBUG] Error: ${err}')
			}
			continue
		}

		models_resp := json.decode(ModelsResponse, req.body) or {
			if debug {
				println('[DEBUG] Parse error: ${err}')
			}
			continue
		}

		for model in models_resp.data {
			// Skip permission entries
			if !model.id.starts_with('modelperm-') && !model.id.starts_with('chatcmpl-') {
				max_tok := if model.max_model_len > 0 { model.max_model_len } else { 8192 }
				models << ModelInfo{
					id: model.id
					endpoint: endpoint
					max_tokens: max_tok
				}

				if debug {
					println('[DEBUG] Discovered: ${model.id}')
				}
			}
		}
	}

	return models
}

pub fn (client &UncloseAI) list_models() []ModelInfo {
	return client.models
}

fn (client &UncloseAI) resolve_model(model_id string) !ModelInfo {
	if client.models.len == 0 {
		return error('No models available')
	}

	if model_id == '' {
		return client.models[0]
	}

	for model in client.models {
		if model.id == model_id {
			return model
		}
	}

	return error('Model ${model_id} not found')
}

pub fn (client &UncloseAI) chat(messages []ChatMessage, model_id string, max_tokens int, temperature f64) !ChatResponse {
	model_info := client.resolve_model(model_id)!

	request := ChatRequest{
		model: model_info.id
		messages: messages
		max_tokens: if max_tokens > 0 { max_tokens } else { 100 }
		temperature: temperature
		stream: false
	}

	request_json := json.encode(request)

	mut req := http.new_request(.post, '${model_info.endpoint}/chat/completions', request_json)
	req.add_header(.content_type, 'application/json')

	if client.api_key != '' {
		req.add_header(.authorization, 'Bearer ${client.api_key}')
	}

	resp := req.do()!
	chat_response := json.decode(ChatResponse, resp.body)!

	return chat_response
}

pub fn (client &UncloseAI) chat_stream(messages []ChatMessage, callback fn (string), model_id string, max_tokens int, temperature f64) ! {
	model_info := client.resolve_model(model_id)!

	request := ChatRequest{
		model: model_info.id
		messages: messages
		max_tokens: if max_tokens > 0 { max_tokens } else { 500 }
		temperature: temperature
		stream: true
	}

	request_json := json.encode(request)

	mut req := http.new_request(.post, '${model_info.endpoint}/chat/completions', request_json)
	req.add_header(.content_type, 'application/json')

	if client.api_key != '' {
		req.add_header(.authorization, 'Bearer ${client.api_key}')
	}

	resp := req.do()!

	// Parse SSE stream line by line
	lines := resp.body.split('\n')
	for line in lines {
		if line.starts_with('data: ') {
			data := line[6..].trim_space()
			if data == '[DONE]' {
				break
			}

			chunk := json.decode(ChatStreamChunk, data) or { continue }

			if chunk.choices.len > 0 {
				content := chunk.choices[0].delta.content
				if content != '' {
					callback(content)
				}
			}
		}
	}
}

pub fn (client &UncloseAI) tts(text string, voice string, model string, format string) ![]u8 {
	if client.tts_endpoints.len == 0 {
		return error('No TTS endpoints available')
	}

	endpoint := client.tts_endpoints[0]

	request := TTSRequest{
		model: if model != '' { model } else { 'tts-1' }
		voice: if voice != '' { voice } else { 'alloy' }
		input: text
	}

	request_json := json.encode(request)

	mut req := http.new_request(.post, '${endpoint}/audio/speech', request_json)
	req.add_header(.content_type, 'application/json')

	if client.api_key != '' {
		req.add_header(.authorization, 'Bearer ${client.api_key}')
	}

	resp := req.do()!

	return resp.body.bytes()
}

// Demo when run as application
fn main() {
	println('=== UncloseAI V Client (with Streaming) ===\n')

	client := uncloseai_new([], [], '', 30, true) or {
		println('Error creating client: ${err}')
		return
	}

	if client.list_models().len == 0 {
		println('ERROR: No models discovered. Set environment variables:')
		println('  MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.')
		return
	}

	println('\nDiscovered ${client.list_models().len} model(s):')
	for model in client.list_models() {
		println('  - ${model.id} (max_tokens: ${model.max_tokens})')
	}
	println('')

	// Non-streaming chat
	println('=== Non-Streaming Chat ===')
	chat_resp := client.chat([
		ChatMessage{
			role: 'system'
			content: 'You are a helpful AI assistant.'
		},
		ChatMessage{
			role: 'user'
			content: 'Explain quantum computing in one sentence.'
		},
	], '', 100, 0.7) or {
		println('Error: ${err}\n')
		return
	}

	println('Response: ${chat_resp.choices[0].message.content}\n')

	// Streaming chat
	println('=== Streaming Chat ===')
	model_id := if client.list_models().len > 1 { client.list_models()[1].id } else { '' }
	model_name := if model_id != '' { model_id } else { client.list_models()[0].id }
	println('Model: ${model_name}')
	print('Response: ')

	client.chat_stream([
		ChatMessage{
			role: 'system'
			content: 'You are a coding assistant.'
		},
		ChatMessage{
			role: 'user'
			content: 'Write a V function to check if a number is prime'
		},
	], fn (content string) {
		print(content)
	}, model_id, 200, 0.7) or { println('\nError: ${err}') }

	println('\n')

	// TTS
	if client.tts_endpoints.len > 0 {
		println('=== TTS Speech Generation ===')
		audio_data := client.tts('Hello from UncloseAI V client! This demonstrates streaming support.',
			'', '', '') or {
			println('✗ TTS Error: ${err}\n')
			return
		}

		os.write_file_array('speech.mp3', audio_data) or {
			println('✗ Error writing file: ${err}\n')
			return
		}

		println('✓ Speech file created: speech.mp3 (${audio_data.len} bytes)\n')
	}

	println('=== Examples Complete ===')
}
