import * as vscode from "vscode"
import * as path from "path"
import * as fs from "fs"
import * as childProcess from "child_process"
import * as readline from "readline"
import { byLengthAsc, Fzf } from "fzf"
import { getBinPath } from "../ripgrep"
import { Package } from "../../shared/package"

export type FileResult = { path: string; type: "file" | "folder"; label?: string }

export async function executeRipgrep({
	args,
	workspacePath,
	limit = 500,
}: {
	args: string[]
	workspacePath: string
	limit?: number
}): Promise<FileResult[]> {
	const rgPath = await getBinPath(vscode.env.appRoot)

	if (!rgPath) {
		throw new Error(`ripgrep not found: ${rgPath}`)
	}

	return new Promise((resolve, reject) => {
		const rgProcess = childProcess.spawn(rgPath, args)
		const rl = readline.createInterface({ input: rgProcess.stdout, crlfDelay: Infinity })
		const fileResults: FileResult[] = []
		const dirSet = new Set<string>() // Track unique directory paths.

		let count = 0

		rl.on("line", (line) => {
			if (count < limit) {
				try {
					const relativePath = path.relative(workspacePath, line)

					// Add the file itself.
					fileResults.push({ path: relativePath, type: "file", label: path.basename(relativePath) })

					// Extract and store all parent directory paths.
					let dirPath = path.dirname(relativePath)

					while (dirPath && dirPath !== "." && dirPath !== "/") {
						dirSet.add(dirPath)
						dirPath = path.dirname(dirPath)
					}

					count++
				} catch (error) {
					// Silently ignore errors processing individual paths.
				}
			} else {
				rl.close()
				rgProcess.kill()
			}
		})

		let errorOutput = ""

		rgProcess.stderr.on("data", (data) => {
			errorOutput += data.toString()
		})

		rl.on("close", () => {
			if (errorOutput && fileResults.length === 0) {
				reject(new Error(`ripgrep process error: ${errorOutput}`))
			} else {
				// Convert directory set to array of directory objects.
				const dirResults = Array.from(dirSet).map((dirPath) => ({
					path: dirPath,
					type: "folder" as const,
					label: path.basename(dirPath),
				}))

				// Combine files and directories and resolve.
				resolve([...fileResults, ...dirResults])
			}
		})

		rgProcess.on("error", (error) => {
			reject(new Error(`ripgrep process error: ${error.message}`))
		})
	})
}

/**
 * Get extra ripgrep arguments based on VSCode search configuration
 */
function getRipgrepSearchOptions(): string[] {
	const config = vscode.workspace.getConfiguration("search")
	const extraArgs: string[] = []

	// Respect VSCode's search.useIgnoreFiles setting
	if (config.get("useIgnoreFiles") === false) {
		extraArgs.push("--no-ignore")
	}

	// Respect VSCode's search.useGlobalIgnoreFiles setting
	if (config.get("useGlobalIgnoreFiles") === false) {
		extraArgs.push("--no-ignore-global")
	}

	// Respect VSCode's search.useParentIgnoreFiles setting
	if (config.get("useParentIgnoreFiles") === false) {
		extraArgs.push("--no-ignore-parent")
	}

	return extraArgs
}

export async function executeRipgrepForFiles(
	workspacePath: string,
	limit?: number,
): Promise<{ path: string; type: "file" | "folder"; label?: string }[]> {
	// Get limit from configuration if not provided
	const effectiveLimit =
		limit ??
		vscode.workspace
			.getConfiguration(Package.commandIDPrefix)
			.get<number>("maximumIndexedFilesForFileSearch", 10000)

	const args = [
		"--files",
		"--follow",
		"--hidden",
		...getRipgrepSearchOptions(),
		"-g",
		"!**/node_modules/**",
		"-g",
		"!**/.git/**",
		"-g",
		"!**/out/**",
		"-g",
		"!**/dist/**",
		workspacePath,
	]

	return executeRipgrep({ args, workspacePath, limit: effectiveLimit })
}

export async function searchWorkspaceFiles(
	query: string,
	workspacePath: string,
	limit: number = 30,
): Promise<{ path: string; type: "file" | "folder"; label?: string }[]> {
	try {
		// Get all files and directories (uses configured limit)
		const allItems = await executeRipgrepForFiles(workspacePath)

		// If no query, just return the top items
		if (!query.trim()) {
			return allItems.slice(0, limit)
		}

		// Create search items for all files AND directories
		const searchItems = allItems.map((item) => ({
			original: item,
			searchStr: `${item.path} ${item.label || ""}`,
		}))

		// Run fzf search on all items
		const fzf = new Fzf(searchItems, {
			selector: (item) => item.searchStr,
			tiebreakers: [byLengthAsc],
			limit: limit,
		})

		// Get all matching results from fzf
		const fzfResults = fzf.find(query).map((result) => result.item.original)

		// Verify types of the shortest results
		const verifiedResults = await Promise.all(
			fzfResults.map(async (result) => {
				const fullPath = path.join(workspacePath, result.path)
				// Verify if the path exists and is actually a directory
				if (fs.existsSync(fullPath)) {
					const isDirectory = fs.lstatSync(fullPath).isDirectory()
					return {
						...result,
						path: result.path.toPosix(),
						type: isDirectory ? ("folder" as const) : ("file" as const),
					}
				}
				// If path doesn't exist, keep original type
				return result
			}),
		)

		return verifiedResults
	} catch (error) {
		console.error("Error in searchWorkspaceFiles:", error)
		return []
	}
}
