/*
 * Copyright (C) 2024 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.quickstep.orientation

import android.annotation.SuppressLint
import android.content.res.Resources
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.ShapeDrawable
import android.util.FloatProperty
import android.util.Pair
import android.view.Gravity
import android.view.MotionEvent
import android.view.Surface
import android.view.VelocityTracker
import android.view.View
import android.view.View.MeasureSpec
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
import androidx.core.util.component1
import androidx.core.util.component2
import androidx.core.view.updateLayoutParams
import com.android.launcher3.DeviceProfile
import com.android.launcher3.Flags
import com.android.launcher3.LauncherAnimUtils
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer
import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds
import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
import com.android.launcher3.touch.SingleAxisSwipeDetector
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.views.IconAppChipView
import kotlin.math.max

open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
    override fun <T> getPrimaryValue(x: T, y: T): T = y

    override fun <T> getSecondaryValue(x: T, y: T): T = x

    override fun getPrimaryValue(x: Int, y: Int): Int = y

    override fun getSecondaryValue(x: Int, y: Int): Int = x

    override fun getPrimaryValue(x: Float, y: Float): Float = y

    override fun getSecondaryValue(x: Float, y: Float): Float = x

    override val isLayoutNaturalToLauncher: Boolean = false

    override fun adjustFloatingIconStartVelocity(velocity: PointF) {
        val oldX = velocity.x
        val oldY = velocity.y
        velocity.set(-oldY, oldX)
    }

    override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
        // We don't need to check the "top" value here because the startRect is in the orientation
        // of the app, not of the fixed portrait launcher.
        if (outStartRect.left > deviceProfile.heightPx) {
            outStartRect.offsetTo(0f, outStartRect.top)
        } else if (outStartRect.left < -deviceProfile.heightPx) {
            outStartRect.offsetTo(0f, outStartRect.top)
        }
    }

    override fun <T> setPrimary(target: T, action: Int2DAction<T>, param: Int) =
        action.call(target, 0, param)

    override fun <T> setPrimary(target: T, action: Float2DAction<T>, param: Float) =
        action.call(target, 0f, param)

    override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
        action.call(target, param, 0f)

    override fun <T> set(
        target: T,
        action: Int2DAction<T>,
        primaryParam: Int,
        secondaryParam: Int,
    ) = action.call(target, secondaryParam, primaryParam)

    override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
        event.getY(pointerIndex)

    override fun getPrimaryVelocity(velocityTracker: VelocityTracker, pointerId: Int): Float =
        velocityTracker.getYVelocity(pointerId)

    override fun getMeasuredSize(view: View): Int = view.measuredHeight

    override fun getPrimarySize(view: View): Int = view.height

    override fun getPrimarySize(rect: RectF): Float = rect.height()

    override fun getStart(rect: RectF): Float = rect.top

    override fun getEnd(rect: RectF): Float = rect.bottom

    override fun rotateInsets(insets: Rect, outInsets: Rect) {
        outInsets.set(insets.bottom, insets.left, insets.top, insets.right)
    }

    override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
        if (isRtl) view.paddingBottom / 2 else -view.paddingTop / 2

    override fun getSecondaryDimension(view: View): Int = view.width

    override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y

    override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X

    override fun getPrimaryScroll(view: View): Int = view.scrollY

    override fun getPrimaryScale(view: View): Float = view.scaleY

    override fun setMaxScroll(event: AccessibilityEvent, maxScroll: Int) {
        event.maxScrollY = maxScroll
    }

    override fun getRecentsRtlSetting(resources: Resources): Boolean = !Utilities.isRtl(resources)

    override val degreesRotated: Float = 90f

    override val rotation: Int = Surface.ROTATION_90

    override fun setPrimaryScale(view: View, scale: Float) {
        view.scaleY = scale
    }

    override fun setSecondaryScale(view: View, scale: Float) {
        view.scaleX = scale
    }

    override fun getChildStart(view: View): Int = view.top

    override fun getCenterForPage(view: View, insets: Rect): Int =
        (view.paddingLeft + view.measuredWidth + insets.left - insets.right - view.paddingRight) / 2

    override fun getScrollOffsetStart(view: View, insets: Rect): Int = insets.top + view.paddingTop

    override fun getScrollOffsetEnd(view: View, insets: Rect): Int =
        view.height - view.paddingBottom - insets.bottom

    override val secondaryTranslationDirectionFactor: Int = 1

    override fun getSplitTranslationDirectionFactor(
        stagePosition: Int,
        deviceProfile: DeviceProfile,
    ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1

    override fun getTaskMenuX(
        x: Float,
        thumbnailView: View,
        deviceProfile: DeviceProfile,
        taskInsetMargin: Float,
        taskViewIcon: View,
    ): Float = thumbnailView.measuredWidth + x - taskInsetMargin

    override fun getTaskMenuY(
        y: Float,
        thumbnailView: View,
        stagePosition: Int,
        taskMenuView: View,
        taskInsetMargin: Float,
        taskViewIcon: View,
    ): Float {
        val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
        var taskMenuY = y + taskInsetMargin

        if (stagePosition == STAGE_POSITION_UNDEFINED) {
            taskMenuY += (thumbnailView.measuredHeight - layoutParams.width) / 2f
        }

        return taskMenuY
    }

    override fun getTaskMenuWidth(
        thumbnailView: View,
        deviceProfile: DeviceProfile,
        @StagePosition stagePosition: Int,
    ): Int =
        when {
            Flags.enableOverviewIconMenu() ->
                thumbnailView.resources.getDimensionPixelSize(
                    R.dimen.task_thumbnail_icon_menu_expanded_width
                )
            stagePosition == STAGE_POSITION_UNDEFINED -> thumbnailView.measuredWidth
            else -> thumbnailView.measuredHeight
        }

    override fun getTaskMenuHeight(
        taskInsetMargin: Float,
        deviceProfile: DeviceProfile,
        taskMenuX: Float,
        taskMenuY: Float,
    ): Int = (taskMenuX - taskInsetMargin).toInt()

    override fun setTaskOptionsMenuLayoutOrientation(
        deviceProfile: DeviceProfile,
        taskMenuLayout: LinearLayout,
        dividerSpacing: Int,
        dividerDrawable: ShapeDrawable,
    ) {
        taskMenuLayout.orientation = LinearLayout.VERTICAL
        dividerDrawable.intrinsicHeight = dividerSpacing
        taskMenuLayout.dividerDrawable = dividerDrawable
    }

    override fun setLayoutParamsForTaskMenuOptionItem(
        lp: LinearLayout.LayoutParams,
        viewGroup: LinearLayout,
        deviceProfile: DeviceProfile,
    ) {
        // Phone fake landscape
        viewGroup.orientation = LinearLayout.HORIZONTAL
        lp.width = ViewGroup.LayoutParams.MATCH_PARENT
        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
    }

    override fun updateDwbBannerLayout(
        taskViewWidth: Int,
        taskViewHeight: Int,
        isGroupedTaskView: Boolean,
        deviceProfile: DeviceProfile,
        snapshotViewWidth: Int,
        snapshotViewHeight: Int,
        banner: View,
    ) {
        banner.pivotX = 0f
        banner.pivotY = 0f
        banner.rotation = degreesRotated
        banner.updateLayoutParams<FrameLayout.LayoutParams> {
            gravity = Gravity.TOP or if (banner.isLayoutRtl) Gravity.END else Gravity.START
            width =
                if (isGroupedTaskView) {
                    snapshotViewHeight
                } else {
                    taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
                }
        }
    }

    override fun getDwbBannerTranslations(
        taskViewWidth: Int,
        taskViewHeight: Int,
        splitBounds: SplitBounds?,
        deviceProfile: DeviceProfile,
        thumbnailViews: Array<View>,
        desiredTaskId: Int,
        banner: View,
    ): Pair<Float, Float> {
        val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
        val translationX = banner.height.toFloat()
        val translationY: Float
        if (splitBounds == null) {
            translationY = snapshotParams.topMargin.toFloat()
        } else {
            if (desiredTaskId == splitBounds.leftTopTaskId) {
                translationY = snapshotParams.topMargin.toFloat()
            } else {
                val topLeftTaskPlusDividerPercent =
                    splitBounds.leftTopTaskPercent + splitBounds.dividerPercent
                translationY =
                    snapshotParams.topMargin +
                        (taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent
            }
        }
        return Pair(translationX, translationY)
    }

    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
    override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
        SingleAxisSwipeDetector.HORIZONTAL

    override fun getUpDirection(isRtl: Boolean): Int =
        if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
        else SingleAxisSwipeDetector.DIRECTION_POSITIVE

    override fun getDownDirection(isRtl: Boolean): Int =
        if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
        else SingleAxisSwipeDetector.DIRECTION_NEGATIVE

    override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
        if (isRtl) displacement < 0 else displacement > 0

    override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1

    override fun getTaskDismissVerticalDirection(): Int = 1

    override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
        secondaryDimension - taskThumbnailBounds.left

    override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
        taskThumbnailBounds.left

    /* -------------------- */

    override fun getChildBounds(
        child: View,
        childStart: Int,
        pageCenter: Int,
        layoutChild: Boolean,
    ): ChildBounds {
        val childHeight = child.measuredHeight
        val childWidth = child.measuredWidth
        val childBottom = childStart + childHeight
        val childLeft = pageCenter - childWidth / 2
        if (layoutChild) {
            child.layout(childLeft, childStart, childLeft + childWidth, childBottom)
        }
        return ChildBounds(childHeight, childWidth, childBottom, childLeft)
    }

    override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int = rect.left

    override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
        // Add "left" side of phone which is actually the top
        listOf(
            SplitPositionOption(
                R.drawable.ic_split_horizontal,
                R.string.recent_task_option_split_screen,
                STAGE_POSITION_TOP_OR_LEFT,
                STAGE_TYPE_MAIN,
            )
        )

    override fun getInitialSplitPlaceholderBounds(
        placeholderHeight: Int,
        placeholderInset: Int,
        dp: DeviceProfile,
        @StagePosition stagePosition: Int,
        out: Rect,
    ) {
        // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
        // which is the same bounds as 0 rotation.
        val width = dp.widthPx
        val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp)
        out.set(0, 0, width, placeholderHeight + insetSizeAdjustment)
        out.inset(placeholderInset, 0)

        // Adjust the top to account for content off screen. This will help to animate the view in
        // with rounded corners.
        val screenWidth = dp.widthPx
        val screenHeight = dp.heightPx
        val totalHeight =
            (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth).toInt()
        out.top -= totalHeight - placeholderHeight
    }

    override fun updateSplitIconParams(
        out: View,
        onScreenRectCenterX: Float,
        onScreenRectCenterY: Float,
        fullscreenScaleX: Float,
        fullscreenScaleY: Float,
        drawableWidth: Int,
        drawableHeight: Int,
        dp: DeviceProfile,
        @StagePosition stagePosition: Int,
    ) {
        val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
        out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
        out.y =
            ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY - 1.0f * drawableHeight / 2)
    }

    /**
     * The split placeholder comes with a default inset to buffer the icon from the top of the
     * screen. But if the device already has a large inset (from cutouts etc), use that instead.
     */
    private fun getPlaceholderSizeAdjustment(dp: DeviceProfile?): Int =
        max((dp!!.insets.top - dp.splitPlaceholderInset).toDouble(), 0.0).toInt()

    override fun setSplitInstructionsParams(
        out: View,
        dp: DeviceProfile,
        splitInstructionsHeight: Int,
        splitInstructionsWidth: Int,
    ) {
        out.pivotX = 0f
        out.pivotY = splitInstructionsHeight.toFloat()
        out.rotation = degreesRotated
        val distanceToEdge =
            out.resources.getDimensionPixelSize(
                R.dimen.split_instructions_bottom_margin_phone_landscape
            )
        // Adjust for any insets on the left edge
        val insetCorrectionX = dp.insets.left
        // Center the view in case of unbalanced insets on top or bottom of screen
        val insetCorrectionY = (dp.insets.bottom - dp.insets.top) / 2
        out.translationX = (distanceToEdge - insetCorrectionX).toFloat()
        out.translationY =
            (-splitInstructionsHeight - splitInstructionsWidth) / 2f + insetCorrectionY
        // Setting gravity to LEFT instead of the lint-recommended START because we always want this
        // view to be screen-left when phone is in landscape, regardless of the RtL setting.
        val lp = out.layoutParams as FrameLayout.LayoutParams
        lp.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
        out.layoutParams = lp
    }

    override fun getFinalSplitPlaceholderBounds(
        splitDividerSize: Int,
        dp: DeviceProfile,
        @StagePosition stagePosition: Int,
        out1: Rect,
        out2: Rect,
    ) {
        // In fake land/seascape, the window bounds are always top and bottom half
        val screenHeight = dp.heightPx
        val screenWidth = dp.widthPx
        out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
        out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
    }

    override fun setSplitTaskSwipeRect(
        dp: DeviceProfile,
        outRect: Rect,
        splitInfo: SplitBounds,
        desiredStagePosition: Int,
    ) {
        val topLeftTaskPercent = splitInfo.leftTopTaskPercent
        val dividerBarPercent = splitInfo.dividerPercent

        if (desiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
            outRect.bottom = outRect.top + (outRect.height() * topLeftTaskPercent).toInt()
        } else {
            outRect.top += (outRect.height() * (topLeftTaskPercent + dividerBarPercent)).toInt()
        }
    }

    /**
     * @param inSplitSelection Whether user currently has a task from this task group staged for
     *   split screen. Currently this state is not reachable in fake landscape.
     */
    override fun measureGroupedTaskViewThumbnailBounds(
        primarySnapshot: View,
        secondarySnapshot: View,
        parentWidth: Int,
        parentHeight: Int,
        splitBoundsConfig: SplitBounds,
        dp: DeviceProfile,
        isRtl: Boolean,
        inSplitSelection: Boolean,
    ) {
        val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
        val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams

        // Swap the margins that are set in TaskView#setRecentsOrientedState()
        secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx
        primaryParams.topMargin = 0

        // Measure and layout the thumbnails bottom up, since the primary is on the visual left
        // (portrait bottom) and secondary is on the right (portrait top)
        val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
        val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
        val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)

        val (taskViewFirst, taskViewSecond) =
            getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)

        primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
        primarySnapshot.measure(
            MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
        )
        val translationY = taskViewFirst.y + spaceAboveSnapshot + dividerBar
        secondarySnapshot.translationY = (translationY - spaceAboveSnapshot).toFloat()
        secondarySnapshot.measure(
            MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
        )
    }

    override fun getGroupedTaskViewSizes(
        dp: DeviceProfile,
        splitBoundsConfig: SplitBounds,
        parentWidth: Int,
        parentHeight: Int,
    ): Pair<Point, Point> {
        val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
        val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
        val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)

        val taskPercent = splitBoundsConfig.leftTopTaskPercent
        val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
        val secondTaskViewSize =
            Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
        return Pair(firstTaskViewSize, secondTaskViewSize)
    }

    override fun setTaskIconParams(
        iconParams: FrameLayout.LayoutParams,
        taskIconMargin: Int,
        taskIconHeight: Int,
        thumbnailTopMargin: Int,
        isRtl: Boolean,
    ) {
        iconParams.gravity =
            if (isRtl) {
                Gravity.START or Gravity.CENTER_VERTICAL
            } else {
                Gravity.END or Gravity.CENTER_VERTICAL
            }
        iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2
        iconParams.leftMargin = 0
        iconParams.topMargin = thumbnailTopMargin / 2
        iconParams.bottomMargin = 0
    }

    override fun setIconAppChipChildrenParams(
        iconParams: FrameLayout.LayoutParams,
        chipChildMarginStart: Int,
    ) {
        iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
        iconParams.marginStart = chipChildMarginStart
        iconParams.topMargin = 0
    }

    override fun setIconAppChipMenuParams(
        iconAppChipView: IconAppChipView,
        iconMenuParams: FrameLayout.LayoutParams,
        iconMenuMargin: Int,
        thumbnailTopMargin: Int,
    ) {
        val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL

        if (isRtl) {
            iconMenuParams.gravity = Gravity.START or Gravity.BOTTOM
            iconMenuParams.marginStart = iconMenuMargin
            iconMenuParams.bottomMargin = iconMenuMargin
            iconAppChipView.pivotX = iconMenuParams.width - iconMenuParams.height / 2f
            iconAppChipView.pivotY = iconMenuParams.height / 2f
        } else {
            iconMenuParams.gravity = Gravity.END or Gravity.TOP
            iconMenuParams.marginStart = 0
            iconMenuParams.bottomMargin = 0
            iconAppChipView.pivotX = iconMenuParams.width / 2f
            iconAppChipView.pivotY = iconMenuParams.width / 2f
        }

        iconMenuParams.topMargin = iconMenuMargin
        iconMenuParams.marginEnd = iconMenuMargin
        iconAppChipView.setSplitTranslationY(0f)
        iconAppChipView.setRotation(degreesRotated)
    }

    /**
     * @param inSplitSelection Whether user currently has a task from this task group staged for
     *   split screen. Currently this state is not reachable in fake landscape.
     */
    override fun setSplitIconParams(
        primaryIconView: View,
        secondaryIconView: View,
        taskIconHeight: Int,
        primarySnapshotWidth: Int,
        primarySnapshotHeight: Int,
        groupedTaskViewHeight: Int,
        groupedTaskViewWidth: Int,
        isRtl: Boolean,
        deviceProfile: DeviceProfile,
        splitConfig: SplitBounds,
        inSplitSelection: Boolean,
        oneIconHiddenDueToSmallWidth: Boolean,
    ) {
        val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx
        val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot
        val dividerBar: Int = getDividerBarSize(totalThumbnailHeight, splitConfig)

        val (topLeftY, bottomRightY) =
            getSplitIconsPosition(
                taskIconHeight,
                primarySnapshotHeight,
                totalThumbnailHeight,
                isRtl,
                deviceProfile.overviewTaskMarginPx,
                dividerBar,
                oneIconHiddenDueToSmallWidth,
            )

        updateSplitIconsPosition(primaryIconView, topLeftY, isRtl)
        updateSplitIconsPosition(secondaryIconView, bottomRightY, isRtl)
    }

    override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
        throw IllegalStateException("Default position not available in fake landscape")
    }

    override fun <T> getSplitSelectTaskOffset(
        primary: FloatProperty<T>,
        secondary: FloatProperty<T>,
        deviceProfile: DeviceProfile,
    ): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)

    override fun getFloatingTaskOffscreenTranslationTarget(
        floatingTask: View,
        onScreenRect: RectF,
        @StagePosition stagePosition: Int,
        dp: DeviceProfile,
    ): Float = floatingTask.translationY - onScreenRect.height()

    override fun setFloatingTaskPrimaryTranslation(
        floatingTask: View,
        translation: Float,
        dp: DeviceProfile,
    ) {
        floatingTask.translationY = translation
    }

    override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
        floatingTask.translationY

    override fun getHandlerTypeForLogging(): TaskSwitcherContainer.OrientationHandler =
        TaskSwitcherContainer.OrientationHandler.LANDSCAPE

    /**
     * Retrieves split icons position
     *
     * @param taskIconHeight The height of the task icon.
     * @param primarySnapshotHeight The height for the primary snapshot (i.e., top-left snapshot).
     * @param totalThumbnailHeight The total height for the group task view.
     * @param isRtl Whether the layout direction is RTL (or false for LTR).
     * @param overviewTaskMarginPx The space under the focused task icon provided by Device Profile.
     * @param dividerSize The size of the divider for the group task view.
     * @return The top-left and right-bottom positions for the icon views.
     */
    @VisibleForTesting
    open fun getSplitIconsPosition(
        taskIconHeight: Int,
        primarySnapshotHeight: Int,
        totalThumbnailHeight: Int,
        isRtl: Boolean,
        overviewTaskMarginPx: Int,
        dividerSize: Int,
        oneIconHiddenDueToSmallWidth: Boolean,
    ): SplitIconPositions {
        return if (Flags.enableOverviewIconMenu()) {
            if (isRtl) {
                SplitIconPositions(-(totalThumbnailHeight - primarySnapshotHeight), 0)
            } else {
                SplitIconPositions(0, primarySnapshotHeight + dividerSize)
            }
        } else {
            if (oneIconHiddenDueToSmallWidth) {
                // Center both icons
                val centerY =
                    primarySnapshotHeight +
                        overviewTaskMarginPx +
                        ((taskIconHeight + dividerSize) / 2)
                SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
            } else {
                val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
                SplitIconPositions(
                    topLeftY = topLeftY,
                    bottomRightY = topLeftY + dividerSize + taskIconHeight,
                )
            }
        }
    }

    /**
     * Updates icon view gravity and translation for split tasks
     *
     * @param iconView View to be updated
     * @param translationY the translationY that should be applied
     * @param isRtl Whether the layout direction is RTL (or false for LTR).
     */
    @SuppressLint("RtlHardcoded")
    @VisibleForTesting
    open fun updateSplitIconsPosition(iconView: View, translationY: Int, isRtl: Boolean) {
        val layoutParams = iconView.layoutParams as FrameLayout.LayoutParams

        if (Flags.enableOverviewIconMenu()) {
            val appChipView = iconView as IconAppChipView
            layoutParams.gravity =
                if (isRtl) Gravity.BOTTOM or Gravity.START else Gravity.TOP or Gravity.END
            appChipView.layoutParams = layoutParams
            appChipView.setSplitTranslationX(0f)
            appChipView.setSplitTranslationY(translationY.toFloat())
        } else {
            layoutParams.gravity = Gravity.TOP or Gravity.RIGHT
            layoutParams.topMargin = translationY
            iconView.translationX = 0f
            iconView.translationY = 0f
            iconView.layoutParams = layoutParams
        }
    }

    /**
     * It calculates the divider's size in the group task view.
     *
     * @param totalThumbnailHeight The total height for the group task view
     * @param splitConfig Contains information about sizes and proportions for split task.
     * @return The divider size for the group task view.
     */
    protected fun getDividerBarSize(totalThumbnailHeight: Int, splitConfig: SplitBounds): Int {
        return Math.round(totalThumbnailHeight * splitConfig.dividerPercent)
    }

    /**
     * Data structure to keep the y position to be used for the split task icon views translation.
     *
     * @param topLeftY The y-axis position for the task view position on the Top or Left side.
     * @param bottomRightY The y-axis position for the task view position on the Bottom or Right
     *   side.
     */
    data class SplitIconPositions(val topLeftY: Int, val bottomRightY: Int)
}
