1package app23import (4 "strings"56 "github.com/FelineStateMachine/puzzletea/puzzle"7 "github.com/FelineStateMachine/puzzletea/registry"8 "github.com/FelineStateMachine/puzzletea/theme"9 "github.com/FelineStateMachine/puzzletea/ui"1011 "charm.land/bubbles/v2/textinput"12 tea "charm.land/bubbletea/v2"13 "charm.land/lipgloss/v2"14)1516const randomSeedModeLabel = "Random"1718func buildSeedModeOptions(definitions []puzzle.Definition) []seedModeOption {19 options := []seedModeOption{{label: randomSeedModeLabel}}2021 for _, def := range definitions {22 if !definitionHasSeededModes(def) {23 continue24 }2526 for _, mode := range def.Modes {27 if !mode.Seeded {28 continue29 }3031 options = append(options, seedModeOption{32 key: seedModeKey(def.Name, ""),33 label: def.Name,34 gameType: def.Name,35 })36 break37 }38 }3940 return options41}4243func seedModeKey(gameType, modeTitle string) string {44 if modeTitle == "" {45 return puzzle.NormalizeName(gameType)46 }47 return puzzle.NormalizeName(gameType) + "::" + puzzle.NormalizeName(modeTitle)48}4950func definitionHasSeededModes(def puzzle.Definition) bool {51 for _, mode := range def.Modes {52 if mode.Seeded {53 return true54 }55 }56 return false57}5859func findSeedModeIndex(options []seedModeOption, key string) int {60 if key == "" {61 return 062 }63 for i := range options {64 if options[i].key == key {65 return i66 }67 }68 return 069}7071func (m model) enterSeedInputView() (model, tea.Cmd) {72 ti := textinput.New()73 ti.Placeholder = "any word or phrase"74 ti.CharLimit = 6475 ti.SetWidth(min(m.width, 48))7677 options := buildSeedModeOptions(registry.Definitions())78 index := findSeedModeIndex(options, m.seed.lastModeKey)7980 m.seed.input = ti81 m.seed.modeOptions = options82 m.seed.modeIndex = index83 m.seed.focus = seedFocusText84 m.state = seedInputView85 return m, m.seed.input.Focus()86}8788func (m model) currentSeedMode() seedModeOption {89 if len(m.seed.modeOptions) == 0 {90 return seedModeOption{label: randomSeedModeLabel}91 }92 if m.seed.modeIndex < 0 || m.seed.modeIndex >= len(m.seed.modeOptions) {93 return m.seed.modeOptions[0]94 }95 return m.seed.modeOptions[m.seed.modeIndex]96}9798func (m model) moveSeedMode(step int) model {99 if len(m.seed.modeOptions) == 0 {100 return m101 }102103 index := m.seed.modeIndex + step104 for index < 0 {105 index += len(m.seed.modeOptions)106 }107 m.seed.modeIndex = index % len(m.seed.modeOptions)108 m.seed.lastModeKey = m.currentSeedMode().key109 return m110}111112func (m model) setSeedFocus(focus seedInputFocus) (model, tea.Cmd) {113 m.seed.focus = focus114 if focus == seedFocusText {115 return m, m.seed.input.Focus()116 }117 m.seed.input.Blur()118 return m, nil119}120121func (m model) handleSeedInputUpdate(msg tea.Msg) (model, tea.Cmd) {122 keyMsg, ok := msg.(tea.KeyPressMsg)123 if ok {124 switch keyMsg.String() {125 case "up", "k":126 return m.setSeedFocus(seedFocusText)127 case "down", "j":128 return m.setSeedFocus(seedFocusMode)129 case "left", "h":130 if m.seed.focus == seedFocusMode {131 return m.moveSeedMode(-1), nil132 }133 case "right", "l":134 if m.seed.focus == seedFocusMode {135 return m.moveSeedMode(1), nil136 }137 }138 }139140 if m.seed.focus != seedFocusText {141 return m, nil142 }143144 var cmd tea.Cmd145 m.seed.input, cmd = m.seed.input.Update(msg)146 return m, cmd147}148149func (m model) seedInputBody() string {150 selector := renderSeedModeTitle(m.currentSeedMode().label, m.seed.modeIndex)151 modePrefix := " "152 if m.seed.focus == seedFocusMode {153 modePrefix = ui.CursorStyle().Render("> ")154 }155156 seedInputView := m.seed.input.View()157 if m.seed.focus != seedFocusText {158 seedInputView = strings.Replace(seedInputView, ">", " ", 1)159 }160161 return lipgloss.JoinVertical(162 lipgloss.Left,163 seedInputView,164 "",165 modePrefix+selector,166 )167}168169func renderSeedModeTitle(label string, colorIndex int) string {170 colors := theme.Current().ThemeColors()171 if len(colors) == 0 {172 return ui.PanelTitle().Render("game: " + label)173 }174175 bg := colors[((colorIndex%len(colors))+len(colors))%len(colors)]176 chip := lipgloss.NewStyle().177 Bold(true).178 Foreground(theme.TextOnBG(bg)).179 Background(bg).180 Render(label)181182 var b strings.Builder183 b.WriteString(ui.PanelTitle().Render("game: "))184 b.WriteString(chip)185 return b.String()186}