package service
import (
"sort"
"strconv"
"strings"
"gitcode-mcp/internal/cache"
)
func nullableLine(line int) *int {
if line <= 0 {
return nil
}
v := line
return &v
}
func sourceSummary(source cache.Source) SourceSummary {
stableID, issueNumber := sourceIssueIdentity(source)
return SourceSummary{RepoID: source.RepoID, ID: source.ID, StableSourceID: stableID, IssueNumber: issueNumber, Path: source.Path, RemoteAlias: remoteAlias(source.Aliases), Kind: source.Kind, Title: source.Title, Status: source.Status, Provenance: string(source.Provenance), UpdatedAt: source.UpdatedAt.UTC()}
}
func sourceRecord(source cache.Source, links []cache.Link, backlinks []BacklinkResult) SourceRecord {
labels := append([]string(nil), source.Labels...)
sort.Strings(labels)
stableID, issueNumber := sourceIssueIdentity(source)
return SourceRecord{RepoID: source.RepoID, ID: source.ID, StableSourceID: stableID, IssueNumber: issueNumber, Path: source.Path, RemoteAlias: remoteAlias(source.Aliases), Kind: source.Kind, Title: source.Title, Body: source.Body, Status: source.Status, Provenance: string(source.Provenance), Labels: labels, Links: linkResults(links), Backlinks: backlinks, UpdatedAt: source.UpdatedAt.UTC()}
}
func sourceIssueIdentity(source cache.Source) (string, int) {
stableID := source.ID
if source.Kind != "issue" && source.Kind != "issues" {
return stableID, 0
}
for _, alias := range source.Aliases {
aliasType := strings.TrimSpace(alias.Remote.Type)
aliasID := strings.TrimSpace(alias.Remote.ID)
if aliasType == "" {
aliasType = strings.TrimSpace(alias.AliasType)
aliasID = strings.TrimSpace(alias.Alias)
}
if aliasType != "issue" {
continue
}
if number, err := strconv.Atoi(aliasID); err == nil && number > 0 {
return stableID, number
}
}
return stableID, 0
}
func linkResults(links []cache.Link) []LinkResult {
out := make([]LinkResult, 0, len(links))
for _, link := range links {
out = append(out, LinkResult{SourceID: link.SourceID, TargetID: link.TargetID, Kind: link.Kind, Text: link.Text})
}
sort.SliceStable(out, func(i, j int) bool {
if out[i].TargetID != out[j].TargetID {
return out[i].TargetID < out[j].TargetID
}
if out[i].Kind != out[j].Kind {
return out[i].Kind < out[j].Kind
}
return out[i].Text < out[j].Text
})
return out
}
func remoteAlias(aliases []cache.Identity) string {
candidates := make([]string, 0, len(aliases))
for _, alias := range aliases {
if alias.Remote.Type != "" && alias.Remote.ID != "" {
candidates = append(candidates, alias.Remote.Type+":"+alias.Remote.ID)
continue
}
if alias.AliasType != "" && alias.Alias != "" {
candidates = append(candidates, alias.AliasType+":"+alias.Alias)
}
}
sort.Strings(candidates)
if len(candidates) == 0 {
return ""
}
return candidates[0]
}
func sliceSources(sources []cache.Source, offset, limit int) []cache.Source {
if offset < 0 {
offset = 0
}
if offset >= len(sources) {
return nil
}
sources = sources[offset:]
if limit > 0 && limit < len(sources) {
return sources[:limit]
}
return sources
}
func sortedKeys(m map[string]struct{}) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
sort.Strings(out)
return out
}
func normalizeFormat(format string) string {
if strings.EqualFold(format, "json") {
return "json"
}
if strings.EqualFold(format, "markdown") || strings.EqualFold(format, "md") {
return "markdown"
}
return "text"
}
func changedIDs(base, head string) []string {
ids := map[string]struct{}{}
for _, line := range strings.Split(head, "\n") {
if line == "" || strings.Contains(base, line) {
continue
}
parts := strings.Split(line, "\t")
if len(parts) > 0 && parts[0] != "" {
ids[parts[0]] = struct{}{}
}
}
return sortedKeys(ids)
}
func simpleDiff(base, head string) string {
if base == head {
return ""
}
return "--- base\n+++ head\n" + head
}