package storytelling

type story struct {
	cleaner ICleaner
}

func NewStory(
	cleaner ICleaner,
) IStory {
	return &story{
		cleaner: cleaner,
	}
}

func (s *story) GetStory(
	scenario *Story,
	codes []string,
) *Story {
	story := &Story{}
	keys := map[string]struct{}{}
	codesWithUseActions := map[string]struct{}{}
	codesHidden := map[string]struct{}{}
	givenApplications := map[string]struct{}{}
	// Введение передаётся игроку всегда — и до первого хода (начало игры),
	// и в дальнейшем (как заголовок истории).
	if scenario.Introduction != nil {
		story.Introduction = &Introduction{
			Text:  s.cleaner.ClearText(scenario.Introduction.Text),
			Audio: scenario.Introduction.Audio,
		}
	}
	for i, code := range codes {
		var prevPlace *Place
		if i > 0 {
			prevPlace, _ = s.findPlace(scenario, codes[i-1])
		}
		currentPlace, _ := s.findPlace(scenario, codes[i])
		var nextPlace *Place
		if i < len(codes)-1 {
			nextPlace, _ = s.findPlace(scenario, codes[i+1])
		}
		// Если следующая точка это открытие двери в текущей точке, то текущая точка считается использованной для действия
		if nextPlace != nil && currentPlace != nil {
			openDoor := false
			for _, door := range currentPlace.Doors {
				if door.Code == nextPlace.Code {
					codesWithUseActions[currentPlace.Code] = struct{}{}
					openDoor = true
					break
				}
			}
			if openDoor {
				// Запрещаем смотреть не выбранные действия
				for _, door := range currentPlace.Doors {
					if door.Code != nextPlace.Code {
						codesHidden[door.Code] = struct{}{}
					}
				}
			}
		}
		// Собираем ключи
		if currentPlace != nil {
			for _, key := range currentPlace.Keys {
				keys[key.Name] = struct{}{}
			}
		}
		place, ok := s.findResultPlace(scenario, code, prevPlace, codesHidden)
		if !ok {
			story.Places = append(story.Places, s.buildNotFoundPlace(code))
			continue
		}
		newPlace := s.mapPlace(place, nextPlace, codesWithUseActions, keys, givenApplications)
		story.Places = append(story.Places, newPlace)
		for _, application := range newPlace.Applications {
			givenApplications[application.Name] = struct{}{}
		}
	}
	return story
}

func (s *story) findPlace(
	scenario *Story,
	code string,
) (*Place, bool) {
	for _, place := range scenario.Places {
		if s.cleaner.ClearCode(place.Code) == s.cleaner.ClearCode(code) {
			return place, true
		}
	}
	return nil, false
}

func (s *story) findResultPlace(
	scenario *Story,
	code string,
	prevPlace *Place,
	codesHidden map[string]struct{},
) (*Place, bool) {
	if _, ok := codesHidden[code]; ok {
		return nil, false
	}
	open := false
	if prevPlace != nil {
		for _, door := range prevPlace.Doors {
			if door.Code == code {
				open = true
				break
			}
		}
	}
	for _, place := range scenario.Places {
		if s.cleaner.ClearCode(place.Code) == s.cleaner.ClearCode(code) {
			if place.Hidden && !open {
				return nil, false
			}
			return place, true
		}
	}
	return nil, false
}

func (s *story) buildNotFoundPlace(code string) *Place {
	return &Place{
		Code: code,
		Name: "Не найдено",
		Text: "Такой точки не существует.",
	}
}

func (s *story) mapPlace(
	place *Place,
	nextPlace *Place,
	codesWithUseActions map[string]struct{},
	keys map[string]struct{},
	givenApplications map[string]struct{},
) *Place {
	var applications []*Application
	if len(place.Applications) > 0 {
		applications = make([]*Application, 0, len(place.Applications))
		for _, application := range place.Applications {
			if _, ok := givenApplications[application.Name]; ok {
				continue
			}
			applications = append(
				applications,
				&Application{
					Name:     s.cleaner.ClearText(application.Name),
					Image:    application.Image,
					FileType: application.FileType,
				},
			)
		}
	}
	if len(applications) == 0 {
		applications = nil
	}

	var doors []*Door
	_, useAction := codesWithUseActions[place.Code]
	if len(place.Doors) > 0 && !useAction {
		doors = make([]*Door, 0, len(place.Doors))
		for _, door := range place.Doors {
			if nextPlace != nil {
				if door.Code != nextPlace.Code {
					continue
				}
			}
			hasKeys := true
			for _, doorKey := range door.Keys {
				if _, ok := keys[doorKey.Name]; !ok {
					hasKeys = false
				}
			}
			if !hasKeys {
				continue
			}
			doors = append(
				doors,
				&Door{
					Code: door.Code,
					Name: door.Name,
				},
			)
		}
	}
	if len(doors) == 0 {
		doors = nil
	}

	return &Place{
		Code:         place.Code,
		Name:         place.Name,
		Text:         s.cleaner.ClearText(place.Text),
		Image:        place.Image,
		Applications: applications,
		Doors:        doors,
	}
}
