//go:build ignore

// gen_plugins generates plugins_gen.go from:
//   1. plugins.json — built-in plugins (in cyrene-plugins)
//   2. ../plugins/*/plugin.json — user plugins (auto-discovered)
//
// Usage: go run gen_plugins.go

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

type PluginEntry struct {
	Name        string `json:"name"`
	Import      string `json:"import,omitempty"`   // built-in: explicit import
	Struct      string `json:"struct"`
	Constructor string `json:"constructor,omitempty"`
}

type PluginManifest struct {
	Name   string `json:"name"`
	Struct string `json:"struct"`
}

// ── load built-in plugins from plugins.json ──

func loadBuiltinPlugins() ([]PluginEntry, error) {
	data, err := os.ReadFile("../plugins.json")
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("read plugins.json: %w", err)
	}
	var cfg struct {
		Version string        `json:"version"`
		Plugins []PluginEntry `json:"plugins"`
	}
	if err := json.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parse plugins.json: %w", err)
	}
	return cfg.Plugins, nil
}

// ── auto-discover user plugins from ../plugins/ ──

func discoverUserPlugins() ([]PluginEntry, error) {
	pluginsDir := filepath.Join("..", "..", "plugins")
	entries, err := os.ReadDir(pluginsDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("read plugins dir: %w", err)
	}

	var result []PluginEntry
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		dir := filepath.Join(pluginsDir, e.Name())

		// 必须有 plugin.json
		manifestPath := filepath.Join(dir, "plugin.json")
		manifestData, err := os.ReadFile(manifestPath)
		if err != nil {
			continue
		}
		var m PluginManifest
		if err := json.Unmarshal(manifestData, &m); err != nil {
			fmt.Fprintf(os.Stderr, "⚠ skip %s: bad plugin.json: %v\n", e.Name(), err)
			continue
		}
		if m.Name == "" {
			m.Name = e.Name()
		}

		// 从 go.mod 读取模块路径作为 import
		modPath := filepath.Join(dir, "go.mod")
		modData, err := os.ReadFile(modPath)
		if err != nil {
			fmt.Fprintf(os.Stderr, "⚠ skip %s: no go.mod: %v\n", e.Name(), err)
			continue
		}
		importPath := parseModule(modData)
		if importPath == "" {
			fmt.Fprintf(os.Stderr, "⚠ skip %s: cannot parse module from go.mod\n", e.Name())
			continue
		}

		result = append(result, PluginEntry{
			Name:   m.Name,
			Import: importPath,
			Struct: m.Struct,
		})
		fmt.Printf("  ✓ discovered %s → %s\n", m.Name, importPath)
	}

	sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
	return result, nil
}

func parseModule(data []byte) string {
	lines := strings.Split(string(data), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "module ") {
			return strings.TrimSpace(strings.TrimPrefix(line, "module "))
		}
	}
	return ""
}

// ── generate plugins_gen.go ──

