// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package ap

import (
	"fmt"
	"net/url"
	"time"

	"code.superseriousbusiness.org/activity/streams"
	"code.superseriousbusiness.org/activity/streams/vocab"
	"code.superseriousbusiness.org/gotosocial/internal/gtserror"
)

// MustGet performs the given 'Get$Property(with) (T, error)' signature function, panicking on error.
// func MustGet[W, T any](fn func(W) (T, error), with W) T {
// 	t, err := fn(with)
// 	if err != nil {
// 		panicfAt(3, "error getting property on %T: %w", with, err)
// 	}
// 	return t
// }

// MustSet performs the given 'Set$Property(with, T) error' signature function, panicking on error.
func MustSet[W, T any](fn func(W, T) error, with W, value T) {
	err := fn(with, value)
	if err != nil {
		panicfAt(3, "error setting property on %T: %w", with, err)
	}
}

// AppendSet performs the given 'Append$Property(with, ...T) error' signature function, panicking on error.
// func MustAppend[W, T any](fn func(W, ...T) error, with W, values ...T) {
// 	err := fn(with, values...)
// 	if err != nil {
// 		panicfAt(3, "error appending properties on %T: %w", with, err)
// 	}
// }

// GetJSONLDId returns the ID of 'with', or nil.
func GetJSONLDId(with WithJSONLDId) *url.URL {
	idProp := with.GetJSONLDId()
	if idProp == nil || !idProp.IsXMLSchemaAnyURI() {
		return nil
	}
	return idProp.Get()
}

// SetJSONLDId sets the given URL to the JSONLD ID of 'with'.
func SetJSONLDId(with WithJSONLDId, id *url.URL) {
	idProp := with.GetJSONLDId()
	if idProp == nil {
		idProp = streams.NewJSONLDIdProperty()
		with.SetJSONLDId(idProp)
	}
	idProp.SetIRI(id)
}

// SetJSONLDIdStr sets the given string to the JSONLDID of 'with'. Returns error
func SetJSONLDIdStr(with WithJSONLDId, id string) error {
	u, err := url.Parse(id)
	if err != nil {
		return fmt.Errorf("error parsing id url: %w", err)
	}
	SetJSONLDId(with, u)
	return nil
}

// GetTo returns the IRIs contained in the To property of 'with'. Panics on entries with missing ID.
func GetTo(with WithTo) []*url.URL {
	toProp := with.GetActivityStreamsTo()
	return getIRIs[vocab.ActivityStreamsToPropertyIterator](toProp)
}

// AppendTo appends the given IRIs to the To property of 'with'.
func AppendTo(with WithTo, to ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsToPropertyIterator] {
		toProp := with.GetActivityStreamsTo()
		if toProp == nil {
			toProp = streams.NewActivityStreamsToProperty()
			with.SetActivityStreamsTo(toProp)
		}
		return toProp
	}, to...)
}

// GetCc returns the IRIs contained in the Cc property of 'with'. Panics on entries with missing ID.
func GetCc(with WithCc) []*url.URL {
	ccProp := with.GetActivityStreamsCc()
	return extractIRIs[vocab.ActivityStreamsCcPropertyIterator](ccProp)
}

// AppendCc appends the given IRIs to the Cc property of 'with'.
func AppendCc(with WithCc, cc ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsCcPropertyIterator] {
		ccProp := with.GetActivityStreamsCc()
		if ccProp == nil {
			ccProp = streams.NewActivityStreamsCcProperty()
			with.SetActivityStreamsCc(ccProp)
		}
		return ccProp
	}, cc...)
}

// GetBcc returns the IRIs contained in the Bcc property of 'with'. Panics on entries with missing ID.
func GetBcc(with WithBcc) []*url.URL {
	bccProp := with.GetActivityStreamsBcc()
	return extractIRIs[vocab.ActivityStreamsBccPropertyIterator](bccProp)
}

// AppendBcc appends the given IRIs to the Bcc property of 'with'.
func AppendBcc(with WithBcc, bcc ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsBccPropertyIterator] {
		bccProp := with.GetActivityStreamsBcc()
		if bccProp == nil {
			bccProp = streams.NewActivityStreamsBccProperty()
			with.SetActivityStreamsBcc(bccProp)
		}
		return bccProp
	}, bcc...)
}

