Files
2026-08-29 02:17:36 +03:00

87 lines
2.6 KiB
Go

package domain
import (
"fmt"
contract "git.cin.su/m/Assistant/contracts/situation"
)
// FocusStack is a rebuildable, situation-owned projection. Push suspends the
// previous active frame; Pop resumes its parent and never deletes a frame.
type FocusStack struct{ frames []contract.FocusFrame }
func NewFocusStack(frames []contract.FocusFrame) (FocusStack, error) {
stack := FocusStack{frames: append([]contract.FocusFrame(nil), frames...)}
for _, frame := range stack.frames {
if err := frame.Validate(); err != nil {
return FocusStack{}, err
}
}
return stack, nil
}
func (s FocusStack) Frames() []contract.FocusFrame {
return append([]contract.FocusFrame(nil), s.frames...)
}
func (s FocusStack) Current() (contract.FocusFrame, error) {
for i := len(s.frames) - 1; i >= 0; i-- {
if s.frames[i].State == contract.FocusActive {
return s.frames[i], nil
}
}
return contract.FocusFrame{}, ErrNoActiveFocus
}
func (s FocusStack) Push(frame contract.FocusFrame) (FocusStack, error) {
if err := frame.Validate(); err != nil {
return FocusStack{}, err
}
if frame.State != contract.FocusActive {
return FocusStack{}, fmt.Errorf("pushed frame must be active")
}
frames := s.Frames()
if len(frames) > 0 {
current, err := s.Current()
if err == nil {
for i := range frames {
if frames[i].ID == current.ID {
frames[i].State = contract.FocusSuspended
break
}
}
frame.ParentFrameRef = &current.ID
}
}
frames = append(frames, frame)
return FocusStack{frames: frames}, nil
}
func (s FocusStack) Pop() (FocusStack, contract.FocusFrame, error) {
current, err := s.Current()
if err != nil {
return FocusStack{}, contract.FocusFrame{}, err
}
frames := s.Frames()
for i := range frames {
if frames[i].ID == current.ID {
frames[i].State = contract.FocusSuspended
break
}
}
if current.ParentFrameRef != nil {
for i := range frames {
if frames[i].ID == *current.ParentFrameRef {
frames[i].State = contract.FocusActive
break
}
}
}
return FocusStack{frames: frames}, current, nil
}
// ProjectFocusProjection retains the V1 API as a read projection only.
func ProjectFocusProjection(frame contract.FocusFrame, project contract.ProjectDescriptor) contract.ActiveProjectFocus {
return contract.ActiveProjectFocus{ActorID: frame.ActorID, PrincipalID: frame.PrincipalID, ProjectID: contract.ProjectID(project.ID), ProjectDisplayName: project.DisplayName, Provenance: contract.ProjectResolutionProvenance(frame.Provenance), ConfidenceMillis: frame.ConfidenceMillis, SourceUtteranceID: frame.SourceUtteranceRef, UpdatedAtUnixMillis: int64(frame.RecordedAt)}
}