import 'dart:async';
import 'dart:developer';

import 'package:hydrated_bloc/hydrated_bloc.dart';

import '../../../../../storage/settings.dart';
import '../../../../../utils/debouncer.dart';
import '../../../../../view/pages/settings/data/default_settings.dart';

class SettingsCubit extends HydratedCubit<Settings> {
  static const _debounceTag = 'settings_persist';
  bool _emitScheduled = false;

  SettingsCubit() : super(DefaultSettings.get());

  Settings val({bool write = false}) {
    if (write) {
      // Defer the emit until the synchronous mutation on the returned object
      // has finished — without this microtask the cubit emits a copy captured
      // *before* the assignment, so listeners see the old value.
      if (!_emitScheduled) {
        _emitScheduled = true;
        scheduleMicrotask(() {
          _emitScheduled = false;
          _emitFreshInstance();
        });
      }
      Debouncer.debounce(
        _debounceTag,
        const Duration(milliseconds: 500),
        _emitFreshInstance,
      );
    }
    return state;
  }

  void _emitFreshInstance() {
    try {
      emit(Settings.fromJson(state.toJson()));
    } catch (e) {
      log('Failed to refresh settings state: $e');
    }
  }

  Future<void> reset() async {
    emit(DefaultSettings.get());
  }

  // Modules missing from a stale persisted moduleOrder are handled at read
  // time by AppModule.effectiveModuleOrder (inserted at their default
  // position) — no healing on hydration needed.
  @override
  Settings fromJson(Map<String, dynamic> json) {
    try {
      return Settings.fromJson(json);
    } catch (_) {
      try {
        return Settings.fromJson(
          _mergeSettings(json, DefaultSettings.get().toJson()),
        );
      } catch (_) {
        return DefaultSettings.get();
      }
    }
  }

  @override
  Map<String, dynamic>? toJson(Settings state) => state.toJson();

  Map<String, dynamic> _mergeSettings(
    Map<String, dynamic> oldMap,
    Map<String, dynamic> newMap,
  ) {
    final merged = Map<String, dynamic>.from(newMap);
    oldMap.forEach((key, value) {
      if (merged.containsKey(key)) {
        if (value is Map<String, dynamic> &&
            merged[key] is Map<String, dynamic>) {
          merged[key] = _mergeSettings(
            value,
            merged[key] as Map<String, dynamic>,
          );
        } else {
          merged[key] = value;
        }
      }
    });
    return merged;
  }
}
