package net.sergeych.toread

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.ZoomIn
import androidx.compose.material.icons.filled.ZoomOut
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.isMetaPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.focusable
import kotlinx.coroutines.launch

private const val MinImageScale = 1f
private const val MaxImageScale = 8f

@Composable
internal fun ImageViewer(
    image: ViewedBookImage,
    onBack: () -> Unit,
) {
    var scale by remember(image) { mutableStateOf(MinImageScale) }
    var offset by remember(image) { mutableStateOf(Offset.Zero) }
    val focusRequester = remember { FocusRequester() }
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()
    val imageBackgroundColor = readerImageBackgroundColor()
    val imageAspectRatio = image.bitmap.width.toFloat() / image.bitmap.height.toFloat()

    fun setScale(next: Float) {
        scale = next.coerceIn(MinImageScale, MaxImageScale)
        if (scale == MinImageScale) offset = Offset.Zero
    }

    fun panBy(delta: Offset) {
        if (scale > MinImageScale) {
            offset += delta
        } else {
            offset = Offset.Zero
        }
    }

    val transformState = rememberTransformableState { zoomChange, panChange, _ ->
        setScale(scale * zoomChange)
        panBy(panChange)
    }

    fun copyImage() {
        scope.launch {
            val message = if (copyImageToClipboard(image.bytes, image.mimeType, image.title)) {
                strings.imageCopied
            } else {
                strings.copyFailed
            }
            snackbarHostState.showSnackbar(message, duration = SnackbarDuration.Short)
        }
    }

    LaunchedEffect(image) {
        focusRequester.requestFocus()
    }

    Scaffold(
        contentWindowInsets = WindowInsets(0, 0, 0, 0),
        snackbarHost = { SnackbarHost(snackbarHostState) },
        topBar = {
            ThemedTopBarSurface {
                Row(
                    modifier = Modifier.fillMaxWidth().height(48.dp),
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    IconButton(onClick = onBack) {
                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = strings.back)
                    }
                    Text(
                        image.title,
                        style = MaterialTheme.typography.titleSmall,
                        maxLines = 1,
                        modifier = Modifier.weight(1f),
                    )
                    IconButton(onClick = { setScale(scale * 1.25f) }) {
                        Icon(Icons.Filled.ZoomIn, contentDescription = strings.zoomIn)
                    }
                    IconButton(onClick = { setScale(scale / 1.25f) }, enabled = scale > MinImageScale) {
                        Icon(Icons.Filled.ZoomOut, contentDescription = strings.zoomOut)
                    }
                    IconButton(
                        onClick = ::copyImage,
                    ) {
                        Icon(Icons.Filled.ContentCopy, contentDescription = strings.copyImage)
                    }
                }
            }
        },
    ) { padding ->
        BoxWithConstraints(
            modifier = Modifier
                .fillMaxSize()
                .padding(padding)
                .background(readerBackground())
                .focusRequester(focusRequester)
                .onPreviewKeyEvent { event ->
                    if (event.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
                    when {
                        event.key == Key.Escape -> {
                            onBack()
                            true
                        }
                        event.key == Key.Plus || event.key == Key.Equals -> {
                            setScale(scale * 1.25f)
                            true
                        }
                        event.key == Key.Minus -> {
                            setScale(scale / 1.25f)
                            true
                        }
                        event.key == Key.C && (event.isCtrlPressed || event.isMetaPressed) -> {
                            copyImage()
                            true
                        }
                        else -> false
                    }
                }
                .focusable()
                .transformable(transformState)
                .pointerInput(image, scale) {
                    detectDragGestures { change, dragAmount ->
                        if (change.positionChange() != Offset.Zero) change.consume()
                        panBy(dragAmount)
                    }
                },
            contentAlignment = Alignment.Center,
        ) {
            val imageModifier = if (maxWidth / maxHeight > imageAspectRatio) {
                Modifier.fillMaxHeight().aspectRatio(imageAspectRatio)
            } else {
                Modifier.fillMaxWidth().aspectRatio(imageAspectRatio)
            }
            Box(
                modifier = imageModifier
                    .graphicsLayer {
                        scaleX = scale
                        scaleY = scale
                        translationX = offset.x
                        translationY = offset.y
                    }
                    .background(imageBackgroundColor),
            ) {
                Image(
                    bitmap = image.bitmap,
                    contentDescription = image.title,
                    modifier = Modifier.fillMaxSize(),
                    contentScale = ContentScale.Fit,
                )
            }
        }
    }
}
