package context

import (
	"context"
	"time"

	jsoniter "github.com/json-iterator/go"
	"github.com/yaoapp/gou/plan"
	"github.com/yaoapp/kun/log"
)

// Context the context
type Context struct {
	context.Context
	Sid         string `json:"sid" yaml:"-"`           // Session ID
	ChatID      string `json:"chat_id,omitempty"`      // Chat ID, use to select chat
	AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant

	// CUI Context
	Namespace string                 `json:"namespace,omitempty"`
	Stack     string                 `json:"stack,omitempty"`    // will be removed in the future
	Path      string                 `json:"pathname,omitempty"` // wiil be rename to path
	FormData  map[string]interface{} `json:"formdata,omitempty"`
	Field     *Field                 `json:"field,omitempty"`
	Config    map[string]interface{} `json:"config,omitempty"`
	Signal    interface{}            `json:"signal,omitempty"`

	// Locale information
	Locale string `json:"locale,omitempty"` // Locale
	Theme  string `json:"theme,omitempty"`  // Theme

	Silent         bool   `json:"silent,omitempty"`          // Silent mode
	ClientType     string `json:"client_type,omitempty"`     // The request client type. SDK, Desktop, Web, JSSDK, etc. the default is Web
	HistoryVisible bool   `json:"history_visible,omitempty"` // History visible, default is true, if false, the history will not be displayed in the UI
	Retry          bool   `json:"retry,omitempty"`           // Retry mode
	RetryTimes     uint8  `json:"retry_times,omitempty"`     // Retry times

	Vision    bool `json:"vision,omitempty"`    // Vision support
	Search    bool `json:"search,omitempty"`    // Search support
	Knowledge bool `json:"knowledge,omitempty"` // Knowledge support

	Args        []interface{} `json:"args,omitempty"` // Arguments for call
	SharedSpace plan.Space    `json:"-"`              // Shared space
}

// Field the context field
type Field struct {
	Name     string                 `json:"name,omitempty"`
	Type     string                 `json:"type,omitempty"`
	Bind     string                 `json:"bind,omitempty"`
	Props    map[string]interface{} `json:"props,omitempty"`
	Children []interface{}          `json:"children,omitempty"`
}

// FileUpload the file upload
type FileUpload struct {
	Name     string `json:"name,omitempty"`
	Type     string `json:"type,omitempty"`
	Size     int64  `json:"size,omitempty"`
	TempFile string `json:"temp_file,omitempty"`
}

const (

	// ClientTypeAgent is the client type for Agent
	ClientTypeAgent = "agent"

	// ClientTypeWeb is the client type for Web (Default UI)
	ClientTypeWeb = "web"

	// ClientTypeAndroid is the client type for Android
	ClientTypeAndroid = "android"

	// ClientTypeIOS is the client type for IOS
	ClientTypeIOS = "ios"

	// ClientTypeJSSDK is the client type for JSSDK
	ClientTypeJSSDK = "jssdk"

	// ClientTypeMacOS is the client type for MacOS Desktop
	ClientTypeMacOS = "macos"

	// ClientTypeWindows is the client type for Windows Desktop
	ClientTypeWindows = "windows"

	// ClientTypeLinux is the client type for Linux Desktop
	ClientTypeLinux = "linux"
)

// SupportedClientTypes is the supported client types
var SupportedClientTypes = map[string]bool{
	ClientTypeAgent:   true,
	ClientTypeWeb:     true,
	ClientTypeAndroid: true,
	ClientTypeIOS:     true,
	ClientTypeJSSDK:   true,
	ClientTypeMacOS:   true,
	ClientTypeWindows: true,
	ClientTypeLinux:   true,
}

