// Version: 1.3.0 | Created: 2026-04-01 | Updated: 2026-04-03
// Call controller — manages LiveKit room connection lifecycle.
// Transitions through idle → connecting → active → ended states.

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../data/livekit_service.dart';
import '../domain/call_state.dart';

part 'call_controller.g.dart';

@Riverpod(keepAlive: true)
class CallController extends _$CallController {
  Timer? _durationTimer;
  Duration _elapsed = Duration.zero;
  EventsListener<RoomEvent>? _roomListener;

  @override
  CallState build() {
    ref.onDispose(() {
      _durationTimer?.cancel();
      _durationTimer = null;
      _roomListener?.dispose();
      _roomListener = null;
      ref.read(liveKitServiceProvider).disconnect();
    });
    return const CallState.idle();
  }

  /// Join a LiveKit room via MatrixRTC JWT endpoint.
  ///
  /// On success, starts a timer to track call duration and transitions to
  /// [CallActive]. On failure, transitions to [CallEnded] with a reason.
  Future<void> joinCall(String roomId, {bool withVideo = true}) async {
    state = CallState.connecting(roomId: roomId);

    final service = ref.read(liveKitServiceProvider);
    final result = await service.connect(roomId);

    switch (result) {
      case LiveKitConnected(:final room):
        // Enable camera and microphone on the local participant.
        final local = room.localParticipant;
        if (local != null) {
          if (withVideo) {
            await local.setCameraEnabled(true);
          }
          await local.setMicrophoneEnabled(true);
        }
        _startTimer(roomId, room, isVideo: withVideo);
      case LiveKitFailed(:final failure):
        state = CallEnded(
          reason: switch (failure) {
            LiveKitNotAuthenticated() => 'Not authenticated.',
            LiveKitJwtFetchFailed(:final message) =>
              'Could not connect: $message',
            LiveKitConnectFailed(:final message) => 'Call failed: $message',
          },
        );
    }
  }

  /// Toggle microphone on/off during an active call.
  Future<void> toggleAudio() async {
    final current = state;
    if (current is! CallActive) return;

    final room = ref.read(liveKitServiceProvider).activeRoom;
    final local = room?.localParticipant;
    if (local == null) return;

    final newEnabled = !current.isAudioEnabled;
    await local.setMicrophoneEnabled(newEnabled);
    state = current.copyWith(isAudioEnabled: newEnabled);
  }

  /// Toggle camera on/off during an active call.
  Future<void> toggleVideo() async {
    final current = state;
    if (current is! CallActive) return;

    final room = ref.read(liveKitServiceProvider).activeRoom;
    final local = room?.localParticipant;
    if (local == null) return;

    final newEnabled = !current.isVideoEnabled;
    await local.setCameraEnabled(newEnabled);
    state = current.copyWith(isVideoEnabled: newEnabled);
  }

  /// Hang up — disconnect from LiveKit and transition to [CallEnded].
  Future<void> endCall() async {
    _durationTimer?.cancel();
    _durationTimer = null;
    await ref.read(liveKitServiceProvider).disconnect();
    state = const CallState.ended();
  }

  void _startTimer(String roomId, Room room, {required bool isVideo}) {
    _elapsed = Duration.zero;
    state = CallActive(
      roomId: roomId,
      duration: _elapsed,
      isVideoEnabled: isVideo,
      isAudioEnabled: true,
    );

    _durationTimer = Timer.periodic(const Duration(seconds: 1), (_) {
      _elapsed += const Duration(seconds: 1);
      final current = state;
      if (current is CallActive) {
        state = current.copyWith(duration: _elapsed);
      }
    });

    // Auto-end when the remote party leaves (1:1 call behaviour).
    // Wait 5 seconds after the last remote participant disconnects before
    // ending — gives time for brief network interruptions to recover.
    _roomListener?.dispose();
    _roomListener = room.createListener();
    _roomListener!.on<ParticipantDisconnectedEvent>((event) {
      debugPrint(
        '[Call] Participant left: ${event.participant.identity}, '
        'remaining: ${room.remoteParticipants.length}',
      );
      if (room.remoteParticipants.isEmpty) {
        debugPrint('[Call] No remote participants — auto-ending in 5s');
        Future.delayed(const Duration(seconds: 5), () {
          // Re-check in case someone rejoined during the delay.
          if (room.remoteParticipants.isEmpty && state is CallActive) {
            endCall();
          }
        });
      }
    });
  }
}