// GetURL returns IRIs contained
// in the URL property of 'with'.
func GetURL(with WithURL) []*url.URL {
	urlProp := with.GetActivityStreamsUrl()
	if urlProp == nil || urlProp.Len() == 0 {
		return nil
	}
	urls := make([]*url.URL, 0, urlProp.Len())
	for i := 0; i < urlProp.Len(); i++ {
		at := urlProp.At(i)

		// See if it's a plain URI.
		if at.IsXMLSchemaAnyURI() {
			u := at.GetXMLSchemaAnyURI()
			urls = append(urls, u)
			continue
		}

		// See if it's a Link obj
		// with an href property.
		if at.IsActivityStreamsLink() {
			l := at.GetActivityStreamsLink()
			hr := l.GetActivityStreamsHref()
			if hr == nil {
				// No href.
				continue
			}

			if hr.IsXMLSchemaAnyURI() {
				u := hr.Get()
				urls = append(urls, u)
			} else if hr.IsIRI() {
				u := hr.GetIRI()
				urls = append(urls, u)
			}
		}
	}
	return urls
}

// AppendURL appends the given URLs to the URL property of 'with'.
func AppendURL(with WithURL, url ...*url.URL) {
	if len(url) == 0 {
		return
	}
	urlProp := with.GetActivityStreamsUrl()
	if urlProp == nil {
		urlProp = streams.NewActivityStreamsUrlProperty()
		with.SetActivityStreamsUrl(urlProp)
	}
	for _, u := range url {
		urlProp.AppendXMLSchemaAnyURI(u)
	}
}

// GetActorIRIs returns the IRIs contained in the Actor property of 'with'.
func GetActorIRIs(with WithActor) []*url.URL {
	actorProp := with.GetActivityStreamsActor()
	return extractIRIs[vocab.ActivityStreamsActorPropertyIterator](actorProp)
}

// AppendActorIRIs appends the given IRIs to the Actor property of 'with'.
func AppendActorIRIs(with WithActor, actor ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsActorPropertyIterator] {
		actorProp := with.GetActivityStreamsActor()
		if actorProp == nil {
			actorProp = streams.NewActivityStreamsActorProperty()
			with.SetActivityStreamsActor(actorProp)
		}
		return actorProp
	}, actor...)
}

// GetObjectIRIs returns the IRIs contained in the Object property of 'with'.
func GetObjectIRIs(with WithObject) []*url.URL {
	objectProp := with.GetActivityStreamsObject()
	return extractIRIs[vocab.ActivityStreamsObjectPropertyIterator](objectProp)
}

// AppendObjectIRIs appends the given IRIs to the Object property of 'with'.
func AppendObjectIRIs(with WithObject, object ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsObjectPropertyIterator] {
		objectProp := with.GetActivityStreamsObject()
		if objectProp == nil {
			objectProp = streams.NewActivityStreamsObjectProperty()
			with.SetActivityStreamsObject(objectProp)
		}
		return objectProp
	}, object...)
}

// AppendInstrumentIRIs appends the given IRIs to the Instrument property of 'with'.
func AppendInstrumentIRIs(with WithInstrument, instrument ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsInstrumentPropertyIterator] {
		instrumentProp := with.GetActivityStreamsInstrument()
		if instrumentProp == nil {
			instrumentProp = streams.NewActivityStreamsInstrumentProperty()
			with.SetActivityStreamsInstrument(instrumentProp)
		}
		return instrumentProp
	}, instrument...)
}

// GetResultIRIs returns the IRIs contained in the `result` property of 'with'.
func GetResultIRIs(with WithResult) []*url.URL {
	resultProp := with.GetActivityStreamsResult()
	return extractIRIs(resultProp)
}

// AppendResultIRIs appends the given IRIs to the Result property of 'with'.
func AppendResultIRIs(with WithResult, result ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsResultPropertyIterator] {
		resultProp := with.GetActivityStreamsResult()
		if resultProp == nil {
			resultProp = streams.NewActivityStreamsResultProperty()
			with.SetActivityStreamsResult(resultProp)
		}
		return resultProp
	}, result...)
}

// GetTargetIRIs returns the IRIs contained in the Target property of 'with'.
func GetTargetIRIs(with WithTarget) []*url.URL {
	targetProp := with.GetActivityStreamsTarget()
	return extractIRIs[vocab.ActivityStreamsTargetPropertyIterator](targetProp)
}

