package fuzzyfinder

import (
	"context"
	"sync"
)

type opt struct {
	mode          mode
	previewFunc   func(i, width, height int) string
	multi         bool
	hotReload     bool
	hotReloadLock sync.Locker
	promptString  string
	header        string
	beginAtTop    bool
	context       context.Context
	query         string
	selectOne     bool
	preselected   func(i int) bool
}

type mode int

const (
	// ModeSmart enables a smart matching. It is the default matching mode.
	// At the beginning, matching mode is ModeCaseInsensitive, but it switches
	// over to ModeCaseSensitive if an upper case character is inputted.
	ModeSmart mode = iota
	// ModeCaseSensitive enables a case-sensitive matching.
	ModeCaseSensitive
	// ModeCaseInsensitive enables a case-insensitive matching.
	ModeCaseInsensitive
)

var defaultOption = opt{
	promptString:  "> ",
	hotReloadLock: &sync.Mutex{}, // this won't resolve the race condition but avoid nil panic
	preselected:   func(i int) bool { return false },
}

// Option represents available fuzzy-finding options.
type Option func(*opt)

// WithMode specifies a matching mode. The default mode is ModeSmart.
func WithMode(m mode) Option {
	return func(o *opt) {
		o.mode = m
	}
}

// WithPreviewWindow enables to display a preview for the selected item.
// The argument f receives i, width and height. i is the same as Find's one.
// width and height are the size of the terminal so that you can use these to adjust
// a preview content. Note that width and height are calculated as a rune-based length.
//
// If there is no selected item, previewFunc passes -1 to previewFunc.
//
// If f is nil, the preview feature is disabled.
func WithPreviewWindow(f func(i, width, height int) string) Option {
	return func(o *opt) {
		o.previewFunc = f
	}
}

// WithHotReload reloads the passed slice automatically when some entries are appended.
// The caller must pass a pointer of the slice instead of the slice itself.
//
// Deprecated: use WithHotReloadLock instead.
func WithHotReload() Option {
	return func(o *opt) {
		o.hotReload = true
	}
}

// WithHotReloadLock reloads the passed slice automatically when some entries are appended.
// The caller must pass a pointer of the slice instead of the slice itself.
// The caller must pass a RLock which is used to synchronize access to the slice.
// The caller MUST NOT lock in the itemFunc passed to Find / FindMulti because it will be locked by the fuzzyfinder.
// If used together with WithPreviewWindow, the caller MUST use the RLock only in the previewFunc passed to WithPreviewWindow.
func WithHotReloadLock(lock sync.Locker) Option {
	return func(o *opt) {
		o.hotReload = true
		o.hotReloadLock = lock
	}
}

type cursorPosition int

const (
	CursorPositionBottom cursorPosition = iota
	CursorPositionTop
)

// WithCursorPosition sets the initial position of the cursor
//
// If Find is called with WithCursorPosition and WithPreselected, the cursor will be positioned at the first preselected item.
func WithCursorPosition(position cursorPosition) Option {
	return func(o *opt) {
		switch position {
		case CursorPositionTop:
			o.beginAtTop = true
		case CursorPositionBottom:
			o.beginAtTop = false
		}
	}
}

// WithPromptString changes the prompt string. The default value is "> ".
func WithPromptString(s string) Option {
	return func(o *opt) {
		o.promptString = s
	}
}

// withMulti enables to select multiple items by tab key.
func withMulti() Option {
	return func(o *opt) {
		o.multi = true
	}
}

// WithHeader enables to set the header.
func WithHeader(s string) Option {
	return func(o *opt) {
		o.header = s
	}
}

// WithContext enables closing the fuzzy finder from parent.
func WithContext(ctx context.Context) Option {
	return func(o *opt) {
		o.context = ctx
	}
}

// WithQuery enables to set the initial query.
func WithQuery(s string) Option {
	return func(o *opt) {
		o.query = s
	}
}

// WithQuery enables to set the initial query.
func WithSelectOne() Option {
	return func(o *opt) {
		o.selectOne = true
	}
}

// WithPreselected enables to specify which items should be preselected.
// The argument f is a function that returns true for items that should be preselected.
// i is the same index value passed to itemFunc in Find or FindMulti.
// This option is effective in both Find and FindMulti, but in Find mode only
// the first preselected item will be considered.
//
// If Find is called with WithCursorPosition and WithPreselected, the cursor will be positioned at the first preselected item.
func WithPreselected(f func(i int) bool) Option {
	return func(o *opt) {
		o.preselected = f
	}
}
