package services import ( "encoding/json" "strconv" "github.com/andyleap/hnh-map/internal/app" "github.com/andyleap/hnh-map/internal/app/store" "go.etcd.io/bbolt" ) // MapService handles map, markers, grids, tiles business logic. type MapService struct { st *store.Store gridStorage string gridUpdates *app.Topic[app.TileData] mergeUpdates *app.Topic[app.Merge] getChars func() []app.Character } // MapServiceDeps holds dependencies for MapService. type MapServiceDeps struct { Store *store.Store GridStorage string GridUpdates *app.Topic[app.TileData] MergeUpdates *app.Topic[app.Merge] GetChars func() []app.Character } // NewMapService creates a MapService. func NewMapService(d MapServiceDeps) *MapService { return &MapService{ st: d.Store, gridStorage: d.GridStorage, gridUpdates: d.GridUpdates, mergeUpdates: d.MergeUpdates, getChars: d.GetChars, } } // GridStorage returns the grid storage path. func (s *MapService) GridStorage() string { return s.gridStorage } // GetCharacters returns all characters (from in-memory map). func (s *MapService) GetCharacters() []app.Character { if s.getChars == nil { return nil } return s.getChars() } // GetMarkers returns all markers as FrontendMarker list. func (s *MapService) GetMarkers() ([]app.FrontendMarker, error) { var markers []app.FrontendMarker err := s.st.View(func(tx *bbolt.Tx) error { grid := s.st.GetMarkersGridBucket(tx) if grid == nil { return nil } grids := tx.Bucket(store.BucketGrids) if grids == nil { return nil } return grid.ForEach(func(k, v []byte) error { marker := app.Marker{} json.Unmarshal(v, &marker) graw := grids.Get([]byte(marker.GridID)) if graw == nil { return nil } g := app.GridData{} json.Unmarshal(graw, &g) markers = append(markers, app.FrontendMarker{ Image: marker.Image, Hidden: marker.Hidden, ID: marker.ID, Name: marker.Name, Map: g.Map, Position: app.Position{ X: marker.Position.X + g.Coord.X*100, Y: marker.Position.Y + g.Coord.Y*100, }, }) return nil }) }) return markers, err } // GetMaps returns maps, optionally filtering hidden for non-admin. func (s *MapService) GetMaps(showHidden bool) (map[int]*app.MapInfo, error) { maps := make(map[int]*app.MapInfo) err := s.st.View(func(tx *bbolt.Tx) error { return s.st.ForEachMap(tx, func(k, v []byte) error { mapid, err := strconv.Atoi(string(k)) if err != nil { return nil } mi := &app.MapInfo{} json.Unmarshal(v, mi) if mi.Hidden && !showHidden { return nil } maps[mapid] = mi return nil }) }) return maps, err } // GetConfig returns config (title) and auths for session. func (s *MapService) GetConfig(auths app.Auths) (app.Config, error) { config := app.Config{Auths: auths} err := s.st.View(func(tx *bbolt.Tx) error { title := s.st.GetConfig(tx, "title") if title != nil { config.Title = string(title) } return nil }) return config, err } // GetPage returns page title. func (s *MapService) GetPage() (app.Page, error) { p := app.Page{} err := s.st.View(func(tx *bbolt.Tx) error { title := s.st.GetConfig(tx, "title") if title != nil { p.Title = string(title) } return nil }) return p, err } // GetGrid returns GridData by ID. func (s *MapService) GetGrid(id string) (*app.GridData, error) { var gd *app.GridData err := s.st.View(func(tx *bbolt.Tx) error { raw := s.st.GetGrid(tx, id) if raw == nil { return nil } gd = &app.GridData{} return json.Unmarshal(raw, gd) }) return gd, err } // GetTile returns TileData for map/zoom/coord. func (s *MapService) GetTile(mapID int, c app.Coord, zoom int) *app.TileData { var td *app.TileData s.st.View(func(tx *bbolt.Tx) error { raw := s.st.GetTile(tx, mapID, zoom, c.Name()) if raw != nil { td = &app.TileData{} json.Unmarshal(raw, td) } return nil }) return td } // ReportMerge sends a merge event. func (s *MapService) ReportMerge(from, to int, shift app.Coord) { s.mergeUpdates.Send(&app.Merge{ From: from, To: to, Shift: shift, }) }