// SPDX-FileCopyrightText: 2026 Reactorcoremeltdown
// SPDX-License-Identifier: GPL-3.0-or-later

package space.rcmd.android.sizzle

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import space.rcmd.android.sizzle.input.GamepadInput
import space.rcmd.android.sizzle.input.KeyboardInput
import space.rcmd.android.sizzle.input.MidiInput
import space.rcmd.android.sizzle.playback.PlaybackService
import space.rcmd.android.sizzle.ui.AppViewModel
import space.rcmd.android.sizzle.ui.SizzleApp as SizzleAppUi
import space.rcmd.android.sizzle.ui.theme.SizzleTheme

/**
 * The single Activity. Its jobs are narrow and clear:
 *  1. build the [AppViewModel] wired to the app-wide singletons,
 *  2. host the Compose UI, themed by the ViewModel's current palette,
 *  3. forward hardware key / motion events into the keyboard & gamepad handlers,
 *  4. start MIDI listening and the background playback service.
 *
 * All four input methods converge on the same InputRouter -> ViewModel path, so
 * there is no per-input special-casing beyond the translation done here.
 */
class MainActivity : ComponentActivity() {

    private val app get() = application as SizzleApp

    // Handlers are app-wide singletons (shared with the Settings rebind UI).
    private val keyboard: KeyboardInput get() = app.keyboardInput
    private val gamepad: GamepadInput get() = app.gamepadInput
    private val midi: MidiInput get() = app.midiInput

    private val viewModel: AppViewModel by lazy {
        ViewModelProvider(
            this,
            viewModelFactory {
                initializer {
                    AppViewModel(
                        app.project, app.audioEngine, app.inputRouter,
                        app.gamepadInput, app.midiInput, app.keyboardInput,
                        app.bindingStore, app.presetLibrary, app.songLibrary,
                        app.settingsStore, app.themeLibrary, app.applicationContext,
                    )
                }
            },
        )[AppViewModel::class.java]
    }

    private val requestPermissions = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions(),
    ) { /* result ignored: features degrade gracefully if denied */ }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        askForPermissions()

        setContent {
            // viewModel.palette is Compose state, so reading it here recomposes
            // the whole theme when the user picks a new colour scheme in Settings.
            SizzleTheme(palette = viewModel.palette) {
                SizzleAppUi(viewModel)
            }
        }
    }

    override fun onStart() {
        super.onStart()
        midi.start()
    }

    override fun onResume() {
        super.onResume()
        // Start the playback service only once we're definitively in the
        // foreground — a plain startService() from a not-yet-foreground state
        // throws BackgroundServiceStartNotAllowedException on Android 12+.
        runCatching { PlaybackService.start(this) }
    }

    override fun onStop() {
        // Autosave the project whenever we leave the foreground, so a later
        // system-kill (or crash) resumes from here on next launch.
        app.projectStore.save(app.project)
        midi.stop()
        super.onStop()
    }

    // ---- Hardware input forwarding ----
    // Gamepad events are intercepted at DISPATCH time — before Compose's focus
    // system can consume D-pad / button keys — so USB *and* Bluetooth pads work
    // reliably. The gamepad handler ignores non-gamepad events, so anything else
    // (keyboard keys, text-field typing, UI) flows through the normal path below.
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        val handledByPad = when (event.action) {
            KeyEvent.ACTION_DOWN -> gamepad.onKeyDown(event.keyCode, event)
            KeyEvent.ACTION_UP -> gamepad.onKeyUp(event.keyCode, event)
            else -> false
        }
        return handledByPad || super.dispatchKeyEvent(event)
    }

    // Analog sticks / triggers / D-pad hats arrive as generic motion events;
    // intercept them the same way (the handler ignores non-joystick motion).
    override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean =
        gamepad.onMotion(event) || super.dispatchGenericMotionEvent(event)

    // Physical keyboard reaches here via the normal path (gamepad events were
    // already handled in dispatchKeyEvent and never arrive here).
    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean =
        keyboard.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)

    override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean =
        keyboard.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event)

    // ---- (helper removed; viewModel.palette is read directly in setContent) ----

    // ---- Permissions ----
    private fun askForPermissions() {
        val needed = buildList {
            add(Manifest.permission.RECORD_AUDIO)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                add(Manifest.permission.POST_NOTIFICATIONS)
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                add(Manifest.permission.BLUETOOTH_CONNECT)
            }
        }.filter {
            ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
        }
        if (needed.isNotEmpty()) requestPermissions.launch(needed.toTypedArray())
    }
}