// AppendTargetIRIs appends the given IRIs to the Target property of 'with'.
func AppendTargetIRIs(with WithTarget, target ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsTargetPropertyIterator] {
		targetProp := with.GetActivityStreamsTarget()
		if targetProp == nil {
			targetProp = streams.NewActivityStreamsTargetProperty()
			with.SetActivityStreamsTarget(targetProp)
		}
		return targetProp
	}, target...)
}

// GetAttributedTo returns the IRIs contained in the AttributedTo property of 'with'.
func GetAttributedTo(with WithAttributedTo) []*url.URL {
	attribProp := with.GetActivityStreamsAttributedTo()
	return extractIRIs[vocab.ActivityStreamsAttributedToPropertyIterator](attribProp)
}

// AppendAttributedTo appends the given IRIs to the AttributedTo property of 'with'.
func AppendAttributedTo(with WithAttributedTo, attribTo ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsAttributedToPropertyIterator] {
		attribProp := with.GetActivityStreamsAttributedTo()
		if attribProp == nil {
			attribProp = streams.NewActivityStreamsAttributedToProperty()
			with.SetActivityStreamsAttributedTo(attribProp)
		}
		return attribProp
	}, attribTo...)
}

// GetInteractingObject returns IRIs contained in the interactingObject property of 'with'.
func GetInteractingObject(with WithInteractingObject) []*url.URL {
	intObjProp := with.GetGoToSocialInteractingObject()
	return getIRIs(intObjProp)
}

// AppendInteractingObject appends the given IRIs to the interactingObject property of 'with'.
func AppendInteractingObject(with WithInteractingObject, interactingObject ...*url.URL) {
	appendIRIs(func() Property[vocab.GoToSocialInteractingObjectPropertyIterator] {
		intObjProp := with.GetGoToSocialInteractingObject()
		if intObjProp == nil {
			intObjProp = streams.NewGoToSocialInteractingObjectProperty()
			with.SetGoToSocialInteractingObject(intObjProp)
		}
		return intObjProp
	}, interactingObject...)
}

// GetInteractionTarget returns IRIs contained in the interactionTarget property of 'with'.
func GetInteractionTarget(with WithInteractionTarget) []*url.URL {
	intTargetProp := with.GetGoToSocialInteractionTarget()
	return getIRIs(intTargetProp)
}

// AppendInteractionTarget appends the given IRIs to the interactionTarget property of 'with'.
func AppendInteractionTarget(with WithInteractionTarget, interactionTarget ...*url.URL) {
	appendIRIs(func() Property[vocab.GoToSocialInteractionTargetPropertyIterator] {
		intTargetProp := with.GetGoToSocialInteractionTarget()
		if intTargetProp == nil {
			intTargetProp = streams.NewGoToSocialInteractionTargetProperty()
			with.SetGoToSocialInteractionTarget(intTargetProp)
		}
		return intTargetProp
	}, interactionTarget...)
}

// GetInReplyTo returns the IRIs contained in the InReplyTo property of 'with'.
func GetInReplyTo(with WithInReplyTo) []*url.URL {
	replyProp := with.GetActivityStreamsInReplyTo()
	return extractIRIs[vocab.ActivityStreamsInReplyToPropertyIterator](replyProp)
}

// AppendInReplyTo appends the given IRIs to the InReplyTo property of 'with'.
func AppendInReplyTo(with WithInReplyTo, replyTo ...*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsInReplyToPropertyIterator] {
		replyProp := with.GetActivityStreamsInReplyTo()
		if replyProp == nil {
			replyProp = streams.NewActivityStreamsInReplyToProperty()
			with.SetActivityStreamsInReplyTo(replyProp)
		}
		return replyProp
	}, replyTo...)
}

// GetInbox returns the IRI contained in the Inbox property of 'with'.
func GetInbox(with WithInbox) *url.URL {
	inboxProp := with.GetActivityStreamsInbox()
	if inboxProp == nil || !inboxProp.IsIRI() {
		return nil
	}
	return inboxProp.GetIRI()
}

// SetInbox sets the given IRI on the Inbox property of 'with'.
func SetInbox(with WithInbox, inbox *url.URL) {
	inboxProp := with.GetActivityStreamsInbox()
	if inboxProp == nil {
		inboxProp = streams.NewActivityStreamsInboxProperty()
		with.SetActivityStreamsInbox(inboxProp)
	}
	inboxProp.SetIRI(inbox)
}

