// Version: 1.0.0 | Created: 2026-07-04
// Panic button repository — config in Matrix account data plus the trigger
// action: send an alert text message and (when the browser grants it) an
// m.location pin to the configured room.
//
// The alert message is sent FIRST so a denied/slow geolocation permission can
// never block the emergency alert itself.
//
// NOTE web-only geolocation (dart:js_interop + package:web). When native
// Android/iOS builds happen, gate this behind a conditional import.

import 'dart:async';
import 'dart:js_interop';

import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:web/web.dart' as web;

import '../../../core/network/matrix_client.dart';

part 'panic_repository.g.dart';

/// Account-data event type holding the panic configuration.
const kPanicConfigType = 'au.m8chat.panic';

/// Panic button configuration.
class PanicConfig {
  const PanicConfig({required this.roomId, required this.message});

  final String roomId;
  final String message;

  static PanicConfig? fromAccountData(Client client) {
    final content = client.accountData[kPanicConfigType]?.content;
    final roomId = content?['room_id'];
    if (roomId is! String || roomId.isEmpty) return null;
    final message = content?['message'];
    return PanicConfig(
      roomId: roomId,
      message: message is String && message.isNotEmpty
          ? message
          : 'EMERGENCY: I need help. This is an automated alert from M8Chat.',
    );
  }
}

class PanicRepository {
  PanicRepository({required Client client}) : _client = client;

  final Client _client;

  PanicConfig? get config => PanicConfig.fromAccountData(_client);

  Future<void> saveConfig({
    required String roomId,
    required String message,
  }) async {
    final userId = _client.userID;
    if (userId == null) throw Exception('Not logged in');
    await _client.setAccountData(userId, kPanicConfigType, {
      'room_id': roomId,
      'message': message,
    });
  }

  Future<void> clearConfig() async {
    final userId = _client.userID;
    if (userId == null) return;
    await _client.setAccountData(userId, kPanicConfigType, {});
  }

  /// Fires the panic alert. Returns an error string, or null on success.
  Future<String?> trigger() async {
    final cfg = config;
    if (cfg == null) return 'Panic button is not configured.';
    final room = _client.getRoomById(cfg.roomId);
    if (room == null) return 'Configured room is no longer available.';

    // 1. The alert text goes out first — never blocked by geolocation.
    try {
      await room.sendTextEvent('\u{1F6A8} ${cfg.message}');
    } on Exception catch (e) {
      return 'Could not send alert: $e';
    }

    // 2. Best-effort location pin.
    final position = await _currentPosition();
    if (position != null) {
      final (lat, lon) = position;
      final geoUri = 'geo:$lat,$lon';
      try {
        await room.sendEvent({
          'msgtype': 'm.location',
          'body': 'Emergency location: $geoUri',
          'geo_uri': geoUri,
          'org.matrix.msc3488.location': {'uri': geoUri},
          'org.matrix.msc3488.asset': {'type': 'm.pin'},
        }, type: 'm.room.message');
      } on Exception catch (e) {
        debugPrint('[Panic] location send failed: $e');
      }
    } else {
      debugPrint('[Panic] no geolocation available — alert sent without it');
    }
    return null;
  }

  /// Browser geolocation with a hard 10s budget. Null when denied/unavailable.
  Future<(double, double)?> _currentPosition() async {
    final completer = Completer<(double, double)?>();
    try {
      web.window.navigator.geolocation.getCurrentPosition(
        (web.GeolocationPosition pos) {
          if (!completer.isCompleted) {
            completer.complete((pos.coords.latitude, pos.coords.longitude));
          }
        }.toJS,
        (web.GeolocationPositionError err) {
          if (!completer.isCompleted) completer.complete(null);
        }.toJS,
        web.PositionOptions(
          enableHighAccuracy: true,
          timeout: 8000,
          maximumAge: 60000,
        ),
      );
    } catch (e) {
      debugPrint('[Panic] geolocation API unavailable: $e');
      return null;
    }
    return completer.future.timeout(
      const Duration(seconds: 10),
      onTimeout: () => null,
    );
  }
}

@Riverpod(keepAlive: true)
PanicRepository panicRepository(Ref ref) {
  return PanicRepository(client: ref.watch(matrixClientProvider));
}
