/*
 * Episteme Reader - A native Android document reader.
 * Copyright (C) 2026 Episteme
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * mail: epistemereader@gmail.com
 */
package com.aryan.reader.pdf

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.ui.zIndex
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.aryan.reader.R
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.changedToUp
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.aryan.reader.pdf.data.PdfTextBox
import timber.log.Timber
import kotlin.math.roundToInt

private enum class ResizeHandle {
    TOP_LEFT, TOP_CENTER, TOP_RIGHT,
    RIGHT_CENTER,
    BOTTOM_RIGHT, BOTTOM_CENTER, BOTTOM_LEFT,
    LEFT_CENTER,
    NONE
}

enum class HandlePosition {
    TOP, BOTTOM, AUTO
}

// Eagerly consumes pointer events so parent scaled pan/zoom gestures don't intercept it
suspend fun PointerInputScope.detectEagerDragGestures(
    onDragStart: (Offset) -> Unit,
    onDragEnd: () -> Unit,
    onDragCancel: () -> Unit,
    onDrag: (PointerInputChange, Offset) -> Unit
) {
    awaitEachGesture {
        var dragStarted = false
        try {
            val down = awaitFirstDown(requireUnconsumed = false)
            down.consume() // Consume immediately
            onDragStart(down.position)
            dragStarted = true
            val pointerId = down.id
            var canceled = false
            while (true) {
                val event = awaitPointerEvent()
                val change = event.changes.firstOrNull { it.id == pointerId }
                if (change == null) {
                    canceled = true
                    break
                }
                if (change.changedToUp()) {
                    change.consume()
                    break
                }
                if (change.positionChanged()) {
                    val dragAmount = change.position - change.previousPosition
                    change.consume()
                    onDrag(change, dragAmount)
                }
            }
            if (canceled) onDragCancel() else onDragEnd()
            dragStarted = false
        } finally {
            if (dragStarted) {
                onDragCancel()
            }
        }
    }
}

