import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:matrix/matrix.dart';
import 'package:pyramid/core/settings_prefs.dart';
import 'package:pyramid/core/theme.dart';
import 'package:pyramid/core/matrix_client.dart';
import 'package:pyramid/core/livekit_call_manager.dart';
import 'package:pyramid/core/voip_manager.dart';
import 'package:pyramid/features/rooms/rooms_provider.dart';

// ─── Theme state ──────────────────────────────────────────────────────────────

final themeModeProvider = StateProvider<bool>((ref) => true); // true = dark
final accentProvider = StateProvider<int>((ref) => 0); // index into accent presets

const accentPresets = [
  (name: 'amber', color: PyramidColors.accentAmber),
  (name: 'coral', color: PyramidColors.accentCoral),
  (name: 'mint', color: PyramidColors.accentMint),
  (name: 'violet', color: PyramidColors.accentViolet),
  (name: 'cobalt', color: PyramidColors.accentCobalt),
  (name: 'lime', color: PyramidColors.accentLime),
];

final radiusProvider = StateProvider<double>((ref) => 12);
final densityProvider = StateProvider<double>((ref) => 1);
final motionProvider = StateProvider<double>((ref) => 1);

// ─── Shell layout state ───────────────────────────────────────────────────────

final railCollapsedProvider = StateProvider<bool>((ref) => false);

// True while the app window is the foreground / focused window.
// Set to false when the lifecycle transitions to inactive/paused/detached.
final windowFocusedProvider = StateProvider<bool>((ref) => true);
final membersPanelOpenProvider = StateProvider<bool>((ref) => false);

// Set to an eventId to make the open chat scroll to + briefly highlight that
// message (e.g. when tapping a search result). ChatView consumes and clears it.
final pendingJumpEventProvider = StateProvider<String?>((ref) => null);

// Currently selected room ID (null = no room selected)
final activeRoomIdProvider = StateProvider<String?>((ref) => null);

// ─── Share-Target ("Teilen nach Pyramid", Android) ───────────────────────────
// Von MainActivity.kt via MethodChannel geliefert; AppShell zeigt dann den
// Raum-Picker. paths = bereits in den App-Cache kopierte Dateien.
class PendingShare {
  final String? text;
  final List<String> paths;
  const PendingShare({this.text, this.paths = const []});

  bool get isEmpty => (text == null || text!.isEmpty) && paths.isEmpty;
}

final pendingShareProvider = StateProvider<PendingShare?>((ref) => null);

// Space filter: null = all rooms, otherwise space ID
final activeSpaceIdProvider = StateProvider<String?>((ref) => 'dms');

// ─── Modal state ─────────────────────────────────────────────────────────────

enum ModalKind { none, settings, search }

final activeModalProvider = StateProvider<ModalKind>((ref) => ModalKind.none);

// ─── Call state ───────────────────────────────────────────────────────────────

final callStateProvider = ChangeNotifierProvider<LiveKitCallManager>((ref) {
  return LiveKitCallManager.instance;
});

final voipStateProvider = ChangeNotifierProvider<PyramidVoipManager>((ref) {
  return PyramidVoipManager.instance;
});

final incomingCallProvider = StateProvider<CallSession?>((ref) => null);

// ─── View mode (chat / voice) ─────────────────────────────────────────────────

enum ViewMode { chat, voice }

final viewModeProvider = StateProvider<ViewMode>((ref) => ViewMode.chat);

// ─── Spaces from Matrix ───────────────────────────────────────────────────────

final spacesProvider = StreamProvider<List<Room>>((ref) async* {
  final client = await ref.watch(matrixClientProvider.future);
  yield _getSpaces(client);
  await for (final _ in client.onSync.stream) {
    yield _getSpaces(client);
  }
});

List<Room> _getSpaces(Client client) {
  return client.rooms.where((r) => r.isSpace).toList();
}

// ─── Toast notifications ─────────────────────────────────────────────────────

class ToastMessage {
  final String text;
  final String icon;
  ToastMessage(this.text, this.icon);
}

