package cmdpacks

import (
	"context"

	tea "github.com/charmbracelet/bubbletea"
	"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
	"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
	"ucl.lmika.dev/ucl"
)

type uiModule struct {
	tableService   *tables.Service
	state          *controllers.State
	ckb            *customKeyBinding
	readController *controllers.TableReadController
}

func (m *uiModule) uiCommand(ctx context.Context, args ucl.CallArgs) (any, error) {
	var (
		name string
		cmd  ucl.Invokable
	)
	if err := args.Bind(&name, &cmd); err != nil {
		return nil, err
	}

	invoker := commandctrl.GetInvoker(ctx)
	invoker.Inst().SetBuiltinInvokable(name, cmd)
	return nil, nil
}

func (m *uiModule) uiPrompt(ctx context.Context, args ucl.CallArgs) (any, error) {
	var prompt string
	if err := args.Bind(&prompt); err != nil {
		return nil, err
	}

	resChan := make(chan string)
	cancelChan := make(chan struct{})
	go func() {
		commandctrl.PostMsg(ctx, events.PromptForInputMsg{
			Prompt: prompt,
			OnDone: func(value string) tea.Msg {
				resChan <- value
				return nil
			},
			OnCancel: func() tea.Msg {
				cancelChan <- struct{}{}
				return nil
			},
		})
	}()

	select {
	case value := <-resChan:
		return value, nil
	case <-cancelChan:
		return nil, nil
	case <-ctx.Done():
		return nil, ctx.Err()
	}
}

func (m *uiModule) uiConfirm(ctx context.Context, args ucl.CallArgs) (any, error) {
	var prompt string
	if err := args.Bind(&prompt); err != nil {
		return nil, err
	}

	resChan := make(chan bool)
	go func() {
		commandctrl.PostMsg(ctx, events.Confirm(prompt, func(value bool) tea.Msg {
			resChan <- value
			return nil
		}))
	}()

	select {
	case value := <-resChan:
		return value, nil
	case <-ctx.Done():
		return nil, ctx.Err()
	}
}

func (m *uiModule) uiInKey(ctx context.Context, args ucl.CallArgs) (any, error) {
	var prompt string
	if err := args.Bind(&prompt); err != nil {
		return nil, err
	}

	resChan := make(chan string)
	cancelChan := make(chan struct{})
	go func() {
		commandctrl.PostMsg(ctx, events.PromptForKeyMsg{
			Prompt: prompt,
			OnDone: func(value string) tea.Msg {
				resChan <- value
				return nil
			},
			OnCancel: func() tea.Msg {
				cancelChan <- struct{}{}
				return nil
			},
		})
	}()

	select {
	case value := <-resChan:
		return value, nil
	case <-cancelChan:
		return nil, nil
	case <-ctx.Done():
		return nil, ctx.Err()
	}
}

func (m *uiModule) uiPromptTable(ctx context.Context, args ucl.CallArgs) (any, error) {
	tables, err := m.tableService.ListTables(context.Background())
	if err != nil {
		return nil, err
	}

	resChan := make(chan string)
	go func() {
		commandctrl.PostMsg(ctx, controllers.PromptForTableMsg{
			Tables: tables,
			OnSelected: func(tableName string) tea.Msg {
				resChan <- tableName
				return nil
			},
		})
	}()

	select {
	case value := <-resChan:
		return value, nil
	case <-ctx.Done():
		return nil, ctx.Err()
	}
}

func (m *uiModule) uiBind(ctx context.Context, args ucl.CallArgs) (any, error) {
	var (
		bindName string
		key      string
		inv      ucl.Invokable
	)

	if args.NArgs() == 2 {
		if err := args.Bind(&key, &inv); err != nil {
			return nil, err
		}
		bindName = "custom." + key
	} else {
		if err := args.Bind(&bindName, &key, &inv); err != nil {
			return nil, err
		}
	}

	invoker := commandctrl.GetInvoker(ctx)

	m.ckb.bindings[bindName] = func() tea.Msg {
		return invoker.Invoke(inv, nil)
	}
	m.ckb.keyBindings[key] = bindName
	return nil, nil
}

func (m *uiModule) uiQuery(ctx context.Context, args ucl.CallArgs) (any, error) {
	q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService)
	if err != nil {
		return nil, err
	}

	commandctrl.PostMsg(ctx, m.readController.RunQuery(q, tableInfo))
	return nil, nil
}

func (m *uiModule) uiFilter(ctx context.Context, args ucl.CallArgs) (any, error) {
	var filter string

	if err := args.Bind(&filter); err != nil {
		return nil, err
	}

	commandctrl.PostMsg(ctx, m.readController.Filter(filter))
	return nil, nil
}

func moduleUI(
	tableService *tables.Service,
	state *controllers.State,
	readController *controllers.TableReadController,
) (ucl.Module, controllers.CustomKeyBindingSource) {
	m := &uiModule{
		tableService:   tableService,
		state:          state,
		readController: readController,
		ckb: &customKeyBinding{
			bindings:    map[string]tea.Cmd{},
			keyBindings: map[string]string{},
		},
	}

	return ucl.Module{
		Name: "ui",
		Builtins: map[string]ucl.BuiltinHandler{
			"command":         m.uiCommand,
			"prompt":          m.uiPrompt,
			"prompt-table":    m.uiPromptTable,
			"prompt-keypress": m.uiInKey,
			"confirm":         m.uiConfirm,
			"query":           m.uiQuery,
			"filter":          m.uiFilter,
			"bind":            m.uiBind,
		},
	}, m.ckb
}

type customKeyBinding struct {
	bindings    map[string]tea.Cmd
	keyBindings map[string]string
}

func (c *customKeyBinding) LookupBinding(theKey string) string {
	return c.keyBindings[theKey]
}

func (c *customKeyBinding) CustomKeyCommand(key string) tea.Cmd {
	bindingName, ok := c.keyBindings[key]
	if !ok {
		return nil
	}

	binding, ok := c.bindings[bindingName]
	if !ok {
		return nil
	}

	return binding
}

func (c *customKeyBinding) UnbindKey(key string) {
	delete(c.keyBindings, key)
}

func (c *customKeyBinding) Rebind(bindingName string, newKey string) error {
	c.keyBindings[newKey] = bindingName
	return nil
}