@Composable
fun ResizableTextBox(
    box: PdfTextBox,
    isSelected: Boolean,
    isEditMode: Boolean,
    isDarkMode: Boolean,
    pageWidthPx: Float,
    pageHeightPx: Float,
    scale: Float = 1f,
    onBoundsChanged: (Rect) -> Unit,
    onTextChanged: (String) -> Unit,
    onSelect: () -> Unit,
    onDragStart: (Offset) -> Unit,
    onDrag: (Offset, Rect) -> Unit,
    onDragEnd: () -> Unit,
    modifier: Modifier = Modifier,
    onDragCancel: () -> Unit = {},
    handlePosition: HandlePosition = HandlePosition.AUTO
) {
    if (pageWidthPx <= 0 || pageHeightPx <= 0) return

    val density = LocalDensity.current
    val focusRequester = remember { FocusRequester() }

    // Counter-scale fixed sizes so they render proportionally regardless of the zoom level
    val handleSize = (10f / scale).dp
    val handleTouchSize = (40f / scale).dp
    val handleSizePx = with(density) { handleSize.toPx() }
    val halfHandlePx = handleSizePx / 2f
    val handleTouchSizePx = with(density) { handleTouchSize.toPx() }

    val borderColor = if (isDarkMode) Color.White else Color.Black
    val handleColor = if (isDarkMode) Color.White else Color.Black

    var isDraggingOrResizing by remember { mutableStateOf(false) }

    val fontFamily = remember(box.fontPath, box.fontName) {
        Timber.tag("PdfFontDebug").d("Rendering Box ${box.id}: FontPath=${box.fontPath}, FontName=${box.fontName}")
        if (box.fontPath != null) {
            PdfFontCache.getFontFamily(box.fontPath)
        } else {
            when (box.fontName) {
                "Serif" -> FontFamily.Serif
                "Sans" -> FontFamily.SansSerif
                "Monospace" -> FontFamily.Monospace
                "Cursive" -> FontFamily.Cursive
                else -> FontFamily.Default
            }
        }
    }
    androidx.compose.runtime.SideEffect {
        Timber.tag("PdfTextBoxDebug").v("ResizableTextBox Recompose [ID: ${box.id}] | isSelected=$isSelected | scale=$scale | pagePx=${pageWidthPx}x${pageHeightPx} | bounds=${box.relativeBounds}")
    }
    var currentRectPx by remember {
        mutableStateOf(
            Rect(
                left = box.relativeBounds.left * pageWidthPx,
                top = box.relativeBounds.top * pageHeightPx,
                right = box.relativeBounds.right * pageWidthPx,
                bottom = box.relativeBounds.bottom * pageHeightPx
            )
        )
    }

    LaunchedEffect(isSelected) {
        if (isSelected && isEditMode) {
            focusRequester.requestFocus()
        }
    }

    LaunchedEffect(box.relativeBounds, pageWidthPx, pageHeightPx) {
        if (!isDraggingOrResizing) {
            val newPx = Rect(
                left = box.relativeBounds.left * pageWidthPx,
                top = box.relativeBounds.top * pageHeightPx,
                right = box.relativeBounds.right * pageWidthPx,
                bottom = box.relativeBounds.bottom * pageHeightPx
            )
            Timber.tag("PdfTextBoxDebug").d("LaunchedEffect bounds recalculation [ID: ${box.id}] | currentRectPx=$newPx")

            if (kotlin.math.abs(newPx.left - currentRectPx.left) > 1f ||
                kotlin.math.abs(newPx.top - currentRectPx.top) > 1f ||
                kotlin.math.abs(newPx.width - currentRectPx.width) > 1f ||
                kotlin.math.abs(newPx.height - currentRectPx.height) > 1f
            ) {
                currentRectPx = newPx
            }
        }
    }

    val requiredBottomSpacePx = with(density) { 60.dp.toPx() } / scale

    // Freeze handle position while dragging to prevent UI jumping
    var isHandleAtTop by remember { mutableStateOf(false) }

    LaunchedEffect(currentRectPx, pageHeightPx, handlePosition, requiredBottomSpacePx, isDraggingOrResizing) {
        if (!isDraggingOrResizing) {
            isHandleAtTop = when (handlePosition) {
                HandlePosition.TOP -> true
                HandlePosition.BOTTOM -> false
                HandlePosition.AUTO -> {
                    if (pageHeightPx <= 0f) false
                    else (pageHeightPx - currentRectPx.bottom) < requiredBottomSpacePx
                }
            }
        }
    }

    Box(
        modifier = modifier
            .zIndex(if (isSelected) 10f else 0f)
            .graphicsLayer {
                translationX = currentRectPx.left - halfHandlePx
                translationY = currentRectPx.top - halfHandlePx
            }
            .size(
                width = with(density) { (currentRectPx.width + handleSizePx).toDp() },
                height = with(density) { (currentRectPx.height + handleSizePx).toDp() }
            )
    ) {
        // --- 1. Content Body ---
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(handleSize / 2)
                .pointerInput(Unit) {
                    detectTapGestures {
                        Timber.tag("PdfTextBoxDebug").d("TextBox Tapped[ID: ${box.id}]")
                        onSelect()
                    }
                }
                .then(
                    if (isSelected) Modifier.border((1.5f / scale).dp, borderColor) else Modifier
                )
        ) {
            BasicTextField(
                value = box.text,
                onValueChange = onTextChanged,
                modifier = Modifier
                    .fillMaxSize()
                    .padding(8.dp)
                    .verticalScroll(rememberScrollState())
                    .focusRequester(focusRequester),
                textStyle = TextStyle(
                    color = box.color,
                    background = box.backgroundColor,
                    fontFamily = fontFamily,
                    fontSize = with(LocalDensity.current) {
                        (box.fontSize * pageHeightPx).toSp()
                    },
                    fontWeight = if (box.isBold) FontWeight.Bold else FontWeight.Normal,
                    fontStyle = if (box.isItalic) FontStyle.Italic else FontStyle.Normal,
                    textDecoration = run {
                        val decs = mutableListOf<TextDecoration>()
                        if (box.isUnderline) decs.add(TextDecoration.Underline)
                        if (box.isStrikeThrough) decs.add(TextDecoration.LineThrough)
                        if (decs.isEmpty()) TextDecoration.None else TextDecoration.combine(decs)
                    }
                ),
                cursorBrush = SolidColor(if (isDarkMode) Color.White else MaterialTheme.colorScheme.primary),
                enabled = isEditMode && isSelected,
                readOnly = !isEditMode
            )
        }

        if (isSelected) {
            val handles = ResizeHandle.entries.filter { it != ResizeHandle.NONE }

            fun getHandleCenter(handle: ResizeHandle, w: Float, h: Float): Offset {
                return when (handle) {
                    ResizeHandle.TOP_LEFT -> Offset(halfHandlePx, halfHandlePx)
                    ResizeHandle.TOP_CENTER -> Offset(halfHandlePx + w / 2, halfHandlePx)
                    ResizeHandle.TOP_RIGHT -> Offset(halfHandlePx + w, halfHandlePx)
                    ResizeHandle.RIGHT_CENTER -> Offset(halfHandlePx + w, halfHandlePx + h / 2)
                    ResizeHandle.BOTTOM_RIGHT -> Offset(halfHandlePx + w, halfHandlePx + h)
                    ResizeHandle.BOTTOM_CENTER -> Offset(halfHandlePx + w / 2, halfHandlePx + h)
                    ResizeHandle.BOTTOM_LEFT -> Offset(halfHandlePx, halfHandlePx + h)
                    ResizeHandle.LEFT_CENTER -> Offset(halfHandlePx, halfHandlePx + h / 2)
                    else -> Offset.Zero
                }
            }

            handles.forEach { handle ->
                val center = getHandleCenter(handle, currentRectPx.width, currentRectPx.height)

                Box(
                    modifier = Modifier
                        .offset {
                            IntOffset(
                                (center.x - handleTouchSizePx / 2).roundToInt(),
                                (center.y - handleTouchSizePx / 2).roundToInt()
                            )
                        }
                        .size(handleTouchSize)
                        .pointerInput(onBoundsChanged) {
                            detectEagerDragGestures(
                                onDragStart = {
                                    Timber.tag("PdfTextBoxDebug").d("ResizeHandle DragStart[ID: ${box.id}] Handle=$handle")
                                    isDraggingOrResizing = true
                                },
                                onDragEnd = {
                                    isDraggingOrResizing = false
                                    val normalized = Rect(
                                        left = currentRectPx.left / pageWidthPx,
                                        top = currentRectPx.top / pageHeightPx,
                                        right = currentRectPx.right / pageWidthPx,
                                        bottom = currentRectPx.bottom / pageHeightPx
                                    )
                                    Timber.tag("PdfTextBoxDebug").d("ResizeHandle DragEnd [ID: ${box.id}] finalNormalized=$normalized")
                                    onBoundsChanged(normalized)
                                },
                                onDragCancel = { isDraggingOrResizing = false }
                            ) { change, dragAmount ->
                                Timber.tag("PdfTextBoxDebug").v("ResizeHandle Drag [ID: ${box.id}] Handle=$handle | dragAmount=$dragAmount")

                                var l = currentRectPx.left
                                var t = currentRectPx.top
                                var r = currentRectPx.right
                                var b = currentRectPx.bottom
                                val dx = dragAmount.x
                                val dy = dragAmount.y
                                val minSize = 50f / scale

                                when (handle) {
                                    ResizeHandle.TOP_LEFT -> {
                                        l = (l + dx).coerceIn(0f, maxOf(0f, r - minSize))
                                        t = (t + dy).coerceIn(0f, maxOf(0f, b - minSize))
                                    }
                                    ResizeHandle.TOP_CENTER -> t = (t + dy).coerceIn(0f, maxOf(0f, b - minSize))
                                    ResizeHandle.TOP_RIGHT -> {
                                        r = (r + dx).coerceIn(l + minSize, maxOf(l + minSize, pageWidthPx))
                                        t = (t + dy).coerceIn(0f, maxOf(0f, b - minSize))
                                    }
                                    ResizeHandle.RIGHT_CENTER -> r = (r + dx).coerceIn(l + minSize, maxOf(l + minSize, pageWidthPx))
                                    ResizeHandle.BOTTOM_RIGHT -> {
                                        r = (r + dx).coerceIn(l + minSize, maxOf(l + minSize, pageWidthPx))
                                        b = (b + dy).coerceIn(t + minSize, maxOf(t + minSize, pageHeightPx))
                                    }
                                    ResizeHandle.BOTTOM_CENTER -> b = (b + dy).coerceIn(t + minSize, maxOf(t + minSize, pageHeightPx))
                                    ResizeHandle.BOTTOM_LEFT -> {
                                        l = (l + dx).coerceIn(0f, maxOf(0f, r - minSize))
                                        b = (b + dy).coerceIn(t + minSize, maxOf(t + minSize, pageHeightPx))
                                    }
                                    ResizeHandle.LEFT_CENTER -> l = (l + dx).coerceIn(0f, maxOf(0f, r - minSize))
                                    else -> {}
                                }
                                currentRectPx = Rect(l, t, r, b)
                            }
                        }
                ) {
                    Box(
                        modifier = Modifier
                            .size(handleSize)
                            .background(handleColor, CircleShape)
                            .align(Alignment.Center)
                    )
                }
            }

            DragPill(
                isDarkMode = isDarkMode,
                scale = scale,
                modifier = Modifier
                    .align(if (isHandleAtTop) Alignment.TopCenter else Alignment.BottomCenter)
                    .offset(y = if (isHandleAtTop) (-32f / scale).dp else (32f / scale).dp)
                    .zIndex(20f)
                    .pointerInput(pageWidthPx, pageHeightPx, onDragStart, onDragEnd, onDragCancel) {
                        detectEagerDragGestures(
                            onDragStart = { offset ->
                                Timber.tag("PdfTextBoxDebug").d("DragPill DragStart [ID: ${box.id}] at offset=$offset")
                                isDraggingOrResizing = true
                                onDragStart(offset)
                            },
                            onDragEnd = {
                                isDraggingOrResizing = false
                                val normalized = Rect(
                                    left = currentRectPx.left / pageWidthPx,
                                    top = currentRectPx.top / pageHeightPx,
                                    right = currentRectPx.right / pageWidthPx,
                                    bottom = currentRectPx.bottom / pageHeightPx
                                )
                                Timber.tag("PdfTextBoxDebug").d("DragPill DragEnd[ID: ${box.id}] finalNormalized=$normalized")
                                onBoundsChanged(normalized)
                                onDragEnd()
                            },
                            onDragCancel = {
                                isDraggingOrResizing = false
                                onDragCancel()
                            }
                        ) { change, dragAmount ->
                            val w = currentRectPx.width
                            val h = currentRectPx.height
                            val rawLeft = currentRectPx.left + dragAmount.x
                            val rawTop = currentRectPx.top + dragAmount.y
                            val newLeft = rawLeft.coerceIn(0f, maxOf(0f, pageWidthPx - w))
                            val newTop = rawTop.coerceIn(0f, maxOf(0f, pageHeightPx - h))
                            val newRect = Rect(newLeft, newTop, newLeft + w, newTop + h)
                            currentRectPx = newRect
                            onDrag(dragAmount, newRect)
                        }
                    }
            )
        }
    }
}

@Composable
private fun DragPill(
    modifier: Modifier = Modifier,
    isDarkMode: Boolean,
    scale: Float = 1f
) {
    Surface(
        modifier = modifier
            .size(width = (48f / scale).dp, height = (24f / scale).dp),
        shape = CircleShape,
        color = if (isDarkMode) Color.White else Color.Black,
        contentColor = if (isDarkMode) Color.Black else Color.White,
        shadowElevation = (4f / scale).dp
    ) {
        Box(contentAlignment = Alignment.Center) {
            Icon(
                painter = painterResource(id = R.drawable.drag_handle),
                contentDescription = stringResource(R.string.content_desc_drag_text_box),
                modifier = Modifier.size((20f / scale).dp)
            )
        }
    }
}
