package com.phenomrom.iwanttoheal;

import android.os.Bundle;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

import androidx.activity.OnBackPressedCallback;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;

import com.getcapacitor.BridgeActivity;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/** Routes every Thor controller event into one JavaScript input service. */
public abstract class ControllerBridgeActivity extends BridgeActivity {
    private static final float AXIS_DEAD_ZONE = 0.45f;
    private static final long REPEAT_THROTTLE_MS = 55L;
    private final long[] lastDpadDispatchAt = new long[16];
    private final Set<String> activeMotionTokens = new HashSet<>();
    private final Map<String, Long> lastMotionDispatchAt = new HashMap<>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {
                dispatchNativeControllerToken("Button1", false);
            }
        });
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        WindowManager.LayoutParams attributes = getWindow().getAttributes();
        attributes.preferredRefreshRate = 60.0f;
        getWindow().setAttributes(attributes);
        if (bridge != null && bridge.getWebView() != null) {
            bridge.getWebView().setOverScrollMode(View.OVER_SCROLL_NEVER);
            bridge.getWebView().setFocusable(true);
            bridge.getWebView().setFocusableInTouchMode(true);
            bridge.getWebView().requestFocus();
        }
        enterImmersiveMode();
    }

    @Override
    public void onPause() {
        clearHeldControllerState();
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        enterImmersiveMode();
        if (bridge != null && bridge.getWebView() != null) bridge.getWebView().requestFocus();
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) enterImmersiveMode();
        else clearHeldControllerState();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && bridge != null) {
            bridge.getWebView().requestFocus();
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        String token = controllerToken(event.getKeyCode());
        if (token == null || bridge == null) return super.dispatchKeyEvent(event);
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            boolean repeat = event.getRepeatCount() > 0;
            if (isDpadToken(token) && shouldThrottleDpad(token)) return true;
            dispatchNativeControllerToken(token, repeat);
        }
        return true;
    }

    @Override
    public boolean dispatchGenericMotionEvent(MotionEvent event) {
        if (
            bridge == null
                || event.getActionMasked() != MotionEvent.ACTION_MOVE
                || !isControllerMotionEvent(event)
        ) {
            return super.dispatchGenericMotionEvent(event);
        }

        float leftStickX = event.getAxisValue(MotionEvent.AXIS_X);
        float leftStickY = event.getAxisValue(MotionEvent.AXIS_Y);
        float rightStickX = controllerAxisValue(event, MotionEvent.AXIS_Z, MotionEvent.AXIS_RX);
        float rightStickY = controllerAxisValue(event, MotionEvent.AXIS_RZ, MotionEvent.AXIS_RY);
        dispatchNativeControllerMotion(leftStickX, leftStickY, rightStickX, rightStickY);

        Set<String> currentTokens = new HashSet<>();
        addAxisTokens(currentTokens, event.getAxisValue(MotionEvent.AXIS_HAT_X), "Button14", "Button15");
        addAxisTokens(currentTokens, event.getAxisValue(MotionEvent.AXIS_HAT_Y), "Button12", "Button13");
        addAxisTokens(currentTokens, leftStickX, "Axis0-", "Axis0+");
        addAxisTokens(currentTokens, leftStickY, "Axis1-", "Axis1+");

        long now = SystemClock.uptimeMillis();
        for (String token : currentTokens) {
            boolean repeat = activeMotionTokens.contains(token);
            long lastDispatch = lastMotionDispatchAt.containsKey(token)
                ? lastMotionDispatchAt.get(token)
                : 0L;
            if (!repeat || now - lastDispatch >= REPEAT_THROTTLE_MS) {
                dispatchNativeControllerToken(token, repeat);
                lastMotionDispatchAt.put(token, now);
            }
        }
        activeMotionTokens.clear();
        activeMotionTokens.addAll(currentTokens);
        return true;
    }

    private void enterImmersiveMode() {
        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
        WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(
            getWindow(),
            getWindow().getDecorView()
        );
        controller.hide(WindowInsetsCompat.Type.systemBars());
        controller.setSystemBarsBehavior(
            WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        );
    }

    private boolean isControllerMotionEvent(MotionEvent event) {
        int source = event.getSource();
        return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
            || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
            || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
    }

    private void addAxisTokens(Set<String> tokens, float value, String negative, String positive) {
        if (value <= -AXIS_DEAD_ZONE) tokens.add(negative);
        if (value >= AXIS_DEAD_ZONE) tokens.add(positive);
    }

    private float controllerAxisValue(MotionEvent event, int primaryAxis, int fallbackAxis) {
        InputDevice device = event.getDevice();
        if (device != null && device.getMotionRange(primaryAxis) != null) {
            return event.getAxisValue(primaryAxis);
        }
        return event.getAxisValue(fallbackAxis);
    }

    private void dispatchNativeControllerToken(String token, boolean repeat) {
        if (bridge == null || bridge.getWebView() == null) return;
        String script =
            "window.dispatchEvent(new CustomEvent('iwt-native-controller',"
                + "{detail:{token:'" + token + "',repeat:" + repeat + "}}));";
        bridge.getWebView().post(() -> {
            bridge.getWebView().requestFocus();
            bridge.getWebView().evaluateJavascript(script, null);
        });
    }

    private void dispatchNativeControllerMotion(float moveX, float moveY, float lookX, float lookY) {
        if (bridge == null || bridge.getWebView() == null) return;
        String script =
            "window.dispatchEvent(new CustomEvent('iwt-native-controller-motion',"
                + "{detail:{moveX:" + moveX + ",moveY:" + moveY
                + ",lookX:" + lookX + ",lookY:" + lookY + "}}));";
        bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript(script, null));
    }

    private void clearHeldControllerState() {
        activeMotionTokens.clear();
        lastMotionDispatchAt.clear();
        dispatchNativeControllerMotion(0.0f, 0.0f, 0.0f, 0.0f);
        if (bridge == null || bridge.getWebView() == null) return;
        bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript(
            "window.dispatchEvent(new Event('iwt-native-controller-reset'));",
            null
        ));
    }

    private boolean shouldThrottleDpad(String token) {
        int index = Integer.parseInt(token.substring("Button".length()));
        long now = SystemClock.uptimeMillis();
        if (now - lastDpadDispatchAt[index] < REPEAT_THROTTLE_MS) return true;
        lastDpadDispatchAt[index] = now;
        return false;
    }

    private boolean isDpadToken(String token) {
        return token.equals("Button12")
            || token.equals("Button13")
            || token.equals("Button14")
            || token.equals("Button15");
    }

    private String controllerToken(int keyCode) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BUTTON_A:
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_NUMPAD_ENTER:
                return "Button0";
            case KeyEvent.KEYCODE_BUTTON_B:
            case KeyEvent.KEYCODE_ESCAPE:
                return "Button1";
            case KeyEvent.KEYCODE_BUTTON_X: return "Button2";
            case KeyEvent.KEYCODE_BUTTON_Y: return "Button3";
            case KeyEvent.KEYCODE_BUTTON_L1: return "Button4";
            case KeyEvent.KEYCODE_BUTTON_R1: return "Button5";
            case KeyEvent.KEYCODE_BUTTON_L2: return "Button6";
            case KeyEvent.KEYCODE_BUTTON_R2: return "Button7";
            case KeyEvent.KEYCODE_BUTTON_SELECT: return "Button8";
            case KeyEvent.KEYCODE_BUTTON_START: return "Button9";
            case KeyEvent.KEYCODE_BUTTON_THUMBL: return "Button10";
            case KeyEvent.KEYCODE_BUTTON_THUMBR: return "Button11";
            case KeyEvent.KEYCODE_DPAD_UP: return "Button12";
            case KeyEvent.KEYCODE_DPAD_DOWN: return "Button13";
            case KeyEvent.KEYCODE_DPAD_LEFT: return "Button14";
            case KeyEvent.KEYCODE_DPAD_RIGHT: return "Button15";
            default: return null;
        }
    }
}
