package entkit

import (
	"embed"
	"entgo.io/contrib/entgql"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
	"github.com/Masterminds/sprig/v3"
	"text/template"
)

const AnnotationKey = "ENTKIT"

var (
	//go:embed templates/*
	_templates embed.FS
)

// ExtensionOption is a functional option for Extension
type ExtensionOption = func(*Extension) error

// Extension main struct
type Extension struct {
	entc.DefaultExtension
	IgnoreUncommittedChanges *bool   // Ignore uncommitted changes warning message
	GraphqlURL               *string // Graphql URL
	Prefix                   *string // General prefix for all generated resources

	GoJs                GoJSOptions         // TODO: must be moved
	ForceGraph2D        ForceGraph2DOptions // TODO: must be moved
	DefaultEdgesDiagram string              // TODO: must be moved

	Auth        *Auth
	Generators  []*Generator
	Environment *Environment
	Meta        map[string]any // Meta to share with frontend application

	FileHeader *string

	funcMap template.FuncMap
}

type GoJSOptions struct {
	Enabled    bool   `json:"Enabled,omitempty"`
	LicenseKey string `json:"LicenseKey,omitempty"`
}

type ForceGraph2DOptions struct {
	Enabled bool `json:"Enabled,omitempty"`
}

// WithGenerator add generator to extension
func WithGenerator(name string, adapter GeneratorAdapter, options ...GeneratorOption) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.Generators = append(ex.Generators, NewGenerator(ex, name, adapter, options...))
		return nil
	}
}

// IgnoreUncommittedChanges ignore uncommitted changes warning message
func IgnoreUncommittedChanges() ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.IgnoreUncommittedChanges = BoolP(true)
		return nil
	}
}

// WithGraphqlURL define graphql server url
func WithGraphqlURL(url string) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.GraphqlURL = StringP(url)
		WithMeta("GraphqlURL", url)
		return nil
	}
}

// WithPrefix define typescript types/vars prefix
func WithPrefix(prefix string) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.Prefix = StringP(prefix)
		return nil
	}
}

// WithAuth configure authentication and authorization
func WithAuth(options ...AuthOption) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.Auth = NewAuth(ex, options...)
		return nil
	}
}

// WithDefaultEdgesDiagram set default edges graph/diagram view component name
func WithDefaultEdgesDiagram(name string) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.DefaultEdgesDiagram = name
		return nil
	}
}

// WithGoJs use gojs for edges diagrams
func WithGoJs(options GoJSOptions) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.GoJs = options
		return nil
	}
}

// WithForceGraph2D use react-force-graph-2d for edges diagrams
func WithForceGraph2D(options ForceGraph2DOptions) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.ForceGraph2D = options
		return nil
	}
}

// WithMeta add metadata to `{AppPath}/entkit.json`
func WithMeta(key string, value any) ExtensionOption {
	return func(ex *Extension) (err error) {
		ex.Meta[key] = value
		return nil
	}
}

// NewExtension initialize extension
func NewExtension(opts ...ExtensionOption) (*Extension, error) {
	ex := &Extension{
		Meta:                map[string]any{},
		Prefix:              StringP("Ent"),
		DefaultEdgesDiagram: "Diagram.GoJS",
		GoJs: GoJSOptions{
			Enabled:    true,
			LicenseKey: "test",
		},
		Generators: []*Generator{},
		FileHeader: StringP(`^ Code generated by EntKit. DO NOT EDIT.
^ ---------------------------------------------------------
^
^ Copyright (C) 2023 EntKit. All Rights Reserved.
^
^ This code is part of the EntKit library and is generated
^ automatically to ensure optimal functionality and maintainability.
^ Any changes made directly to this file may be overwritten
^ by future code generation, leading to unexpected behavior.
^
^ Please refer to the EntKit documentation for instructions on
^ how to modify the library, extend its functionality or contribute
^ to the project: https://entkit.com
^ ---------------------------------------------------------`),
		funcMap: template.FuncMap{},
	}

	ex.Auth = NewAuth(ex)

	_funcMap := template.FuncMap{
		"ER_label":                  ex.ToLabel,
		"ER_fieldTSType":            ex.FieldTSType,
		"ER_tsType":                 ex.TsType,
		"ER_someField":              ex.someField,
		"ER_titleField":             ex.TitleField,
		"ER_someNode":               ex.someNode,
		"ER_indexNode":              ex.IndexNode,
		"ER_mainImageField":         ex.MainImageField,
		"ER_getActionByName":        ex.GetActionByName,
		"ER_prepareName":            ex.PrepareName,
		"ER_replace":                ex.Replace,
		"ER_getNodeAction":          ex.GetNodeAction,
		"ER_nodeActionRoutePattern": ex.NodeActionRoutePattern,
		"ER_nodeConfig":             ex.GetNodeAnnotations,
		"ER_fieldConfig":            ex.GetFieldAnnotations,
		"ER_edgeConfig":             ex.GetEdgeAnnotations,
	}

	if len(ex.funcMap) == 0 {
		for k, v := range _funcMap {
			ex.funcMap[k] = v
		}

		for k, v := range sprig.FuncMap() {
			ex.funcMap[k] = v
		}

		for k, v := range gen.Funcs {
			ex.funcMap[k] = v
		}

		for k, v := range entgql.TemplateFuncs {
			ex.funcMap[k] = v
		}
	}

	for _, opt := range opts {
		if err := opt(ex); err != nil {
			return nil, err
		}
	}

	ex.Environment = &Environment{
		Meta:       ex.Meta,
		GraphqlURL: PString(ex.GraphqlURL),
		Auth:       ex.Auth.GetEnvironmentConfig(),
		AppPath:    "/",
	}

	return ex, nil
}

// Annotations Define Ent annotations
type Annotations struct {
	Prefix string
	Auth   *Auth
}

// Name of the annotation. Used by the codegen templates.
func (Annotations) Name() string {
	return "ENTKIT"
}

// Annotations Define Ent annotations
func (ex *Extension) Annotations() []entc.Annotation {
	return []entc.Annotation{
		Annotations{
			Prefix: PString(ex.Prefix),
			Auth:   ex.Auth,
		},
	}
}

// Templates Define Ent templates
func (ex *Extension) Templates() []*gen.Template {
	return []*gen.Template{
		gen.MustParse(gen.NewTemplate("search_query_apply").
			Funcs(ex.funcMap).
			ParseFS(_templates, "templates/search_query_apply.tmpl")),
		gen.MustParse(gen.NewTemplate("auth").
			Funcs(ex.funcMap).
			ParseFS(_templates, "templates/auth.tmpl")),
	}
}

// Hooks Define Ent hooks
func (ex *Extension) Hooks() []gen.Hook {
	return []gen.Hook{
		GeneratorHook(ex),
		GenerateAuthResourcesHook(ex),
	}
}
