// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

package shortcuts

import (
	"errors"
	"fmt"
	"strconv"
	"sync"

	"github.com/linuxdeepin/dde-daemon/keybinding/util"
	"github.com/linuxdeepin/go-lib/pinyin_search"
)

// BaseShortcut is the base struct for all shortcut types
type BaseShortcut struct {
	mu             sync.Mutex       // Mutex for concurrent safety
	Id             string           // Shortcut ID
	Type           int32            // Shortcut type
	Keystrokes     []*Keystroke `json:"Accels"` // Key sequence
	Name           string           // Shortcut name
	nameBlocksInit bool             // Flag if name blocks are initialized
	nameBlocks     pinyin_search.Blocks // Pinyin blocks for name search
}

// String returns the string representation of BaseShortcut
func (sb *BaseShortcut) String() string {
	sb.mu.Lock()
	str := fmt.Sprintf("Shortcut{id=%s type=%d name=%q keystrokes=%v}", sb.Id, sb.Type, sb.Name, sb.Keystrokes)
	sb.mu.Unlock()
	return str
}

// Marshal serializes BaseShortcut to JSON string
func (sb *BaseShortcut) Marshal() (string, error) {
	sb.mu.Lock()
	defer sb.mu.Unlock()
	return util.MarshalJSON(sb)
}

// GetId returns the shortcut ID
func (sb *BaseShortcut) GetId() string {
	return sb.Id
}

// idType2Uid generates unique ID from ID and type
func idType2Uid(id string, type0 int32) string {
	return strconv.Itoa(int(type0)) + id
}

// GetUid returns the unique ID for the shortcut
func (sb *BaseShortcut) GetUid() string {
	return idType2Uid(sb.Id, sb.Type)
}

// GetKeystrokes returns the key sequence
func (sb *BaseShortcut) GetKeystrokes() []*Keystroke {
	sb.mu.Lock()
	defer sb.mu.Unlock()
	return sb.Keystrokes
}

// getKeystrokesStrv returns key sequence as string array
func (sb *BaseShortcut) getKeystrokesStrv() []string {
	keystrokes := sb.GetKeystrokes()
	strv := make([]string, len(keystrokes))
	for i, k := range keystrokes {
		strv[i] = k.String()
	}
	return strv
}

// setKeystrokes sets the key sequence
func (sb *BaseShortcut) setKeystrokes(val []*Keystroke) {
	sb.mu.Lock()
	sb.Keystrokes = val
	sb.mu.Unlock()
}

// GetType returns the shortcut type
func (sb *BaseShortcut) GetType() int32 {
	return sb.Type
}

// GetName returns the shortcut name
func (sb *BaseShortcut) GetName() string {
	return sb.Name
}

// GetAction returns the associated action (defaults to no-op)
func (sb *BaseShortcut) GetAction() *Action {
	return ActionNoOp
}

// GetKeystrokesModifiable returns if keystrokes can be modified
func (sb *BaseShortcut) GetKeystrokesModifiable() bool {
	return sb.Type != ShortcutTypeFake
}

// ShouldEmitSignalChanged returns if change signal should be emitted
func (sb *BaseShortcut) ShouldEmitSignalChanged() bool {
	return false
}

// GetNameBlocks returns pinyin blocks for name search
func (sb *BaseShortcut) GetNameBlocks() pinyin_search.Blocks {
	if sb.nameBlocksInit {
		return sb.nameBlocks
	}
	sb.initNameBlocks()
	return sb.nameBlocks
}

// initNameBlocks initializes pinyin blocks for name
func (sb *BaseShortcut) initNameBlocks() {
	name := sb.GetName()
	sb.nameBlocks = pinyin_search.Split(name)
	sb.nameBlocksInit = true
}

// Shortcut type constants
const (
	ShortcutTypeSystem int32 = iota // System shortcut
	ShortcutTypeCustom              // Custom shortcut
	ShortcutTypeMedia               // Media shortcut
	ShortcutTypeWM                  // Window manager shortcut
	ShortcutTypeFake                // Fake shortcut
)

type Shortcut interface {
	GetId() string
	GetUid() string
	GetType() int32

	GetName() string
	GetNameBlocks() pinyin_search.Blocks

	GetKeystrokesModifiable() bool
	getKeystrokesStrv() []string
	GetKeystrokes() []*Keystroke
	setKeystrokes([]*Keystroke)
	SaveKeystrokes() error
	ReloadKeystrokes() bool

	GetAction() *Action
	Marshal() (string, error)
	ShouldEmitSignalChanged() bool
}

// Common shortcut errors
var ErrOpNotSupported = errors.New("operation is not supported")
var ErrTypeAssertionFail = errors.New("type assertion failed")
var ErrNilAction = errors.New("action is nil")
var ErrInvalidActionType = errors.New("invalid action type")

// FakeShortcut represents a non-persistent shortcut
type FakeShortcut struct {
	BaseShortcut
	action *Action // Associated action
}

// NewFakeShortcut creates a new FakeShortcut
func NewFakeShortcut(action *Action) *FakeShortcut {
	return &FakeShortcut{
		BaseShortcut: BaseShortcut{
			Type: ShortcutTypeFake,
		},
		action: action,
	}
}

// GetAction returns the associated action
func (s *FakeShortcut) GetAction() *Action {
	return s.action
}

// SaveKeystrokes is not supported for fake shortcuts
func (s *FakeShortcut) SaveKeystrokes() error {
	return ErrOpNotSupported
}

// ReloadKeystrokes is not supported for fake shortcuts
func (s *FakeShortcut) ReloadKeystrokes() bool {
	return false
}
