/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.compose

import android.content.Context
import android.graphics.Point
import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/** The Compose facade, when Compose is available. */
object ComposeFacade : BaseComposeFacade {
    override fun isComposeAvailable(): Boolean = true

    override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl

    override fun setPeopleSpaceActivityContent(
        activity: ComponentActivity,
        viewModel: PeopleViewModel,
        onResult: (PeopleViewModel.Result) -> Unit,
    ) {
        activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
    }

    override fun setCommunalEditWidgetActivityContent(
        activity: ComponentActivity,
        viewModel: BaseCommunalViewModel,
        onOpenWidgetPicker: () -> Unit,
        onEditDone: () -> Unit,
    ) {
        activity.setContent {
            PlatformTheme {
                CommunalHub(
                    viewModel = viewModel,
                    onOpenWidgetPicker = onOpenWidgetPicker,
                    onEditDone = onEditDone,
                )
            }
        }
    }

    override fun createFooterActionsView(
        context: Context,
        viewModel: FooterActionsViewModel,
        qsVisibilityLifecycleOwner: LifecycleOwner,
    ): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
        }
    }

    override fun createSceneContainerView(
        scope: CoroutineScope,
        context: Context,
        viewModel: SceneContainerViewModel,
        windowInsets: StateFlow<WindowInsets?>,
        sceneByKey: Map<SceneKey, Scene>,
    ): View {
        return ComposeView(context).apply {
            setContent {
                PlatformTheme {
                    DisplayCutoutProvider(
                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
                    ) {
                        SceneContainer(
                            viewModel = viewModel,
                            sceneByKey =
                                sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
                        )
                    }
                }
            }
        }
    }

    override fun createCommunalView(
        context: Context,
        viewModel: BaseCommunalViewModel,
    ): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
        }
    }

    override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
        }
    }

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun displayCutoutFromWindowInsets(
        scope: CoroutineScope,
        context: Context,
        windowInsets: StateFlow<WindowInsets?>,
    ): StateFlow<DisplayCutout> =
        windowInsets
            .map {
                val boundingRect = it?.displayCutout?.boundingRectTop
                val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0
                val left = boundingRect?.left?.toDp(context) ?: 0.dp
                val top = boundingRect?.top?.toDp(context) ?: 0.dp
                val right = boundingRect?.right?.toDp(context) ?: 0.dp
                val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp
                val location =
                    when {
                        width <= 0f -> CutoutLocation.NONE
                        left <= 0.dp -> CutoutLocation.LEFT
                        right >= getDisplayWidth(context) -> CutoutLocation.RIGHT
                        else -> CutoutLocation.CENTER
                    }
                DisplayCutout(
                    left,
                    top,
                    right,
                    bottom,
                    location,
                )
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout())

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun getDisplayWidth(context: Context): Dp {
        val point = Point()
        checkNotNull(context.display).getRealSize(point)
        return point.x.dp
    }

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun Int.toDp(context: Context): Dp {
        return (this.toFloat() / context.resources.displayMetrics.density).dp
    }
}
