package story_service import ( "context" "evening_detective/internal/modules/cleaner" "evening_detective/internal/modules/formatter" "evening_detective/internal/services/story_service/models" "regexp" "strings" ) type StoryService struct { cleaner cleaner.ICleaner formatter formatter.IFormatter story *models.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) *models.Place { if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") { return models.NewClientErrorPlace(code) } clearCode := s.cleaner.ClearCode(code) for _, place := range s.story.Places { if s.cleaner.ClearCode(place.Code) == clearCode { applications := make([]*models.Application, 0, len(place.Applications)) for _, application := range place.Applications { applications = append( applications, models.NewApplication( s.cleaner.ClearText(application.Name), ), ) } doors := make([]*models.Door, 0, len(place.Doors)) for _, door := range place.Doors { doors = append( doors, models.NewDoor( door.Code, door.Name, models.WithDoorShow(door.Show), ), ) } return models.NewPlace( place.Code, place.Name, s.cleaner.ClearText(place.Text), models.WithPlaceApplication(applications...), models.WithPlaceHidden(place.Hidden), models.WithPlaceDoors(doors...), ) } } return models.NewNotFoundPlace(code) } func (s *StoryService) GetPlaces(codes []string) []*models.Place { places := make([]*models.Place, 0, 100) mOpen := map[string]any{} mDeleted := map[string]any{} for i, code := range codes { place := s.GetPlace(code) if _, ok := mOpen[place.Code]; place.Hidden && !ok { place = models.NewNotFoundPlace(place.Code) } places = append(places, place) for j, door := range places[i].Doors { if _, ok := mDeleted[door.Code]; ok { places[i].Doors[j].Show = false continue } mOpen[door.Code] = struct{}{} } if i > 0 { for j, door := range places[i-1].Doors { if door.Code != place.Code && door.Show { places[i-1].Doors[j].Show = false delete(mOpen, door.Code) mDeleted[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, models.NewPlace( node.Code, node.Name, s.formatter.FormatText(node.Text), models.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] = models.NewPlace( node.Code, node.Name, s.formatter.FormatText(node.Text), models.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] = models.NewPlace( code, node.Name, s.formatter.FormatText(node.Text), models.WithPlaceApplication(s.getApplications(node)...), ) break } } return s.Update(ctx) } func (s *StoryService) getApplications(node *GraphNode) []*models.Application { nodeApplications := make([]*models.Application, 0, len(node.Applications)) for _, application := range node.Applications { nodeApplications = append( nodeApplications, &models.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) }