package story_service import ( "context" "evening_detective/internal/modules/cleaner" "evening_detective/internal/modules/formatter" "regexp" "strings" ) type StoryService struct { cleaner cleaner.ICleaner formatter formatter.IFormatter story *Story storyStorage IStoryStorage } func NewStoryService( cleaner cleaner.ICleaner, formatter formatter.IFormatter, storyStorage IStoryStorage, ) (*StoryService, error) { s := &StoryService{ cleaner: cleaner, formatter: formatter, storyStorage: storyStorage, } story, err := s.storyStorage.Load(context.Background()) if err != nil { return nil, err } s.story = story return s, nil } func (s *StoryService) Update(ctx context.Context) error { if err := s.storyStorage.Save(ctx, s.story); err != nil { return err } story, err := s.storyStorage.Load(ctx) if err != nil { return err } s.story = story return nil } func (s *StoryService) GetPlace(code string) *Place { if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") { return NewClientErrorPlace(code) } clearCode := s.cleaner.ClearCode(code) for _, place := range s.story.Places { if s.cleaner.ClearCode(place.Code) == clearCode { applications := make([]*Application, 0, len(place.Applications)) for _, application := range place.Applications { name := s.cleaner.ClearText(application.Name) applications = append( applications, &Application{ Name: name, }, ) } return NewPlace( place.Code, place.Name, s.cleaner.ClearText(place.Text), WithPlaceApplication(applications...), WithPlaceHidden(place.Hidden), WithPlaceDoors(place.Doors...), ) } } return NewNotFoundPlace(code) } func (s *StoryService) GetPlaces(codes []string) []*Place { places := make([]*Place, 0, 100) m := map[string]any{} for _, code := range codes { place := s.GetPlace(code) _, ok := m[place.Code] if place.Hidden && !ok { place = NewNotFoundPlace(place.Code) } places = append(places, place) for _, door := range place.Doors { m[door.Code] = struct{}{} } } return places } func (s *StoryService) UpdatePlace(ctx context.Context, code string, node *GraphNode) error { if code != "" && node.Code == "" { return s.deletePlace(ctx, code) } if code == "" && node.Code != "" { return s.addPlace(ctx, node) } if code == "" || node.Code == "" { return nil } return s.updatePlace(ctx, code, node) } func (s *StoryService) deletePlace(ctx context.Context, code string) error { for i := range s.story.Places { if s.story.Places[i].Code == code { s.story.Places = append(s.story.Places[:i], s.story.Places[i+1:]...) break } } return s.Update(ctx) } func (s *StoryService) addPlace(ctx context.Context, node *GraphNode) error { s.story.Places = append( s.story.Places, NewPlace( node.Code, node.Name, s.formatter.FormatText(node.Text), WithPlaceApplication(s.getApplications(node)...), ), ) return s.Update(ctx) } func (s *StoryService) updatePlace(ctx context.Context, code string, node *GraphNode) error { for i := range s.story.Places { if s.story.Places[i].Code == code { s.story.Places[i] = NewPlace( node.Code, node.Name, s.formatter.FormatText(node.Text), WithPlaceApplication(s.getApplications(node)...), ) return s.Update(ctx) } } for i := range s.story.Places { if s.story.Places[i].Code == node.Code { s.story.Places[i] = NewPlace( code, node.Name, s.formatter.FormatText(node.Text), WithPlaceApplication(s.getApplications(node)...), ) break } } return s.Update(ctx) } func (s *StoryService) getApplications(node *GraphNode) []*Application { nodeApplications := make([]*Application, 0, len(node.Applications)) for _, application := range node.Applications { nodeApplications = append( nodeApplications, &Application{ Name: application.Name, }, ) } return nodeApplications } func (s *StoryService) GetGraph(ctx context.Context) *Graph { m := make(map[string]string, len(s.story.Places)) nodes := make([]*GraphNode, 0, len(s.story.Places)) for _, place := range s.story.Places { m[s.cleaner.ClearCode(place.Code)] = place.Code applications := make([]*GraphApplication, 0, len(place.Applications)) for _, application := range place.Applications { applications = append( applications, &GraphApplication{ Name: application.Name, }, ) } nodes = append( nodes, &GraphNode{ Code: place.Code, Name: place.Name, Text: place.Text, Applications: applications, }, ) } edges := make([]*GraphEdge, 0, len(s.story.Places)*3) for _, place := range s.story.Places { placeLinks := s.findPlaceLinksInText(place.Text) for _, placeLink := range placeLinks { edges = append( edges, &GraphEdge{ From: m[s.cleaner.ClearCode(place.Code)], To: m[s.cleaner.ClearCode(placeLink)], Type: "node", }, ) } for _, application := range place.Applications { placeLinks := s.findPlaceLinksInText(application.Name) for _, placeLink := range placeLinks { edges = append( edges, &GraphEdge{ From: m[s.cleaner.ClearCode(place.Code)], To: m[s.cleaner.ClearCode(placeLink)], Type: "application", }, ) } } } return &Graph{ Nodes: nodes, Edges: edges, } } func (s *StoryService) findPlaceLinksInText(text string) []string { re := regexp.MustCompile(`\(\[[a-zA-Zа-яА-Я\d-]+\]\)`) return re.FindAllString(text, -1) }