package invalid.lena.rsend

import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.os.storage.StorageManager
import android.widget.Button
import android.widget.TextView
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.nio.file.Files

// FolderPickerActivity browses the real filesystem and returns one directory's
// absolute path in the "path" result extra. The app holds all-files access, so
// rsync works on real paths; this picker stays on real paths too (no SAF, no
// content URIs). Browsing is rooted at primary shared storage and cannot go
// above it; SD cards and other volumes are still reachable by typing the path.
class FolderPickerActivity : AppCompatActivity() {

    companion object {
        private const val MAX_VISIBLE_DIRECTORIES = 500
    }

    private lateinit var pathView: TextView
    private lateinit var list: android.widget.LinearLayout
    private lateinit var use: Button
    private lateinit var root: File
    private lateinit var current: File

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_picker)
        setSupportActionBar(findViewById(R.id.toolbar))
        title = "Pick folder"
        fitSystemBars()
        pathView = findViewById(R.id.currentPath)
        list = findViewById(R.id.list)

        root = (getSystemService(StorageManager::class.java).primaryStorageVolume.directory
            ?: Environment.getExternalStorageDirectory()).canonicalFile

        // Start where the field already points, if that is a directory under the
        // root; otherwise at the root itself.
        val start = intent.getStringExtra("start")?.let { safeDirectory(File(it)) }
        current = start ?: root

        use = findViewById(R.id.use)
        use.setOnClickListener {
            setResult(RESULT_OK, Intent().putExtra("path", current.absolutePath))
            finish()
        }
        findViewById<Button>(R.id.cancel).setOnClickListener { finish() }

        // Hardware back walks up the tree until the root, then leaves the screen.
        onBackPressedDispatcher.addCallback(this) {
            if (current.absolutePath != root.absolutePath) {
                current = current.parentFile ?: root
                render()
            } else {
                isEnabled = false
                onBackPressedDispatcher.onBackPressed()
            }
        }

        render()
    }

    private fun render() {
        pathView.text = current.absolutePath
        list.removeAllViews()

        if (current.absolutePath != root.absolutePath && current.parentFile != null) {
            addRow("..", current.parentFile!!)
        }

        // null means the directory could not be read at all, which is a
        // different thing from having no subfolders and must not read as an
        // invitation to pick it.
        val listed = try {
            Files.newDirectoryStream(current.toPath()) { Files.isDirectory(it) }.use { stream ->
                val found = stream.asSequence().map { it.toFile() }.mapNotNull(::safeDirectory)
                    .distinctBy { it.absolutePath }.take(MAX_VISIBLE_DIRECTORIES + 1).toList()
                Listing(
                    found.take(MAX_VISIBLE_DIRECTORIES).sortedBy { it.name.lowercase() },
                    found.size > MAX_VISIBLE_DIRECTORIES,
                )
            }
        } catch (_: Exception) {
            null
        }
        use.isEnabled = listed != null
        val dirs = listed?.directories ?: emptyList()
        for (d in dirs) addRow(d.name, d)

        if (listed?.truncated == true) {
            list.addView(message("More folders exist. Type a deeper path to reach one not shown."))
        } else if (dirs.isEmpty()) {
            list.addView(
                message(
                    if (listed == null) {
                        "Cannot read this folder. Check all-files access on the main screen."
                    } else {
                        "No subfolders here. Tap \"Use this folder\" to pick it."
                    }
                )
            )
        }
    }

    private data class Listing(val directories: List<File>, val truncated: Boolean)

    private fun message(value: String): TextView = TextView(this).apply {
        text = value
        setTextAppearance(R.style.TextAppearance_Rsend_Caption)
    }

    // Anchored at a path separator so /storage/emulated/0-evil does not pass
    // for a root of /storage/emulated/0.
    private fun underRoot(f: File): Boolean =
        f.absolutePath == root.absolutePath || f.absolutePath.startsWith(root.absolutePath + "/")

    private fun safeDirectory(file: File): File? = try {
        file.canonicalFile.takeIf { it.isDirectory && underRoot(it) }
    } catch (_: Exception) {
        null
    }

    private fun addRow(label: String, dir: File) {
        val row = layoutInflater.inflate(R.layout.item_dir, list, false)
        row.findViewById<TextView>(R.id.dirName).text = label
        row.setOnClickListener { current = dir; render() }
        list.addView(row)
    }
}