// GetOutbox returns the IRI contained in the Outbox property of 'with'.
func GetOutbox(with WithOutbox) *url.URL {
	outboxProp := with.GetActivityStreamsOutbox()
	if outboxProp == nil || !outboxProp.IsIRI() {
		return nil
	}
	return outboxProp.GetIRI()
}

// SetOutbox sets the given IRI on the Outbox property of 'with'.
func SetOutbox(with WithOutbox, outbox *url.URL) {
	outboxProp := with.GetActivityStreamsOutbox()
	if outboxProp == nil {
		outboxProp = streams.NewActivityStreamsOutboxProperty()
		with.SetActivityStreamsOutbox(outboxProp)
	}
	outboxProp.SetIRI(outbox)
}

// GetFollowers returns the IRI contained in the Following property of 'with'.
func GetFollowing(with WithFollowing) *url.URL {
	followProp := with.GetActivityStreamsFollowing()
	if followProp == nil || !followProp.IsIRI() {
		return nil
	}
	return followProp.GetIRI()
}

// SetFollowers sets the given IRI on the Following property of 'with'.
func SetFollowing(with WithFollowing, following *url.URL) {
	followProp := with.GetActivityStreamsFollowing()
	if followProp == nil {
		followProp = streams.NewActivityStreamsFollowingProperty()
		with.SetActivityStreamsFollowing(followProp)
	}
	followProp.SetIRI(following)
}

// GetFollowers returns the IRI contained in the Followers property of 'with'.
func GetFollowers(with WithFollowers) *url.URL {
	followProp := with.GetActivityStreamsFollowers()
	if followProp == nil || !followProp.IsIRI() {
		return nil
	}
	return followProp.GetIRI()
}

// SetFollowers sets the given IRI on the Followers property of 'with'.
func SetFollowers(with WithFollowers, followers *url.URL) {
	followProp := with.GetActivityStreamsFollowers()
	if followProp == nil {
		followProp = streams.NewActivityStreamsFollowersProperty()
		with.SetActivityStreamsFollowers(followProp)
	}
	followProp.SetIRI(followers)
}

// GetFeatured returns the IRI contained in the Featured property of 'with'.
func GetFeatured(with WithFeatured) *url.URL {
	featuredProp := with.GetTootFeatured()
	if featuredProp == nil || !featuredProp.IsIRI() {
		return nil
	}
	return featuredProp.GetIRI()
}

// SetFeatured sets the given IRI on the Featured property of 'with'.
func SetFeatured(with WithFeatured, featured *url.URL) {
	featuredProp := with.GetTootFeatured()
	if featuredProp == nil {
		featuredProp = streams.NewTootFeaturedProperty()
		with.SetTootFeatured(featuredProp)
	}
	featuredProp.SetIRI(featured)
}

// GetMovedTo returns the IRI contained in the movedTo property of 'with'.
func GetMovedTo(with WithMovedTo) *url.URL {
	movedToProp := with.GetActivityStreamsMovedTo()
	if movedToProp == nil || !movedToProp.IsIRI() {
		return nil
	}
	return movedToProp.GetIRI()
}

// SetMovedTo sets the given IRI on the movedTo property of 'with'.
func SetMovedTo(with WithMovedTo, movedTo *url.URL) {
	movedToProp := with.GetActivityStreamsMovedTo()
	if movedToProp == nil {
		movedToProp = streams.NewActivityStreamsMovedToProperty()
		with.SetActivityStreamsMovedTo(movedToProp)
	}
	movedToProp.SetIRI(movedTo)
}

// GetAlsoKnownAs returns the IRI contained in the alsoKnownAs property of 'with'.
func GetAlsoKnownAs(with WithAlsoKnownAs) []*url.URL {
	alsoKnownAsProp := with.GetActivityStreamsAlsoKnownAs()
	return getIRIs[vocab.ActivityStreamsAlsoKnownAsPropertyIterator](alsoKnownAsProp)
}

// SetAlsoKnownAs sets the given IRIs on the alsoKnownAs property of 'with'.
func SetAlsoKnownAs(with WithAlsoKnownAs, alsoKnownAs []*url.URL) {
	appendIRIs(func() Property[vocab.ActivityStreamsAlsoKnownAsPropertyIterator] {
		alsoKnownAsProp := with.GetActivityStreamsAlsoKnownAs()
		if alsoKnownAsProp == nil {
			alsoKnownAsProp = streams.NewActivityStreamsAlsoKnownAsProperty()
			with.SetActivityStreamsAlsoKnownAs(alsoKnownAsProp)
		}
		return alsoKnownAsProp
	}, alsoKnownAs...)
}

