import '../core/api/meinweimar_api_client.dart';
import '../core/localization/app_localizations.dart';
import '../core/text/wp_text.dart';
import '../models/localized_content.dart';
import '../models/poi.dart';
import 'pagination_guard.dart';
class PoiRepository {
const PoiRepository(this.api);
final MeinWeimarApiClient api;
static const int _pageSize = 100;
static const int _maxPages = 100;
Future<List<Poi>> fetchAll({required String languageCode}) async {
final result = <Poi>[];
final seenIds = <String>{};
for (var page = 1; page <= _maxPages; page++) {
final data = await api.getJson(
'/pois',
query: {
'lang': languageCode,
'per_page': '$_pageSize',
'page': '$page',
},
);
final rawItems = (data['items'] as List? ?? const [])
.whereType<Map<String, dynamic>>()
.toList(growable: false);
if (rawItems.isEmpty) break;
var newItemsOnPage = 0;
for (final json in rawItems) {
final poi = _fromJson(json, languageCode);
if (seenIds.add(poi.id)) {
result.add(poi);
newItemsOnPage++;
}
}
if (PaginationGuard.shouldStop(
rawItemCount: rawItems.length,
newItemCount: newItemsOnPage,
pageSize: _pageSize,
)) {
break;
}
}
return result;
}
Future<Poi> fetchOne({
required String id,
required String languageCode,
}) async {
final data = await api.getJson(
'/pois/$id',
query: {'lang': languageCode},
);
return _fromJson(data, languageCode);
}
Poi _fromJson(Map<String, dynamic> j, String lang) {
final location = j['location'] as Map<String, dynamic>? ?? const {};
final address = j['address'] as Map<String, dynamic>? ?? const {};
final content = j['content'] as Map<String, dynamic>? ?? const {};
final ticket = j['ticket'] as Map<String, dynamic>? ?? const {};
final topics = (j['topics'] as List? ?? const [])
.whereType<Map<String, dynamic>>()
.toList();
final names = topics
.map((e) => WpText.plain(e['name']))
.where((e) => e.isNotEmpty)
.toList();
final slugs = topics
.map((e) => e['slug']?.toString() ?? '')
.where((e) => e.isNotEmpty)
.toList();
final eras = (j['eras'] as List? ?? j['era'] as List? ?? const [])
.whereType<Map>()
.map((e) => Map<String, dynamic>.from(e))
.toList();
final eraNames = eras
.map((e) => WpText.plain(e['name']))
.where((e) => e.isNotEmpty)
.toList();
final eraSlugs = eras
.map((e) => e['slug']?.toString() ?? '')
.where((e) => e.isNotEmpty)
.toList();
final category = names.contains('Bauhaus')
? 'Bauhaus'
: names.contains('Klassik')
? 'Klassik'
: (names.isNotEmpty
? names.first
: AppLocalizations.tFor(lang, 'sight'));
final title = WpText.plain(content['title'] ?? j['title']);
final shortText =
WpText.plain(content['short_text'] ?? j['short_description']);
final guide = WpText.plain(content['guide_text']);
final audio = content['audio'] as Map<String, dynamic>?;
final image = j['image'] as Map<String, dynamic>?;
final featured = WpText.url(image?['url']);
final featuredCaption = _imageCaption(image?['caption']);
final gallery = (j['gallery'] as List? ?? const [])
.whereType<Map<String, dynamic>>()
.map((image) => WpText.url(image['url']) ?? '')
.where((url) => url.trim().isNotEmpty && url != featured)
.toSet()
.toList(growable: false);
return Poi(
id: j['id'].toString(),
title: LocalizedContent({lang: title}),
shortDescription: LocalizedContent({lang: shortText}),
guideText: LocalizedContent({lang: guide}),
latitude: (location['lat'] as num?)?.toDouble() ?? 0,
longitude: (location['lng'] as num?)?.toDouble() ?? 0,
category: category,
address: WpText.nullablePlain(address['formatted']),
imageUrl: featured,
imageCaption: featuredCaption,
galleryUrls: gallery,
audioUrls: audio?['url'] == null
? const {}
: {lang: WpText.url(audio!['url']) ?? ''},
affiliateTicketUrl:
ticket['available'] == true ? WpText.url(ticket['url']) : null,
affiliateProvider: WpText.nullablePlain(ticket['provider']),
ticketButtonLabel: WpText.nullablePlain(ticket['label']),
openingHours: WpText.nullablePlain(j['opening_hours']),
websiteUrl: WpText.url(j['website_url']),
highlight: j['highlight'] == true,
familyFriendly: j['family_friendly'] == true,
wheelchairAccessible: j['wheelchair_accessible'] == true,
freeAccess: j['free_access'] == true,
topicSlugs: slugs,
topicNames: names,
eraSlugs: eraSlugs,
eraNames: eraNames,
);
}
String? _imageCaption(dynamic value) {
if (value == null) return null;
dynamic raw = value;
if (value is Map) {
raw = value['rendered'] ?? value['raw'] ?? value['text'];
}
final caption = WpText.plain(raw).trim();
return caption.isEmpty ? null : caption;
}
}

