1package usecase
2
3import (
4 "context"
5 "fmt"
6
7 "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/domain/entity"
9)
10
11// ExecuteContextMenuActionInput identifies the action and its context.
12type ExecuteContextMenuActionInput struct {
13 Action port.MenuAction
14 Context port.MenuContext
15}
16
17// ExecuteContextMenuActionUseCase handles shared context menu actions.
18type ExecuteContextMenuActionUseCase struct {
19 clipboard port.Clipboard
20 resolver port.ImageDataResolver
21 saver port.ResolvedImageSaver
22 delegator port.MenuActionDelegator
23}
24
25var _ port.ContextMenuActionExecutor = (*ExecuteContextMenuActionUseCase)(nil)
26
27// NewExecuteContextMenuActionUseCase creates a new ExecuteContextMenuActionUseCase.
28func NewExecuteContextMenuActionUseCase(
29 clipboard port.Clipboard,
30 resolver port.ImageDataResolver,
31 saver port.ResolvedImageSaver,
32 delegator port.MenuActionDelegator,
33) *ExecuteContextMenuActionUseCase {
34 return &ExecuteContextMenuActionUseCase{
35 clipboard: clipboard,
36 resolver: resolver,
37 saver: saver,
38 delegator: delegator,
39 }
40}
41
42// ContextMenuActionExecutorFactory creates ExecuteContextMenuActionUseCases.
43type ContextMenuActionExecutorFactory struct{}
44
45var _ port.ContextMenuActionExecutorFactory = (*ContextMenuActionExecutorFactory)(nil)
46
47// NewContextMenuActionExecutor creates a new shared context menu action executor.
48func (*ContextMenuActionExecutorFactory) NewContextMenuActionExecutor(
49 clipboard port.Clipboard,
50 resolver port.ImageDataResolver,
51 saver port.ResolvedImageSaver,
52 delegator port.MenuActionDelegator,
53) port.ContextMenuActionExecutor {
54 return NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
55}
56
57// Execute handles the requested action.
58func (uc *ExecuteContextMenuActionUseCase) Execute(ctx context.Context, input ExecuteContextMenuActionInput) error {
59 return uc.ExecuteMenuAction(ctx, input.Action, input.Context)
60}
61
62// ExecuteMenuAction handles the requested action.
63func (uc *ExecuteContextMenuActionUseCase) ExecuteMenuAction(
64 ctx context.Context, action port.MenuAction, menuContext port.MenuContext,
65) error {
66 return uc.executeMenuAction(ctx, action, menuContext)
67}
68
69func (uc *ExecuteContextMenuActionUseCase) executeMenuAction(
70 ctx context.Context, action port.MenuAction, menuContext port.MenuContext,
71) error {
72 switch action {
73 case port.MenuActionCopyLink:
74 if menuContext.LinkURI == "" {
75 return fmt.Errorf("copy link: link URI not available")
76 }
77 if uc.clipboard == nil {
78 return fmt.Errorf("copy link: clipboard not available")
79 }
80 if err := uc.clipboard.WriteText(ctx, menuContext.LinkURI); err != nil {
81 return fmt.Errorf("copy link: %w", err)
82 }
83 return nil
84 case port.MenuActionCopyImage:
85 if uc.clipboard == nil {
86 return fmt.Errorf("copy image: clipboard not available")
87 }
88 image, err := uc.resolveImageData(ctx, menuContext.ImageURI)
89 if err != nil {
90 return fmt.Errorf("copy image: %w", err)
91 }
92 if err := uc.clipboard.WriteImage(ctx, image); err != nil {
93 return fmt.Errorf("copy image: %w", err)
94 }
95 return nil
96 case port.MenuActionSaveImage:
97 if uc.saver == nil {
98 return fmt.Errorf("save image: image saver not available")
99 }
100 image, err := uc.resolveImageData(ctx, menuContext.ImageURI)
101 if err != nil {
102 return fmt.Errorf("save image: %w", err)
103 }
104 if err := uc.saver.SaveResolvedImage(ctx, image, menuContext); err != nil {
105 return fmt.Errorf("save image: %w", err)
106 }
107 return nil
108 case port.MenuActionCopySelection:
109 // When selection text is present and clipboard wiring is available, copy it here.
110 // Otherwise intentionally fall through to DelegateMenuAction so native browser copy
111 // behavior can handle document selections that are only accessible inside the engine.
112 if menuContext.SelectionText != "" && uc.clipboard != nil {
113 if err := uc.clipboard.WriteText(ctx, menuContext.SelectionText); err != nil {
114 return fmt.Errorf("copy selection: %w", err)
115 }
116 return nil
117 }
118 }
119
120 // All other normalized actions are delegated to the engine/UI layer.
121 if uc.delegator == nil {
122 return fmt.Errorf("delegate action %s: menu action delegator not available", action)
123 }
124 if err := uc.delegator.DelegateMenuAction(ctx, action, menuContext); err != nil {
125 return fmt.Errorf("delegate action %s: %w", action, err)
126 }
127 return nil
128}
129
130func (uc *ExecuteContextMenuActionUseCase) resolveImageData(ctx context.Context, uri string) (entity.ImageData, error) {
131 if uri == "" {
132 return entity.ImageData{}, fmt.Errorf("image URI not available")
133 }
134 if uc.resolver == nil {
135 return entity.ImageData{}, fmt.Errorf("image data resolver not available")
136 }
137
138 image, err := uc.resolver.ResolveImageData(ctx, uri)
139 if err != nil {
140 return entity.ImageData{}, err
141 }
142 if len(image.Bytes) == 0 {
143 return entity.ImageData{}, fmt.Errorf("image data not available")
144 }
145
146 return image, nil
147}