package domain import ( "sync" ) type VaultStats struct { TotalNotes int TotalWords int TotalLinks int TotalTags int AvgNoteLength int OrphanedNotes int } type Vault struct { rootPath string notes map[string]*Note graph *Graph tags map[string]int mutex sync.RWMutex } func NewVault(rootPath string) *Vault { return &Vault{ rootPath: rootPath, notes: make(map[string]*Note), graph: NewGraph(), tags: make(map[string]int), } } func (v *Vault) RootPath() string { return v.rootPath } func (v *Vault) AddNote(note *Note) error { v.mutex.Lock() defer v.mutex.Unlock() if _, exists := v.notes[note.Path()]; exists { return ErrNoteAlreadyExists } v.notes[note.Path()] = note for _, link := range note.ExtractLinks() { v.graph.AddEdge(note.Path(), link.Target()) } for _, tag := range note.Tags() { v.tags[tag.Name()]++ } return nil } func (v *Vault) RemoveNote(path string) error { v.mutex.Lock() defer v.mutex.Unlock() note, exists := v.notes[path] if !exists { return ErrNoteNotFound } for _, tag := range note.Tags() { v.tags[tag.Name()]-- if v.tags[tag.Name()] <= 0 { delete(v.tags, tag.Name()) } } v.graph.RemoveNode(path) delete(v.notes, path) return nil } func (v *Vault) GetNote(path string) (*Note, error) { v.mutex.RLock() defer v.mutex.RUnlock() note, exists := v.notes[path] if !exists { return nil, ErrNoteNotFound } return note, nil } func (v *Vault) UpdateNote(note *Note) error { v.mutex.Lock() defer v.mutex.Unlock() existingNote, exists := v.notes[note.Path()] if !exists { return ErrNoteNotFound } for _, tag := range existingNote.Tags() { v.tags[tag.Name()]-- if v.tags[tag.Name()] <= 0 { delete(v.tags, tag.Name()) } } v.graph.RemoveNode(note.Path()) v.notes[note.Path()] = note for _, link := range note.ExtractLinks() { v.graph.AddEdge(note.Path(), link.Target()) } for _, tag := range note.Tags() { v.tags[tag.Name()]++ } return nil } func (v *Vault) ListNotes() []*Note { v.mutex.RLock() defer v.mutex.RUnlock() notes := make([]*Note, 0, len(v.notes)) for _, note := range v.notes { notes = append(notes, note) } return notes } func (v *Vault) GetStats() VaultStats { v.mutex.RLock() defer v.mutex.RUnlock() totalWords := 0 totalLinks := 0 for _, note := range v.notes { totalWords += note.WordCount() totalLinks += len(note.ExtractLinks()) } avgLength := 0 if len(v.notes) > 0 { avgLength = totalWords / len(v.notes) } orphaned := v.countOrphanedNotes() return VaultStats{ TotalNotes: len(v.notes), TotalWords: totalWords, TotalLinks: totalLinks, TotalTags: len(v.tags), AvgNoteLength: avgLength, OrphanedNotes: orphaned, } } func (v *Vault) GetBacklinks(path string) []string { return v.graph.GetBacklinks(path) } func (v *Vault) GetOutlinks(path string) []string { return v.graph.GetOutlinks(path) } func (v *Vault) GetAllTags() map[string]int { v.mutex.RLock() defer v.mutex.RUnlock() tags := make(map[string]int) for tag, count := range v.tags { tags[tag] = count } return tags } func (v *Vault) countOrphanedNotes() int { orphaned := 0 for path := range v.notes { if len(v.graph.GetBacklinks(path)) == 0 && len(v.graph.GetOutlinks(path)) == 0 { orphaned++ } } return orphaned } func (v *Vault) Clear() { v.mutex.Lock() defer v.mutex.Unlock() v.notes = make(map[string]*Note) v.graph.Clear() v.tags = make(map[string]int) }