// GetPublished returns the time contained in the Published property of 'with'.
func GetPublished(with WithPublished) time.Time {
	publishProp := with.GetActivityStreamsPublished()
	if publishProp == nil || !publishProp.IsXMLSchemaDateTime() {
		return time.Time{}
	}
	return publishProp.Get()
}

// SetPublished sets the given time on the Published property of 'with'.
func SetPublished(with WithPublished, published time.Time) {
	publishProp := with.GetActivityStreamsPublished()
	if publishProp == nil {
		publishProp = streams.NewActivityStreamsPublishedProperty()
		with.SetActivityStreamsPublished(publishProp)
	}
	publishProp.Set(published)
}

// GetUpdated returns the time contained in the Updated property of 'with'.
func GetUpdated(with WithUpdated) time.Time {
	updateProp := with.GetActivityStreamsUpdated()
	if updateProp == nil || !updateProp.IsXMLSchemaDateTime() {
		return time.Time{}
	}
	return updateProp.Get()
}

// SetUpdated sets the given time on the Updated property of 'with'.
func SetUpdated(with WithUpdated, updated time.Time) {
	updateProp := with.GetActivityStreamsUpdated()
	if updateProp == nil {
		updateProp = streams.NewActivityStreamsUpdatedProperty()
		with.SetActivityStreamsUpdated(updateProp)
	}
	updateProp.Set(updated)
}

// GetEndTime returns the time contained in the EndTime property of 'with'.
func GetEndTime(with WithEndTime) time.Time {
	endTimeProp := with.GetActivityStreamsEndTime()
	if endTimeProp == nil || !endTimeProp.IsXMLSchemaDateTime() {
		return time.Time{}
	}
	return endTimeProp.Get()
}

// SetEndTime sets the given time on the EndTime property of 'with'.
func SetEndTime(with WithEndTime, end time.Time) {
	endTimeProp := with.GetActivityStreamsEndTime()
	if endTimeProp == nil {
		endTimeProp = streams.NewActivityStreamsEndTimeProperty()
		with.SetActivityStreamsEndTime(endTimeProp)
	}
	endTimeProp.Set(end)
}

// GetEndTime returns the times contained in the Closed property of 'with'.
func GetClosed(with WithClosed) []time.Time {
	closedProp := with.GetActivityStreamsClosed()
	if closedProp == nil || closedProp.Len() == 0 {
		return nil
	}
	closed := make([]time.Time, 0, closedProp.Len())
	for i := 0; i < closedProp.Len(); i++ {
		at := closedProp.At(i)
		if at.IsXMLSchemaDateTime() {
			t := at.GetXMLSchemaDateTime()
			closed = append(closed, t)
		}
	}
	return closed
}

// AppendClosed appends the given times to the Closed property of 'with'.
func AppendClosed(with WithClosed, closed ...time.Time) {
	if len(closed) == 0 {
		return
	}
	closedProp := with.GetActivityStreamsClosed()
	if closedProp == nil {
		closedProp = streams.NewActivityStreamsClosedProperty()
		with.SetActivityStreamsClosed(closedProp)
	}
	for _, closed := range closed {
		closedProp.AppendXMLSchemaDateTime(closed)
	}
}

// GetVotersCount returns the integer contained in the VotersCount property of 'with', if found.
func GetVotersCount(with WithVotersCount) int {
	votersProp := with.GetTootVotersCount()
	if votersProp == nil || !votersProp.IsXMLSchemaNonNegativeInteger() {
		return 0
	}
	return votersProp.Get()
}

// SetVotersCount sets the given count on the VotersCount property of 'with'.
func SetVotersCount(with WithVotersCount, count int) {
	votersProp := with.GetTootVotersCount()
	if votersProp == nil {
		votersProp = streams.NewTootVotersCountProperty()
		with.SetTootVotersCount(votersProp)
	}
	votersProp.Set(count)
}

// GetDiscoverable returns the boolean contained in the Discoverable property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetDiscoverable(with WithDiscoverable) bool {
	discoverProp := with.GetTootDiscoverable()
	if discoverProp == nil || !discoverProp.IsXMLSchemaBoolean() {
		return false
	}
	return discoverProp.Get()
}