final toastProvider = StateProvider<ToastMessage?>((ref) => null);

// Remembers the last active room per space section (key = spaceId or null)
final lastRoomPerSpaceProvider = StateProvider<Map<String?, String?>>((ref) => {});

// Space IDs the user just accepted an invite for. The server confirms the join
// immediately, but Continuwuity's incremental /sync sometimes does not flip the
// local membership from invite → join. We treat these as joined optimistically
// so the UI switches to the channel list (channels load via the space hierarchy).
// Persisted so the user does not have to re-accept on every app start.
final forceJoinedSpacesProvider =
    StateNotifierProvider<StringSetPref, Set<String>>(
        (ref) => StringSetPref('force_joined_spaces'));

// ─── Voice channel state ──────────────────────────────────────────────────────

// ID of the Matrix room the user is currently in as a voice channel (null = none)
final activeVoiceRoomIdProvider = StateProvider<String?>((ref) => null);

const kVoicePresenceType = 'io.pyramid.voice.presence';

// Local override cache: spaceId → bannerMxcUri (set immediately on upload,
// before the server sync delivers the state event back)
final spaceBannerOverrideProvider =
    StateProvider<Map<String, String?>>((ref) => {});

// ─── Server-level banner ──────────────────────────────────────────────────────
// Persisted to SharedPreferences keyed by homeserver URL.
// Key in store: homeserverUrl → mxcUri

class ServerBannerNotifier extends AsyncNotifier<Map<String, String>> {
  @override
  Future<Map<String, String>> build() => loadAllServerBanners();

  Future<void> set(String homeserverUrl, String? mxcUri) async {
    final current = Map<String, String>.from(state.valueOrNull ?? {});
    if (mxcUri == null || mxcUri.isEmpty) {
      current.remove(homeserverUrl);
    } else {
      current[homeserverUrl] = mxcUri;
    }
    await saveServerBanner(homeserverUrl, mxcUri);
    state = AsyncData(current);
  }
}

final serverBannerProvider =
    AsyncNotifierProvider<ServerBannerNotifier, Map<String, String>>(
  ServerBannerNotifier.new,
);

// Active voice participants for a room, read from Matrix state events
final voiceParticipantsProvider =
    Provider.family<List<Map<String, dynamic>>, String>((ref, roomId) {
  ref.watch(roomListProvider); // Rebuild on every sync so presence updates show
  final client = ref.watch(matrixClientProvider).valueOrNull;
  if (client == null) return [];
  final room = client.getRoomById(roomId);
  if (room == null) return [];
  final states = room.states[kVoicePresenceType] ?? {};
  final now = DateTime.now().millisecondsSinceEpoch;
  final result = <Map<String, dynamic>>[];
  for (final entry in states.entries) {
    final content = entry.value.content;
    if (content['active'] != true) continue;
    final joinedAt = content['joined_at'] as int? ?? 0;
    if (now - joinedAt > 10 * 60 * 1000) continue;
    result.add({
      'userId': entry.key,
      'displayName': content['display_name'] as String? ?? entry.key,
      'avatarUrl': content['avatar_url'] as String?,
    });
  }

  // Optimistically insert the local user immediately (state event may not
  // have synced back from the server yet after joining)
  final activeVoiceRoomId = ref.watch(activeVoiceRoomIdProvider);
  if (activeVoiceRoomId == roomId && client.userID != null) {
    final userId = client.userID!;
    if (!result.any((p) => p['userId'] == userId)) {
      String displayName = userId.split(':').first.replaceAll('@', '');
      String? avatarUrl;
      for (final r in client.rooms) {
        try {
          final m = r.getParticipants().firstWhere((u) => u.id == userId);
          if (m.displayName != null) {
            displayName = m.displayName!;
            avatarUrl = m.avatarUrl?.toString();
            break;
          }
        } catch (_) {}
      }
      result.insert(0, {
        'userId': userId,
        'displayName': displayName,
        'avatarUrl': avatarUrl,
      });
    }
  }

  return result;
});
