package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"fn-registry/registry"
)

func buildCppCommand(fn *registry.Function, registryRoot, absPath string, args []string) (*exec.Cmd, error) {
	cppRoot := filepath.Join(registryRoot, "cpp")
	buildDir := filepath.Join(cppRoot, "build", "linux")

	// Ensure build directory exists and cmake is configured
	if _, err := os.Stat(filepath.Join(buildDir, "CMakeCache.txt")); os.IsNotExist(err) {
		fmt.Fprintf(os.Stderr, "[fn run] configuring cmake for cpp...\n")
		configure := exec.Command("cmake", "-B", buildDir, "-S", cppRoot)
		configure.Dir = registryRoot
		configure.Stdout = os.Stderr
		configure.Stderr = os.Stderr
		if err := configure.Run(); err != nil {
			return nil, fmt.Errorf("cmake configure failed: %w", err)
		}
	}

	dir := filepath.Dir(absPath)

	// Check if the function's directory has its own CMakeLists.txt (app with main)
	localCMake := filepath.Join(dir, "CMakeLists.txt")
	hasMain := false
	if _, err := os.Stat(localCMake); err == nil {
		hasMain = true
	}
	// Also check for main.cpp in the same directory
	mainCpp := filepath.Join(dir, "main.cpp")
	if _, err := os.Stat(mainCpp); err == nil {
		hasMain = true
	}

	if hasMain {
		// Build and run the app binary
		targetName := filepath.Base(dir)
		build := exec.Command("cmake", "--build", buildDir, "--target", targetName)
		build.Dir = registryRoot
		build.Stdout = os.Stderr
		build.Stderr = os.Stderr
		fmt.Fprintf(os.Stderr, "[fn run] building target %s...\n", targetName)
		if err := build.Run(); err != nil {
			return nil, fmt.Errorf("cmake build failed: %w", err)
		}

		// Find the built binary
		binaryPath := findBinary(buildDir, targetName)
		if binaryPath == "" {
			return nil, fmt.Errorf("built binary %q not found in %s", targetName, buildDir)
		}

		cmd := exec.Command(binaryPath, args...)
		cmd.Dir = dir
		return cmd, nil
	}

	// Library code: compile-check only (like go vet)
	build := exec.Command("cmake", "--build", buildDir)
	build.Dir = registryRoot
	build.Stdout = os.Stderr
	build.Stderr = os.Stderr
	fmt.Fprintf(os.Stderr, "[fn run] %s is library code — running compile check\n", fn.ID)

	if err := build.Run(); err != nil {
		return nil, fmt.Errorf("compile check failed: %w", err)
	}

	// Return a no-op command that just prints success
	cmd := exec.Command("echo", fmt.Sprintf("[fn run] %s compiled successfully", fn.ID))
	return cmd, nil
}

// findBinary searches for an executable in the build tree.
func findBinary(buildDir, name string) string {
	// Common locations cmake puts binaries
	candidates := []string{
		filepath.Join(buildDir, name),
		filepath.Join(buildDir, "apps", name, name),
	}

	for _, c := range candidates {
		if info, err := os.Stat(c); err == nil && !info.IsDir() {
			// Check if executable
			if info.Mode()&0111 != 0 {
				return c
			}
		}
	}

	// Walk the build directory as fallback
	var found string
	filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
		if err != nil || info.IsDir() {
			return nil
		}
		if info.Name() == name && info.Mode()&0111 != 0 {
			found = path
			return filepath.SkipAll
		}
		return nil
	})

	// Also try without extension match for paths with subdirectories
	if found == "" {
		filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
			if err != nil || info.IsDir() {
				return nil
			}
			base := strings.TrimSuffix(info.Name(), filepath.Ext(info.Name()))
			if base == name && info.Mode()&0111 != 0 {
				found = path
				return filepath.SkipAll
			}
			return nil
		})
	}

	return found
}
