import 'dart:async';
import 'dart:convert';
import 'package:ainoval/config/app_config.dart' hide LogLevel;
import 'package:ainoval/models/ai_request_models.dart';
import 'package:ainoval/models/chat_models.dart';
import 'package:ainoval/models/import_status.dart';
import 'package:ainoval/models/model_info.dart';
import 'package:ainoval/models/user_ai_model_config_model.dart';
import 'package:ainoval/services/api_service/base/api_exception.dart';
import 'package:ainoval/services/auth_service.dart';
import 'package:ainoval/utils/logger.dart';
import 'package:dio/dio.dart';

/// API客户端基类
///
/// 负责处理与后端API的基础通信，使用Dio包实现HTTP请求
class ApiClient {
  ApiClient({Dio? dio, AuthService? authService}) {
    _authService = authService;
    _dio = dio ?? _createDio();
  }
  late final Dio _dio;
  AuthService? _authService;

  /// 设置AuthService实例（用于处理401错误）
  void setAuthService(AuthService authService) {
    _authService = authService;
  }

  /// 创建并配置Dio实例
  Dio _createDio() {
    final dio = Dio(
      BaseOptions(
        baseUrl: AppConfig.apiBaseUrl,
        connectTimeout: const Duration(seconds: 30),
        receiveTimeout: const Duration(minutes: 5), // 🚀 增加到5分钟，支持长时间AI响应
        sendTimeout: const Duration(seconds: 30),
        contentType: 'application/json',
        responseType: ResponseType.json,
      ),
    );

    // 添加拦截器
    dio.interceptors.add(_createAuthInterceptor());
    dio.interceptors.add(_createResponseInterceptor());
    dio.interceptors.add(_createLogInterceptor());

    return dio;
  }

  /// 规范化后端响应数据，避免在 Web 端出现 LegacyJavaScriptObject
  dynamic _normalizeResponseData(dynamic raw) {
    if (raw == null) return null;
    // 如果是 JSON 字符串，先尝试解码
    if (raw is String) {
      final trimmed = raw.trim();
      if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
          (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
        try {
          return jsonDecode(trimmed);
        } catch (_) {
          return raw;
        }
      }
      return raw;
    }
    // 对于 Web 端返回的 JS 对象，使用 JSON 循环编码/解码，转为 Dart 原生 Map/List
    try {
      final encoded = jsonEncode(raw);
      if (encoded.isNotEmpty && (encoded.startsWith('{') || encoded.startsWith('['))) {
        return jsonDecode(encoded);
      }
    } catch (_) {
      // 忽略，按原样返回
    }
    return raw;
  }

  /// 创建认证拦截器
  Interceptor _createAuthInterceptor() {
    return InterceptorsWrapper(
      onRequest: (options, handler) {
        final token = AppConfig.authToken;
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        
        // 添加用户ID头部（后端需要X-User-Id头部）
        final userId = AppConfig.userId;
        if (userId != null) {
          options.headers['X-User-Id'] = userId;
        }
        
        return handler.next(options);
      },
    );
  }

  /// 创建响应拦截器（处理401错误）
  Interceptor _createResponseInterceptor() {
    return InterceptorsWrapper(
      onError: (DioException error, ErrorInterceptorHandler handler) async {
        // 检查是否为401未授权错误
        if (error.response?.statusCode == 401) {
          AppLogger.w('ApiClient', 'Token过期或无效，执行自动登出');
          
          // 执行登出操作
          if (_authService != null) {
            try {
              await _authService!.logout();
            } catch (e) {
              AppLogger.e('ApiClient', '自动登出失败', e);
            }
          }
        }
        return handler.next(error);
      },
    );
  }

  /// 创建日志拦截器
  Interceptor _createLogInterceptor() {
    final currentLogLevel = AppConfig.logLevel;

    return LogInterceptor(
      requestBody: currentLogLevel == LogLevel.warning,
      responseBody: currentLogLevel == LogLevel.warning,
      error: currentLogLevel == LogLevel.debug ||
          currentLogLevel == LogLevel.error,
      requestHeader: currentLogLevel == LogLevel.warning,
      responseHeader: currentLogLevel == LogLevel.warning,
    );
  }

