import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:aves/app_flavor.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/modules/app.dart';
import 'package:aves/model/settings/modules/collection.dart';
import 'package:aves/model/settings/modules/debug.dart';
import 'package:aves/model/settings/modules/display.dart';
import 'package:aves/model/settings/modules/info.dart';
import 'package:aves/model/settings/modules/navigation.dart';
import 'package:aves/model/settings/modules/privacy.dart';
import 'package:aves/model/settings/modules/screen_saver.dart';
import 'package:aves/model/settings/modules/search.dart';
import 'package:aves/model/settings/modules/slideshow.dart';
import 'package:aves/model/settings/modules/viewer.dart';
import 'package:aves/model/settings/modules/widget.dart';
import 'package:aves/ref/bursts.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves_map/aves_map.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:collection/collection.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:latlong2/latlong.dart';

final Settings settings = Settings._private();

class Settings
    with
        ChangeNotifier,
        SettingsAccess,
        SearchSettings,
        AppSettings,
        CollectionSettings,
        DebugSettings,
        DisplaySettings,
        InfoSettings,
        NavigationSettings,
        PrivacySettings,
        ScreenSaverSettings,
        WidgetSettings {
  final Set<StreamSubscription> _subscriptions = {};
  final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change');
  final StreamController<SettingsChangedEvent> _updateStreamController = StreamController.broadcast();
  final StreamController<SettingsChangedEvent> _updateTileExtentStreamController = StreamController.broadcast();

  @override
  Stream<SettingsChangedEvent> get updateStream => _updateStreamController.stream;

  Stream<SettingsChangedEvent> get updateTileExtentStream => _updateTileExtentStreamController.stream;

  @override
  bool get initialized => store.initialized;

  @override
  SettingsStore get store => settingsStore;

  Settings._private() {
    if (kFlutterMemoryAllocationsEnabled) ChangeNotifier.maybeDispatchObjectCreation(this);
  }

  Future<void> init({
    required bool monitorPlatformSettings,
    required bool shouldSanitize,
  }) async {
    await store.init();
    resetAppliedLocale();
    _unregister();
    _register(monitorPlatformSettings);
    initAppSettings();
    if (shouldSanitize) {
      await sanitize();
    }
  }

  void _unregister() {
    //albumGrouping.removeListener(saveAlbumGroups);
    //tagGrouping.removeListener(saveTagGroups);
    _subscriptions
      ..forEach((sub) => sub.cancel())
      ..clear();
  }

void _register(bool monitorPlatformSettings) {
  // 🔥 album grouping rimosso
  // 🔥 dynamic albums rimossi
  // 🔥 tag grouping: nessun salvataggio gruppi

  // ascolta SOLO i cambiamenti del grouping TAG (versione minimal)
  _subscriptions.add(
    tagGrouping.eventBus.on<GroupUriChangedEvent>().listen(_onGroupingChange),
  );

  // mantieni il monitoraggio delle impostazioni di sistema
  if (monitorPlatformSettings) {
    _subscriptions.add(
      _platformSettingsChangeChannel
          .receiveBroadcastStream()
          .listen((event) => _onPlatformSettingsChanged(event as Map?)),
    );
  }
}

void _onGroupingChange(GroupUriChangedEvent event) {
  // nuovo modello: nessun aggiornamento necessario
}

  Future<void> reload() => store.reload();

  Future<void> reset({required bool includeInternalKeys}) async {
    if (includeInternalKeys) {
      await store.clear();
    } else {
      await Future.forEach<String>(store.getKeys().whereNot(SettingKeys.isInternalKey), store.remove);
    }
  }

  Future<void> setContextualDefaults(AppFlavor flavor) async {
    // performance
    final performanceClass = await deviceService.getPerformanceClass();
    enableBlurEffect = performanceClass >= 29;

    final androidInfo = await DeviceInfoPlugin().androidInfo;
    final manufacturer = androidInfo.manufacturer.toLowerCase();
    final pattern = BurstPatterns.byManufacturer[manufacturer];
    collectionBurstPatterns = pattern != null ? [pattern] : [];

    // availability
    if (flavor.hasMapStyleDefault) {
      final defaultMapStyle = mobileServices.defaultMapStyle;
      if (defaultMapStyle != null && mobileServices.mapStyles.contains(defaultMapStyle)) {
        mapStyle = defaultMapStyle;
      } else {
        final styles = EntryMapStyles.baseStyles;
        mapStyle = styles[Random().nextInt(styles.length)];
      }
    }

    if (settings.useTvLayout) {
      applyTvSettings();
    }
  }

  void applyTvSettings() {
    themeBrightness = AvesThemeBrightness.dark;
    maxBrightness = MaxBrightness.never;
    mustBackTwiceToExit = false;
    // address `TV-BU` / `TV-BY` requirements from https://developer.android.com/docs/quality-guidelines/tv-app-quality
    drawerTypeBookmarks = [
      null,
      MimeFilter.video,
      FavouriteFilter.instance,
    ];
    drawerPageBookmarks = [
      SearchPage.routeName,
    ];
    bottomNavigationActions = [];
    showOverlayOnOpening = false;
    showOverlayMinimap = false;
    showOverlayZoomLevel = false;
    showOverlayThumbnailPreview = false;
    viewerGestureSideTapNext = false;
    viewerUseCutout = true;
    enableBin = false;
    showPinchGestureAlternatives = true;
    resetShowTitleQuery();
  }

Future<void> sanitize() async {
  if (timeToTakeAction == AccessibilityTimeout.system &&
      !await AccessibilityService.hasRecommendedTimeouts()) {
    set(SettingKeys.timeToTakeActionKey, null);
  }

  if (viewerUseCutout != SettingsDefaults.viewerUseCutout &&
      !await windowService.isCutoutAware()) {
    set(SettingKeys.viewerUseCutoutKey, null);
  }

  collectionBurstPatterns =
      collectionBurstPatterns.where(BurstPatterns.options.contains).toList();
}

  // tag editor

  bool get tagEditorCurrentFilterSectionExpanded => getBool(SettingKeys.tagEditorCurrentFilterSectionExpandedKey) ?? SettingsDefaults.tagEditorCurrentFilterSectionExpanded;

  set tagEditorCurrentFilterSectionExpanded(bool newValue) => set(SettingKeys.tagEditorCurrentFilterSectionExpandedKey, newValue);

  String? get tagEditorExpandedSection => getString(SettingKeys.tagEditorExpandedSectionKey);

  set tagEditorExpandedSection(String? newValue) => set(SettingKeys.tagEditorExpandedSectionKey, newValue);

  // converter

  String get convertMimeType => getString(SettingKeys.convertMimeTypeKey) ?? SettingsDefaults.convertMimeType;

  set convertMimeType(String newValue) => set(SettingKeys.convertMimeTypeKey, newValue);

  int get convertQuality => getInt(SettingKeys.convertQualityKey) ?? SettingsDefaults.convertQuality;

  set convertQuality(int newValue) => set(SettingKeys.convertQualityKey, newValue);

  bool get convertWriteMetadata => getBool(SettingKeys.convertWriteMetadataKey) ?? SettingsDefaults.convertWriteMetadata;

  set convertWriteMetadata(bool newValue) => set(SettingKeys.convertWriteMetadataKey, newValue);

  // map

  EntryMapStyle? get mapStyle {
    var preferred = getString(SettingKeys.mapStyleKey);

    // backward compatibility with definition as enum
    const oldEnumPrefix = 'EntryMapStyle.';
    if (preferred != null && preferred.startsWith(oldEnumPrefix)) {
      preferred = preferred.substring(oldEnumPrefix.length);
      if (preferred.isEmpty) preferred = null;
    }

    if (preferred == null) return null;

    final styles = [...availability.mapStyles, ...customMapStyles];
    return styles.firstWhereOrNull((v) => v.key == preferred) ?? styles.first;
  }

  set mapStyle(EntryMapStyle? newValue) => set(SettingKeys.mapStyleKey, newValue?.key);

  LatLng? get mapDefaultCenter {
    final json = getString(SettingKeys.mapDefaultCenterKey);
    return json != null ? LatLng.fromJson(jsonDecode(json)) : null;
  }

  set mapDefaultCenter(LatLng? newValue) => set(SettingKeys.mapDefaultCenterKey, newValue != null ? jsonEncode(newValue.toJson()) : null);

  Set<EntryMapStyle> get customMapStyles => (getStringList(SettingKeys.customMapStylesKey) ?? []).map(EntryMapStyle.fromJson).nonNulls.toSet();

  set customMapStyles(Set<EntryMapStyle> newValue) => set(SettingKeys.customMapStylesKey, newValue.map((filter) => filter.toJson()).toList());

  // bin

  bool get enableBin => getBool(SettingKeys.enableBinKey) ?? SettingsDefaults.enableBin;

  set enableBin(bool newValue) => set(SettingKeys.enableBinKey, newValue);

  // accessibility

  bool get showPinchGestureAlternatives => getBool(SettingKeys.showPinchGestureAlternativesKey) ?? SettingsDefaults.showPinchGestureAlternatives;

  set showPinchGestureAlternatives(bool newValue) => set(SettingKeys.showPinchGestureAlternativesKey, newValue);

  AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(SettingKeys.accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values);

  bool get animate => accessibilityAnimations.animate;

  set accessibilityAnimations(AccessibilityAnimations newValue) => set(SettingKeys.accessibilityAnimationsKey, newValue.toString());

  AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(SettingKeys.timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);

  set timeToTakeAction(AccessibilityTimeout newValue) => set(SettingKeys.timeToTakeActionKey, newValue.toString());

  // platform settings

  void _onPlatformSettingsChanged(Map? fields) {
    fields?.forEach((key, value) {
      switch (key) {
        case SettingKeys.platformAccelerometerRotationKey:
          if (value is num) {
            isRotationLocked = value == 0;
          }
        case SettingKeys.platformTransitionAnimationScaleKey:
          if (value is num) {
            areAnimationsRemoved = value == 0;
          }
        case SettingKeys.platformLongPressTimeoutMillisKey:
          if (value is num) {
            longPressTimeoutMillis = value.toInt();
          }
      }
    });
  }

  bool get isRotationLocked => getBool(SettingKeys.platformAccelerometerRotationKey) ?? SettingsDefaults.isRotationLocked;

  set isRotationLocked(bool newValue) => set(SettingKeys.platformAccelerometerRotationKey, newValue);

  bool get areAnimationsRemoved => getBool(SettingKeys.platformTransitionAnimationScaleKey) ?? SettingsDefaults.areAnimationsRemoved;

  set areAnimationsRemoved(bool newValue) => set(SettingKeys.platformTransitionAnimationScaleKey, newValue);

  Duration get longPressTimeout => Duration(milliseconds: getInt(SettingKeys.platformLongPressTimeoutMillisKey) ?? kLongPressTimeout.inMilliseconds);

  set longPressTimeoutMillis(int newValue) => set(SettingKeys.platformLongPressTimeoutMillisKey, newValue);

  // import/export

  Map<String, dynamic> export() => Map.fromEntries(
    store.getKeys().whereNot(SettingKeys.isInternalKey).map((k) => MapEntry(k, store.get(k))),
  );

Future<void> import(Object jsonMap) async {
  if (jsonMap is! Map) {
    debugPrint('failed to import settings for jsonMap=$jsonMap');
    return;
  }

  // reset per ripristinare i default
  await reset(includeInternalKeys: false);

  jsonMap.cast<String, Object?>().forEach((key, newValue) {
    final oldValue = store.get(key);

    // null → rimuovi
    if (newValue == null) {
      store.remove(key);
    }

    // double
    else if (key.startsWith(SettingKeys.tileExtentPrefixKey)) {
      if (newValue is double) store.setDouble(key, newValue);
    }

    // string
    else if (key.startsWith(SettingKeys.tileLayoutPrefixKey)) {
      if (newValue is String) store.setString(key, newValue);
    }

    // bool
    else if (key.startsWith(SettingKeys.showTitleQueryPrefixKey)) {
      if (newValue is bool) store.setBool(key, newValue);
    }

    // 🔥 gestione compatta dei tipi base
    else if (newValue is int) {
      store.setInt(key, newValue);
    } else if (newValue is double) {
      store.setDouble(key, newValue);
    } else if (newValue is bool) {
      store.setBool(key, newValue);
    } else if (newValue is String) {
      store.setString(key, newValue);
    } else if (newValue is List) {
      store.setStringList(key, newValue.cast<String>());
    }

    // notifica se cambiato
    if (oldValue != newValue) {
      notifyKeyChange(key, oldValue, newValue);
    }
  });

  await sanitize();
  notifyListeners();
}

  @override
  void notifyKeyChange(String key, Object? oldValue, Object? newValue) {
    _updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue));
    if (key.startsWith(SettingKeys.tileExtentPrefixKey)) {
      _updateTileExtentStreamController.add(SettingsChangedEvent(key, oldValue, newValue));
    }
  }
}
