package main

import (
	"context"
	"fmt"
	"log"
	"sort"
	"strings"

	"github.com/wailsapp/wails/v2/pkg/runtime"
	"lmika.dev/pkg/modash/moslice"
	"ucl.lmika.dev/ucl"
	"ucl.lmika.dev/ucl/builtins"
)

// App struct
type App struct {
	store   *Store
	uclInst *ucl.Inst

	ctx context.Context
}

// NewApp creates a new App application struct
func NewApp(store *Store) *App {
	uclInst := ucl.New(
		ucl.WithModule(builtins.CSV(nil)),
		ucl.WithModule(builtins.Fns()),
		ucl.WithModule(builtins.FS(nil)),
		ucl.WithModule(builtins.Itrs()),
		ucl.WithModule(builtins.Lists()),
		ucl.WithModule(builtins.Log(nil)),
		ucl.WithModule(builtins.OS()),
		ucl.WithModule(builtins.Strs()),
		ucl.WithModule(builtins.Time()),
	)
	return &App{
		store:   store,
		uclInst: uclInst,
	}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = context.WithValue(ctx, uclInstKey, a.uclInst)
}

func (a *App) LoadCurrentBuffer() (string, error) {
	return a.store.LoadBuffer()
}

func (a *App) SaveCurrentBuffer(buffer string) error {
	return a.store.SaveBuffer(buffer)
}

func (a *App) ListProcessors() (resp []ListProcessorsResponse) {
	for k, v := range TextFilters {
		resp = append(resp, ListProcessorsResponse{Name: k, Label: v.Label})
	}
	sort.Slice(resp, func(i, j int) bool { return resp[i].Label < resp[j].Label })
	return resp
}

func (a *App) TriggerTextProcess(action string) {
	runtime.EventsEmit(a.ctx, "request-text-process", RequestTextProcess{
		Action: action,
	})
}

func (a *App) PromptUser(label string, resp func(string)) {
	runtime.EventsOnce(a.ctx, "prompt-response", func(data ...interface{}) {
		if len(data) == 0 {
			return
		}
		ans, ok := data[0].(string)
		if !ok {
			return
		}
		resp(ans)
	})
	runtime.EventsEmit(a.ctx, "prompt-request", PromptRequest{
		Label: label,
	})
}

func (a *App) ProcessText(req ProcessTextRequest) {
	filter, ok := TextFilters[req.Action]
	if !ok {
		log.Printf("Unknown filter: [%s]", req.Action)
		return
	}

	applyFilter := func(filter TextFilter) {
		resp, err := moslice.MapWithError(req.Input, func(span TextSpan) (TextSpan, error) {
			outRes, err := filter(a.ctx, span.Text)
			if err != nil {
				return TextSpan{}, err
			}

			return TextSpan{
				Text:   outRes.Output,
				Pos:    span.Pos,
				Len:    span.Len,
				Append: outRes.Append,
			}, nil
		})
		if err != nil {
			runtime.EventsEmit(a.ctx, "set-statusbar-message", SetStatusbarMessage{
				Message: fmt.Sprintf("Error running filter: %v", err.Error()),
				Error:   true,
			})
			return
		}

		runtime.EventsEmit(a.ctx, "process-text-response", ProcessTextResponse{
			Output: resp,
		})
	}

	switch {
	case filter.Analyze != nil:
		inBfr := strings.Builder{}
		for _, span := range req.Input {
			if inBfr.Len() > 0 {
				inBfr.WriteString("\n")
			}
			inBfr.WriteString(span.Text)
		}

		msg, err := filter.Analyze(a.ctx, inBfr.String())
		if err != nil {
			runtime.EventsEmit(a.ctx, "set-statusbar-message", SetStatusbarMessage{
				Message: fmt.Sprintf("Error running analysis: %v", err.Error()),
				Error:   true,
			})
			return
		}

		runtime.EventsEmit(a.ctx, "set-statusbar-message", SetStatusbarMessage{
			Message: msg,
		})
	case filter.Filter != nil:
		applyFilter(filter.Filter)
	case filter.FilterWithArg != nil:
		a.PromptUser(filter.FilterWithArg.Label, func(ans string) {
			filter, err := filter.FilterWithArg.OnConfirm(a.ctx, ans)
			if err != nil {
				runtime.EventsEmit(a.ctx, "set-statusbar-message", SetStatusbarMessage{
					Message: fmt.Sprintf("Error running filter: %v", err.Error()),
					Error:   true,
				})
				return
			}
			applyFilter(filter)
		})
	}
}