// New create a new context
func New(sid, cid, payload string) Context {

	// Validate the client type
	ctx := Context{
		Context:        context.Background(),
		SharedSpace:    plan.NewMemorySharedSpace(),
		Sid:            sid,
		ChatID:         cid,
		HistoryVisible: true,
		ClientType:     ClientTypeWeb,
		Silent:         false,
	}

	if payload == "" {
		return ctx
	}

	err := jsoniter.Unmarshal([]byte(payload), &ctx)
	if err != nil {
		log.Error("%s", err.Error())
	}

	return ctx
}

// NewWithCancel create a new context with cancel
func NewWithCancel(sid, cid, payload string) (Context, context.CancelFunc) {
	ctx := New(sid, cid, payload)
	return WithCancel(ctx)
}

// WithAssistantID set the assistant ID
func WithAssistantID(ctx Context, assistantID string) Context {
	ctx.AssistantID = assistantID
	return ctx
}

// WithSilent set the silent mode
func WithSilent(ctx Context, silent bool) Context {
	ctx.Silent = silent
	return ctx
}

// WithClientType set the client type
func WithClientType(ctx Context, clientType string) Context {
	// Validate the client type
	if !SupportedClientTypes[clientType] {
		log.Error("[Neo] Invalid client type: %s", clientType)
		return ctx
	}
	ctx.ClientType = clientType
	return ctx
}

// WithLocale set the locale
func WithLocale(ctx Context, locale string) Context {
	ctx.Locale = locale
	return ctx
}

// WithHistoryVisible set the history visible
func WithHistoryVisible(ctx Context, historyVisible bool) Context {
	ctx.HistoryVisible = historyVisible
	return ctx
}

// NewWithTimeout create a new context with timeout
func NewWithTimeout(sid, cid, payload string, timeout time.Duration) (Context, context.CancelFunc) {
	ctx := New(sid, cid, payload)
	return WithTimeout(ctx, timeout)
}

// WithCancel create a new context
func WithCancel(parent Context) (Context, context.CancelFunc) {
	new, cancel := context.WithCancel(parent.Context)
	parent.Context = new
	return parent, cancel
}

// WithTimeout create a new context
func WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) {
	new, cancel := context.WithTimeout(parent.Context, timeout)
	parent.Context = new
	return parent, cancel
}

// Release the context
func (ctx *Context) Release() {
	ctx.SharedSpace.Clear()
	ctx.SharedSpace = nil
	ctx = nil
}

// Map the context to a map
func (ctx *Context) Map() map[string]interface{} {
	data := map[string]interface{}{
		"sid":       ctx.Sid,
		"knowledge": ctx.Knowledge,
		"vision":    ctx.Vision,
		"search":    ctx.Search,
	}

	if ctx.ChatID != "" {
		data["chat_id"] = ctx.ChatID
	}
	if ctx.AssistantID != "" {
		data["assistant_id"] = ctx.AssistantID
	}
	if ctx.Stack != "" {
		data["stack"] = ctx.Stack
	}

	// Silent mode
	if ctx.Silent {
		data["silent"] = ctx.Silent
	}

	// History visible
	data["history_visible"] = ctx.HistoryVisible

	// Client type
	data["client_type"] = ctx.ClientType

	// Retry mode
	if ctx.Retry {
		data["retry"] = ctx.Retry
	}

	// Arguments for call
	if ctx.Args != nil && len(ctx.Args) > 0 {
		data["args"] = ctx.Args
	}

	// Retry times
	data["retry_times"] = ctx.RetryTimes

	if ctx.Path != "" {
		data["pathname"] = ctx.Path
	}
	if len(ctx.FormData) > 0 {
		data["formdata"] = ctx.FormData
	}
	if ctx.Field != nil {
		data["field"] = ctx.Field
	}
	if ctx.Namespace != "" {
		data["namespace"] = ctx.Namespace
	}
	if len(ctx.Config) > 0 {
		data["config"] = ctx.Config
	}
	if ctx.Signal != nil {
		data["signal"] = ctx.Signal
	}

	// Locale
	data["locale"] = ctx.Locale

	// Theme
	data["theme"] = ctx.Theme

	return data
}
