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

import 'dart:io';
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;

// UncloseAI Dart Library
// OpenAI-compatible API client with streaming support
// Compatible with vLLM, Ollama, and OpenAI-compatible endpoints

class ModelInfo {
  final String id;
  final String endpoint;
  final int maxTokens;

  ModelInfo(this.id, this.endpoint, this.maxTokens);
}

// UncloseAI Client class
class UncloseAI {
  final List<ModelInfo> models = [];
  final List<String> ttsEndpoints = [];
  final int timeout;

  UncloseAI({this.timeout = 30}) {
    _discoverModels();
  }

  // Discover models from environment variables
  void _discoverModels() {
    print('Initializing UncloseAI client...');

    // Discover chat/code models
    for (int i = 1; i <= 9999; i++) {
      final endpoint = Platform.environment['MODEL_ENDPOINT_$i'];
      if (endpoint == null) break;

      print('Endpoint $i: $endpoint');
      _discoverModelsFromEndpoint(endpoint);
    }

    // Discover TTS endpoints
    for (int i = 1; i <= 9999; i++) {
      final endpoint = Platform.environment['TTS_ENDPOINT_$i'];
      if (endpoint == null) break;
      ttsEndpoints.add(endpoint);
    }

    print('Discovered ${models.length} models, ${ttsEndpoints.length} TTS endpoints\n');
  }

  // Discover models from a specific endpoint (synchronous for simplicity)
  void _discoverModelsFromEndpoint(String endpoint) {
    // Note: This is simplified - in real usage you'd make this async
    try {
      final response = http.get(Uri.parse('$endpoint/models'))
          .timeout(const Duration(seconds: 10))
          .then((response) {
        final data = jsonDecode(response.body);
        if (data['data'] != null) {
          for (var model in data['data']) {
            final modelId = model['id'] as String;

            // Skip modelperm-* entries
            if (modelId.startsWith('modelperm-')) continue;

            final maxTokens = model['max_model_len'] as int? ?? 8192;
            models.add(ModelInfo(modelId, endpoint, maxTokens));
          }
        }
      });
    } catch (e) {
      // Silently skip failed endpoints
    }
  }

  // Non-streaming chat completion
  Future<String> chat(
    List<Map<String, String>> messages, {
    int modelIdx = 0,
    int maxTokens = 100,
    double temperature = 0.7,
  }) async {
    if (modelIdx >= models.length) {
      throw Exception('Invalid model index');
    }

    final model = models[modelIdx];
    final url = '${model.endpoint}/chat/completions';

    final request = {
      'model': model.id,
      'messages': messages,
      'stream': false,
      'max_tokens': maxTokens,
      'temperature': temperature,
    };

    final response = await http.post(
      Uri.parse(url),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(request),
    ).timeout(Duration(seconds: timeout));

    final data = jsonDecode(response.body);
    return data['choices'][0]['message']['content'] as String;
  }

  // Streaming chat completion - returns a Stream of content chunks
  Stream<String> chatStream(
    List<Map<String, String>> messages, {
    int modelIdx = 0,
    int maxTokens = 500,
    double temperature = 0.7,
  }) async* {
    if (modelIdx >= models.length) {
      throw Exception('Invalid model index');
    }

    final model = models[modelIdx];
    final url = '${model.endpoint}/chat/completions';

    final request = {
      'model': model.id,
      'messages': messages,
      'stream': true,
      'max_tokens': maxTokens,
      'temperature': temperature,
    };

    final httpRequest = http.Request('POST', Uri.parse(url));
    httpRequest.headers['Content-Type'] = 'application/json';
    httpRequest.body = jsonEncode(request);

    final streamedResponse = await httpRequest.send()
        .timeout(Duration(seconds: timeout));

    await for (var chunk in streamedResponse.stream.transform(utf8.decoder).transform(const LineSplitter())) {
      if (!chunk.startsWith('data: ')) continue;

      final data = chunk.substring(6).trim();
      if (data == '[DONE]') break;

      try {
        final json = jsonDecode(data);
        final content = json['choices']?[0]?['delta']?['content'];
        if (content != null) {
          yield content as String;
        }
      } catch (e) {
        // Skip malformed JSON
      }
    }
  }

  // Text-to-speech generation
  Future<bool> tts(
    String text, {
    String voice = 'alloy',
    String outputFile = '/tmp/speech.mp3',
  }) async {
    if (ttsEndpoints.isEmpty) return false;

    final endpoint = ttsEndpoints[0];
    final url = '$endpoint/audio/speech';

    final request = {
      'model': 'tts-1',
      'voice': voice,
      'input': text,
    };

    try {
      final response = await http.post(
        Uri.parse(url),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(request),
      ).timeout(Duration(seconds: timeout));

      final file = File(outputFile);
      await file.writeAsBytes(response.bodyBytes);
      return true;
    } catch (e) {
      return false;
    }
  }
}

// Demo program showing library usage
void main() async {
  print('=== UncloseAI Dart Client (with Streaming) ===\n');

  // Initialize client
  final client = UncloseAI();

  // Wait a moment for async discovery to complete
  await Future.delayed(const Duration(seconds: 2));

  if (client.models.isEmpty) {
    print('ERROR: No models discovered');
    exit(1);
  }

  // Non-streaming chat example
  print('=== Non-Streaming Chat ===');
  print('Model: ${client.models[0].id}');

  try {
    final messages = [
      {'role': 'user', 'content': 'Explain quantum computing in one sentence'}
    ];
    final response = await client.chat(messages);
    print('Response: $response\n');
  } catch (e) {
    print('Error: $e\n');
  }

  // Streaming chat example
  final modelIdx = client.models.length >= 2 ? 1 : 0;
  print('=== Streaming Chat ===');
  print('Model: ${client.models[modelIdx].id}');
  stdout.write('Response: ');

  try {
    final messages = [
      {'role': 'user', 'content': 'Write a hello world program in Dart'}
    ];
    await for (var content in client.chatStream(messages, modelIdx: modelIdx)) {
      stdout.write(content);
    }
    print('\n');
  } catch (e) {
    print('\nError: $e\n');
  }

  // TTS example
  if (client.ttsEndpoints.isNotEmpty) {
    print('=== TTS Speech Generation ===');
    print('Model: tts-1');

    if (await client.tts('Hello from UncloseAI Dart client!',
        outputFile: '/tmp/speech.mp3')) {
      print('Audio saved to /tmp/speech.mp3');
    } else {
      print('TTS failed');
    }
  }

  print('\n=== Examples Complete ===');
}
