CommandFinder.kt

package com.depanalyzer.parser

import java.io.File
import java.util.concurrent.TimeUnit

object CommandFinder {
    private const val VERSION_TIMEOUT_SECONDS = 5L

    fun findCommand(
        projectDir: File,
        wrapperName: String,
        wrapperNameWindows: String,
        globalCommand: String,
        globalCommandWindows: String,
        verbose: Boolean = false
    ): String? {
        require(projectDir.exists() && projectDir.isDirectory) { "Project directory must exist" }

        val isWindows = isWindowsOS()

        val candidates = listOf(
            Pair(wrapperNameWindows, true),
            Pair(wrapperName, false)
        )

        for ((wrapperFileName, isWindowsVariant) in candidates) {
            val wrapperFile = File(projectDir, wrapperFileName)

            val wrapperExists = if (isWindowsVariant) {
                wrapperFile.exists()
            } else {
                wrapperFile.exists() && wrapperFile.canExecute()
            }

            if (wrapperExists) {
                if (verbose) {
                    System.err.println("[CommandFinder] Found project wrapper: ${wrapperFile.absolutePath}")
                }
                return wrapperFile.absolutePath
            }

            if (verbose) {
                System.err.println("[CommandFinder] Project wrapper not found: $wrapperFileName")
            }
        }

        val globalCmd = if (isWindows) globalCommandWindows else globalCommand

        if (isCommandAvailable(globalCmd, verbose)) {
            if (verbose) {
                System.err.println("[CommandFinder] Found global command: $globalCmd")
            }
            return globalCmd
        }

        if (verbose) {
            System.err.println("[CommandFinder] Global command not found: $globalCmd")
        }

        return null
    }

    fun isCommandAvailable(command: String, verbose: Boolean = false): Boolean {
        return try {
            val process = ProcessBuilder(command, "--version")
                .redirectErrorStream(true)
                .start()

            val completed = process.waitFor(VERSION_TIMEOUT_SECONDS, TimeUnit.SECONDS)

            if (completed) {
                val success = process.exitValue() == 0
                if (verbose && !success) {
                    System.err.println("[CommandFinder] Command '$command --version' exited with code ${process.exitValue()}")
                }
                success
            } else {
                process.destroyForcibly()
                if (verbose) {
                    System.err.println("[CommandFinder] Command '$command --version' timed out")
                }
                false
            }
        } catch (e: Exception) {
            if (verbose) {
                System.err.println("[CommandFinder] Exception checking command '$command': ${e.message}")
            }
            false
        }
    }

    fun getCommandVersion(command: String, verbose: Boolean = false): String? {
        return try {
            val process = ProcessBuilder(command, "--version")
                .redirectErrorStream(true)
                .start()

            val completed = process.waitFor(VERSION_TIMEOUT_SECONDS, TimeUnit.SECONDS)

            if (!completed) {
                process.destroyForcibly()
                return null
            }

            if (process.exitValue() == 0) {
                process.inputStream.bufferedReader().readText().trim().takeIf { it.isNotEmpty() }
            } else {
                if (verbose) {
                    System.err.println("[CommandFinder] Command '$command --version' exited with code ${process.exitValue()}")
                }
                null
            }
        } catch (e: Exception) {
            if (verbose) {
                System.err.println("[CommandFinder] Exception getting version for '$command': ${e.message}")
            }
            null
        }
    }

    private fun isWindowsOS(): Boolean {
        return System.getProperty("os.name").lowercase().contains("windows")
    }
}