  /// 基础POST请求方法
  Future<dynamic> post(String path, {dynamic data, Options? options}) async {
    try {
      // 添加日志记录，显示请求正文
      AppLogger.d('ApiClient', '发送POST请求到 $path');
      
      if (data != null) {
        try {
          final String jsonData = jsonEncode(data);
          AppLogger.d('ApiClient', '请求正文: $jsonData');
        } catch (e) {
          AppLogger.d('ApiClient', '请求正文(无法序列化): $data');
        }
      }
      
      final response = await _dio.post(path, data: data, options: options);
      return _normalizeResponseData(response.data);
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'post 执行出错，路径: $path', e);
      throw ApiException(-1, '执行 POST 请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 基础流式POST请求方法
  ///
  /// 返回原始字节流 Stream<List<int>>
  Future<Stream<List<int>>> postStream(String path,
      {dynamic data, Options? options}) async {
    try {
      final response = await _dio.post<ResponseBody>(
        path,
        data: data,
        options:
            (options ?? Options()).copyWith(responseType: ResponseType.stream),
      );
      if (response.data != null) {
        return response.data!.stream;
      } else {
        AppLogger.w('ApiClient', 'postStream 收到空的响应数据，路径: $path');
        return Stream.error(ApiException(-1, '流式请求收到空的响应数据'));
      }
    } on DioException catch (e) {
      AppLogger.e('ApiClient', 'postStream 请求失败，路径: $path', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'postStream 执行出错，路径: $path', e);
      throw ApiException(-1, '执行流式请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 辅助方法：处理字节流，解码，解析 SSE 或单行 JSON 数组，并生成指定类型的流
  Stream<T> _processStream<T>({
    required Future<Stream<List<int>>> byteStreamFuture,
    required T Function(Map<String, dynamic>) fromJson,
    required String logContext,
  }) {
    final controller = StreamController<T>();
    int retryCount = 0;
    const maxRetries = 3;

    Future<void> processStream() async {
      try {
        final byteStream = await byteStreamFuture;
        final stringStream = utf8.decoder.bind(byteStream);

        await for (final rawLine in stringStream.transform(const LineSplitter())) {
          try {
            final line = rawLine.trim();

            if (line.isEmpty) {
              continue;
            }

            if (line.startsWith('data:')) {
              final eventData = line.substring(5).trim();
              if (eventData.isNotEmpty && eventData != '[DONE]') {
                final json = jsonDecode(eventData);
                if (json is Map<String, dynamic>) {
                  final item = fromJson(json);
                  AppLogger.v('ApiClient',
                      '[$logContext] 解析 SSE 数据: ${item.runtimeType}');
                  if (!controller.isClosed) {
                    controller.add(item);
                  }
                } else {
                  AppLogger.w('ApiClient',
                      '[$logContext] SSE 数据不是有效的 JSON 对象: $eventData');
                }
              } else if (eventData == '[DONE]') {
                AppLogger.i('ApiClient', '[$logContext] 收到 SSE 流结束标记 [DONE]');
              }
            } else if (line.startsWith('[') && line.endsWith(']')) {
              AppLogger.v('ApiClient',
                  '[$logContext] 检测到单行 JSON 数组，尝试解析，长度: ${line.length}');
              final decodedList = jsonDecode(line);
              if (decodedList is List) {
                int count = 0;
                for (final itemJson in decodedList) {
                  await Future.delayed(Duration.zero);

                  if (controller.isClosed) break;

                  if (itemJson is Map<String, dynamic>) {
                    try {
                      final item = fromJson(itemJson);
                      AppLogger.v('ApiClient',
                          '[$logContext] 解析 JSON 数组元素 ${++count}: ${item.runtimeType}');
                      if (!controller.isClosed) {
                        controller.add(item);
                      }
                    } catch (e, stackTrace) {
                      AppLogger.e(
                          'ApiClient',
                          '[$logContext] 从 JSON 数组元素转换失败: $itemJson',
                          e,
                          stackTrace);
                    }
                  } else {
                    AppLogger.w('ApiClient',
                        '[$logContext] JSON 数组中的元素不是 Map: $itemJson');
                  }
                }
                AppLogger.i('ApiClient', '[$logContext] 成功处理 $count 个 JSON 数组元素');
              } else {
                AppLogger.w('ApiClient', '[$logContext] 解析为 JSON 但不是列表: "$line"');
              }
            } else {
              AppLogger.v(
                  'ApiClient', '[$logContext] 忽略非 SSE 且非 JSON 数组的行: "$line"');
            }
          } catch (e, stackTrace) {
            AppLogger.e('ApiClient', '[$logContext] 解析流式响应行失败: "$rawLine"', e,
                stackTrace);
          }
          if (controller.isClosed) break;
        }
        AppLogger.i('ApiClient', '[$logContext] 流式字符串处理完成');
        if (!controller.isClosed) {
          controller.close();
        }
      } catch (error, stackTrace) {
        AppLogger.e('ApiClient', '[$logContext] 获取或解码流式字节流失败', error, stackTrace);
        
        if (retryCount < maxRetries) {
          retryCount++;
          AppLogger.i('ApiClient', '[$logContext] 尝试重试 ($retryCount/$maxRetries)');
          await Future.delayed(Duration(seconds: retryCount * 2)); // 指数退避
          return processStream();
        }
        
        if (!controller.isClosed) {
          final apiError = (error is ApiException)
              ? error
              : ApiException(
                  -1, '[$logContext] 启动或解码流式请求失败: ${error.toString()}');
          controller.addError(apiError, stackTrace);
          controller.close();
        }
      }
    }

    processStream();
    return controller.stream;
  }

  /// 基础GET请求方法，返回流
  Future<Stream<List<int>>> getStream(String path, {Options? options}) async {
    try {
      final response = await _dio.get<ResponseBody>(
        path,
        options: (options ?? Options()).copyWith(responseType: ResponseType.stream),
      );
      if (response.data != null) {
        return response.data!.stream;
      } else {
        AppLogger.w('ApiClient', 'getStream 收到空的响应数据，路径: $path');
        return Stream.error(ApiException(-1, '流式请求收到空的响应数据'));
      }
    } on DioException catch (e) {
      AppLogger.e('ApiClient', 'getStream 请求失败，路径: $path', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'getStream 执行出错，路径: $path', e);
      throw ApiException(-1, '执行流式请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 基础GET请求方法
  Future<dynamic> get(String path, {Options? options}) async {
    try {
      final response = await _dio.get(path, options: options);
      return _normalizeResponseData(response.data);
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'get 执行出错，路径: $path', e);
      throw ApiException(-1, '执行 GET 请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 支持查询参数的GET请求方法
  Future<dynamic> getWithParams(String path, {Map<String, dynamic>? queryParameters, Options? options}) async {
    try {
      final response = await _dio.get(path, queryParameters: queryParameters, options: options);
      return _normalizeResponseData(response.data);
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'getWithParams 执行出错，路径: $path', e);
      throw ApiException(-1, '执行 GET 请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 基础PUT请求方法
  Future<dynamic> put(String path, {dynamic data, Options? options}) async {
    try {
      final response = await _dio.put(path, data: data, options: options);
      return _normalizeResponseData(response.data);
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'put 执行出错，路径: $path', e);
      throw ApiException(-1, '执行 PUT 请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 基础PATCH请求方法
  Future<dynamic> patch(String path, {dynamic data, Options? options}) async {
    try {
      final response = await _dio.patch(path, data: data, options: options);
      return _normalizeResponseData(response.data);
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'patch 执行出错，路径: $path', e);
      throw ApiException(-1, '执行 PATCH 请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 基础DELETE请求方法
  Future<dynamic> delete(String path, {dynamic data, Options? options}) async {
    try {
      final response = await _dio.delete(path, data: data, options: options);
      return _normalizeResponseData(response.data);
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'delete 执行出错，路径: $path', e);
      throw ApiException(-1, '执行 DELETE 请求时发生意外错误: ${e.toString()}');
    }
  }

  /// 支持查询参数的DELETE请求方法
  Future<dynamic> deleteWithParams(String path, {Map<String, dynamic>? queryParameters, dynamic data, Options? options}) async {
    try {
      final response = await _dio.delete(path, queryParameters: queryParameters, data: data, options: options);
      return _normalizeResponseData(response.data);
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', 'deleteWithParams 执行出错，路径: $path', e);
      throw ApiException(-1, '执行 DELETE 请求时发生意外错误: ${e.toString()}');
    }
  }

  //==== 小说相关接口 ====//

  /// 导入小说文件
  Future<String> importNovel(List<int> fileBytes, String fileName) async {
    try {
      // 获取当前用户ID
      final userId = AppConfig.userId;
      
      // 创建 MultipartFile
      final formData = FormData.fromMap({
        'file': MultipartFile.fromBytes(
          fileBytes,
          filename: fileName,
        ),
        // 添加用户ID字段，虽然后端应该能从token中获取，这里作为备用
        if (userId != null) 'userId': userId,
      });

      // 设置接收 JobId 的选项
      final options = Options(
        contentType: 'multipart/form-data',
        responseType: ResponseType.json,
      );

      // 发送上传请求
      final response = await _dio.post(
        '/novels/import',
        data: formData,
        options: options,
      );

      // 响应应该包含一个 jobId
      if (response.data is Map<String, dynamic> &&
          response.data.containsKey('jobId')) {
        return response.data['jobId'];
      } else {
        AppLogger.e('ApiClient', '导入小说响应格式不正确: ${response.data}');
        throw ApiException(-1, '导入请求响应格式不正确');
      }
    } on DioException catch (e) {
      AppLogger.e('ApiClient', '导入小说文件失败', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '导入小说文件失败', e);
      throw ApiException(-1, '导入小说文件失败: ${e.toString()}');
    }
  }
  
  /// 取消导入任务
  Future<bool> cancelImport(String jobId) async {
    try {
      AppLogger.i('ApiClient', '发送取消导入任务请求: jobId=$jobId');
      
      // 使用基础POST方法发送取消请求
      final response = await post('/novels/import/$jobId/cancel');
      
      if (response is Map<String, dynamic> && response.containsKey('status')) {
        final success = response['status'] == 'success';
        AppLogger.i('ApiClient', '取消导入任务结果: ${success ? '成功' : '失败'}, jobId=$jobId');
        return success;
      }
      
      AppLogger.w('ApiClient', '取消导入任务响应格式不正确: $response');
      return false;
    } catch (e) {
      AppLogger.e('ApiClient', '取消导入任务失败: jobId=$jobId', e);
      return false;
    }
  }

  // === 新的三步导入流程API方法 ===

  /// 第一步：上传文件获取预览会话ID
  Future<String> uploadFileForPreview(List<int> fileBytes, String fileName) async {
    try {
      // 获取当前用户ID
      final userId = AppConfig.userId;
      
      // 创建 MultipartFile
      final formData = FormData.fromMap({
        'file': MultipartFile.fromBytes(
          fileBytes,
          filename: fileName,
        ),
        // 添加用户ID字段，虽然后端应该能从token中获取，这里作为备用
        if (userId != null) 'userId': userId,
      });

      // 设置接收 JSON 的选项
      final options = Options(
        contentType: 'multipart/form-data',
        responseType: ResponseType.json,
      );

      // 发送上传请求
      final response = await _dio.post(
        '/novels/import/upload-preview',
        data: formData,
        options: options,
      );

      // 响应应该包含一个 previewSessionId
      if (response.data is Map<String, dynamic> &&
          response.data.containsKey('previewSessionId')) {
        return response.data['previewSessionId'];
      } else {
        AppLogger.e('ApiClient', '上传预览文件响应格式不正确: ${response.data}');
        throw ApiException(-1, '上传预览文件响应格式不正确');
      }
    } on DioException catch (e) {
      AppLogger.e('ApiClient', '上传预览文件失败', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '上传预览文件失败', e);
      throw ApiException(-1, '上传预览文件失败: ${e.toString()}');
    }
  }

  /// 第二步：获取导入预览
  Future<Map<String, dynamic>> getImportPreview({
    required String fileSessionId,
    String? customTitle,
    int? chapterLimit,
    bool enableSmartContext = true,
    bool enableAISummary = false,
    String? aiConfigId,
    int previewChapterCount = 10,
  }) async {
    try {
      final requestData = {
        'fileSessionId': fileSessionId,
        'enableSmartContext': enableSmartContext,
        'enableAISummary': enableAISummary,
        'previewChapterCount': previewChapterCount,
        if (customTitle != null) 'customTitle': customTitle,
        if (chapterLimit != null) 'chapterLimit': chapterLimit,
        if (aiConfigId != null) 'aiConfigId': aiConfigId,
      };

      final response = await _dio.post(
        '/novels/import/preview',
        data: requestData,
        options: Options(responseType: ResponseType.json),
      );
      
      if (response.data is Map<String, dynamic>) {
        return response.data as Map<String, dynamic>;
      } else {
        AppLogger.e('ApiClient', '获取导入预览响应格式不正确: ${response.data}');
        throw ApiException(-1, '获取导入预览响应格式不正确');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '获取导入预览失败', e);
      if (e is ApiException) {
        rethrow;
      }
      throw ApiException(-1, '获取导入预览失败: ${e.toString()}');
    }
  }

  /// 第三步：确认并开始导入
  Future<String> confirmAndStartImport({
    required String previewSessionId,
    required String finalTitle,
    List<int>? selectedChapterIndexes,
    bool enableSmartContext = true,
    bool enableAISummary = false,
    String? aiConfigId,
  }) async {
    try {
      final requestData = {
        'previewSessionId': previewSessionId,
        'finalTitle': finalTitle,
        'enableSmartContext': enableSmartContext,
        'enableAISummary': enableAISummary,
        'acknowledgeRisks': true,
        if (selectedChapterIndexes != null) 'selectedChapterIndexes': selectedChapterIndexes,
        if (aiConfigId != null) 'aiConfigId': aiConfigId,
        if (AppConfig.userId != null) 'userId': AppConfig.userId,
      };

      final response = await _dio.post(
        '/novels/import/confirm',
        data: requestData,
        options: Options(responseType: ResponseType.json),
      );
      
      if (response.data is Map<String, dynamic> && 
          (response.data as Map<String, dynamic>).containsKey('jobId')) {
        return (response.data as Map<String, dynamic>)['jobId'];
      } else {
        AppLogger.e('ApiClient', '确认导入响应格式不正确: ${response.data}');
        throw ApiException(-1, '确认导入响应格式不正确');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '确认导入失败', e);
      if (e is ApiException) {
        rethrow;
      }
      throw ApiException(-1, '确认导入失败: ${e.toString()}');
    }
  }

  /// 清理预览会话
  Future<void> cleanupPreviewSession(String previewSessionId) async {
    try {
      final requestData = {
        'previewSessionId': previewSessionId,
      };

      await _dio.post(
        '/novels/import/cleanup-preview',
        data: requestData,
        options: Options(responseType: ResponseType.json),
      );
    } catch (e) {
      AppLogger.e('ApiClient', '清理预览会话失败', e);
      // 清理失败不抛出异常，只记录日志
    }
  }

  /// 长时间运行的 SSE 连接（适用于小说导入等耗时操作）
  Stream<ImportStatus> connectToLongRunningSSE(String jobId) {
    final controller = StreamController<ImportStatus>();
    final url = '${_dio.options.baseUrl}/novels/import/$jobId/status';
    
    AppLogger.i('ApiClient', '[SSE Connect] 准备连接到: $url');
    
    // 创建一个专用的 Dio 实例
    final dioForSSE = Dio();
    dioForSSE.options.baseUrl = _dio.options.baseUrl;
    
    // 设置认证令牌
    final token = AppConfig.authToken;
    if (token != null) {
      dioForSSE.options.headers['Authorization'] = 'Bearer $token';
    }
    
    // 设置 SSE 相关的请求头
    dioForSSE.options.headers['Accept'] = 'text/event-stream';
    dioForSSE.options.headers['Cache-Control'] = 'no-cache';
    dioForSSE.options.headers['Connection'] = 'keep-alive';
    
    // 设置响应类型为流
    dioForSSE.options.responseType = ResponseType.stream;
    
    // 极大延长超时时间，最多等待3小时
    dioForSSE.options.receiveTimeout = const Duration(hours: 3);
    dioForSSE.options.connectTimeout = const Duration(minutes: 2);
    
    // 关闭校验，允许所有状态码
    dioForSSE.options.validateStatus = (_) => true;

    AppLogger.i('ApiClient', '开始连接到长时间运行的 SSE，超时设置为3小时');

    // 定义心跳计时器
    Timer? heartbeatTimer;
    DateTime lastEventTime = DateTime.now();
    int heartbeatCount = 0;
    
    Future<void> connect() async {
       AppLogger.i('ApiClient', '[SSE Connect] 开始执行 dioForSSE.get(url)...');
       try {
         final responseFuture = dioForSSE.get<ResponseBody>(url); // Explicitly type ResponseBody

         AppLogger.i('ApiClient', '[SSE Connect] dioForSSE.get(url) Future 创建成功，等待响应...');

         responseFuture.then((response) {
           AppLogger.i('ApiClient', '[SSE Connect] .then() 回调被执行，状态码: ${response.statusCode}');

           if (response.statusCode != 200) {
             AppLogger.e('ApiClient', '[SSE Error] 连接失败: HTTP ${response.statusCode}，响应头: ${response.headers}');
             if (!controller.isClosed) {
                controller.addError(ApiException(
                  response.statusCode ?? -1, '[SSE Error] 连接失败: HTTP ${response.statusCode}'));
                controller.close();
             }
             return;
           }

           AppLogger.i('ApiClient', '[SSE Connect] 连接成功，开始接收事件，响应头: ${response.headers}');

           final responseBody = response.data;
           if (responseBody == null) {
              AppLogger.e('ApiClient', '[SSE Error] 响应体或流为空');
               if (!controller.isClosed) {
                 controller.addError(ApiException(-1, '[SSE Error] 响应体或流为空'));
                 controller.close();
               }
               return;
           }

           final stream = responseBody.stream;

           AppLogger.i('ApiClient', '[SSE Connect] 数据流已获取，设置心跳和监听器...');

           // 心跳检测逻辑 (保持不变)
           lastEventTime = DateTime.now(); // Reset last event time on successful connect
           heartbeatTimer?.cancel(); // Cancel previous timer if any
           heartbeatTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
              // ... (heartbeat logic as before) ...
              final now = DateTime.now();
              final difference = now.difference(lastEventTime);
              heartbeatCount++;
              AppLogger.i('ApiClient', '[SSE Heartbeat] #$heartbeatCount: 距上次事件 ${difference.inSeconds} 秒');
              if (difference.inMinutes >= 2 && !controller.isClosed) {
                 AppLogger.w('ApiClient', '[SSE Heartbeat] 已 ${difference.inMinutes} 分钟未收到事件，发送本地进度更新');
                 controller.add(ImportStatus(
                   status: 'PROCESSING',
                   message: '导入处理中，已等待 ${difference.inMinutes} 分钟...'
                 ));
                 if (difference.inMinutes >= 5) {
                   AppLogger.e('ApiClient', '[SSE Heartbeat] 已 ${difference.inMinutes} 分钟未收到事件，关闭连接');
                    if (!controller.isClosed) {
                       controller.addError(ApiException(-1, '[SSE Error] 连接超时'));
                       controller.close(); // Closing the controller will trigger onDone/onError
                    }
                    timer.cancel(); // Stop this timer
                 }
              }
           });


           // Stream 监听逻辑 (基本保持不变, 增加日志)
           String buffer = '';
           stream.listen(
             (data) {
               lastEventTime = DateTime.now(); // Update time on receiving data
               AppLogger.v('ApiClient', '[SSE Data] 收到原始数据块 (长度: ${data.length})');
               try {
                  String chunk = utf8.decode(data);
                  AppLogger.i('ApiClient', '[SSE Data] 解码后数据块: $chunk');
                  buffer += chunk;
                  while (buffer.contains('\n\n')) {
                     int endIndex = buffer.indexOf('\n\n');
                     String message = buffer.substring(0, endIndex).trim();
                     buffer = buffer.substring(endIndex + 2);
                     AppLogger.i('ApiClient', '[SSE Parse] 解析出完整消息: $message');
                     // ... (message parsing logic as before) ...
                      List<String> lines = message.split('\n');
                      Map<String, String> eventData = {};
                      for (String line in lines) {
                         if (line.startsWith('id:')) {
                           eventData['id'] = line.substring(3).trim();
                         } else if (line.startsWith('event:')) {
                           eventData['event'] = line.substring(6).trim();
                         } else if (line.startsWith('data:')) {
                           eventData['data'] = line.substring(5).trim();
                         } else if (line.startsWith(':')) {
                           AppLogger.i('ApiClient', '[SSE Comment] 收到服务器心跳注释: ${line.substring(1).trim()}');
                         }
                      }
                      if (eventData.containsKey('data')) {
                         try {
                           final json = jsonDecode(eventData['data']!);
                           if (json is Map<String, dynamic>) {
                             final status = ImportStatus.fromJson(json);
                             AppLogger.i('ApiClient', '[SSE Status] 收到状态: ${status.status} - ${status.message}');
                             if (!controller.isClosed) controller.add(status);
                             if (status.status == 'COMPLETED' || status.status == 'FAILED') {
                               AppLogger.i('ApiClient', '[SSE Status] 收到最终状态，关闭连接');
                               heartbeatTimer?.cancel();
                               if (!controller.isClosed) controller.close();
                             }
                           }
                         } catch (e, stack) {
                           AppLogger.e('ApiClient', '[SSE Parse] 解析 SSE data 失败: ${eventData['data']}', e, stack);
                         }
                      } else {
                          // ... (direct message parsing logic as before) ...
                          if (message.isNotEmpty && message != '[DONE]') {
                             try {
                               Map<String, dynamic>? json;
                               if (message.startsWith('{') && message.endsWith('}')) {
                                 json = jsonDecode(message) as Map<String, dynamic>?;
                               }
                               if (json != null && json.containsKey('status')) {
                                  final status = ImportStatus.fromJson(json);
                                  AppLogger.i('ApiClient', '[SSE Parse] 直接解析消息为状态: ${status.status}');
                                  if (!controller.isClosed) controller.add(status);
                                   if (status.status == 'COMPLETED' || status.status == 'FAILED') {
                                     AppLogger.i('ApiClient', '[SSE Status] 收到最终状态，关闭连接');
                                     heartbeatTimer?.cancel();
                                     if (!controller.isClosed) controller.close();
                                   }
                               }
                             } catch (e) {
                               // Ignore non-JSON messages
                               AppLogger.v('ApiClient', '[SSE Parse] 消息不是有效JSON，忽略: $message');
                             }
                           }
                      }
                  }
               } catch (e, stack) {
                 AppLogger.e('ApiClient', '[SSE Error] 处理数据块失败', e, stack);
               }
             },
             onError: (e, stack) {
               AppLogger.e('ApiClient', '[SSE Error] 流错误', e, stack);
               heartbeatTimer?.cancel();
               if (!controller.isClosed) {
                 controller.addError(
                     e is ApiException ? e : ApiException(-1, '[SSE Error] 读取流错误: $e'), stack);
                 controller.close();
               }
             },
             onDone: () {
               AppLogger.i('ApiClient', '[SSE Connect] 流已关闭 (onDone)');
               heartbeatTimer?.cancel();
               if (!controller.isClosed) {
                 controller.close();
               }
             },
           );

         }).catchError((e, stack) {
           // 这个 catchError 主要捕获 Future 本身的错误，比如 dio().get() 失败
            AppLogger.e('ApiClient', '[SSE Error] dioForSSE.get(url) Future 失败', e, stack);
            heartbeatTimer?.cancel();
             if (!controller.isClosed) {
               controller.addError(
                   e is ApiException ? e : ApiException(-1, '[SSE Error] 连接或读取流失败: $e'), stack);
               controller.close();
             }
         });

       } catch (e, stack) {
         // 这个 catch 主要捕获调用 dioForSSE.get(url) 时的同步错误
         AppLogger.e('ApiClient', '[SSE Error] 调用 dioForSSE.get(url) 时发生同步错误', e, stack);
          heartbeatTimer?.cancel(); // Ensure timer is cancelled
          if (!controller.isClosed) {
              controller.addError(ApiException(-1, '[SSE Error] 启动连接时出错: $e'), stack);
              controller.close();
          }
       }
    }

    // Start the connection process
    connect();

    // 当流被取消时，确保清理资源 (保持不变)
    controller.onCancel = () {
      heartbeatTimer?.cancel();
      AppLogger.i('ApiClient', '[SSE Connect] 流已被外部取消 (onCancel)');
      // Dio 会自动取消请求，但我们确保计时器停止
    };

    return controller.stream;
  }

  /// 获取小说导入状态 SSE 流（长时间运行版本）
  Stream<ImportStatus> getImportStatusStream(String jobId) {
    AppLogger.i('ApiClient', '获取导入状态流，使用长时间运行的 SSE 连接');
    
    // 创建一个StreamController，用于处理自动重试逻辑
    final controller = StreamController<ImportStatus>();
    int retryCount = 0;
    const maxRetries = 3;
    StreamSubscription? subscription;
    
    // 定义连接函数
    void connect() {
      AppLogger.i('ApiClient', '连接到导入状态流，尝试 #${retryCount + 1}');
      subscription = connectToLongRunningSSE(jobId).listen(
        (status) {
          // 正常转发状态更新
          controller.add(status);
          
          // 如果是完成或失败状态，关闭控制器
          if (status.status == 'COMPLETED' || status.status == 'FAILED') {
            AppLogger.i('ApiClient', '收到最终状态：${status.status}，关闭状态流');
            if (!controller.isClosed) {
              controller.close();
            }
          }
        },
        onError: (error, stack) {
          AppLogger.e('ApiClient', '导入状态流出错', error, stack);
          
          // 如果还可以重试，则重试
          if (retryCount < maxRetries) {
            retryCount++;
            // 指数退避策略
            final delay = Duration(seconds: retryCount * 3);
            AppLogger.i('ApiClient', '将在 ${delay.inSeconds} 秒后重试连接 ($retryCount/$maxRetries)');
            
            // 延迟后重试
            Future.delayed(delay, () {
              if (!controller.isClosed) {
                connect();
              }
            });
          } else {
            // 超过重试次数，将错误转发给上层
            AppLogger.e('ApiClient', '导入状态流重试耗尽，传递错误');
            if (!controller.isClosed) {
              controller.addError(error, stack);
              controller.close();
            }
          }
        },
        onDone: () {
          AppLogger.i('ApiClient', '导入状态流已完成');
          if (!controller.isClosed) {
            controller.close();
          }
        },
      );
    }
    
    // 启动连接
    connect();
    
    // 当流被取消时清理资源
    controller.onCancel = () {
      subscription?.cancel();
      AppLogger.i('ApiClient', '导入状态流已被取消');
    };
    
    return controller.stream;
  }

  /// 根据作者ID获取小说列表
  Future<dynamic> getNovelsByAuthor(String authorId) async {
    return post('/novels/get-by-author', data: {'authorId': authorId});
  }

  /// 根据ID获取小说详情
  Future<dynamic> getNovelDetailById(String id) async {
    return post('/novels/get-with-scenes', data: {'id': id});
  }

  /// 分页加载小说详情和场景内容
  /// 基于上次编辑章节为中心，获取前后指定数量的章节及其场景内容
  Future<dynamic> getNovelWithPaginatedScenes(String novelId, String lastEditedChapterId, {int chaptersLimit = 5}) async {
    try {
      AppLogger.i('ApiClient', '分页加载小说详情: $novelId, 中心章节: $lastEditedChapterId, 限制: $chaptersLimit');
      final response = await post('/novels/get-with-paginated-scenes', data: {
        'novelId': novelId,
        'lastEditedChapterId': lastEditedChapterId,
        'chaptersLimit': chaptersLimit
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '分页加载小说详情失败', e);
      rethrow;
    }
  }

  /// 加载更多场景内容
  /// 根据方向（向上或向下或中心）加载更多章节的场景内容
  /// direction可以是：up、down或center
  /// - up: 加载fromChapterId之前的章节
  /// - down: 加载fromChapterId之后的章节
  /// - center: 只加载fromChapterId章节或前后各加载几章
  Future<dynamic> loadMoreScenes(String novelId, String actId, String fromChapterId, String direction, {int chaptersLimit = 3}) async {
    try {
      AppLogger.i('ApiClient', '加载更多场景: $novelId, 卷: $actId, 从章节: $fromChapterId, 方向: $direction, 限制: $chaptersLimit');
      final response = await post('/novels/load-more-scenes', data: {
        'novelId': novelId,
        'actId': actId,
        'fromChapterId': fromChapterId,
        'direction': direction,
        'chaptersLimit': chaptersLimit
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '加载更多场景失败', e);
      rethrow;
    }
  }

  /// 获取当前章节后面指定数量的章节和场景内容
  /// 允许跨卷加载，专门用于阅读器的分批加载
  Future<dynamic> getChaptersAfter(String novelId, String currentChapterId, {int chaptersLimit = 3, required bool includeCurrentChapter}) async {
    try {
      AppLogger.i('ApiClient', '获取后续章节: $novelId, 当前章节: $currentChapterId, 限制: $chaptersLimit, includeCurrentChapter: $includeCurrentChapter');
      final response = await post('/novels/get-chapters-after', data: {
        'novelId': novelId,
        'currentChapterId': currentChapterId,
        'chaptersLimit': chaptersLimit,
        'includeCurrentChapter': includeCurrentChapter
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '获取后续章节失败', e);
      rethrow;
    }
  }

  /// 创建小说
  Future<dynamic> createNovel(Map<String, dynamic> novelData) async {
    return post('/novels/create', data: novelData);
  }

  /// 更新小说
  Future<dynamic> updateNovel(Map<String, dynamic> novelData) async {
    try {
      final response = await post('/novels/update', data: novelData);
      return response;
    } catch (e) {
      AppLogger.e('Services/api_service/base/api_client', '更新小说数据失败', e);
      rethrow;
    }
  }

  /// 更新小说及其场景内容
  Future<dynamic> updateNovelWithScenes(
      Map<String, dynamic> novelWithScenesData) async {
    AppLogger.i('/novels/update-with-scenes', '开始更新小说及场景数据');
    AppLogger.d('/novels/update-with-scenes', '发送的数据: $novelWithScenesData');
    try {
      final response =
          await post('/novels/update-with-scenes', data: novelWithScenesData);
      AppLogger.i('/novels/update-with-scenes', '更新成功');
      return response;
    } catch (e) {
      AppLogger.e('/novels/update-with-scenes',
          '更新小说及场景数据失败，发送的数据: $novelWithScenesData', e);
      rethrow;
    }
  }

  /// 删除小说
  Future<dynamic> deleteNovel(String id) async {
    return post('/novels/delete', data: {'id': id});
  }

  /// 根据标题搜索小说
  Future<dynamic> searchNovelsByTitle(String title) async {
    return post('/novels/search-by-title', data: {'title': title});
  }

  //==== 场景相关接口 ====//

  /// 根据ID获取场景内容
  Future<dynamic> getSceneById(
      String novelId, String chapterId, String sceneId) async {
    try {
      final response = await post('/scenes/get', data: {
        'id': sceneId,
      });
      return response;
    } catch (e) {
      AppLogger.e('Services/api_service/base/api_client', '获取场景数据失败', e);
      rethrow;
    }
  }

  /// 根据章节ID获取所有场景
  Future<dynamic> getScenesByChapter(String novelId, String chapterId) async {
    return post('/scenes/get-by-chapter',
        data: {'novelId': novelId, 'chapterId': chapterId});
  }

  /// 创建场景,未使用
  Future<dynamic> createScene(Map<String, dynamic> sceneData) async {
    return post('/scenes/create', data: sceneData);
  }

  /// 更新场景 (调用后端的 upsert 接口)
  Future<dynamic> updateScene(Map<String, dynamic> sceneData) async {
    try {
      final response = await post('/scenes/upsert', data: sceneData);
      return response;
    } catch (e) {
      AppLogger.e(
          'Services/api_service/base/api_client', '更新/创建场景数据失败', e); // 更新日志消息
      rethrow;
    }
  }

  /// 更新场景并保存历史版本
  Future<dynamic> updateSceneWithHistory(String novelId, String chapterId,
      String sceneId, String content, String userId, String reason) async {
    return post('/scenes/update-with-history', data: {
      'novelId': novelId,
      'chapterId': chapterId,
      'sceneId': sceneId,
      'content': content,
      'userId': userId,
      'reason': reason
    });
  }

  /// 获取场景历史版本
  Future<dynamic> getSceneHistory(
      String novelId, String chapterId, String sceneId) async {
    return post('/scenes/history',
        data: {'novelId': novelId, 'chapterId': chapterId, 'sceneId': sceneId});
  }

  /// 恢复场景历史版本
  Future<dynamic> restoreSceneVersion(String novelId, String chapterId,
      String sceneId, int historyIndex, String userId, String reason) async {
    return post('/scenes/restore', data: {
      'novelId': novelId,
      'chapterId': chapterId,
      'sceneId': sceneId,
      'historyIndex': historyIndex,
      'userId': userId,
      'reason': reason
    });
  }

  /// 比较场景版本
  Future<dynamic> compareSceneVersions(String novelId, String chapterId,
      String sceneId, int versionIndex1, int versionIndex2) async {
    return post('/scenes/compare', data: {
      'novelId': novelId,
      'chapterId': chapterId,
      'sceneId': sceneId,
      'versionIndex1': versionIndex1,
      'versionIndex2': versionIndex2
    });
  }

  //==== 编辑器相关接口 ====//

  /// 获取编辑器内容
  Future<dynamic> getEditorContent(
      String novelId, String chapterId, String sceneId) async {
    return post('/editor/get-content',
        data: {'novelId': novelId, 'chapterId': chapterId, 'sceneId': sceneId});
  }

  /// 保存编辑器内容
  Future<dynamic> saveEditorContent(
      String novelId, String chapterId, Map<String, dynamic> content) async {
    return post('/editor/save-content',
        data: {'novelId': novelId, 'chapterId': chapterId, 'content': content});
  }

  /// 获取修订历史
  Future<dynamic> getRevisionHistory(String novelId, String chapterId) async {
    return post('/editor/get-revisions',
        data: {'novelId': novelId, 'chapterId': chapterId});
  }

  /// 创建修订版本
  Future<dynamic> createRevision(
      String novelId, String chapterId, Map<String, dynamic> revision) async {
    return post('/editor/create-revision', data: {
      'novelId': novelId,
      'chapterId': chapterId,
      'revision': revision
    });
  }

  /// 应用修订版本
  Future<dynamic> applyRevision(
      String novelId, String chapterId, String revisionId) async {
    return post('/editor/apply-revision', data: {
      'novelId': novelId,
      'chapterId': chapterId,
      'revisionId': revisionId
    });
  }

  //==== 用户编辑器设置相关接口 ====//

  /// 获取用户编辑器设置
  Future<dynamic> getUserEditorSettings(String userId) async {
    return get('/api/user-editor-settings/$userId');
  }

  /// 保存用户编辑器设置
  Future<dynamic> saveUserEditorSettings(String userId, Map<String, dynamic> settings) async {
    return post('/api/user-editor-settings/$userId', data: settings);
  }

  /// 更新用户编辑器设置
  Future<dynamic> updateUserEditorSettings(String userId, Map<String, dynamic> settings) async {
    return patch('/api/user-editor-settings/$userId', data: settings);
  }

  /// 重置用户编辑器设置为默认值
  Future<dynamic> resetUserEditorSettings(String userId) async {
    return post('/api/user-editor-settings/$userId/reset');
  }

  /// 删除用户编辑器设置
  Future<dynamic> deleteUserEditorSettings(String userId) async {
    return delete('/api/user-editor-settings/$userId');
  }

  //==== AI 聊天相关接口 (新) ====//

  /// 创建 AI 聊天会话 (非流式)
  Future<ChatSession> createAiChatSession({
    required String userId,
    required String novelId,
    String? modelName,
    Map<String, dynamic>? metadata,
  }) async {
    try {
      final response = await post('/ai-chat/sessions/create', data: {
        'userId': userId,
        'novelId': novelId,
        'modelName': modelName,
        'metadata': metadata,
      });
      return ChatSession.fromJson(response);
    } catch (e) {
      AppLogger.e('ApiClient', '创建 AI 会话失败', e);
      rethrow;
    }
  }

  /// 获取特定 AI 会话 (非流式) - 现在返回包含AI配置的响应
  Future<Map<String, dynamic>> getAiChatSessionWithConfig(String userId, String sessionId, {String? novelId}) async {
    try {
      AppLogger.d('ApiClient', '获取AI会话（含配置）: userId=$userId, sessionId=$sessionId, novelId=$novelId');
      final requestData = {
        'userId': userId,
        'sessionId': sessionId,
      };
      
      // 🚀 添加novelId支持
      if (novelId != null) {
        requestData['novelId'] = novelId;
      }
      
      final response = await post('/ai-chat/sessions/get', data: requestData);
      
      if (response is Map<String, dynamic>) {
        // 解析会话信息
        final sessionData = response['session'];
        if (sessionData != null) {
          final session = ChatSession.fromJson(sessionData);
          AppLogger.d('ApiClient', '解析会话成功: ${session.title}, hasAIConfig=${response["aiConfig"] != null}');
          return {
            'session': session,
            'aiConfig': response['aiConfig'],
            'presetId': response['presetId'],
          };
        } else {
          throw ApiException(-1, '响应中没有找到会话数据');
        }
      } else {
        throw ApiException(-1, '响应格式不正确: $response');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '获取 AI 会话（含配置）失败 (ID: $sessionId)', e);
      rethrow;
    }
  }

  /// 获取特定 AI 会话 (非流式) - 兼容旧版本
  Future<ChatSession> getAiChatSession(String userId, String sessionId, {String? novelId}) async {
    final response = await getAiChatSessionWithConfig(userId, sessionId, novelId: novelId);
    return response['session'] as ChatSession;
  }

  /// 获取用户的所有 AI 会话 (流式)
  ///
  /// 返回 ChatSession 流
  Stream<ChatSession> listAiChatUserSessionsStream(String userId,
      {int page = 0, int size = 100, String? novelId}) {
    final requestData = {'userId': userId};
    
    // 🚀 添加novelId支持
    if (novelId != null) {
      requestData['novelId'] = novelId;
    }
    
    final byteStreamFuture =
        postStream('/ai-chat/sessions/list', data: requestData);
    return _processStream<ChatSession>(
      byteStreamFuture: byteStreamFuture,
      fromJson: ChatSession.fromJson,
      logContext: 'listAiChatUserSessionsStream',
    );
  }

  /// 更新 AI 会话 (非流式)
  Future<ChatSession> updateAiChatSession({
    required String userId,
    required String sessionId,
    required Map<String, dynamic> updates,
    String? novelId,
  }) async {
    try {
      final requestData = {
        'userId': userId,
        'sessionId': sessionId,
        'updates': updates,
      };
      
      // 🚀 添加novelId支持
      if (novelId != null) {
        requestData['novelId'] = novelId;
      }
      
      final response = await post('/ai-chat/sessions/update', data: requestData);
      return ChatSession.fromJson(response);
    } catch (e) {
      AppLogger.e('ApiClient', '更新 AI 会话失败 (ID: $sessionId)', e);
      rethrow;
    }
  }

  /// 删除 AI 会话 (非流式)
  Future<void> deleteAiChatSession(String userId, String sessionId, {String? novelId}) async {
    try {
      final requestData = {
        'userId': userId,
        'sessionId': sessionId,
      };
      
      // 🚀 添加novelId支持
      if (novelId != null) {
        requestData['novelId'] = novelId;
      }
      
      await post('/ai-chat/sessions/delete', data: requestData);
    } catch (e) {
      AppLogger.e('ApiClient', '删除 AI 会话失败 (ID: $sessionId)', e);
      rethrow;
    }
  }

  /// 发送 AI 消息 (非流式)
  Future<ChatMessage> sendAiChatMessage({
    required String userId,
    required String sessionId,
    required String content,
    Map<String, dynamic>? metadata,
    String? novelId, // 🚀 添加novelId支持
  }) async {
    try {
      final requestData = {
        'userId': userId,
        'sessionId': sessionId,
        'content': content,
        'metadata': metadata,
      };
      
      // 🚀 添加novelId支持
      if (novelId != null) {
        requestData['novelId'] = novelId;
      }
      
      final response = await post('/ai-chat/messages/send', data: requestData);
      return ChatMessage.fromJson(response);
    } catch (e) {
      AppLogger.e('ApiClient', '发送 AI 消息失败 (SessionID: $sessionId)', e);
      rethrow;
    }
  }

  /// 流式发送 AI 消息
  ///
  /// 返回解析后的 ChatMessage 流
  /// 如果提供了config，会在发送消息的同时保存配置
  Stream<ChatMessage> streamAiChatMessage({
    required String userId,
    required String sessionId,
    required String content,
    Map<String, dynamic>? metadata,
    Map<String, dynamic>? config, // 🚀 新增：AI配置参数
    String? novelId, // 🚀 添加novelId支持
  }) {
    // 🚀 构建请求数据，包含配置信息
    final requestData = {
      'userId': userId,
      'sessionId': sessionId,
      'content': content,
      'metadata': metadata,
    };
    
    // 🚀 如果有配置，添加到请求中
    if (config != null) {
      requestData['config'] = config;
    }
    
    // 🚀 添加novelId支持
    if (novelId != null) {
      requestData['novelId'] = novelId;
    }
    
    final byteStreamFuture = postStream('/ai-chat/messages/stream', data: requestData);

    return _processStream<ChatMessage>(
      byteStreamFuture: byteStreamFuture,
      fromJson: ChatMessage.fromJson,
      logContext: 'streamAiChatMessage',
    );
  }

  /// 获取 AI 会话消息历史 (流式)
  ///
  /// 返回 ChatMessage 流
  Stream<ChatMessage> getAiChatMessageHistoryStream(
      String userId, String sessionId,
      {int limit = 100, String? novelId}) {
    final requestData = {
      'userId': userId,
      'sessionId': sessionId,
      'limit': limit,
    };
    
    // 🚀 添加novelId支持
    if (novelId != null) {
      requestData['novelId'] = novelId;
    }
    
    final byteStreamFuture =
        postStream('/ai-chat/messages/history', data: requestData);

    return _processStream<ChatMessage>(
      byteStreamFuture: byteStreamFuture,
      fromJson: ChatMessage.fromJson,
      logContext: 'getAiChatMessageHistoryStream',
    );
  }

  /// 获取特定 AI 消息 (非流式)
  Future<ChatMessage> getAiChatMessage(String userId, String messageId) async {
    try {
      final response = await post('/ai-chat/messages/get', data: {
        'userId': userId,
        'messageId': messageId,
      });
      return ChatMessage.fromJson(response);
    } catch (e) {
      AppLogger.e('ApiClient', '获取 AI 消息失败 (ID: $messageId)', e);
      rethrow;
    }
  }

  /// 删除 AI 消息 (非流式)
  Future<void> deleteAiChatMessage(String userId, String messageId) async {
    try {
      await post('/ai-chat/messages/delete', data: {
        'userId': userId,
        'messageId': messageId,
      });
    } catch (e) {
      AppLogger.e('ApiClient', '删除 AI 消息失败 (ID: $messageId)', e);
      rethrow;
    }
  }

  /// 获取 AI 会话消息数量 (非流式)
  Future<int> countAiChatSessionMessages(String sessionId) async {
    try {
      final response =
          await post('/ai-chat/messages/count', data: {'id': sessionId});
      if (response is int) {
        return response;
      } else if (response is String) {
        return int.tryParse(response) ??
            (throw ApiException(-1, '无法解析消息数量响应: $response'));
      } else if (response is Map<String, dynamic> &&
          response.containsKey('count')) {
        final count = response['count'];
        if (count is int) return count;
      }
      throw ApiException(-1, '无法解析消息数量响应: $response');
    } catch (e) {
      AppLogger.e('ApiClient', '获取消息数量失败 (SessionID: $sessionId)', e);
      rethrow;
    }
  }

  /// 获取用户 AI 会话数量 (非流式)
  Future<int> countAiChatUserSessions(String userId) async {
    try {
      final response =
          await post('/ai-chat/sessions/count', data: {'id': userId});
      if (response is int) {
        return response;
      } else if (response is String) {
        return int.tryParse(response) ??
            (throw ApiException(-1, '无法解析会话数量响应: $response'));
      } else if (response is Map<String, dynamic> &&
          response.containsKey('count')) {
        final count = response['count'];
        if (count is int) return count;
      }
      throw ApiException(-1, '无法解析会话数量响应: $response');
    } catch (e) {
      AppLogger.e('ApiClient', '获取用户会话数量失败 (UserID: $userId)', e);
      rethrow;
    }
  }

  /// 获取会话的AI配置 (非流式)
  Future<Map<String, dynamic>?> getAiChatSessionConfig(String userId, String sessionId) async {
    try {
      AppLogger.d('ApiClient', '获取会话AI配置: userId=$userId, sessionId=$sessionId');
      
      final response = await post('/ai-chat/sessions/config/get', data: {
        'userId': userId,
        'sessionId': sessionId,
      });
      
      if (response is Map<String, dynamic>) {
        AppLogger.d('ApiClient', '获取会话AI配置响应: hasConfig=${response['config'] != null}, presetId=${response['presetId']}');
        return response; // 返回完整响应，包含config、sessionId、presetId等字段
      }
      return null;
    } catch (e) {
      AppLogger.e('ApiClient', '获取会话AI配置失败 (SessionID: $sessionId)', e);
      return null; // 配置获取失败不应该阻止会话加载
    }
  }

  /// 保存会话的AI配置 (非流式)
  Future<bool> saveAiChatSessionConfig(String userId, String sessionId, Map<String, dynamic> config) async {
    try {
      final response = await post('/ai-chat/sessions/config/save', data: {
        'userId': userId,
        'sessionId': sessionId,
        'config': config,
      });
      
      if (response is Map<String, dynamic>) {
        return response['success'] == true;
      }
      return false;
    } catch (e) {
      AppLogger.e('ApiClient', '保存会话AI配置失败 (SessionID: $sessionId)', e);
      return false;
    }
  }

  //==== 用户 AI 模型配置相关接口 (新) ====//
  final String _userAIConfigBasePath = '/user-ai-configs';

  /// 获取系统支持的 AI 提供商列表
  Future<List<String>> listAIProviders() async {
    final path = '$_userAIConfigBasePath/providers/list';
    try {
      // 后端返回 Flux<String>，在 Dio 拦截器/转换器中转为 List<dynamic>
      final responseData = await post(path);
      if (responseData is List) {
        // 确保列表中的每个元素都转换为 String
        final providers = responseData.map((item) => item.toString()).toList();
        return providers;
      } else {
        AppLogger.e('ApiClient', 'listAIProviders 响应格式错误: $responseData');
        throw ApiException(-1, '获取可用提供商列表响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '获取可用 AI 提供商列表失败', e);
      rethrow; // post 方法已经处理了 DioException
    }
  }

  /// 获取指定 AI 提供商支持的模型列表
  Future<List<ModelInfo>> listAIModelsForProvider(
      {required String provider}) async {
    final path = '$_userAIConfigBasePath/providers/models/list';
    final body = {'provider': provider};
    try {
      // Backend returns Flux<ModelInfo>, Dio post likely collects it into List<dynamic>
      final responseData = await post(path, data: body);
      if (responseData is List) {
        // Parse the list of JSON maps into a list of ModelInfo objects
        final models = responseData
            .map((json) => ModelInfo.fromJson(json as Map<String, dynamic>))
            .toList();
        return models;
      } else {
        AppLogger.e(
            'ApiClient', 'listAIModelsForProvider 响应格式错误: $responseData');
        throw ApiException(-1, '获取模型列表响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '获取提供商 $provider 的模型列表失败', e);
      rethrow;
    }
  }

  /// 添加新的用户 AI 模型配置
  Future<UserAIModelConfigModel> addAIConfiguration({
    required String userId,
    required String provider,
    required String modelName,
    String? alias,
    required String apiKey,
    String? apiEndpoint,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/create';
    final body = <String, dynamic>{
      'provider': provider,
      'modelName': modelName,
      'apiKey': apiKey, // API Key 由后端处理加密
      if (alias != null) 'alias': alias,
      if (apiEndpoint != null) 'apiEndpoint': apiEndpoint,
    };
    try {
      final responseData = await post(path, data: body);
      AppLogger.i('ApiClient', '添加配置成功，响应数据: $responseData');
      
      if (responseData is Map<String, dynamic>) {
        // 添加字段检查日志
        AppLogger.d('ApiClient', '响应字段检查:');
        AppLogger.d('ApiClient', '  id: ${responseData['id']} (${responseData['id'].runtimeType})');
        AppLogger.d('ApiClient', '  userId: ${responseData['userId']} (${responseData['userId'].runtimeType})');
        AppLogger.d('ApiClient', '  provider: ${responseData['provider']} (${responseData['provider'].runtimeType})');
        AppLogger.d('ApiClient', '  modelName: ${responseData['modelName']} (${responseData['modelName'].runtimeType})');
        AppLogger.d('ApiClient', '  alias: ${responseData['alias']} (${responseData['alias'].runtimeType})');
        AppLogger.d('ApiClient', '  apiEndpoint: ${responseData['apiEndpoint']} (${responseData['apiEndpoint'].runtimeType})');
        AppLogger.d('ApiClient', '  isValidated: ${responseData['isValidated']} (${responseData['isValidated'].runtimeType})');
        AppLogger.d('ApiClient', '  isDefault: ${responseData['isDefault']} (${responseData['isDefault'].runtimeType})');
        AppLogger.d('ApiClient', '  createdAt: ${responseData['createdAt']} (${responseData['createdAt'].runtimeType})');
        AppLogger.d('ApiClient', '  updatedAt: ${responseData['updatedAt']} (${responseData['updatedAt'].runtimeType})');
        AppLogger.d('ApiClient', '  apiKey: ${responseData['apiKey']} (${responseData['apiKey'].runtimeType})');
        
        return UserAIModelConfigModel.fromJson(responseData);
      } else {
        AppLogger.e('ApiClient', 'addAIConfiguration 响应格式错误: $responseData');
        throw ApiException(-1, '添加配置响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '添加 AI 配置失败 for user $userId', e);
      rethrow;
    }
  }

  /// 获取用户所有AI配置(普通接口，不含解密的API密钥)
  Future<List<UserAIModelConfigModel>> listAIConfigurations({
    required String userId,
    bool? validatedOnly,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/list';
    final body = <String, dynamic>{};
    if (validatedOnly != null) {
      body['validatedOnly'] = validatedOnly;
    }
    try {
      // 如果 body 为空，data 应该传 null
      final responseData = await post(path, data: body.isEmpty ? null : body);
      if (responseData is List) {
        final configs = responseData
            .map((json) =>
                UserAIModelConfigModel.fromJson(json as Map<String, dynamic>))
            .toList();
        return configs;
      } else {
        AppLogger.e('ApiClient', 'listAIConfigurations 响应格式错误: $responseData');
        throw ApiException(-1, '列出配置响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '列出 AI 配置失败 for user $userId', e);
      rethrow;
    }
  }

  /// 获取用户所有AI配置，包含解密后的API密钥
  Future<List<UserAIModelConfigModel>> listAIConfigurationsWithDecryptedKeys({
    required String userId,
    bool? validatedOnly,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/list-with-api-keys';
    final body = <String, dynamic>{};
    if (validatedOnly != null) {
      body['validatedOnly'] = validatedOnly;
    }
    try {
      // 如果 body 为空，data 应该传 null
      final responseData = await post(path, data: body.isEmpty ? null : body);
      if (responseData is List) {
        final configs = responseData
            .map((json) =>
                UserAIModelConfigModel.fromJson(json as Map<String, dynamic>))
            .toList();
        return configs;
      } else {
        AppLogger.e('ApiClient', 'listAIConfigurationsWithDecryptedKeys 响应格式错误: $responseData');
        throw ApiException(-1, '获取带解密API密钥的配置列表响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '获取带解密API密钥的AI配置列表失败 for user $userId', e);
      rethrow;
    }
  }

  /// 获取指定 ID 的用户 AI 模型配置
  Future<UserAIModelConfigModel> getAIConfigurationById({
    required String userId,
    required String configId,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/get/$configId';
    try {
      // POST with no body
      final responseData = await post(path);
      if (responseData is Map<String, dynamic>) {
        return UserAIModelConfigModel.fromJson(responseData);
      } else {
        AppLogger.e('ApiClient',
            'getAIConfigurationById 响应格式错误 ($userId/$configId): $responseData');
        throw ApiException(-1, '获取配置详情响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '获取 AI 配置失败 ($userId / $configId)', e);
      rethrow;
    }
  }

  /// 更新指定 ID 的用户 AI 模型配置
  Future<UserAIModelConfigModel> updateAIConfiguration({
    required String userId,
    required String configId,
    String? alias,
    String? apiKey,
    String? apiEndpoint,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/update/$configId';
    final body = <String, dynamic>{};
    if (alias != null) body['alias'] = alias;
    if (apiKey != null) body['apiKey'] = apiKey; // 明文发送
    if (apiEndpoint != null) body['apiEndpoint'] = apiEndpoint;

    // 前端仓库层应该已经做了空检查，但以防万一
    if (body.isEmpty) {
      AppLogger.w('ApiClient', '尝试更新配置但没有提供字段 ($userId/$configId)');
      // 可以选择抛出错误或返回当前配置（需要额外调用 get）
      // 这里选择继续发送请求，让后端处理或返回错误
      // throw ApiException(-1, 'Update called with no fields to update');
    }

    try {
      final responseData = await post(path, data: body);
      if (responseData is Map<String, dynamic>) {
        return UserAIModelConfigModel.fromJson(responseData);
      } else {
        AppLogger.e('ApiClient',
            'updateAIConfiguration 响应格式错误 ($userId/$configId): $responseData');
        throw ApiException(-1, '更新配置响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '更新 AI 配置失败 ($userId / $configId)', e);
      rethrow;
    }
  }

  /// 删除指定 ID 的用户 AI 模型配置
  Future<void> deleteAIConfiguration({
    required String userId,
    required String configId,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/delete/$configId';
    try {
      // POST with no body. Expect 204 No Content for success.
      // Dio's post method should handle 204 correctly (doesn't throw by default).
      // The response.data might be null or empty string for 204.
      await post(path);
      // 不需要检查返回值，如果 post 没抛异常就认为成功
    } catch (e) {
      AppLogger.e('ApiClient', '删除 AI 配置失败 ($userId / $configId)', e);
      // 如果是 404 Not Found 等，post 会抛出 ApiException
      rethrow;
    }
  }

  /// 手动触发指定配置的 API Key 验证
  Future<UserAIModelConfigModel> validateAIConfiguration({
    required String userId,
    required String configId,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/validate/$configId';
    try {
      // POST with no body
      final responseData = await post(path);
      if (responseData is Map<String, dynamic>) {
        return UserAIModelConfigModel.fromJson(responseData);
      } else {
        AppLogger.e('ApiClient',
            'validateAIConfiguration 响应格式错误 ($userId/$configId): $responseData');
        throw ApiException(-1, '验证配置响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '验证 AI 配置失败 ($userId / $configId)', e);
      rethrow;
    }
  }

  /// 设置指定配置为用户的默认模型
  Future<UserAIModelConfigModel> setDefaultAIConfiguration({
    required String userId,
    required String configId,
  }) async {
    final path = '$_userAIConfigBasePath/users/$userId/set-default/$configId';
    try {
      // POST with no body
      final responseData = await post(path);
      if (responseData is Map<String, dynamic>) {
        return UserAIModelConfigModel.fromJson(responseData);
      } else {
        AppLogger.e('ApiClient',
            'setDefaultAIConfiguration 响应格式错误 ($userId/$configId): $responseData');
        throw ApiException(-1, '设置默认配置响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '设置默认 AI 配置失败 ($userId / $configId)', e);
      rethrow;
    }
  }

  /// 获取提供商的模型列表能力
  Future<String> getProviderCapability(String providerName) async {
    try {
      final response = await _dio.get<String>(
        '/api/models/providers/$providerName/capability',
      );
      return response.data ?? 'NO_LISTING';
    } on DioException catch (e) {
      AppLogger.e('ApiClient', '获取提供商能力失败，provider: $providerName', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '获取提供商能力时发生意外错误，provider: $providerName', e);
      throw ApiException(-1, '获取提供商能力失败: ${e.toString()}');
    }
  }

  /// 使用API密钥获取指定提供商的模型列表
  Future<List<ModelInfo>> listAIModelsWithApiKey({
    required String provider,
    required String apiKey,
    String? apiEndpoint,
  }) async {
    final path = '/api/models/providers/$provider/info/auth'; // Correct endpoint for auth models
    try {
      Map<String, dynamic> queryParams = {
        'apiKey': apiKey,
      };
      
      if (apiEndpoint != null && apiEndpoint.isNotEmpty) {
        queryParams['apiEndpoint'] = apiEndpoint;
      }
      
      // Use _dio.get directly to pass queryParameters
      final response = await _dio.get(path, queryParameters: queryParams);
      final responseData = response.data;
      
      if (responseData is List) {
        // Parse the list of JSON maps into a list of ModelInfo objects
        final models = responseData
            .map((json) => ModelInfo.fromJson(json as Map<String, dynamic>))
            .toList();
        return models;
      } else {
        AppLogger.w('ApiClient', '使用API密钥获取模型列表返回格式不正确: $responseData');
        return []; // Return empty list on format error
      }
    } on DioException catch (e) {
      AppLogger.e('ApiClient', '使用API密钥获取模型列表失败，provider: $provider', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '使用API密钥获取模型列表时发生意外错误，provider: $provider', e);
      throw ApiException(-1, '使用API密钥获取模型列表失败: ${e.toString()}');
    }
  }

  //==== 小说片段相关接口 ====//

  /// 创建片段
  Future<dynamic> createSnippet(Map<String, dynamic> snippetData) async {
    try {
      AppLogger.d('ApiClient', '创建片段: $snippetData');
      final response = await post('/novel-snippets/create', data: snippetData);
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '创建片段失败', e);
      rethrow;
    }
  }

  /// 获取小说的所有片段（分页）
  Future<dynamic> getSnippetsByNovelId(String novelId, {int page = 0, int size = 20}) async {
    try {
      final response = await post('/novel-snippets/get-by-novel', data: {
        'novelId': novelId,
        'page': page,
        'size': size,
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '获取小说片段列表失败: novelId=$novelId', e);
      rethrow;
    }
  }

  /// 获取片段详情
  Future<dynamic> getSnippetDetail(String snippetId) async {
    try {
      final response = await post('/novel-snippets/get-detail', data: {
        'snippetId': snippetId,
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '获取片段详情失败: snippetId=$snippetId', e);
      rethrow;
    }
  }

  /// 更新片段内容
  Future<dynamic> updateSnippetContent(Map<String, dynamic> contentData) async {
    try {
      AppLogger.d('ApiClient', '更新片段内容: $contentData');
      final response = await post('/novel-snippets/update-content', data: contentData);
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '更新片段内容失败', e);
      rethrow;
    }
  }

  /// 更新片段标题
  Future<dynamic> updateSnippetTitle(Map<String, dynamic> titleData) async {
    try {
      AppLogger.d('ApiClient', '更新片段标题: $titleData');
      final response = await post('/novel-snippets/update-title', data: titleData);
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '更新片段标题失败', e);
      rethrow;
    }
  }

  /// 收藏/取消收藏片段
  Future<dynamic> updateSnippetFavorite(Map<String, dynamic> favoriteData) async {
    try {
      AppLogger.d('ApiClient', '更新片段收藏状态: $favoriteData');
      final response = await post('/novel-snippets/update-favorite', data: favoriteData);
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '更新片段收藏状态失败', e);
      rethrow;
    }
  }

  /// 获取片段历史记录
  Future<dynamic> getSnippetHistory(String snippetId, {int page = 0, int size = 10}) async {
    try {
      final response = await post('/novel-snippets/get-history', data: {
        'snippetId': snippetId,
        'page': page,
        'size': size,
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '获取片段历史记录失败: snippetId=$snippetId', e);
      rethrow;
    }
  }

  /// 预览历史版本内容
  Future<dynamic> previewSnippetHistoryVersion(String snippetId, int version) async {
    try {
      final response = await post('/novel-snippets/preview-history', data: {
        'snippetId': snippetId,
        'version': version,
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '预览片段历史版本失败: snippetId=$snippetId, version=$version', e);
      rethrow;
    }
  }

  /// 回退到历史版本（创建新片段）
  Future<dynamic> revertSnippetToVersion(Map<String, dynamic> revertData) async {
    try {
      AppLogger.d('ApiClient', '回退片段版本: $revertData');
      final response = await post('/novel-snippets/revert-to-version', data: revertData);
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '回退片段版本失败', e);
      rethrow;
    }
  }

  /// 删除片段
  Future<void> deleteSnippet(String snippetId) async {
    try {
      await post('/novel-snippets/delete', data: {
        'snippetId': snippetId,
      });
    } catch (e) {
      AppLogger.e('ApiClient', '删除片段失败: snippetId=$snippetId', e);
      rethrow;
    }
  }

  /// 获取用户收藏的片段
  Future<dynamic> getFavoriteSnippets({int page = 0, int size = 20}) async {
    try {
      final response = await post('/novel-snippets/get-favorites', data: {
        'page': page,
        'size': size,
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '获取收藏片段失败', e);
      rethrow;
    }
  }

  /// 搜索片段
  Future<dynamic> searchSnippets(String novelId, String searchText, {int page = 0, int size = 20}) async {
    try {
      final response = await post('/novel-snippets/search', data: {
        'novelId': novelId,
        'searchText': searchText,
        'page': page,
        'size': size,
      });
      return response;
    } catch (e) {
      AppLogger.e('ApiClient', '搜索片段失败: novelId=$novelId, searchText=$searchText', e);
      rethrow;
    }
  }

  //==== 旧的聊天相关接口 ====//
  /*
  /// 获取聊天会话列表
  Future<dynamic> getChatSessions(String novelId) async {
    return post('/chats/get-by-novel', data: {'novelId': novelId});
  }
  // ... 其他旧方法 ...
  */

  /// 处理Dio错误
  ApiException _handleDioError(DioException error) {
    AppLogger.e('ApiClient', 'DioException类型: ${error.type}, 请求路径: ${error.requestOptions.path}');
    AppLogger.e('ApiClient', '响应状态码: ${error.response?.statusCode}');
    AppLogger.e('ApiClient', '响应数据: ${error.response?.data}');
    
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return ApiException(408, '请求超时，请稍后重试');
      case DioExceptionType.badResponse:
        final statusCode = error.response?.statusCode ?? 500;
        
        // 特殊处理401错误
        if (statusCode == 401) {
          return ApiException(401, '登录已过期，请重新登录');
        }
        
        final message = _getErrorMessageFromResponse(error.response);
        AppLogger.w('ApiClient', '从响应中提取错误消息: $message (状态码: $statusCode)');
        return ApiException(statusCode, message);
      case DioExceptionType.cancel:
        return ApiException(499, '请求被取消');
      case DioExceptionType.connectionError:
        return ApiException(0, '网络连接失败，请检查您的网络连接');
      default:
        return ApiException(-1, '请求失败: ${error.message}');
    }
  }

  /// 从响应中获取错误信息
  String _getErrorMessageFromResponse(Response? response) {
    if (response == null) return '未知错误';

    try {
      final data = response.data;
      AppLogger.d('ApiClient', '解析错误响应数据类型: ${data.runtimeType}, 内容: $data');
      
      if (data is Map<String, dynamic>) {
        // 尝试多种可能的错误字段名
        String? message = data['message'] ?? 
                         data['error'] ?? 
                         data['msg'] ?? 
                         data['errorMessage'] ?? 
                         data['detail'];
                         
        if (message != null && message.isNotEmpty) {
          AppLogger.d('ApiClient', '从响应中提取到错误消息: $message');
          return message;
        }
        
        // 如果找不到明确的错误字段，尝试找任何包含错误信息的字段
        for (final entry in data.entries) {
          if (entry.value is String && (entry.value as String).isNotEmpty) {
            AppLogger.d('ApiClient', '使用字段 ${entry.key} 作为错误消息: ${entry.value}');
            return entry.value as String;
          }
        }
        
        return '请求失败';
      } else if (data is String && data.isNotEmpty) {
        AppLogger.d('ApiClient', '直接使用字符串响应作为错误消息: $data');
        return data;
      }
      
      final fallbackMessage = response.statusMessage ?? '未知错误';
      AppLogger.d('ApiClient', '使用状态消息作为错误消息: $fallbackMessage');
      return fallbackMessage;
    } catch (e) {
      AppLogger.w('ApiClient', '解析错误响应时出现异常', e);
      return response.statusMessage ?? '未知错误';
    }
  }

  /// 关闭客户端
  void dispose() {
    _dio.close();
  }

  /// 获取小说的场景摘要数据（用于Plan视图）
  /// 
  /// 与完整场景数据不同，只包含摘要信息，减少数据传输量
  Future<Map<String, dynamic>?> getNovelWithSceneSummaries(String novelId) async {
    try {
      final response = await _dio.post('/novels//get-with-scene-summaries', 
          data: {
            'id': novelId,
          });
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '获取小说场景摘要数据失败: $novelId', e);
      return null;
    }
  }

  /// 获取小说及其所有场景
  /// 
  /// 与分页加载不同，一次性获取小说的所有场景数据
  Future<Map<String, dynamic>?> getNovelWithAllScenes(String novelId) async {
    try {
      final response = await _dio.post('/novels/get-with-scenes', data: {
        'id': novelId,
      });
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '获取小说及其所有场景失败: $novelId', e);
      return null;
    }
  }

  /// 移动场景（用于Plan视图拖拽功能）
  Future<Map<String, dynamic>?> moveScene(
    String novelId,
    String sourceActId,
    String sourceChapterId,
    String sourceSceneId,
    String targetActId,
    String targetChapterId,
    int targetIndex,
  ) async {
    try {
      final data = {
        'sourceActId': sourceActId,
        'sourceChapterId': sourceChapterId,
        'sourceSceneId': sourceSceneId,
        'targetActId': targetActId,
        'targetChapterId': targetChapterId,
        'targetIndex': targetIndex,
      };
      
      final response = await _dio.post(
        '/novels/$novelId/scenes/move',
        data: data,
      );
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '移动场景失败: $novelId', e);
      return null;
    }
  }

  /// 更新小说元数据（标题、作者、系列）
  Future<Map<String, dynamic>?> updateNovelMetadata(
    String novelId, 
    String title, 
    String author, 
    String? series
  ) async {
    try {
      final data = {
        'title': title,
        'author': author,
        'series': series,
      };
      
      final response = await _dio.post(
        '/novels/$novelId/metadata',
        data: data,
      );
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '更新小说元数据失败: $novelId', e);
      throw ApiException(-1, '更新小说元数据失败: ${e.toString()}');
    }
  }

  /// 获取封面图片上传凭证
  Future<Map<String, dynamic>> getCoverUploadCredential(String novelId) async {
    try {
      final response = await _dio.post(
        '/novels/$novelId/cover-upload-credential',
        data: {
          'fileName': 'cover.jpg',
          'contentType': 'image/jpeg'
        },
      );
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '获取封面上传凭证失败: $novelId', e);
      throw ApiException(-1, '获取封面上传凭证失败: ${e.toString()}');
    }
  }

  /// 更新小说封面URL
  Future<Map<String, dynamic>?> updateNovelCover(String novelId, String coverUrl) async {
    try {
      final data = {
        'coverUrl': coverUrl,
      };
      
      final response = await _dio.post(
        '/novels/$novelId/cover',
        data: data,
      );
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '更新小说封面失败: $novelId', e);
      throw ApiException(-1, '更新小说封面失败: ${e.toString()}');
    }
  }

  /// 归档小说
  Future<Map<String, dynamic>?> archiveNovel(String novelId) async {
    try {
      final response = await _dio.post(
        '/novels/$novelId/archive',
      );
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '归档小说失败: $novelId', e);
      throw ApiException(-1, '归档小说失败: ${e.toString()}');
    }
  }

  /// 删除场景
  Future<Map<String, dynamic>?> deleteScene(
    String novelId,
    String actId,
    String chapterId,
    String sceneId,
  ) async {
    try {
      final data = {
        'novelId': novelId,
        'actId': actId,
        'chapterId': chapterId,
        'sceneId': sceneId,
      };
      
      final response = await _dio.post(
        '/novels/delete-scene',
        data: data,
      );
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '删除场景失败: $novelId', e);
      throw ApiException(-1, '删除场景失败: ${e.toString()}');
    }
  }

  /// 删除章节
  Future<Map<String, dynamic>?> deleteChapter(
    String novelId,
    String actId,
    String chapterId,
  ) async {
    try {
      final data = {
        'novelId': novelId,
        'actId': actId,
        'chapterId': chapterId,
      };
      
      final response = await _dio.post(
        '/novels/delete-chapter',
        data: data,
      );
      return response.data;
    } on DioException catch (e) {
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '删除章节失败: $novelId, $chapterId', e);
      throw ApiException(-1, '删除章节失败: ${e.toString()}');
    }
  }

  /// 更新小说最后编辑的章节ID
  Future<void> updateLastEditedChapter(String novelId, String chapterId) async {
    final data = {
      'novelId': novelId,
      'chapterId': chapterId,
    };
    
    await post('/novels/update-last-edited-chapter', data: data);
  }

  /// 批量更新场景内容
  Future<void> updateScenesBatch(String novelId, List<Map<String, dynamic>> scenes) async {
    final data = {
      'novelId': novelId,
      'scenes': scenes,
    };
    
    await post('/scenes/update-batch', data: data);
  }

  /// 批量更新小说字数统计
  Future<void> updateNovelWordCounts(String novelId, Map<String, int> sceneWordCounts) async {
    final data = {
      'novelId': novelId,
      'sceneWordCounts': sceneWordCounts,
    };
    
    await post('/novels/update-word-counts', data: data);
  }

  /// 更新小说结构（不包含场景内容）
  Future<void> updateNovelStructure(Map<String, dynamic> novelStructure) async {
    await post('/novels/update-structure', data: novelStructure);
  }

  /// 细粒度添加卷 - 只提供必要信息
  Future<Map<String, dynamic>> addActFine(String novelId, String title, {String? description}) async {
    final data = {
      'novelId': novelId,
      'title': title,
    };
    
    if (description != null) {
      data['description'] = description;
    }
    
    return await post('/novels/add-act-fine', data: data);
  }
  
  /// 细粒度添加章节 - 只提供必要信息
  Future<Map<String, dynamic>> addChapterFine(String novelId, String actId, String title, {String? description}) async {
    final data = {
      'novelId': novelId,
      'actId': actId,
      'title': title,
    };
    
    if (description != null) {
      data['description'] = description;
    }
    
    return await post('/novels/add-chapter-fine', data: data);
  }
  
  /// 细粒度添加场景 - 只提供必要信息
  Future<Map<String, dynamic>> addSceneFine(String novelId, String chapterId, String title, 
      {String? summary, int? position}) async {
    final data = {
      'novelId': novelId,
      'chapterId': chapterId,
      'title': title,
    };
    
    if (summary != null) {
      data['summary'] = summary;
    }
    
    if (position != null) {
      data['position'] = position.toString();
    }
    
    return await post('/scenes/add-scene-fine', data: data);
  }
  
  /// 细粒度批量添加场景 - 一次添加多个场景到同一章节
  Future<List<Map<String, dynamic>>> addScenesBatchFine(String novelId, String chapterId, 
      List<Map<String, dynamic>> scenes) async {
    final data = {
      'novelId': novelId,
      'chapterId': chapterId,
      'scenes': scenes,
    };
    
    return await post('/novels/upsert-chapter-scenes-batch', data: data);
  }
  
  /// 细粒度删除卷 - 只提供ID
  Future<bool> deleteActFine(String novelId, String actId) async {
    final data = {
      'novelId': novelId,
      'actId': actId,
    };
    
    return await post('/novels/delete-act-fine', data: data);
  }
  
  /// 细粒度删除章节 - 只提供ID
  Future<bool> deleteChapterFine(String novelId, String actId, String chapterId) async {
    final data = {
      'novelId': novelId,
      'actId': actId,
      'chapterId': chapterId,
    };
    
    return await post('/novels/delete-chapter-fine', data: data);
  }
  
  /// 细粒度删除场景 - 只提供ID
  Future<bool> deleteSceneFine(String sceneId) async {
    final data = {
      'sceneId': sceneId,
    };
    
    return await post('/scenes/delete-scene-fine', data: data);
  }

  Future<dynamic> getNovelDetailByIdText(String id) {
    return post('/novels/get-with-scenes-text', data: {'id': id});
  }

  /// 通用流式处理方法，允许外部类使用
  ///
  /// 处理字节流，解码，解析 SSE 或单行 JSON 数组，并生成指定类型的流
  Stream<T> processUniversalStream<T>({
    required Future<Stream<List<int>>> byteStreamFuture,
    required T Function(Map<String, dynamic>) fromJson,
    required String logContext,
  }) {
    return _processStream<T>(
      byteStreamFuture: byteStreamFuture,
      fromJson: fromJson,
      logContext: logContext,
    );
  }

  /// 通用AI请求 - 流式
  ///
  /// 发送通用AI请求并返回流式响应
  Stream<T> streamUniversalAiRequest<T>({
    required String path,
    required Map<String, dynamic> requestData,
    required T Function(Map<String, dynamic>) fromJson,
  }) {
    final byteStreamFuture = postStream(path, data: requestData);
    return processUniversalStream<T>(
      byteStreamFuture: byteStreamFuture,
      fromJson: fromJson,
      logContext: 'streamUniversalAiRequest',
    );
  }

  /// 通用AI请求 - 预览
  ///
  /// 获取构建的提示内容，不实际发送给AI
  Future<UniversalAIPreviewResponse> previewUniversalAiRequest(Map<String, dynamic> requestData) async {
    try {
      AppLogger.d('ApiClient', '发送AI预览请求');
      
      final response = await post('/ai/universal/preview', data: requestData);
      
      if (response is Map<String, dynamic>) {
        return UniversalAIPreviewResponse.fromJson(response);
      } else {
        throw ApiException(-1, '预览响应格式错误');
      }
    } catch (e) {
      AppLogger.e('ApiClient', '预览AI请求失败', e);
      rethrow;
    }
  }

  //==== 公共模型相关接口 ====//

  /// 获取公共模型列表
  /// 只包含向前端暴露的安全信息，不含API Keys等敏感数据
  /// 用户必须登录才能访问此接口
  Future<List<Map<String, dynamic>>> getPublicModels() async {
    try {
      AppLogger.d('ApiClient', '🔍 获取公共模型列表');
      final response = await _dio.get('/public-models');
      
      dynamic rawData;
      if (response.data is Map<String, dynamic>) {
        final Map<String, dynamic> responseMap = response.data;
        if (responseMap.containsKey('data')) {
          rawData = responseMap['data'];
        } else if (responseMap.containsKey('success') && responseMap['success'] == true) {
          rawData = responseMap['data'] ?? responseMap;
        } else {
          rawData = responseMap;
        }
      } else {
        rawData = response.data;
      }
      
      if (rawData is List) {
        AppLogger.d('ApiClient', '✅ 获取公共模型列表成功: 共${rawData.length}个模型');
        return rawData.cast<Map<String, dynamic>>();
      } else {
        AppLogger.w('ApiClient', '❌ 公共模型列表响应格式错误: 期望List但收到${rawData.runtimeType}');
        return [];
      }
    } on DioException catch (e) {
      AppLogger.e('ApiClient', '❌ 获取公共模型列表失败', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '❌ 获取公共模型列表时发生意外错误', e);
      throw ApiException(-1, '获取公共模型列表失败: ${e.toString()}');
    }
  }

  //==== 用户积分相关接口 ====//

  /// 获取当前用户的积分余额
  Future<Map<String, dynamic>> getUserCredits() async {
    try {
      AppLogger.d('ApiClient', '🔍 获取用户积分余额');
      final response = await _dio.get('/credits/balance');
      
      dynamic rawData;
      if (response.data is Map<String, dynamic>) {
        final Map<String, dynamic> responseMap = response.data;
        if (responseMap.containsKey('data')) {
          rawData = responseMap['data'];
        } else if (responseMap.containsKey('success') && responseMap['success'] == true) {
          rawData = responseMap['data'] ?? responseMap;
        } else {
          rawData = responseMap;
        }
      } else {
        rawData = response.data;
      }
      
      if (rawData is Map<String, dynamic>) {
        AppLogger.d('ApiClient', '✅ 获取用户积分余额成功: ${rawData['credits']}');
        return rawData;
      } else {
        AppLogger.w('ApiClient', '❌ 用户积分响应格式错误: 期望Map但收到${rawData.runtimeType}');
        throw ApiException(-1, '用户积分响应格式错误');
      }
    } on DioException catch (e) {
      AppLogger.e('ApiClient', '❌ 获取用户积分余额失败', e);
      throw _handleDioError(e);
    } catch (e) {
      AppLogger.e('ApiClient', '❌ 获取用户积分余额时发生意外错误', e);
      throw ApiException(-1, '获取用户积分余额失败: ${e.toString()}');
    }
  }
}
