// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
package tools

import (
	"embed"
	"fmt"
	"log"
	"os"
	"runtime"
	"strings"

	gojq "github.com/itchyny/gojq/cli"
	envsubst "github.com/nuvolaris/envsubst/cmd/envsubstmain"
	replace "github.com/nuvolaris/go-replace"
	"github.com/nuvolaris/goawk"
	"github.com/nuvolaris/gron"
	"github.com/nuvolaris/jj"
	"golang.org/x/exp/slices"
)

//go:embed *.md
var markDownHelp embed.FS

var tracing = os.Getenv("TRACE") != ""

func trace(args ...any) {
	if tracing {
		log.Println(append([]any{"TRACE: "}, args...))
	}
}

// shared with main
func GetOS() string {
	res := os.Getenv("__OS")
	if res == "" {
		res = runtime.GOOS
	}
	return res
}

func GetARCH() string {
	res := os.Getenv("__ARCH")
	if res == "" {
		res = runtime.GOARCH
	}
	return res
}

type Tool struct {
	Name   string
	HasDoc bool
}

// available in taskfiles
// note some of them are implemented in main.go (config, retry)
// Note the comment with @DOC which is used to generate the list of tools in documentation
// put one here if you add a markdown file for a tool
var ToolList = []Tool{
	{"wsk", false},
	{"awk", false},
	{"jq", false},
	{"sh", true}, // @DOC
	{"envsubst", false},
	{"filetype", true}, // @DOC
	{"random", true},   // @DOC
	{"datefmt", true},  // @DOC
	{"die", false},
	{"urlenc", true}, // @DOC
	{"replace", false},
	{"base64", true},       // @DOC
	{"validate", true},     // @DOC
	{"echoif", true},       // @DOC
	{"echoifempty", true},  // @DOC
	{"echoifexists", true}, // @DOC
	{"needupdate", true},   // @DOC
	{"gron", false},
	{"jj", false},
	{"rename", true},     // @DOC
	{"remove", true},     // @DOC
	{"executable", true}, // @DOC
	{"empty", true},      // @DOC
	{"extract", true},    // @DOC
	{"opspath", true},    // @DOC
}

func IsTool(name string) bool {
	for _, s := range ToolList {
		if s.Name == name {
			return true
		}
	}
	return false
}

func RunTool(name string, args []string) (int, error) {
	switch name {
	case "wsk":
		//fmt.Println("=== wsk ===")
		cmd := append([]string{"wsk"}, args...)
		if err := Wsk(cmd); err != nil {
			return 1, err
		}

	case "awk":
		// fmt.Println("=== awk ===")
		os.Args = append([]string{"goawk"}, args...)
		if err := goawk.AwkMain(); err != nil {
			return 1, err
		}

	case "jq":
		os.Args = append([]string{"gojq"}, args...)
		return gojq.Run(), nil

	case "sh":
		os.Args = append([]string{"sh"}, args...)
		return Sh()

	case "envsubst":
		os.Args = append([]string{"envsubst"}, args...)
		if err := envsubst.EnvsubstMain(); err != nil {
			return 1, err
		}

	case "filetype":
		os.Args = append([]string{"mkdir"}, args...)
		if err := Filetype(); err != nil {
			return 1, err
		}

	case "random":
		if err := RandTool(args...); err != nil {
			return 1, err
		}

	case "datefmt":
		if err := DateFmtTool(append([]string{"datefmt"}, args...)); err != nil {
			return 1, err
		}

	case "die":
		if len(args) > 0 {
			fmt.Println(strings.Join(args, " "))
		}
		return 1, nil

	case "urlenc":
		os.Args = append([]string{"urlenc"}, args...)
		if err := URLEncTool(); err != nil {
			return 1, err
		}

	case "replace":
		os.Args = append([]string{"replace"}, args...)
		return replace.ReplaceMain()

	case "base64":
		os.Args = append([]string{"base64"}, args...)
		if err := base64Tool(); err != nil {
			return 1, err
		}

	case "validate":
		os.Args = append([]string{"validate"}, args...)
		if err := validateTool(); err != nil {
			return 1, err
		}

	case "echoif":
		os.Args = append([]string{"echoif"}, args...)
		if err := echoIfTool(); err != nil {
			return 1, err
		}

	case "echoifempty":
		os.Args = append([]string{"echoifempty"}, args...)
		if err := echoIfEmptyTool(); err != nil {
			return 1, err
		}

	case "echoifexists":
		os.Args = append([]string{"echoifexists"}, args...)
		if err := echoIfExistsTool(); err != nil {
			return 1, err
		}

	case "needupdate":
		if err := needUpdateTool(args); err != nil {
			return 1, err
		}

	case "gron":
		os.Args = append([]string{"gron"}, args...)
		return gron.GronMain()

	case "jj":
		os.Args = append([]string{"jj"}, args...)
		return jj.JJMain()

	case "rename":
		os.Args = append([]string{"rename"}, args...)
		return Rename()

	case "remove":
		os.Args = append([]string{"remove"}, args...)
		return Remove()

	case "executable":
		os.Args = append([]string{"executable"}, args...)
		return Executable()

	case "extract":
		os.Args = append([]string{"extract"}, args...)
		return Extract()

	case "empty":
		os.Args = append([]string{"empty"}, args...)
		return Empty()
	case "opspath":
		os.Args = append([]string{"opspath"}, args...)
		return OpsPath()

	default:
		return 1, fmt.Errorf("unknown tool")
	}
	return 0, nil
}

func MergeToolsList(mainTools []string) []string {
	availableTools := mainTools
	for _, tool := range ToolList {
		availableTools = append(availableTools, tool.Name)
	}
	return availableTools
}

func Help(mainTools []string) {
	fmt.Println("Tools (use -<tool> -h for help):")
	availableTools := MergeToolsList(mainTools)
	slices.Sort(availableTools)
	for _, x := range availableTools {
		fmt.Printf("-%s\n", x)
	}
}

func GetMarkDown(toolName string) (string, error) {
	// extract markdown from embedded resource
	markDownFile := fmt.Sprintf("%s.md", toolName)
	fileData, err := markDownHelp.ReadFile(markDownFile)
	if err != nil {
		return "", err
	}
	return string(fileData), nil
}

func MarkdownHelp(toolName string) string {
	// extract markdown from embedded resource
	fileData, err := GetMarkDown(toolName)
	if err != nil {
		return ""
	}
	result := string(fileData)
	// convert to markdown
	help := MarkdownToText(result)

	// return opts and help markdown
	return help
}
