1package styles
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7
8 "github.com/charmbracelet/lipgloss"
9
10 "github.com/bnema/dumber/internal/domain/entity"
11)
12
13// ConfigSchemaRenderer renders configuration schema information.
14type ConfigSchemaRenderer struct {
15 theme *Theme
16}
17
18// NewConfigSchemaRenderer creates a new ConfigSchemaRenderer.
19func NewConfigSchemaRenderer(theme *Theme) *ConfigSchemaRenderer {
20 return &ConfigSchemaRenderer{theme: theme}
21}
22
23// Render renders the configuration schema in styled format.
24func (r *ConfigSchemaRenderer) Render(keys []entity.ConfigKeyInfo) string {
25 if len(keys) == 0 {
26 return r.theme.Subtle.Render("No configuration keys found")
27 }
28
29 // Group keys by section
30 sections := groupBySection(keys)
31
32 // Build output
33 var parts []string
34
35 // Header
36 header := r.renderHeader()
37 parts = append(parts, header, "")
38
39 // Render each section
40 sectionOrder := []string{
41 "Appearance",
42 "Logging",
43 "History",
44 "Search",
45 "Dmenu",
46 "Workspace",
47 "Session",
48 "Omnibox",
49 "Content Filtering",
50 "Rendering",
51 "Media",
52 "Update",
53 "Debug",
54 "Performance",
55 "Runtime",
56 "Database",
57 }
58
59 for _, section := range sectionOrder {
60 if sectionKeys, ok := sections[section]; ok {
61 parts = append(parts, r.renderSection(section, sectionKeys), "")
62 }
63 }
64
65 return strings.Join(parts, "\n")
66}
67
68// RenderJSON renders the configuration schema as JSON.
69func (*ConfigSchemaRenderer) RenderJSON(keys []entity.ConfigKeyInfo) (string, error) {
70 data, err := json.MarshalIndent(keys, "", " ")
71 if err != nil {
72 return "", fmt.Errorf("marshal schema: %w", err)
73 }
74 return string(data), nil
75}
76
77func (r *ConfigSchemaRenderer) renderHeader() string {
78 iconStyle := lipgloss.NewStyle().Foreground(r.theme.Accent)
79 title := fmt.Sprintf("%s %s", iconStyle.Render(IconConfig), r.theme.Title.Render("Config Schema Reference"))
80 return title
81}
82
83func groupBySection(keys []entity.ConfigKeyInfo) map[string][]entity.ConfigKeyInfo {
84 sections := make(map[string][]entity.ConfigKeyInfo)
85 for _, key := range keys {
86 sections[key.Section] = append(sections[key.Section], key)
87 }
88 return sections
89}
90
91func (r *ConfigSchemaRenderer) renderSection(name string, keys []entity.ConfigKeyInfo) string {
92 var lines []string
93
94 for _, key := range keys {
95 lines = append(lines, r.renderKey(key))
96 }
97
98 body := strings.Join(lines, "\n")
99
100 // Create section header (no extra padding/margin)
101 sectionHeader := r.theme.Highlight.Render(name)
102
103 // Build box content: header directly followed by keys
104 boxContent := sectionHeader + "\n" + body
105
106 // Use box style without top padding
107 boxStyle := r.theme.Box.PaddingTop(0)
108
109 return boxStyle.Render(boxContent)
110}
111
112func (r *ConfigSchemaRenderer) renderKey(key entity.ConfigKeyInfo) string {
113 keyStyle := r.theme.Normal.Bold(true)
114 typeStyle := r.theme.Subtle
115 defaultStyle := lipgloss.NewStyle().Foreground(r.theme.Accent)
116 descStyle := r.theme.Subtle
117 valuesStyle := r.theme.Normal
118
119 // Line 1: key name, type, default
120 line1 := fmt.Sprintf(
121 "%s %s %s",
122 keyStyle.Render(key.Key),
123 typeStyle.Render(key.Type),
124 defaultStyle.Render(key.Default),
125 )
126
127 // Line 2: description (indented)
128 line2 := fmt.Sprintf(" %s", descStyle.Render(key.Description))
129
130 result := line1 + "\n" + line2
131
132 // Line 3 (optional): values or range
133 if len(key.Values) > 0 {
134 valuesText := "Values: " + strings.Join(key.Values, ", ")
135 line3 := fmt.Sprintf(" %s", valuesStyle.Render(valuesText))
136 result += "\n" + line3
137 } else if key.Range != "" {
138 rangeText := "Range: " + key.Range
139 line3 := fmt.Sprintf(" %s", valuesStyle.Render(rangeText))
140 result += "\n" + line3
141 }
142
143 return result
144}