func (p *Processor) removeMatchingInvalidRDFs(view string, opts *processorOpts) (string, error) {
	if !opts.removeInvalidRDF && !opts.validateRDF {
		return view, nil
	}
	views := strings.Split(view, "\n")
	var filteredViews []string
	var foundInvalid bool
	for _, v := range views {
		_, err := ld.ParseNQuads(v)
		if err != nil {
			if !strings.Contains(err.Error(), handleNormalizeErr) {
				return "", err
			}
			foundInvalid = true
			continue
		}
		filteredViews = append(filteredViews, v)
	}
	if !foundInvalid {
		return view, nil
	} else if opts.validateRDF {
		return "", ErrInvalidRDFFound
	}
	filteredView := strings.Join(filteredViews, "\n")
	logger.Debugf("Found invalid RDF dataset, Canonicalizing JSON-LD again after removing invalid data ")
	return p.normalizeFilteredDataset(filteredView)
}
removeMatchingInvalidRDFs validates normalized view to find any invalid RDF and
returns filtered view after removing all invalid data except the ones given in rdfMatches argument.
[Note : handling invalid RDF data, by following pattern https://github.com/digitalbazaar/jsonld.js/issues/199]
package jsonld
import (
	"errors"
	"fmt"
	"strings"
	"github.com/google/uuid"
	"github.com/piprate/json-gold/ld"
	"github.com/hyperledger/aries-framework-go/pkg/common/log"
)
const (
	format             = "application/n-quads"
	defaultAlgorithm   = "URDNA2015"
	handleNormalizeErr = "error while parsing N-Quads; invalid quad. line:"
)
var logger = log.New("aries-framework/json-ld-processor")
var ErrInvalidRDFFound = errors.New("invalid JSON-LD context")
type processorOpts struct {
	removeInvalidRDF bool
	frameBlankNodes  bool
	validateRDF      bool
	documentLoader   ld.DocumentLoader
	externalContexts []string
}
type ProcessorOpts func(opts *processorOpts)
func WithRemoveAllInvalidRDF() ProcessorOpts {
	return func(opts *processorOpts) {
		opts.removeInvalidRDF = true
	}
}
func WithFrameBlankNodes() ProcessorOpts {
	return func(opts *processorOpts) {
		opts.frameBlankNodes = true
	}
}
func WithDocumentLoader(loader ld.DocumentLoader) ProcessorOpts {
	return func(opts *processorOpts) {
		opts.documentLoader = loader
	}
}
func WithExternalContext(context ...string) ProcessorOpts {
	return func(opts *processorOpts) {
		opts.externalContexts = context
	}
}
func WithValidateRDF() ProcessorOpts {
	return func(opts *processorOpts) {
		opts.validateRDF = true
	}
}
type Processor struct {
	algorithm string
}
func NewProcessor(algorithm string) *Processor {
	if algorithm == "" {
		return Default()
	}
	return &Processor{algorithm}
}
func Default() *Processor {
	return &Processor{defaultAlgorithm}
}
func (p *Processor) GetCanonicalDocument(doc map[string]interface{}, opts ...ProcessorOpts) ([]byte, error) {
	procOptions := prepareOpts(opts)
	ldOptions := ld.NewJsonLdOptions("")
	ldOptions.ProcessingMode = ld.JsonLd_1_1
	ldOptions.Algorithm = p.algorithm
	ldOptions.Format = format
	ldOptions.ProduceGeneralizedRdf = true
	ldOptions.DocumentLoader = procOptions.documentLoader
	if len(procOptions.externalContexts) > 0 {
		doc["@context"] = AppendExternalContexts(doc["@context"], procOptions.externalContexts...)
	}
	proc := ld.NewJsonLdProcessor()
	view, err := proc.Normalize(doc, ldOptions)
	if err != nil {
		return nil, fmt.Errorf("failed to normalize JSON-LD document: %w", err)
	}
	result, ok := view.(string)
	if !ok {
		return nil, fmt.Errorf("failed to normalize JSON-LD document, invalid view")
	}
	result, err = p.removeMatchingInvalidRDFs(result, procOptions)
	if err != nil {
		return nil, err
	}
	return []byte(result), nil
}
func AppendExternalContexts(context interface{}, extraContexts ...string) []interface{} {
	var contexts []interface{}
	switch c := context.(type) {
	case string:
		contexts = append(contexts, c)
	case []interface{}:
		contexts = append(contexts, c...)
	}
	for i := range extraContexts {
		contexts = append(contexts, extraContexts[i])
	}
	return contexts
}
func (p *Processor) Compact(input, context map[string]interface{},
	opts ...ProcessorOpts) (map[string]interface{}, error) {
	procOptions := prepareOpts(opts)
	ldOptions := ld.NewJsonLdOptions("")
	ldOptions.ProcessingMode = ld.JsonLd_1_1
	ldOptions.Format = format
	ldOptions.ProduceGeneralizedRdf = true
	ldOptions.DocumentLoader = procOptions.documentLoader
	if context == nil {
		inputContext := input["@context"]
		if len(procOptions.externalContexts) > 0 {
			inputContext = AppendExternalContexts(inputContext, procOptions.externalContexts...)
			input["@context"] = inputContext
		}
		context = map[string]interface{}{"@context": inputContext}
	}
	return ld.NewJsonLdProcessor().Compact(input, context, ldOptions)
}
func (p *Processor) Frame(inputDoc map[string]interface{}, frameDoc map[string]interface{},
	opts ...ProcessorOpts) (map[string]interface{}, error) {
	procOptions := prepareOpts(opts)
	ldOptions := ld.NewJsonLdOptions("")
	ldOptions.ProcessingMode = ld.JsonLd_1_1
	ldOptions.Format = format
	ldOptions.ProduceGeneralizedRdf = true
	ldOptions.OmitGraph = true
	ldOptions.DocumentLoader = procOptions.documentLoader
	proc := ld.NewJsonLdProcessor()
	hasBlankBaseID := false
	if checkID, ok := inputDoc["id"]; !ok || checkID == "" {
		hasBlankBaseID = true
		inputDoc["id"] = fmt.Sprintf("urn:uuid:%s", uuid.New().String())
		frameDoc["id"] = inputDoc["id"]
	}
	inputDocCopy, randomIds, err := removeDuplicateIDs(inputDoc, proc, ldOptions)
	if err != nil {
		return nil, fmt.Errorf("removing duplicate ids failed: %w", err)
	}
	inputDocCopy, err = p.transformBlankNodes(inputDocCopy, opts...)
	if err != nil {
		return nil, fmt.Errorf("transforming frame input failed: %w", err)
	}
	framedInputDoc, err := proc.Frame(inputDocCopy, frameDoc, ldOptions)
	if err != nil {
		return nil, fmt.Errorf("framing failed: %w", err)
	}
	framedInputDoc["@context"] = frameDoc["@context"]
	if hasBlankBaseID {
		delete(framedInputDoc, "id")
		delete(frameDoc, "id")
	}
	if len(randomIds) == 0 {
		return framedInputDoc, nil
	}
	visitJSONMap(framedInputDoc, func(key, val string) (string, bool) {
		v, ok := randomIds[val]
		return v, ok
	})
	return framedInputDoc, nil
}
func removeDuplicateIDs(inputDoc map[string]interface{}, proc *ld.JsonLdProcessor,
	options *ld.JsonLdOptions) (map[string]interface{}, map[string]string, error) {
	duplicatedIDs, err := getDuplicatedIDs(inputDoc, proc, options)
	if err != nil {
		return nil, nil, fmt.Errorf("get duplicated ids: %w", err)
	}
	var inputDocCopy map[string]interface{}
	var randomIds map[string]string
	if len(duplicatedIDs) > 0 {
		inputDocCopy = copyMap(inputDoc)
		randomIds = make(map[string]string)
		visitJSONMap(inputDocCopy, func(key, id string) (string, bool) {
			if _, ok := duplicatedIDs[id]; !ok {
				return "", false
			}
			randomID := fmt.Sprintf("urn:uuid:%s", uuid.New().String())
			randomIds[randomID] = id
			return randomID, true
		})
	} else {
		inputDocCopy = inputDoc
	}
	return inputDocCopy, randomIds, nil
}
func copyMap(m map[string]interface{}) map[string]interface{} {
	cm := make(map[string]interface{})
	for k, v := range m {
		vm, ok := v.(map[string]interface{})
		if ok {
			cm[k] = copyMap(vm)
		} else {
			cm[k] = v
		}
	}
	return cm
}
func getDuplicatedIDs(doc map[string]interface{}, proc *ld.JsonLdProcessor, options *ld.JsonLdOptions) (
	map[string]bool, error) {
	expand, err := proc.Expand(doc, options)
	if err != nil {
		return nil, err
	}
	ids := make(map[string]bool)
	visitJSONArray(expand, func(key, val string) (string, bool) {
		if key == "@id" {
			if _, ok := ids[val]; ok {
				ids[val] = true
			} else {
				ids[val] = false
			}
		}
		return "", false
	})
	for k, v := range ids {
		if !v {
			delete(ids, k)
		}
	}
	return ids, nil
}
func visitJSONArray(a []interface{}, visitFunc func(key, val string) (string, bool)) {
	for i, v := range a {
		switch kv := v.(type) {
		case string:
			if newValue, ok := visitFunc("", kv); ok {
				a[i] = newValue
			}
		case []interface{}:
			visitJSONArray(kv, visitFunc)
		case map[string]interface{}:
			visitJSONMap(kv, visitFunc)
		}
	}
}
func visitJSONMap(m map[string]interface{}, visitFunc func(key, val string) (string, bool)) {
	for k, v := range m {
		switch kv := v.(type) {
		case string:
			if newID, ok := visitFunc(k, kv); ok {
				m[k] = newID
			}
		case []interface{}:
			visitJSONArray(kv, visitFunc)
		case map[string]interface{}:
			visitJSONMap(kv, visitFunc)
		}
	}
}