// SPDX-License-Identifier: GPL-3.0-or-later

package projector

import (
	"time"

	"github.com/netdata/netdata/go/plugins/pkg/l2topology/internal/model"
	"github.com/netdata/netdata/go/plugins/pkg/topology/graph"
)

type segmentProjectionBuilder struct {
	attachments               []model.Attachment
	adjacencies               []model.Adjacency
	layer                     string
	source                    string
	collectedAt               time.Time
	deviceByID                map[string]model.Device
	ifaceByDeviceIndex        map[string]model.Interface
	ifIndexByDeviceName       map[string]int
	bridgeLinks               []bridgeBridgeLinkRecord
	reporterAliases           map[string][]string
	endpointMatchByID         map[string]graph.Match
	endpointLabelsByID        map[string]map[string]string
	actorIndex                map[string]struct{}
	probabilisticConnectivity bool
	strategyConfig            topologyInferenceStrategyConfig
	out                       projectedSegments
	segmentIDs                []string
	segmentMatchByID          map[string]graph.Match
	segmentByID               map[string]*bridgeDomainSegment
	deviceSegmentEdgeSeen     map[string]struct{}
	endpointSegmentEdgeSeen   map[string]struct{}
	endpointSegmentCandidates map[string][]string
	segmentPortKeys           map[string]map[string]struct{}
	segmentIfIndexes          map[string]map[string]struct{}
	segmentIfNames            map[string]map[string]struct{}
	rawFDBObservations        fdbReporterObservation
	rawFDBReporterHints       map[string]map[string][]bridgePortRef
	fdbObservations           fdbReporterObservation
	fdbOwners                 map[string]fdbEndpointOwner
	deviceIdentityByID        map[string]topologyIdentityKeySet
	reporterSegmentIndex      segmentReporterIndex
	aliasOwnerIDs             map[string]map[string]struct{}
	managedDeviceIDs          map[string]struct{}
	managedDeviceIDList       []string
	allowedEndpointBySegment  map[string]map[string]struct{}
	strictEndpointBySegment   map[string]map[string]struct{}
	probableEndpointBySegment map[string]map[string]struct{}
	probableAttachmentModes   map[string]map[string]string
	assignedEndpoints         map[string]struct{}
	baseCandidatesByEndpoint  map[string][]string
	probableCandidatesByEP    map[string][]string
	strictLinkedEndpoints     map[string]struct{}
}

func newSegmentProjectionBuilder(
	attachments []model.Attachment,
	adjacencies []model.Adjacency,
	layer string,
	source string,
	collectedAt time.Time,
	deviceByID map[string]model.Device,
	ifaceByDeviceIndex map[string]model.Interface,
	ifIndexByDeviceName map[string]int,
	bridgeLinks []bridgeBridgeLinkRecord,
	reporterAliases map[string][]string,
	endpointMatchByID map[string]graph.Match,
	endpointLabelsByID map[string]map[string]string,
	actorIndex map[string]struct{},
	probabilisticConnectivity bool,
	strategyConfig topologyInferenceStrategyConfig,
) *segmentProjectionBuilder {
	return &segmentProjectionBuilder{
		attachments:               attachments,
		adjacencies:               adjacencies,
		layer:                     layer,
		source:                    source,
		collectedAt:               collectedAt,
		deviceByID:                deviceByID,
		ifaceByDeviceIndex:        ifaceByDeviceIndex,
		ifIndexByDeviceName:       ifIndexByDeviceName,
		bridgeLinks:               bridgeLinks,
		reporterAliases:           reporterAliases,
		endpointMatchByID:         endpointMatchByID,
		endpointLabelsByID:        endpointLabelsByID,
		actorIndex:                actorIndex,
		probabilisticConnectivity: probabilisticConnectivity,
		strategyConfig:            strategyConfig,
		out: projectedSegments{
			actors: make([]projectedActor, 0),
			links:  make([]graph.Link, 0),
		},
	}
}

func (b *segmentProjectionBuilder) build() projectedSegments {
	if len(b.attachments) == 0 && len(b.adjacencies) == 0 {
		return b.out
	}
	if !b.initializeSegments() {
		return b.out
	}
	endpointIDs := b.initializeEndpointCandidates()
	b.assignProbableEndpoints(endpointIDs)
	b.assignRemainingProbableEndpoints(endpointIDs)
	b.emitLinks()
	sortTopologyLinks(b.out.links)
	return b.out
}
