// Package main provides a CLI tool for rendering skeleton templates locally.
//
// This is used for testing templates without needing Gitea/rdev infrastructure.
// The tool renders the monorepo skeleton plus all component types to a local directory.
//
// Usage:
//
//	go run ./cmd/render-skeleton -output ./examples/full-monorepo
package main

import (
	"flag"
	"fmt"
	"maps"
	"os"
	"path/filepath"
	"strings"

	"github.com/orchard9/rdev/internal/adapter/templates"
)

// Component defines a component to render.
type Component struct {
	Type    string
	Name    string
	Port    string
	DestDir string // "services", "workers", "apps", or "cli"
}

// defaultComponents are the components to render in a full example project.
var defaultComponents = []Component{
	{Type: "service", Name: "example-api", Port: "8001", DestDir: "services"},
	{Type: "worker", Name: "example-worker", Port: "", DestDir: "workers"},
	{Type: "app-astro", Name: "example-astro", Port: "4321", DestDir: "apps"},
	{Type: "app-react", Name: "example-react", Port: "5173", DestDir: "apps"},
	{Type: "app-nextjs", Name: "example-nextjs", Port: "3000", DestDir: "apps"},
	{Type: "cli", Name: "example-cli", Port: "", DestDir: "cli"},
}

func main() {
	outputDir := flag.String("output", "", "Output directory for rendered skeleton")
	projectName := flag.String("project", "test-project", "Project name")
	goModule := flag.String("module", "git.threesix.ai/threesix/test-project", "Go module path")
	domain := flag.String("domain", "test.threesix.ai", "Domain for the project")
	flag.Parse()

	if *outputDir == "" {
		fmt.Fprintln(os.Stderr, "Usage: render-skeleton -output <dir> [-project <name>] [-module <path>] [-domain <domain>]")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "Example:")
		fmt.Fprintln(os.Stderr, "  go run ./cmd/render-skeleton -output ./examples/full-monorepo")
		os.Exit(1)
	}

	// Base variables for the project
	vars := map[string]string{
		"PROJECT_NAME": *projectName,
		"GO_MODULE":    *goModule,
		"DOMAIN":       *domain,
		"DESCRIPTION":  "Test project for skeleton verification",
		"GIT_URL":      "https://" + strings.TrimPrefix(*goModule, "git."),
	}

	fmt.Printf("Rendering skeleton to %s\n", *outputDir)
	fmt.Printf("  PROJECT_NAME: %s\n", vars["PROJECT_NAME"])
	fmt.Printf("  GO_MODULE:    %s\n", vars["GO_MODULE"])
	fmt.Printf("  DOMAIN:       %s\n", vars["DOMAIN"])
	fmt.Println()

	// Clean output directory if it exists (but preserve .git if present)
	if err := cleanOutputDir(*outputDir); err != nil {
		fmt.Fprintf(os.Stderr, "Error cleaning output directory: %v\n", err)
		os.Exit(1)
	}

	// Render skeleton
	fmt.Println("Rendering skeleton...")
	if err := templates.RenderSkeletonToDir(*outputDir, vars); err != nil {
		fmt.Fprintf(os.Stderr, "Error rendering skeleton: %v\n", err)
		os.Exit(1)
	}

	// Render all components
	for _, c := range defaultComponents {
		fmt.Printf("Rendering component: %s (%s)\n", c.Name, c.Type)

		// Component-specific variables
		componentVars := copyVars(vars)
		componentVars["COMPONENT_NAME"] = c.Name
		componentVars["PORT"] = c.Port

		// For frontend apps, inject the primary service name/port for API proxy
		if strings.HasPrefix(c.Type, "app-") {
			if svc := findFirstService(defaultComponents); svc != nil {
				componentVars["SERVICE_NAME"] = svc.Name
				componentVars["SERVICE_PORT"] = svc.Port
			}
		}

		// Determine destination path
		destPath := filepath.Join(c.DestDir, c.Name)

		if err := templates.RenderComponentToDir(*outputDir, c.Type, destPath, componentVars); err != nil {
			fmt.Fprintf(os.Stderr, "Error rendering component %s: %v\n", c.Name, err)
			os.Exit(1)
		}
	}

	// Update monorepo files
	fmt.Println("\nUpdating monorepo files...")
	if err := updateGoWork(*outputDir, defaultComponents); err != nil {
		fmt.Fprintf(os.Stderr, "Error updating go.work: %v\n", err)
		os.Exit(1)
	}
	if err := updateProcfile(*outputDir, defaultComponents); err != nil {
		fmt.Fprintf(os.Stderr, "Error updating Procfile: %v\n", err)
		os.Exit(1)
	}

	// Remove .gitkeep files from directories that now have content
	if err := removeGitkeeps(*outputDir); err != nil {
		fmt.Fprintf(os.Stderr, "Error removing .gitkeep files: %v\n", err)
		os.Exit(1)
	}

	fmt.Println("\nDone!")
	fmt.Printf("Rendered %d components to %s\n", len(defaultComponents), *outputDir)
	fmt.Println("\nNext steps:")
	fmt.Println("  cd " + *outputDir)
	fmt.Println("  go work sync")
	fmt.Println("  go build ./...")
	fmt.Println("  pnpm install")
	fmt.Println("  pnpm -r typecheck")
}