func generate(plugins []PluginEntry) error {
	var sb strings.Builder
	sb.WriteString("// Code generated by gen_plugins.go; DO NOT EDIT.\n\n")
	sb.WriteString("package main\n\n")
	sb.WriteString("import (\n")
	sb.WriteString("\tplgSDK \"git.yeij.top/AskaEth/Cyrene-Plugins/sdk\"\n")

	aliases := make(map[string]string)
	imported := make(map[string]bool)
	for _, p := range plugins {
		pkgAlias := p.Name + "pkg"
		aliases[p.Name] = pkgAlias
		if !imported[p.Import] {
			sb.WriteString(fmt.Sprintf("\t%s \"%s\"\n", pkgAlias, p.Import))
			imported[p.Import] = true
		}
	}

	sb.WriteString(")\n\n")
	sb.WriteString("func registerPlugins(registry interface{ Register(plgSDK.Tool) error }) {\n")

	for _, p := range plugins {
		alias := aliases[p.Name]
		if p.Constructor != "" {
			if p.Name == "file_ops" || p.Name == "http_request" {
				sb.WriteString(fmt.Sprintf("\tfor _, t := range %s.%s(nil).Tools() {\n", alias, p.Constructor))
			} else {
				sb.WriteString(fmt.Sprintf("\tfor _, t := range %s.%s().Tools() {\n", alias, p.Constructor))
			}
		} else {
			sb.WriteString(fmt.Sprintf("\tfor _, t := range (&%s.%s{}).Tools() {\n", alias, p.Struct))
		}
		sb.WriteString("\t\tregistry.Register(t)\n")
		sb.WriteString("\t}\n")
	}

	sb.WriteString("}\n")

	outPath := "plugins_gen.go"
	if err := os.WriteFile(outPath, []byte(sb.String()), 0644); err != nil {
		return fmt.Errorf("write %s: %w", outPath, err)
	}
	fmt.Printf("Generated %s with %d plugins\n", outPath, len(plugins))
	return nil
}

// ── auto-add require + replace directives to go.mod ──

func ensureGoMod(userPlugins []PluginEntry) error {
	modPath := filepath.Join("..", "go.mod")
	data, err := os.ReadFile(modPath)
	if err != nil {
		return fmt.Errorf("read go.mod: %w", err)
	}
	content := string(data)
	pluginsDir := filepath.Join("..", "..", "plugins")
	entries, _ := os.ReadDir(pluginsDir)

	for _, e := range entries {
		if !e.IsDir() { continue }
		dir := filepath.Join(pluginsDir, e.Name())
		modData, err := os.ReadFile(filepath.Join(dir, "go.mod"))
		if err != nil { continue }
		mod := parseModule(modData)
		if mod == "" { continue }

		relPath, _ := filepath.Rel(filepath.Join(".."), dir)
		relPath = strings.ReplaceAll(relPath, "\\", "/")

		// ensure require
		reqLine := fmt.Sprintf("\t%s v0.0.0\n", mod)
		if !strings.Contains(content, reqLine) {
			reqBlock := strings.Index(content, "require (")
			if reqBlock < 0 { continue }
			closeIdx := strings.Index(content[reqBlock:], ")")
			if closeIdx < 0 { continue }
			insertAt := reqBlock + closeIdx
			content = content[:insertAt] + reqLine + content[insertAt:]
		}

		// ensure replace
		replaceLine := fmt.Sprintf("\t%s => %s\n", mod, relPath)
		if !strings.Contains(content, replaceLine) {
			replaceBlock := strings.Index(content, "replace (")
			if replaceBlock < 0 {
				content += fmt.Sprintf("\nreplace (\n%s)\n", replaceLine)
			} else {
				closeIdx := strings.Index(content[replaceBlock:], ")")
				if closeIdx < 0 { continue }
				insertAt := replaceBlock + closeIdx
				content = content[:insertAt] + replaceLine + content[insertAt:]
			}
		}
	}

	return os.WriteFile(modPath, []byte(content), 0644)
}

func main() {
	builtins, err := loadBuiltinPlugins()
	if err != nil {
		fmt.Fprintf(os.Stderr, "load builtins: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Built-in plugins: %d\n", len(builtins))

	fmt.Println("Discovering user plugins...")
	userPlugins, err := discoverUserPlugins()
	if err != nil {
		fmt.Fprintf(os.Stderr, "discover user plugins: %v\n", err)
		os.Exit(1)
	}

	// 自动补 go.mod replace 指令
	if len(userPlugins) > 0 {
		if err := ensureGoMod(userPlugins); err != nil {
			fmt.Fprintf(os.Stderr, "ensure go.mod: %v\n", err)
		}
	}

	all := append(builtins, userPlugins...)
	if err := generate(all); err != nil {
		fmt.Fprintf(os.Stderr, "generate: %v\n", err)
		os.Exit(1)
	}
}
