package com.dtourolle.jellytau

import android.app.Activity
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.drawable.Icon
import android.os.Build
import android.util.Rational
import android.view.ViewGroup
import android.webkit.WebView
import androidx.annotation.RequiresApi
import com.dtourolle.jellytau.player.JellyTauPlayer

/**
 * Drives Android picture-in-picture for native (ExoPlayer) video playback.
 *
 * TRACES: UR-041 | IR-026 | DR-053
 *
 * PiP shrinks the whole Activity into a floating window, so the only thing that
 * should remain visible is the video SurfaceView that [VideoOverlayManager]
 * attached at the bottom of the z-order. The WebView carrying the Svelte UI is
 * hidden for the duration - it is opaque and sits *above* the surface, so
 * leaving it visible would occlude the video entirely.
 *
 * Playback itself is untouched: ExoPlayer keeps rendering into the same surface
 * across the transition, so entering and leaving PiP never interrupts the video.
 */
object PictureInPictureManager {

    private const val TAG = "PictureInPictureManager"

    /** Action for the play/pause RemoteAction shown inside the PiP window. */
    private const val ACTION_MEDIA_CONTROL = "com.dtourolle.jellytau.PIP_MEDIA_CONTROL"
    private const val EXTRA_CONTROL_TYPE = "control_type"
    private const val CONTROL_PLAY = 1
    private const val CONTROL_PAUSE = 2

    /** Request codes must differ per action or the PendingIntents collapse into one. */
    private const val REQUEST_PLAY = 101
    private const val REQUEST_PAUSE = 102

    private var receiver: BroadcastReceiver? = null
    private var hiddenWebView: WebView? = null

    /**
     * Whether this device/OS can do PiP at all. Android 8.0 introduced the API,
     * and the user (or device manufacturer) can disable the feature per-app.
     */
    fun isPipSupported(activity: Activity): Boolean {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
        return activity.packageManager.hasSystemFeature(
            android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE
        )
    }

    /**
     * Whether entering PiP right now makes sense: a native video must actually
     * be playing locally. Audio-only playback and remote/cast sessions render
     * nothing on this device, so a PiP window would be an empty black box.
     */
    fun canEnterPip(activity: Activity): Boolean {
        if (!isPipSupported(activity)) return false
        return try {
            val player = JellyTauPlayer.getInstance()
            player.isPlayingVideo() &&
                player.getSurfaceView() != null &&
                VideoOverlayManager.isVideoSurfaceAttached()
        } catch (e: Exception) {
            android.util.Log.w(TAG, "canEnterPip check failed", e)
            false
        }
    }

    /**
     * Enter picture-in-picture, sizing the window to the video's aspect ratio.
     *
     * @return true if the system accepted the transition.
     */
    fun enterPip(activity: Activity): Boolean {
        if (!canEnterPip(activity)) {
            android.util.Log.d(TAG, "Not entering PiP: no local video playing")
            return false
        }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false

        return try {
            val params = buildParams(activity)
            val entered = activity.enterPictureInPictureMode(params)
            android.util.Log.d(TAG, "enterPictureInPictureMode returned $entered")
            entered
        } catch (e: Exception) {
            // IllegalStateException here means PiP is disallowed (e.g. the user
            // turned it off in system settings). Never crash over it.
            android.util.Log.e(TAG, "Failed to enter PiP", e)
            false
        }
    }

    /**
     * Build PiP params: aspect ratio from the current video, plus a play/pause
     * RemoteAction reflecting the live playback state.
     */
    @RequiresApi(Build.VERSION_CODES.O)
    private fun buildParams(activity: Activity): PictureInPictureParams {
        val builder = PictureInPictureParams.Builder()

        aspectRatioFor()?.let { builder.setAspectRatio(it) }
        builder.setActions(listOf(buildPlayPauseAction(activity)))

        return builder.build()
    }

