package app.chanora.chanora_flutter

import android.os.Build
import android.util.Log
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel

/**
 * BackIntentBridge — Kotlin half of SDD-028 (`BackIntentService`).
 *
 * Trace: SDD-028 (Android back-intent registration paths + deterministic
 * route-pop ordering) / SAD-018.
 *
 * Responsibility (Kotlin side):
 *  * On API 33+ (`Build.VERSION.SDK_INT >= TIRAMISU`) register an
 *    `OnBackInvokedCallback` against `activity.onBackInvokedDispatcher`
 *    at `PRIORITY_DEFAULT`.
 *  * On API < 33, register an `OnBackPressedCallback` (enabled = true)
 *    against `activity.onBackPressedDispatcher`.
 *  * Both callbacks consume the system back event (no super / no
 *    re-dispatch) and forward a single `backIntent` MethodChannel call
 *    to Dart with payload `{"kind": "system_back"}`. The Dart side
 *    (`BackIntentService`) owns the deterministic route-pop policy
 *    (PTT-active → ignore, modal → close, non-root → pop, root →
 *    `exitCandidate`).
 *  * When Dart concludes the event is unhandled at the root route, it
 *    calls back via `popToSystem`, which invokes `activity.finish()`
 *    exactly once (M-2 strict-review fix: guarded by `isFinishing` /
 *    `isDestroyed` so a duplicate Dart-side ExitApp decision cannot
 *    re-enter `finish()`).
 *
 * Threading: all callbacks are dispatched on the Android main thread,
 * matching SDD-028 §4 ("dispatch runs on the platform main thread").
 *
 * ## Lifecycle / ownership (M-3 strict-review fix)
 *
 * Previously this type was a Kotlin `object` (process-wide singleton)
 * that retained a strong reference to a `FlutterActivity`. Even though
 * [detach] cleared the reference, the singleton pattern is an
 * Activity-leak footgun: any caller forgetting `detach()` would pin
 * the Activity for the lifetime of the process.
 *
 * The bridge is now a plain `class`. `MainActivity` constructs one
 * instance in `configureFlutterEngine`, holds it in a private field,
 * and clears the field in `onDestroy` after calling [detach]. The
 * Activity is therefore reachable only via the bridge instance, and
 * the bridge instance is reachable only via `MainActivity` — when
 * `MainActivity` is destroyed, both become eligible for collection.
 */
class BackIntentBridge {

    private companion object {
        private const val TAG = "BackIntentBridge"
        private const val KEY_KIND = "kind"
        private const val VALUE_SYSTEM_BACK = "system_back"
    }

    private var channel: MethodChannel? = null
    private var attachedActivity: FlutterActivity? = null

    // API 33+ path.
    private var onBackInvokedCallback: OnBackInvokedCallback? = null

    // Pre-33 path.
    private var onBackPressedCallback: OnBackPressedCallback? = null

    /**
     * Attach the back-intent bridge to [activity] using [messenger] for
     * the Dart `MethodChannel`. Idempotent: a second call detaches the
     * prior attachment first.
     *
     * The [activity] reference is retained until [detach] is called.
     * The owning `MainActivity` MUST invoke [detach] from its
     * `onDestroy` (see class KDoc on lifecycle / ownership).
     */
    fun attach(activity: FlutterActivity, messenger: BinaryMessenger) {
        // Guard against double-attach (e.g. re-creation under config changes).
        detach()

        // X-2 strict-review fix: channel + method names sourced from the
        // central MethodChannels registry rather than file-local literals.
        val ch = MethodChannel(messenger, MethodChannels.BACK_INTENT)
        channel = ch
        attachedActivity = activity

        // SDD-028: Dart -> Kotlin "popToSystem" closes the activity at root.
        ch.setMethodCallHandler { call, result ->
            when (call.method) {
                MethodChannels.METHOD_POP_TO_SYSTEM -> {
                    // M-2 strict-review fix (SDD-028): guarantee exactly-once
                    // finish(). If Dart issues two ExitApp decisions in rapid
                    // succession we must not re-enter Activity teardown.
                    if (activity.isFinishing || activity.isDestroyed) {
                        Log.i(
                            TAG,
                            "popToSystem received but activity already finishing/destroyed; ignoring duplicate",
                        )
                        result.success(null)
                        return@setMethodCallHandler
                    }
                    activity.finish()
                    result.success(null)
                }
                else -> result.notImplemented()
            }
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            registerApi33(activity, ch)
        } else {
            registerPre33(activity, ch)
        }
    }

    /**
     * Detach from the previously-attached activity and tear down all
     * registrations. Safe to call repeatedly.
     */
    fun detach() {
        val activity = attachedActivity
        if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            onBackInvokedCallback?.let { cb ->
                activity.onBackInvokedDispatcher.unregisterOnBackInvokedCallback(cb)
            }
        }
        onBackInvokedCallback = null

        onBackPressedCallback?.remove()
        onBackPressedCallback = null

        channel?.setMethodCallHandler(null)
        channel = null
        attachedActivity = null
    }

    private fun registerApi33(activity: FlutterActivity, channel: MethodChannel) {
        val cb = OnBackInvokedCallback {
            // SDD-028 §1: callback delegates to BackIntentService.dispatch()
            // on the Dart side and does NOT call any system fallback.
            channel.invokeMethod(
                MethodChannels.METHOD_BACK_INTENT,
                mapOf(KEY_KIND to VALUE_SYSTEM_BACK),
            )
        }
        activity.onBackInvokedDispatcher.registerOnBackInvokedCallback(
            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
            cb,
        )
        onBackInvokedCallback = cb
    }

    private fun registerPre33(activity: FlutterActivity, channel: MethodChannel) {
        val cb = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                // SDD-028 §1: forward to Dart; do NOT re-invoke the
                // system fallback here. If Dart determines the event is
                // unhandled (root route), it calls back via
                // `popToSystem` which executes `activity.finish()`.
                channel.invokeMethod(
                    MethodChannels.METHOD_BACK_INTENT,
                    mapOf(KEY_KIND to VALUE_SYSTEM_BACK),
                )
            }
        }
        // Cast resolves onBackPressedDispatcher via ComponentActivity (FlutterActivity → FragmentActivity → ComponentActivity).
        (activity as ComponentActivity).onBackPressedDispatcher.addCallback(activity, cb)
        onBackPressedCallback = cb
    }
}