// cleanOutputDir removes existing content but preserves .git directory.
func cleanOutputDir(outputDir string) error {
	entries, err := os.ReadDir(outputDir)
	if os.IsNotExist(err) {
		return os.MkdirAll(outputDir, 0755)
	}
	if err != nil {
		return err
	}

	for _, entry := range entries {
		if entry.Name() == ".git" {
			continue // preserve .git
		}
		path := filepath.Join(outputDir, entry.Name())
		if err := os.RemoveAll(path); err != nil {
			return fmt.Errorf("failed to remove %s: %w", path, err)
		}
	}
	return nil
}

// copyVars creates a copy of a variables map.
func copyVars(vars map[string]string) map[string]string {
	return maps.Clone(vars)
}

// updateGoWork adds component modules to go.work.
func updateGoWork(outputDir string, components []Component) error {
	var sb strings.Builder
	sb.WriteString("go 1.25\n\n")
	sb.WriteString("use ./pkg\n")

	for _, c := range components {
		// Only Go components get added to go.work
		switch c.Type {
		case "service", "worker", "cli":
			fmt.Fprintf(&sb, "use ./%s/%s\n", c.DestDir, c.Name)
		}
	}

	return os.WriteFile(filepath.Join(outputDir, "go.work"), []byte(sb.String()), 0644)
}

// updateProcfile adds component processes to Procfile.
func updateProcfile(outputDir string, components []Component) error {
	var lines []string
	lines = append(lines, "# Local development processes")
	lines = append(lines, "")

	for _, c := range components {
		var cmd string
		switch c.Type {
		case "service":
			cmd = fmt.Sprintf("%s: cd %s/%s && make dev", c.Name, c.DestDir, c.Name)
		case "worker":
			cmd = fmt.Sprintf("%s: cd %s/%s && make dev", c.Name, c.DestDir, c.Name)
		case "cli":
			// CLIs don't run as processes
			continue
		case "app-astro", "app-react":
			cmd = fmt.Sprintf("%s: cd %s/%s && pnpm dev", c.Name, c.DestDir, c.Name)
		case "app-nextjs":
			cmd = fmt.Sprintf("%s: cd %s/%s && pnpm dev", c.Name, c.DestDir, c.Name)
		default:
			continue
		}
		lines = append(lines, cmd)
	}
	lines = append(lines, "")

	content := strings.Join(lines, "\n")
	return os.WriteFile(filepath.Join(outputDir, "Procfile"), []byte(content), 0644)
}

// findFirstService returns the first service component, or nil if none.
func findFirstService(components []Component) *Component {
	for i := range components {
		if components[i].Type == "service" {
			return &components[i]
		}
	}
	return nil
}

// removeGitkeeps removes .gitkeep files from directories that have other content.
func removeGitkeeps(outputDir string) error {
	keepDirs := []string{"services", "workers", "apps", "cli", "packages"}

	for _, dir := range keepDirs {
		gitkeepPath := filepath.Join(outputDir, dir, ".gitkeep")
		dirPath := filepath.Join(outputDir, dir)

		entries, err := os.ReadDir(dirPath)
		if err != nil {
			continue // directory might not exist
		}

		// If there's more than just .gitkeep, remove it
		if len(entries) > 1 {
			_ = os.Remove(gitkeepPath) // Ignore error - file may not exist
		}
	}

	return nil
}
