searchTitles method

Future<List<LibrivoxItem>> searchTitles({
  1. required String query,
  2. int page = 1,
  3. int rows = 10,
})

Implementation

Future<List<LibrivoxItem>> searchTitles({
  required String query,
  int page = 1,
  int rows = 10,
}) async {
  try {
    // Validate input parameters
    if (query.trim().isEmpty) {
      throw ArgumentError('Query cannot be empty');
    }
    if (page < 1) {
      throw ArgumentError('Page must be greater than 0');
    }
    if (rows < 1 || rows > 1000) {
      throw ArgumentError('Rows must be between 1 and 1000');
    }

    final uri = Uri.parse(_baseUrl).replace(
      queryParameters: {
        'q': 'collection:(librivoxaudio) AND title:($query)',
        // 'fl[]': 'identifier',
        // 'fl[]': 'title',
        // 'fl[]': 'creator',
        'rows': rows.toString(),
        'page': page.toString(),
        'output': 'json',
      },
    );

    final response = await http.get(uri);

    if (response.statusCode == 404) {
      throw LibrivoxNotFoundException('No results found for query: $query');
    }

    if (response.statusCode != 200) {
      throw LibrivoxApiException(
        'Failed to fetch Librivox titles. Status: ${response.statusCode}',
      );
    }

    final Map<String, dynamic> jsonMap;
    try {
      jsonMap = jsonDecode(response.body) as Map<String, dynamic>;
    } catch (e) {
      throw LibrivoxParseException('Failed to parse response: $e');
    }

    final responseData = jsonMap['response'] as Map<String, dynamic>?;
    if (responseData == null) {
      throw const LibrivoxParseException(
        'Invalid response format: missing response field',
      );
    }

    final docs = (responseData['docs'] as List<dynamic>?) ?? [];

    return docs.map((doc) {
      try {
        final docmap = doc as Map<String, dynamic>;

        // Validate required fields
        final identifier = docmap['identifier'] as String?;
        final title = docmap['title'] as String?;

        if (identifier == null || identifier.isEmpty) {
          throw const LibrivoxParseException(
            'Missing or empty identifier in response',
          );
        }
        if (title == null || title.isEmpty) {
          throw const LibrivoxParseException(
            'Missing or empty title in response',
          );
        }

        return LibrivoxItem(
          id: identifier,
          title: title,
          author: docmap.containsKey('creator')
              ? docmap['creator'] as String?
              : null,
          // releaseDate: docmap.containsKey('date')
          //     ? docmap['date'] as String
          //     : null,
          // description: docmap.containsKey('description')
          //     ? docmap['description'] as String
          //     : null,
          language: docmap.containsKey('language')
              ? docmap['language'] as String?
              : null,
          coverUrl: 'https://archive.org/services/img/$identifier',
        );
      } catch (e) {
        // Skip malformed items but log the error
        throw LibrivoxParseException('Failed to parse item: $e');
      }
    }).toList();
  } catch (e) {
    throw LibrivoxApiException('Unexpected error occurred: $e');
  }
}