// SetDiscoverable sets the given boolean on the Discoverable property of 'with'.
func SetDiscoverable(with WithDiscoverable, discoverable bool) {
	discoverProp := with.GetTootDiscoverable()
	if discoverProp == nil {
		discoverProp = streams.NewTootDiscoverableProperty()
		with.SetTootDiscoverable(discoverProp)
	}
	discoverProp.Set(discoverable)
}

// GetIndexable returns the boolean contained in the Indexable property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetIndexable(with WithIndexable) bool {
	indexableProp := with.GetTootIndexable()
	if indexableProp == nil || !indexableProp.IsXMLSchemaBoolean() {
		return false
	}
	return indexableProp.Get()
}

// SetIndexable sets the given boolean on the Indexable property of 'with'.
func SetIndexable(with WithIndexable, indexable bool) {
	indexableProp := with.GetTootIndexable()
	if indexableProp == nil {
		indexableProp = streams.NewTootIndexableProperty()
		with.SetTootIndexable(indexableProp)
	}
	indexableProp.Set(indexable)
}

// GetManuallyApprovesFollowers returns the boolean contained in the ManuallyApprovesFollowers property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetManuallyApprovesFollowers(with WithManuallyApprovesFollowers) bool {
	mafProp := with.GetActivityStreamsManuallyApprovesFollowers()
	if mafProp == nil || !mafProp.IsXMLSchemaBoolean() {
		return false
	}
	return mafProp.Get()
}

// SetManuallyApprovesFollowers sets the given boolean on the ManuallyApprovesFollowers property of 'with'.
func SetManuallyApprovesFollowers(with WithManuallyApprovesFollowers, manuallyApprovesFollowers bool) {
	mafProp := with.GetActivityStreamsManuallyApprovesFollowers()
	if mafProp == nil {
		mafProp = streams.NewActivityStreamsManuallyApprovesFollowersProperty()
		with.SetActivityStreamsManuallyApprovesFollowers(mafProp)
	}
	mafProp.Set(manuallyApprovesFollowers)
}

// GetHidesToPublicFromUnauthedWeb returns the boolean contained in the hidesToPublicFromUnauthedWeb property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetHidesToPublicFromUnauthedWeb(with WithHidesToPublicFromUnauthedWeb) bool {
	hidesProp := with.GetGoToSocialHidesToPublicFromUnauthedWeb()
	if hidesProp == nil || !hidesProp.IsXMLSchemaBoolean() {
		return false
	}
	return hidesProp.Get()
}

// SetHidesToPublicFromUnauthedWeb sets the given boolean on the hidesToPublicFromUnauthedWeb property of 'with'.
func SetHidesToPublicFromUnauthedWeb(with WithHidesToPublicFromUnauthedWeb, hidesToPublicFromUnauthedWeb bool) {
	hidesProp := with.GetGoToSocialHidesToPublicFromUnauthedWeb()
	if hidesProp == nil {
		hidesProp = streams.NewGoToSocialHidesToPublicFromUnauthedWebProperty()
		with.SetGoToSocialHidesToPublicFromUnauthedWeb(hidesProp)
	}
	hidesProp.Set(hidesToPublicFromUnauthedWeb)
}

// GetHidesCcPublicFromUnauthedWeb returns the boolean contained in the hidesCcPublicFromUnauthedWeb property of 'with'.
//
// Returns default 'true' if property unusable or not set.
func GetHidesCcPublicFromUnauthedWeb(with WithHidesCcPublicFromUnauthedWeb) bool {
	hidesProp := with.GetGoToSocialHidesCcPublicFromUnauthedWeb()
	if hidesProp == nil || !hidesProp.IsXMLSchemaBoolean() {
		return true
	}
	return hidesProp.Get()
}

// SetHidesCcPublicFromUnauthedWeb sets the given boolean on the hidesCcPublicFromUnauthedWeb property of 'with'.
func SetHidesCcPublicFromUnauthedWeb(with WithHidesCcPublicFromUnauthedWeb, hidesCcPublicFromUnauthedWeb bool) {
	hidesProp := with.GetGoToSocialHidesCcPublicFromUnauthedWeb()
	if hidesProp == nil {
		hidesProp = streams.NewGoToSocialHidesCcPublicFromUnauthedWebProperty()
		with.SetGoToSocialHidesCcPublicFromUnauthedWeb(hidesProp)
	}
	hidesProp.Set(hidesCcPublicFromUnauthedWeb)
}

