/**
 * CallManager.ts — the bridge between SIP sessions and the NATIVE call UI.
 *
 * CallKeep gives us the iOS CallKit / Android ConnectionService lock-screen UI.
 * The golden rule on iOS: when a VoIP push arrives you MUST report an incoming
 * call to CallKit almost immediately (PushKit contract) or the OS kills the app
 * and may revoke your push privileges. So the flow is:
 *
 *   VoIP push  --> displayIncomingCall() IMMEDIATELY (native UI rings)
 *              --> SipEngine registers + the real INVITE arrives a beat later
 *              --> we reconcile the two by callId
 *
 * User taps Answer on the lock screen -> CallKeep 'answerCall' -> sip answer().
 * User taps native End                -> CallKeep 'endCall'    -> sip hangup().
 */
import RNCallKeep from 'react-native-callkeep';
import { Platform } from 'react-native';
import InCallManager from 'react-native-incall-manager';
import { sipEngine, ActiveCall } from './SipEngine';

const CALLKEEP_OPTIONS = {
  ios: {
    appName: 'ePhone',
    supportsVideo: false,
    maximumCallGroups: '1',
    maximumCallsPerCallGroup: '1',
  },
  android: {
    alertTitle: 'Permissions required',
    alertDescription: 'ePhone needs access to place and receive calls',
    cancelButton: 'Cancel',
    okButton: 'ok',
    // Foreground service so Android keeps the call alive when screen is off.
    foregroundService: {
      channelId: 'com.ephone.call',
      channelName: 'ePhone active call',
      notificationTitle: 'ePhone call in progress',
    },
    additionalPermissions: [],
    selfManaged: false,
  },
};

class CallManager {
  /** callId -> whether CallKit already knows about this call (dedupe). */
  private known = new Set<string>();

  async setup(): Promise<void> {
    await RNCallKeep.setup(CALLKEEP_OPTIONS);
    RNCallKeep.setAvailable(true); // Android: mark the connection service ready

    // --- Native UI actions -> SIP ---
    RNCallKeep.addEventListener('answerCall', ({ callUUID }) => {
      InCallManager.start({ media: 'audio' });
      sipEngine.answer(callUUID);
    });
    RNCallKeep.addEventListener('endCall', ({ callUUID }) => {
      sipEngine.hangup(callUUID);
      InCallManager.stop();
    });
    RNCallKeep.addEventListener('didPerformSetMutedCallAction', ({ callUUID, muted }) => {
      sipEngine.setMute(callUUID, muted);
    });
    RNCallKeep.addEventListener('didToggleHoldCallAction', ({ callUUID, hold }) => {
      sipEngine.setHold(callUUID, hold);
    });
    RNCallKeep.addEventListener('didPerformDTMFAction', ({ callUUID, digits }) => {
      sipEngine.sendDtmf(callUUID, digits);
    });

    // --- SIP lifecycle -> Native UI ---
    sipEngine.on('incoming', (call: ActiveCall) => this.reportIncoming(call));
    sipEngine.on('confirmed', (callId: string) => {
      RNCallKeep.setCurrentCallActive(callId);   // native UI -> connected/timer
      InCallManager.start({ media: 'audio' });
    });
    sipEngine.on('ended', (callId: string) => {
      RNCallKeep.endCall(callId);
      this.known.delete(callId);
      InCallManager.stop();
    });
  }

  // ---------------------------------------------------------------------------
  // iOS PushKit path: called from the VoIP push handler BEFORE the INVITE lands.
  // We invent/accept a callId, show CallKit now, and SipEngine matches it later.
  // ---------------------------------------------------------------------------
  displayIncomingFromPush(callId: string, handle: string, name: string): void {
    if (this.known.has(callId)) return;
    this.known.add(callId);
    RNCallKeep.displayIncomingCall(callId, handle, name, 'generic', false);
  }

  // SIP INVITE actually arrived — show native UI if the push didn't already.
  private reportIncoming(call: ActiveCall): void {
    if (this.known.has(call.id)) return; // push already displayed it
    this.known.add(call.id);
    RNCallKeep.displayIncomingCall(
      call.id,
      call.remoteNumber,
      call.remoteNumber,
      'number',
      false,
    );
  }

  // ---------------------------------------------------------------------------
  // Outgoing call started from the dialpad (Step 4 UI calls this).
  // ---------------------------------------------------------------------------
  startOutgoing(number: string): string {
    const callId = this.freshUuid();
    this.known.add(callId);
    RNCallKeep.startCall(callId, number, number, 'number', false);
    sipEngine.placeCall(number, callId);
    InCallManager.start({ media: 'audio' });
    return callId;
  }

  toggleSpeaker(on: boolean): void {
    InCallManager.setForceSpeakerphoneOn(on);
  }

  private freshUuid(): string {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
      const r = (Math.random() * 16) | 0;
      const v = c === 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }
}

export const callManager = new CallManager();
