package chat.vojo.app;

import android.content.Context;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.util.Log;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;

import java.util.List;

/**
 * JS → Android bridge for in-call audio OUTPUT routing (loudspeaker ⇄ earpiece)
 * during a DM voice call.
 *
 * WHY THIS EXISTS. Call audio is owned by Chromium's WebRTC stack inside the
 * Capacitor System WebView. For a getUserMedia voice call that stack puts the
 * session in MODE_IN_COMMUNICATION and routes to the EARPIECE by default, and
 * there is no in-WebView lever to move it — the Audio Output Devices API
 * (setSinkId / selectAudioOutput) is unimplemented on Android WebView. So the
 * only way to give the user a «громкая связь / loudspeaker» toggle is to reach
 * the platform AudioManager natively and flip the output device on top of the
 * session the WebView already owns.
 *
 * COEXISTENCE RULE (critical). The WebView's WebRTC is the single owner of the
 * audio session: it already called setMode(MODE_IN_COMMUNICATION) and acquired
 * audio focus. This plugin therefore ONLY flips the output device — it does NOT
 * call setMode(), does NOT request audio focus, and does NOT start its own ADM.
 * Two owners of the same route produce ghost echo / half-muted audio. Mirrors
 * the route-only slice of element-android's DefaultAudioDeviceRouter without
 * taking ownership of the session.
 *
 * API split:
 *   - API 31+ (S): speaker-on = AudioManager.setCommunicationDevice() to the
 *     TYPE_BUILTIN_SPEAKER from getAvailableCommunicationDevices(); speaker-off
 *     = clearCommunicationDevice() so the platform auto-routes to a connected
 *     headset (wired/BT/USB) or the earpiece. clearCommunicationDevice() also
 *     restores the platform default on call end.
 *   - API < 31: legacy AudioManager.setSpeakerphoneOn(boolean). Deprecated but
 *     the only option pre-S; works because the WebView already set
 *     MODE_IN_COMMUNICATION.
 *
 * NOTE (on-device verification pending): some OEM WebView builds resist
 * app-side routing once they own the session. setSpeaker resolves with the
 * route the plugin OBSERVES after the call (getRoute re-read), so the JS side
 * can trust the resolved value rather than assuming the request took.
 */
@CapacitorPlugin(name = "AudioRoute")
public class AudioRoutePlugin extends Plugin {

    private static final String TAG = "AudioRoute";

    private AudioManager am() {
        Context ctx = getContext();
        if (ctx == null) return null;
        return (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
    }

    /**
     * setSpeaker({ on: boolean }) → { speaker: boolean }
     * Flips the call output to the built-in speaker (on) or earpiece (off).
     * Resolves with the route observed AFTER the change so JS state tracks reality.
     */
    @PluginMethod
    public void setSpeaker(PluginCall call) {
        Boolean on = call.getBoolean("on", Boolean.TRUE);
        AudioManager audio = am();
        if (audio == null) {
            call.reject("no_audio_manager");
            return;
        }
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                if (Boolean.TRUE.equals(on)) {
                    AudioDeviceInfo speaker =
                        findCommunicationDevice(audio, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
                    if (speaker != null) {
                        boolean ok = audio.setCommunicationDevice(speaker);
                        Log.d(TAG, "setCommunicationDevice speaker ok=" + ok);
                    } else {
                        Log.w(TAG, "no builtin speaker device");
                    }
                } else {
                    // Speaker OFF: hand routing back to the platform rather than
                    // forcing TYPE_BUILTIN_EARPIECE. Auto-selection prefers a
                    // connected wired / Bluetooth / USB headset and falls back
                    // to the earpiece — forcing the earpiece would yank audio
                    // off a headset the user is actually wearing.
                    audio.clearCommunicationDevice();
                    Log.d(TAG, "clearCommunicationDevice (speaker off -> headset/earpiece)");
                }
            } else {
                // Legacy: relies on the WebView having set MODE_IN_COMMUNICATION.
                // setSpeakerphoneOn(false) lets the system keep a wired headset.
                audio.setSpeakerphoneOn(Boolean.TRUE.equals(on));
                Log.d(TAG, "setSpeakerphoneOn " + on);
            }
        } catch (Throwable t) {
            Log.e(TAG, "setSpeaker failed", t);
            call.reject("set_speaker_failed: " + t.getClass().getSimpleName());
            return;
        }
        JSObject ret = new JSObject();
        ret.put("speaker", isSpeakerOn(audio));
        call.resolve(ret);
    }

    /**
     * getRoute() → { speaker: boolean }
     * Reads the currently active output route.
     */
    @PluginMethod
    public void getRoute(PluginCall call) {
        AudioManager audio = am();
        if (audio == null) {
            call.reject("no_audio_manager");
            return;
        }
        JSObject ret = new JSObject();
        ret.put("speaker", isSpeakerOn(audio));
        call.resolve(ret);
    }

    /**
     * clear() → void
     * Restores the platform-default communication route on call end so the
     * next call / app doesn't inherit a forced speaker. Mandatory teardown.
     */
    @PluginMethod
    public void clear(PluginCall call) {
        AudioManager audio = am();
        if (audio == null) {
            call.resolve();
            return;
        }
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                audio.clearCommunicationDevice();
            } else {
                audio.setSpeakerphoneOn(false);
            }
            Log.d(TAG, "clear: route restored to default");
        } catch (Throwable t) {
            Log.w(TAG, "clear failed", t);
        }
        call.resolve();
    }

    private static AudioDeviceInfo findCommunicationDevice(AudioManager audio, int type) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return null;
        List<AudioDeviceInfo> devices = audio.getAvailableCommunicationDevices();
        for (AudioDeviceInfo dev : devices) {
            if (dev.getType() == type) return dev;
        }
        return null;
    }

    private static boolean isSpeakerOn(AudioManager audio) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            AudioDeviceInfo cur = audio.getCommunicationDevice();
            return cur != null && cur.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
        }
        return audio.isSpeakerphoneOn();
    }
}