// GetApprovedBy returns the URL contained in
// the ApprovedBy property of 'with', if set.
func GetApprovedBy(with WithApprovedBy) *url.URL {
	abProp := with.GetGoToSocialApprovedBy()
	if abProp == nil || !abProp.IsIRI() {
		return nil
	}
	return abProp.Get()
}

// SetApprovedBy sets the given url
// on the ApprovedBy property of 'with'.
func SetApprovedBy(with WithApprovedBy, approvedBy *url.URL) {
	abProp := with.GetGoToSocialApprovedBy()
	if abProp == nil {
		abProp = streams.NewGoToSocialApprovedByProperty()
		with.SetGoToSocialApprovedBy(abProp)
	}
	abProp.Set(approvedBy)
}

// GetLikeAuthorization returns the URL contained in
// the likeAuthorization property of 'with', if set.
func GetLikeAuthorization(with WithLikeAuthorization) *url.URL {
	laProp := with.GetGoToSocialLikeAuthorization()
	if laProp == nil || !laProp.IsIRI() {
		return nil
	}
	return laProp.Get()
}

// SetLikeAuthorization sets the given url on
// the 'likeAuthorization' property of 'with'.
func SetLikeAuthorization(with WithLikeAuthorization, likeAuthorization *url.URL) {
	laProp := with.GetGoToSocialLikeAuthorization()
	if laProp == nil {
		laProp = streams.NewGoToSocialLikeAuthorizationProperty()
		with.SetGoToSocialLikeAuthorization(laProp)
	}
	laProp.Set(likeAuthorization)
}

// GetReplyAuthorization returns the URL contained in
// the replyAuthorization property of 'with', if set.
func GetReplyAuthorization(with WithReplyAuthorization) *url.URL {
	raProp := with.GetGoToSocialReplyAuthorization()
	if raProp == nil || !raProp.IsIRI() {
		return nil
	}
	return raProp.Get()
}

// SetReplyAuthorization sets the given url on
// the 'replyAuthorization' property of 'with'.
func SetReplyAuthorization(with WithReplyAuthorization, replyAuthorization *url.URL) {
	raProp := with.GetGoToSocialReplyAuthorization()
	if raProp == nil {
		raProp = streams.NewGoToSocialReplyAuthorizationProperty()
		with.SetGoToSocialReplyAuthorization(raProp)
	}
	raProp.Set(replyAuthorization)
}

// GetAnnounceAuthorization returns the URL contained in
// the announceAuthorization property of 'with', if set.
func GetAnnounceAuthorization(with WithAnnounceAuthorization) *url.URL {
	aaProp := with.GetGoToSocialAnnounceAuthorization()
	if aaProp == nil || !aaProp.IsIRI() {
		return nil
	}
	return aaProp.Get()
}

// SetAnnounceAuthorization sets the given url on
// the 'announceAuthorization' property of 'with'.
func SetAnnounceAuthorization(with WithAnnounceAuthorization, announceAuthorization *url.URL) {
	aaProp := with.GetGoToSocialAnnounceAuthorization()
	if aaProp == nil {
		aaProp = streams.NewGoToSocialAnnounceAuthorizationProperty()
		with.SetGoToSocialAnnounceAuthorization(aaProp)
	}
	aaProp.Set(announceAuthorization)
}

// GetMediaType returns the string contained in
// the MediaType property of 'with', if set.
func GetMediaType(with WithMediaType) string {
	mtProp := with.GetActivityStreamsMediaType()
	if mtProp == nil || !mtProp.IsRFCRfc2045() {
		return ""
	}
	return mtProp.Get()
}

// SetMediaType sets the given string
// on the MediaType property of 'with'.
func SetMediaType(with WithMediaType, mediaType string) {
	mtProp := with.GetActivityStreamsMediaType()
	if mtProp == nil {
		mtProp = streams.NewActivityStreamsMediaTypeProperty()
		with.SetActivityStreamsMediaType(mtProp)
	}
	mtProp.Set(mediaType)
}

// AppendName appends the given name
// vals to the Name property of 'with'.
func AppendName(with WithName, name ...string) {
	if len(name) == 0 {
		return
	}
	nameProp := with.GetActivityStreamsName()
	if nameProp == nil {
		nameProp = streams.NewActivityStreamsNameProperty()
		with.SetActivityStreamsName(nameProp)
	}
	for _, name := range name {
		nameProp.AppendXMLSchemaString(name)
	}
}