    /**
     * The video's aspect ratio, clamped to the range Android accepts.
     *
     * The platform rejects ratios outside roughly 1:2.39 - 2.39:1 with an
     * IllegalArgumentException, which would otherwise take down the Activity on
     * unusually tall or wide content.
     */
    private fun aspectRatioFor(): Rational? {
        val player = try {
            JellyTauPlayer.getInstance()
        } catch (e: Exception) {
            return null
        }

        val surface = player.getSurfaceView() ?: return null
        // The surface has already been letterboxed to the video's aspect ratio
        // by fitSurfaceToScreen(), so its measured bounds are the video shape.
        val width = surface.width
        val height = surface.height
        if (width <= 0 || height <= 0) return null

        val ratio = width.toDouble() / height.toDouble()
        val minRatio = 1.0 / 2.39
        val maxRatio = 2.39
        val clamped = ratio.coerceIn(minRatio, maxRatio)

        // Scale to integers; Rational(width, height) directly can overflow for
        // large surfaces, and the clamped value may not match the raw pixels.
        return Rational((clamped * 1000).toInt(), 1000)
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private fun buildPlayPauseAction(activity: Activity): RemoteAction {
        val isPlaying = try {
            JellyTauPlayer.getInstance().getExoPlayer().isPlaying
        } catch (e: Exception) {
            false
        }

        val (iconRes, title, controlType, requestCode) = if (isPlaying) {
            Quad(
                android.R.drawable.ic_media_pause,
                "Pause",
                CONTROL_PAUSE,
                REQUEST_PAUSE
            )
        } else {
            Quad(
                android.R.drawable.ic_media_play,
                "Play",
                CONTROL_PLAY,
                REQUEST_PLAY
            )
        }

        val intent = Intent(ACTION_MEDIA_CONTROL)
            .putExtra(EXTRA_CONTROL_TYPE, controlType)
            // Explicit package keeps the broadcast internal to the app.
            .setPackage(activity.packageName)

        val flags = android.app.PendingIntent.FLAG_UPDATE_CURRENT or
            android.app.PendingIntent.FLAG_IMMUTABLE

        val pendingIntent = android.app.PendingIntent.getBroadcast(
            activity,
            requestCode,
            intent,
            flags
        )

        return RemoteAction(
            Icon.createWithResource(activity, iconRes),
            title,
            title,
            pendingIntent
        )
    }

    private data class Quad<A, B, C, D>(
        val first: A,
        val second: B,
        val third: C,
        val fourth: D
    )

    /**
     * Refresh the PiP window's action button so it tracks play/pause state
     * while the window is open. Safe to call when not in PiP (no-op).
     */
    fun updatePipActions(activity: Activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
        if (!activity.isInPictureInPictureMode) return
        try {
            activity.setPictureInPictureParams(buildParams(activity))
        } catch (e: Exception) {
            android.util.Log.w(TAG, "Failed to update PiP actions", e)
        }
    }

    /**
     * Called from MainActivity.onPictureInPictureModeChanged.
     *
     * Entering: hide the WebView so only the video surface shows, and register
     * the receiver backing the PiP play/pause button.
     * Leaving: restore the WebView and unregister.
     */
    fun onPipModeChanged(activity: Activity, isInPipMode: Boolean) {
        if (isInPipMode) {
            hideWebView(activity)
            registerReceiver(activity)
        } else {
            unregisterReceiver(activity)
            showWebView()
            // The surface was laid out against the tiny PiP bounds; re-fit it to
            // the restored full-screen bounds or the video stays postage-stamp sized.
            try {
                JellyTauPlayer.getInstance().fitSurfaceToScreen()
            } catch (e: Exception) {
                android.util.Log.w(TAG, "Failed to re-fit surface after PiP", e)
            }
        }
    }

    private fun hideWebView(activity: Activity) {
        val webView = findWebView(activity.window.decorView)
        if (webView == null) {
            android.util.Log.w(TAG, "No WebView found to hide for PiP")
            return
        }
        // GONE rather than INVISIBLE: the WebView is opaque, and GONE also stops
        // it from consuming layout space in the shrunken window.
        webView.visibility = android.view.View.GONE
        hiddenWebView = webView
        android.util.Log.d(TAG, "WebView hidden for PiP")
    }

    private fun showWebView() {
        hiddenWebView?.let {
            it.visibility = android.view.View.VISIBLE
            android.util.Log.d(TAG, "WebView restored after PiP")
        }
        hiddenWebView = null
    }

    private fun registerReceiver(activity: Activity) {
        if (receiver != null) return

        val r = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                if (intent?.action != ACTION_MEDIA_CONTROL) return
                val player = try {
                    JellyTauPlayer.getInstance()
                } catch (e: Exception) {
                    return
                }
                when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) {
                    CONTROL_PLAY -> player.play()
                    CONTROL_PAUSE -> player.pause()
                }
                // Swap the button to reflect the new state.
                updatePipActions(activity)
            }
        }

        val filter = IntentFilter(ACTION_MEDIA_CONTROL)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            activity.registerReceiver(r, filter, Context.RECEIVER_NOT_EXPORTED)
        } else {
            @Suppress("UnspecifiedRegisterReceiverFlag")
            activity.registerReceiver(r, filter)
        }
        receiver = r
        android.util.Log.d(TAG, "PiP media control receiver registered")
    }

    private fun unregisterReceiver(activity: Activity) {
        receiver?.let {
            try {
                activity.unregisterReceiver(it)
            } catch (e: IllegalArgumentException) {
                // Already unregistered - harmless.
            }
        }
        receiver = null
    }

    private fun findWebView(view: android.view.View): WebView? {
        if (view is WebView) return view
        if (view is ViewGroup) {
            for (i in 0 until view.childCount) {
                findWebView(view.getChildAt(i))?.let { return it }
            }
        }
        return null
    }
}
