1package usecase
2
3import (
4 "context"
5 "errors"
6
7 "github.com/bnema/dumber/internal/application/dto"
8 "github.com/bnema/dumber/internal/application/port"
9 "github.com/bnema/dumber/internal/domain/entity"
10)
11
12// ReadSystemviewConfigUseCase exposes read-side systemview config payloads.
13type ReadSystemviewConfigUseCase struct {
14 reader port.SystemviewConfigReader
15 resolvedAppearance *systemviewResolvedAppearanceResolver
16}
17
18type systemviewResolvedAppearanceResolver struct {
19 externalTheme port.ExternalThemeSource
20 colorResolver port.ColorSchemeResolver
21 prefersDark func() bool
22}
23
24// ReadSystemviewConfigOption customizes ReadSystemviewConfigUseCase behavior.
25type ReadSystemviewConfigOption func(*ReadSystemviewConfigUseCase)
26
27// WithSystemviewResolvedAppearance overlays the current config payload with a display-only
28// resolved appearance snapshot. It uses an isolated theme resolver per read so opening
29// dumb://config cannot mutate the live browser theme resolver's last-good cache.
30func WithSystemviewResolvedAppearance(
31 externalTheme port.ExternalThemeSource,
32 colorResolver port.ColorSchemeResolver,
33 prefersDark func() bool,
34) ReadSystemviewConfigOption {
35 return func(uc *ReadSystemviewConfigUseCase) {
36 uc.resolvedAppearance = &systemviewResolvedAppearanceResolver{
37 externalTheme: externalTheme,
38 colorResolver: colorResolver,
39 prefersDark: prefersDark,
40 }
41 }
42}
43
44// NewReadSystemviewConfigUseCase creates a new read systemview config use case.
45func NewReadSystemviewConfigUseCase(reader port.SystemviewConfigReader, opts ...ReadSystemviewConfigOption) *ReadSystemviewConfigUseCase {
46 if reader == nil {
47 panic("NewReadSystemviewConfigUseCase: reader is nil")
48 }
49 uc := &ReadSystemviewConfigUseCase{reader: reader}
50 for _, opt := range opts {
51 if opt != nil {
52 opt(uc)
53 }
54 }
55 return uc
56}
57
58// ErrNilSystemviewConfigReader is returned when the config reader dependency is nil.
59var ErrNilSystemviewConfigReader = errors.New("systemview config reader is nil")
60
61// Current returns the current systemview config payload.
62func (uc *ReadSystemviewConfigUseCase) Current(ctx context.Context) (dto.SystemviewConfigPayload, error) {
63 if uc == nil || uc.reader == nil {
64 return dto.SystemviewConfigPayload{}, ErrNilSystemviewConfigReader
65 }
66 payload, err := uc.reader.Current(ctx)
67 if err != nil {
68 return dto.SystemviewConfigPayload{}, err
69 }
70 return uc.withResolvedAppearance(ctx, payload), nil
71}
72
73// Default returns the default systemview config payload.
74func (uc *ReadSystemviewConfigUseCase) Default(ctx context.Context) (dto.SystemviewConfigPayload, error) {
75 if uc == nil || uc.reader == nil {
76 return dto.SystemviewConfigPayload{}, ErrNilSystemviewConfigReader
77 }
78 return uc.reader.Default(ctx)
79}
80
81func (uc *ReadSystemviewConfigUseCase) withResolvedAppearance(
82 ctx context.Context,
83 payload dto.SystemviewConfigPayload,
84) dto.SystemviewConfigPayload {
85 if uc == nil || uc.resolvedAppearance == nil {
86 return payload
87 }
88 resolved, ok := uc.resolvedAppearance.resolve(ctx, payload)
89 if !ok {
90 return payload
91 }
92 appearance := dto.WebUIAppearanceWithResolvedTheme(payload.Appearance, resolved)
93 payload.ResolvedAppearance = &appearance
94 return payload
95}
96
97func (r *systemviewResolvedAppearanceResolver) resolve(
98 ctx context.Context,
99 payload dto.SystemviewConfigPayload,
100) (entity.ResolvedTheme, bool) {
101 if r == nil {
102 return entity.ResolvedTheme{}, false
103 }
104 resolver := NewResolveThemeUseCase(r.externalTheme)
105 out, err := resolver.Execute(ctx, ResolveThemeInputFromConfig(
106 &payload.Appearance,
107 payload.DefaultUIScale,
108 nil,
109 r.preference(payload.Appearance.ColorScheme),
110 ))
111 if err != nil {
112 return entity.ResolvedTheme{}, false
113 }
114 return out.Theme, true
115}
116
117func (r *systemviewResolvedAppearanceResolver) preference(colorScheme string) port.ColorSchemePreference {
118 fallback := port.ColorSchemePreference{PrefersDark: true, Source: "systemviews"}
119 if r.colorResolver != nil {
120 fallback = r.colorResolver.Resolve()
121 } else if r.prefersDark != nil {
122 fallback.PrefersDark = r.prefersDark()
123 }
124 return ColorSchemePreferenceFromConfig(colorScheme, fallback)
125}