// AppendSummary appends the given summary
// vals to the Summary property of 'with'.
func AppendSummary(with WithSummary, summary ...string) {
	if len(summary) == 0 {
		return
	}
	summaryProp := with.GetActivityStreamsSummary()
	if summaryProp == nil {
		summaryProp = streams.NewActivityStreamsSummaryProperty()
		with.SetActivityStreamsSummary(summaryProp)
	}
	for _, summary := range summary {
		summaryProp.AppendXMLSchemaString(summary)
	}
}

// SetBlurhash sets the given string
// on the Blurhash property of 'with'.
func SetBlurhash(with WithBlurhash, mediaType string) {
	bProp := with.GetTootBlurhash()
	if bProp == nil {
		bProp = streams.NewTootBlurhashProperty()
		with.SetTootBlurhash(bProp)
	}
	bProp.Set(mediaType)
}

// AppendSensitive appends the given sensitive
// boolean to the `sensitive` property of 'with'.
func AppendSensitive(with WithSensitive, sensitive bool) {
	sProp := with.GetActivityStreamsSensitive()
	if sProp == nil {
		sProp = streams.NewActivityStreamsSensitiveProperty()
		with.SetActivityStreamsSensitive(sProp)
	}
	sProp.AppendXMLSchemaBoolean(sensitive)
}

// AppendContent appends the given content
// string to the `content` property of 'with'.
func AppendContent(with WithContent, content string) {
	cProp := with.GetActivityStreamsContent()
	if cProp == nil {
		cProp = streams.NewActivityStreamsContentProperty()
		with.SetActivityStreamsContent(cProp)
	}
	cProp.AppendXMLSchemaString(content)
}

// AppendContentMap appends the given content
// language map to the `content` property of 'with'.
func AppendContentMap(with WithContent, contentMap map[string]string) {
	cProp := with.GetActivityStreamsContent()
	if cProp == nil {
		cProp = streams.NewActivityStreamsContentProperty()
		with.SetActivityStreamsContent(cProp)
	}
	cProp.AppendRDFLangString(contentMap)
}

// SetReplies sets the given replies collection
// to the `replies` property of 'with'.
func SetReplies(with WithReplies, replies vocab.ActivityStreamsCollection) {
	rProp := with.GetActivityStreamsReplies()
	if rProp == nil {
		rProp = streams.NewActivityStreamsRepliesProperty()
		with.SetActivityStreamsReplies(rProp)
	}
	rProp.SetActivityStreamsCollection(replies)
}

// extractIRIs extracts just the AP IRIs from an iterable
// property that may contain types (with IRIs) or just IRIs.
//
// If you know the property contains only IRIs and no types,
// then use getIRIs instead, since it's slightly faster.
func extractIRIs[T TypeOrIRI](prop Property[T]) []*url.URL {
	if prop == nil || prop.Len() == 0 {
		return nil
	}
	ids := make([]*url.URL, 0, prop.Len())
	for i := 0; i < prop.Len(); i++ {
		at := prop.At(i)
		if t := at.GetType(); t != nil {
			id := GetJSONLDId(t)
			if id != nil {
				ids = append(ids, id)
				continue
			}
		}
		if at.IsIRI() {
			id := at.GetIRI()
			if id != nil {
				ids = append(ids, id)
				continue
			}
		}
	}
	return ids
}

// getIRIs gets AP IRIs from an iterable property of IRIs.
//
// Types will be ignored; to extract IRIs from an iterable
// that may contain types too, use extractIRIs.
func getIRIs[T WithIRI](prop Property[T]) []*url.URL {
	if prop == nil || prop.Len() == 0 {
		return nil
	}
	ids := make([]*url.URL, 0, prop.Len())
	for i := 0; i < prop.Len(); i++ {
		at := prop.At(i)
		if at.IsIRI() {
			id := at.GetIRI()
			if id != nil {
				ids = append(ids, id)
				continue
			}
		}
	}
	return ids
}

func appendIRIs[T WithIRI](getProp func() Property[T], iri ...*url.URL) {
	if len(iri) == 0 {
		return
	}
	prop := getProp()
	if prop == nil {
		// check outside loop.
		panic("prop not set")
	}
	for _, iri := range iri {
		prop.AppendIRI(iri)
	}
}

// panicfAt panics with a call to gtserror.NewfAt() with given args (+1 to calldepth).
func panicfAt(calldepth int, msg string, args ...any) {
	panic(gtserror.NewfAt(calldepth+1, msg, args...))
}
