package solutions.tretter.githugandroid

internal object SandboxShell {
    fun tokenizeCommand(command: String): List<String> {
        return tokenizeShellCommand(command).map { it.value }
    }

    fun tokenizeShellCommand(command: String): List<GitSandboxEngine.ShellToken> {
        val result = mutableListOf<GitSandboxEngine.ShellToken>()
        val current = StringBuilder()
        var quoteChar: Char? = null
        var escaping = false
        var currentQuoted = false
        var skipNext = false

        fun emitCurrent(force: Boolean = false) {
            if (current.isNotEmpty() || force && currentQuoted) {
                result += GitSandboxEngine.ShellToken(value = current.toString(), quoted = currentQuoted)
                current.clear()
                currentQuoted = false
            }
        }

        command.forEachIndexed { index, char ->
            if (skipNext) {
                skipNext = false
                return@forEachIndexed
            }
            when {
                escaping -> {
                    current.append(char)
                    escaping = false
                }
                char == '\\' && quoteChar != '\'' -> {
                    escaping = true
                }
                quoteChar != null -> {
                    if (char == quoteChar) {
                        quoteChar = null
                    } else {
                        current.append(char)
                    }
                }
                char == '"' || char == '\'' -> {
                    quoteChar = char
                    currentQuoted = true
                }
                char.isWhitespace() -> {
                    emitCurrent()
                }
                char == '>' -> {
                    emitCurrent()
                    if (command.getOrNull(index + 1) == '>') {
                        result += GitSandboxEngine.ShellToken(">>")
                        skipNext = true
                    } else if (command.getOrNull(index - 1) != '>') {
                        result += GitSandboxEngine.ShellToken(">")
                    }
                }
                else -> current.append(char)
            }
        }

        if (escaping) {
            current.append('\\')
        }
        emitCurrent()

        return result
    }

    fun expandPathspecs(repo: RepoState, arguments: List<String>): List<String> {
        return expandPathspecTokens(repo, arguments.map { GitSandboxEngine.ShellToken(it) })
    }

    fun expandPathspecTokens(repo: RepoState, arguments: List<GitSandboxEngine.ShellToken>): List<String> {
        return arguments.flatMap { token ->
            val argument = token.value
            if (token.quoted || !argument.hasGlob()) {
                listOf(argument)
            } else {
                val regex = argument.globToRegex()
                repo.files
                    .filterNot { it.deleted }
                    .map { it.name }
                    .filter { regex.matches(it) }
                    .sorted()
                    .ifEmpty { listOf(argument) }
            }
        }
    }

    private fun String.hasGlob(): Boolean = any { it == '*' || it == '?' }

    private fun String.globToRegex(): Regex {
        val pattern = buildString {
            append('^')
            this@globToRegex.forEach { char ->
                when (char) {
                    '*' -> append("[^/]*")
                    '?' -> append("[^/]")
                    '.', '(', ')', '+', '|', '^', '$', '@', '%', '{', '}', '[', ']', '\\' -> {
                        append('\\')
                        append(char)
                    }
                    else -> append(char)
                }
            }
            append('$')
        }
        return Regex(pattern)
    }
}
