//go:build !(js && wasm)

package database

import (
	"lol.mleku.dev/log"
	"next.orly.dev/pkg/database/indexes/types"
	"git.mleku.dev/mleku/nostr/encoders/hex"
)

// TraverseThread performs BFS traversal of thread structure via e-tags.
// Starting from a seed event, it finds all replies/references at each depth.
//
// The traversal works bidirectionally:
// - Forward: Events that the seed references (parents, quoted posts)
// - Backward: Events that reference the seed (replies, reactions, reposts)
//
// Parameters:
// - seedEventID: The event ID to start traversal from
// - maxDepth: Maximum depth to traverse
// - direction: "both" (default), "inbound" (replies to seed), "outbound" (seed's references)
func (d *D) TraverseThread(seedEventID []byte, maxDepth int, direction string) (*GraphResult, error) {
	result := NewGraphResult()

	if len(seedEventID) != 32 {
		return result, ErrEventNotFound
	}

	// Get seed event serial
	seedSerial, err := d.GetSerialById(seedEventID)
	if err != nil {
		log.D.F("TraverseThread: seed event not in database: %s", hex.Enc(seedEventID))
		return result, nil
	}

	// Normalize direction
	if direction == "" {
		direction = "both"
	}

	// Track visited events
	visited := make(map[uint64]bool)
	visited[seedSerial.Get()] = true

	// Current frontier
	currentFrontier := []*types.Uint40{seedSerial}

	consecutiveEmptyDepths := 0

	for currentDepth := 1; currentDepth <= maxDepth; currentDepth++ {
		var nextFrontier []*types.Uint40
		newEventsAtDepth := 0

		for _, eventSerial := range currentFrontier {
			// Get inbound references (events that reference this event)
			if direction == "both" || direction == "inbound" {
				inboundSerials, err := d.GetReferencingEvents(eventSerial, nil)
				if err != nil {
					log.D.F("TraverseThread: error getting inbound refs for serial %d: %v", eventSerial.Get(), err)
				} else {
					for _, refSerial := range inboundSerials {
						if visited[refSerial.Get()] {
							continue
						}
						visited[refSerial.Get()] = true

						eventIDHex, err := d.GetEventIDFromSerial(refSerial)
						if err != nil {
							continue
						}

						result.AddEventAtDepth(eventIDHex, currentDepth)
						newEventsAtDepth++
						nextFrontier = append(nextFrontier, refSerial)
					}
				}
			}

			// Get outbound references (events this event references)
			if direction == "both" || direction == "outbound" {
				outboundSerials, err := d.GetETagsFromEventSerial(eventSerial)
				if err != nil {
					log.D.F("TraverseThread: error getting outbound refs for serial %d: %v", eventSerial.Get(), err)
				} else {
					for _, refSerial := range outboundSerials {
						if visited[refSerial.Get()] {
							continue
						}
						visited[refSerial.Get()] = true

						eventIDHex, err := d.GetEventIDFromSerial(refSerial)
						if err != nil {
							continue
						}

						result.AddEventAtDepth(eventIDHex, currentDepth)
						newEventsAtDepth++
						nextFrontier = append(nextFrontier, refSerial)
					}
				}
			}
		}

		log.T.F("TraverseThread: depth %d found %d new events", currentDepth, newEventsAtDepth)

		if newEventsAtDepth == 0 {
			consecutiveEmptyDepths++
			if consecutiveEmptyDepths >= 2 {
				break
			}
		} else {
			consecutiveEmptyDepths = 0
		}

		currentFrontier = nextFrontier
	}

	log.D.F("TraverseThread: completed with %d total events", result.TotalEvents)

	return result, nil
}

// TraverseThreadFromHex is a convenience wrapper that accepts hex-encoded event ID.
func (d *D) TraverseThreadFromHex(seedEventIDHex string, maxDepth int, direction string) (*GraphResult, error) {
	seedEventID, err := hex.Dec(seedEventIDHex)
	if err != nil {
		return nil, err
	}
	return d.TraverseThread(seedEventID, maxDepth, direction)
}

// GetThreadReplies finds all direct replies to an event.
// This is a convenience method that returns events at depth 1 with inbound direction.
func (d *D) GetThreadReplies(eventID []byte, kinds []uint16) (*GraphResult, error) {
	result := NewGraphResult()

	if len(eventID) != 32 {
		return result, ErrEventNotFound
	}

	eventSerial, err := d.GetSerialById(eventID)
	if err != nil {
		return result, nil
	}

	// Get events that reference this event
	replySerials, err := d.GetReferencingEvents(eventSerial, kinds)
	if err != nil {
		return nil, err
	}

	for _, replySerial := range replySerials {
		eventIDHex, err := d.GetEventIDFromSerial(replySerial)
		if err != nil {
			continue
		}
		result.AddEventAtDepth(eventIDHex, 1)
	}

	return result, nil
}

// GetThreadParents finds events that a given event references (its parents/quotes).
func (d *D) GetThreadParents(eventID []byte) (*GraphResult, error) {
	result := NewGraphResult()

	if len(eventID) != 32 {
		return result, ErrEventNotFound
	}

	eventSerial, err := d.GetSerialById(eventID)
	if err != nil {
		return result, nil
	}

	// Get events that this event references
	parentSerials, err := d.GetETagsFromEventSerial(eventSerial)
	if err != nil {
		return nil, err
	}

	for _, parentSerial := range parentSerials {
		eventIDHex, err := d.GetEventIDFromSerial(parentSerial)
		if err != nil {
			continue
		}
		result.AddEventAtDepth(eventIDHex, 1)
	}

	return result, nil
}
