package com.correx.infrastructure.tools

import com.correx.core.tools.contract.Tool
import com.correx.infrastructure.tools.filesystem.FileDeleteTool
import com.correx.infrastructure.tools.filesystem.FileEditTool
import com.correx.infrastructure.tools.filesystem.FileReadTool
import com.correx.infrastructure.tools.filesystem.FileWriteTool
import com.correx.infrastructure.tools.filesystem.GlobTool
import com.correx.infrastructure.tools.filesystem.GrepTool
import com.correx.infrastructure.tools.filesystem.ListDirTool
import com.correx.infrastructure.tools.shell.ShellTool
import com.correx.infrastructure.tools.web.WebFetchTool
import com.correx.infrastructure.tools.web.WebSearchTool
import io.ktor.client.HttpClient
import java.nio.file.Path

data class ToolConfig(
    val shell: ShellConfig = ShellConfig(),
    val fileRead: FileReadConfig = FileReadConfig(),
    val fileWrite: FileWriteConfig = FileWriteConfig(),
    val fileEdit: FileEditConfig = FileEditConfig(),
    val research: ResearchToolConfig = ResearchToolConfig(),
)

/**
 * Research tool registration. [httpClient] is the single shared client threaded in from the
 * composition root (one per process, not per registry); when null the tools are not built even
 * if [enabled], so tests and the static path never spin up a network client.
 */
class ResearchToolConfig(
    val enabled: Boolean = false,
    val searxngUrl: String = "http://localhost:8888",
    val maxResults: Int = 8,
    val maxFetchBytes: Long = 10_000_000,
    val httpClient: HttpClient? = null,
)

data class FileReadConfig(
    val enabled: Boolean = false,
    val allowedPaths: Set<Path> = emptySet(),
    val workingDir: Path? = null,
)
data class FileWriteConfig(
    val enabled: Boolean = false,
    val allowedPaths: Set<Path> = emptySet(),
    val workingDir: Path? = null,
)

data class FileEditConfig(
    val enabled: Boolean = false,
    val allowedPaths: Set<Path> = emptySet(),
    val workingDir: Path? = null,
)

data class ShellConfig(
    val enabled: Boolean = false,
    val allowedExecutables: Set<String> = emptySet(),
    val workingDir: Path? = null,
)

/**
 * Extension function to convert [ToolConfig] into a list of [Tool] implementations.
 */
fun ToolConfig.buildTools(): List<Tool> = buildList {
    if (shell.enabled) {
        add(
            ShellTool(
                allowedExecutables = shell.allowedExecutables,
                workingDir = shell.workingDir,
            ),
        )
    }
    if (fileRead.enabled) {
        add(
            FileReadTool(
                allowedPaths = fileRead.allowedPaths,
                workingDir = fileRead.workingDir,
            ),
        )
        // list_dir is read-only enumeration; shares the reader's jail + anchor and the same toggle.
        add(
            ListDirTool(
                allowedPaths = fileRead.allowedPaths,
                workingDir = fileRead.workingDir,
            ),
        )
        // glob (find by pattern) + grep (search contents) — read-only search, same jail/anchor/toggle;
        // the affordance weak models need instead of shell find/grep -r.
        add(GlobTool(allowedPaths = fileRead.allowedPaths, workingDir = fileRead.workingDir))
        add(GrepTool(allowedPaths = fileRead.allowedPaths, workingDir = fileRead.workingDir))
    }
    if (fileWrite.enabled) {
        add(
            FileWriteTool(
                allowedPaths = fileWrite.allowedPaths,
                workingDir = fileWrite.workingDir,
            ),
        )
        // file_delete is the explicit, separately-named deletion capability split out of
        // file_write; it shares the writer's path jail and is gated by the same fileWrite toggle.
        add(
            FileDeleteTool(
                allowedPaths = fileWrite.allowedPaths,
                workingDir = fileWrite.workingDir,
            ),
        )
    }
    if (fileEdit.enabled) {
        add(
            FileEditTool(
                allowedPaths = fileEdit.allowedPaths,
                workingDir = fileEdit.workingDir,
            ),
        )
    }
    research.httpClient?.takeIf { research.enabled }?.let { client ->
        add(WebSearchTool(client, searxngBaseUrl = research.searxngUrl, maxResults = research.maxResults))
        add(WebFetchTool(client, maxBytes = research.maxFetchBytes))
    }
}
