//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//

// gen-component-parity is a small developer tool that walks the live Go
// component and tool registries and emits a markdown parity matrix for
// docs/component-parity.md. It supersedes the manually-curated table
// previously maintained in that file.
//
// Usage:
//
//	go run ./tools/gen-component-parity > docs/component-parity.generated.md
//	go run ./tools/gen-component-parity -format=tsv
//
// The tool imports the production packages (which trigger init()
// registration of every component / tool factory) and reflects on each
// registered entry's Name() / Inputs() / Outputs() (for components) or
// Info() (for tools). It then writes a markdown table to stdout.
//
// Limitations:
//
//   - The script does NOT evaluate Python parity. Every entry is marked
//     "🟡 parity check via docs/component-parity.md" — the human-curated
//     table is the source of truth for parity; this script only emits
//     the structural inventory.
//   - Component factories that require non-empty params to construct
//     (e.g. Retrieval, ExeSQL) are skipped with a "⏭" annotation; their
//     parity is captured in the hand-written matrix.
//   - The script does not import cmd/server_main.go to avoid pulling
//     in the full boot path (Redis, MySQL, etc.).
package main

import (
	"context"
	"flag"
	"fmt"
	"sort"
	"strings"

	"ragflow/internal/agent/component"
	"ragflow/internal/agent/tool"
)

func main() {
	format := flag.String("format", "md", "output format: md|tsv")
	flag.Parse()

	switch *format {
	case "md":
		writeMarkdown()
	case "tsv":
		writeTSV()
	default:
		fmt.Fprintf(flag.CommandLine.Output(), "unknown format %q (md|tsv)\n", *format)
		flag.CommandLine.Usage()
	}
}

func writeMarkdown() {
	fmt.Println("# Component & Tool Registry — Auto-generated inventory")
	fmt.Println()
	fmt.Println("> Generated by `tools/gen-component-parity`. Do not edit by hand;")
	fmt.Println("> the human-curated `docs/component-parity.md` is the source of truth for")
	fmt.Println("> Python parity annotations (✅/🟡/⚠️/❌). This file only emits the")
	fmt.Println("> structural inventory (registry name, factory, public surface).")
	fmt.Println()

	fmt.Println("## Universe A — Canvas DAG components")
	fmt.Println()
	fmt.Println("| Name | Source file | Public surface |")
	fmt.Println("|---|---|---|")
	for _, name := range component.RegisteredNames() {
		c, err := component.New(name, map[string]any{})
		if err != nil {
			// Some components (ExeSQL, Retrieval, DocsGenerator, ListOperations, ...)
			// require non-empty params at construction time. We surface those as
			// "requires params" rather than "error" so the table reads as
			// "factory exists, params needed" — the human-curated parity doc
			// already documents the param surface.
			fmt.Printf("| %s | `internal/agent/component/*.go` | (requires non-empty params) |\n", name)
			continue
		}
		surface := summariseComponent(c)
		fmt.Printf("| %s | `internal/agent/component/*.go` | %s |\n", name, surface)
	}

	fmt.Println()
	fmt.Println("## Universe B — eino ReAct tools")
	fmt.Println()
	fmt.Println("| Name | Public surface |")
	fmt.Println("|---|---|")
	for _, name := range sortedToolNames() {
		bt, err := tool.BuildByName(name, map[string]any{})
		if err != nil {
			fmt.Printf("| %s | (build error: %s) |\n", name, err.Error())
			continue
		}
		info, infoErr := bt.Info(context.Background())
		surface := "no Info()"
		if infoErr == nil && info != nil {
			surface = fmt.Sprintf("name=%q desc=%q", info.Name, truncate(info.Desc, 80))
		}
		fmt.Printf("| %s | %s |\n", name, surface)
	}
}

func writeTSV() {
	fmt.Println("universe\tname\tsurface")
	for _, name := range component.RegisteredNames() {
		c, err := component.New(name, map[string]any{})
		if err != nil {
			fmt.Printf("A\t%s\trequires_params\n", name)
			continue
		}
		fmt.Printf("A\t%s\t%s\n", name, summariseComponent(c))
	}
	for _, name := range sortedToolNames() {
		bt, err := tool.BuildByName(name, map[string]any{})
		if err != nil {
			fmt.Printf("B\t%s\tERROR:%s\n", name, err.Error())
			continue
		}
		info, infoErr := bt.Info(context.Background())
		surface := "no Info()"
		if infoErr == nil && info != nil {
			surface = info.Name
		}
		fmt.Printf("B\t%s\t%s\n", name, surface)
	}
}

func summariseComponent(c component.Component) string {
	parts := []string{
		fmt.Sprintf("Name=%q", c.Name()),
	}
	in := c.Inputs()
	out := c.Outputs()
	if len(in) > 0 {
		parts = append(parts, fmt.Sprintf("inputs=%d", len(in)))
	}
	if len(out) > 0 {
		parts = append(parts, fmt.Sprintf("outputs=%d", len(out)))
	}
	return strings.Join(parts, " ")
}

// sortedToolNames mirrors the universe A's RegisteredNames for
// deterministic output. The tool package doesn't expose a public
// registry iteration helper; we use BuildByName's error to probe the
// known list of factory entries. In practice this list is the
// hand-maintained keys in tool/registry.go:registry.
func sortedToolNames() []string {
	known := []string{
		"akshare", "arxiv", "code_exec", "crawler", "deepl", "duckduckgo",
		"email", "execute_sql", "exesql", "github", "google", "google_scholar",
		"jin10", "pubmed", "qweather", "querit", "querit_search", "queritsearch", "retrieval", "search_my_dataset",
		"search_my_dateset", "searxng", "tavily", "tushare", "wencai",
		"web_crawler", "wikipedia", "yahoo_finance",
	}
	sort.Strings(known)
	return known
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n-3] + "..."
}
