import Config

#### Base configuration

verbs = [
  "Boost",
  "Create",
  "Delete",
  "Edit",
  "Flag",
  "Follow",
  "Like",
  "Mention",
  "Message",
  "Read",
  "Reply",
  "Request",
  "See",
  "Tag"
]

# Choose password hashing backend
# Note that this corresponds with our dependencies in mix.exs
hasher = if config_env() in [:dev, :test], do: Pbkdf2, else: Argon2

config :bonfire_data_identity, Bonfire.Data.Identity.Credential, hasher_module: hasher

#### Sentinel Data Services

# Search these apps/extensions for Pointable ecto schema definitions to index
pointable_schema_extensions = [
  :bonfire,
  :bonfire_data_access_control,
  :bonfire_data_activity_pub,
  :bonfire_data_identity,
  :bonfire_data_social,
  :bonfire_data_edges,
  :bonfire_tag,
  :bonfire_classify,
  :bonfire_data_shared_users,
  :bonfire_files,
  :bonfire_quantify,
  :bonfire_geolocate,
  :bonfire_valueflows,
  :bonfire_valueflows_observe
]

config :pointers, :search_path, pointable_schema_extensions

# Search these apps/extensions for context or queries modules to index (i.e. they contain modules with a queries_module/0 or context_modules/0 function)
context_and_queries_extensions =
  pointable_schema_extensions ++
    [
      :bonfire_common,
      :bonfire_me,
      :bonfire_social
    ]

extensions_with_config =
  context_and_queries_extensions ++
    [
      :bonfire_boundaries,
      :bonfire_federate_activitypub,
      :bonfire_search,
      :bonfire_mailer
    ]

config :bonfire, :verb_names, verbs
config :bonfire, :context_modules_search_path, context_and_queries_extensions
config :bonfire, :query_modules_search_path, context_and_queries_extensions
config :bonfire, :config_modules_search_path, extensions_with_config

# Search these apps/extensions for Verbs to index (i.e. they contain modules with a declare_verbs/0 function)
config :bonfire_data_access_control,
  search_path: [
    # :bonfire_me,
    :bonfire_boundaries

    # :bonfire_social,
    # :bonfire,
  ]

#### Alias modules for readability
alias Pointers.Pointer
alias Pointers.Table

alias Bonfire.Data.AccessControl.Acl
alias Bonfire.Data.AccessControl.Circle
alias Bonfire.Data.AccessControl.Encircle
alias Bonfire.Data.AccessControl.Controlled
alias Bonfire.Data.AccessControl.InstanceAdmin
alias Bonfire.Data.AccessControl.Grant
alias Bonfire.Data.AccessControl.Verb

alias Bonfire.Data.ActivityPub.Actor
alias Bonfire.Data.ActivityPub.Peer
alias Bonfire.Data.ActivityPub.Peered

alias Bonfire.Boundaries.Permitted
alias Bonfire.Boundaries.Stereotyped

alias Bonfire.Data.Edges.Edge
alias Bonfire.Data.Edges.EdgeTotal

alias Bonfire.Data.Identity.Account
alias Bonfire.Data.Identity.Accounted
alias Bonfire.Data.Identity.Caretaker
alias Bonfire.Data.Identity.CareClosure
alias Bonfire.Data.Identity.Character
alias Bonfire.Data.Identity.Credential
alias Bonfire.Data.Identity.Email
alias Bonfire.Data.Identity.Named
alias Bonfire.Data.Identity.Self
alias Bonfire.Data.Identity.Settings
alias Bonfire.Data.Identity.User

alias Bonfire.Data.Social.Activity
alias Bonfire.Data.Social.APActivity
alias Bonfire.Data.Social.Article
alias Bonfire.Data.Social.Block
alias Bonfire.Data.Social.Bookmark
alias Bonfire.Data.Social.Boost
alias Bonfire.Data.Social.Created
alias Bonfire.Data.Social.Feed
alias Bonfire.Data.Social.FeedPublish
alias Bonfire.Data.Social.Flag
alias Bonfire.Data.Social.Follow
alias Bonfire.Data.Social.Like
alias Bonfire.Data.Social.Mention
alias Bonfire.Data.Social.Message
alias Bonfire.Data.Social.Post
alias Bonfire.Data.Social.PostContent
alias Bonfire.Data.Social.Profile
alias Bonfire.Data.Social.Replied
alias Bonfire.Data.Social.Request

alias Bonfire.Classify.Category
alias Bonfire.Geolocate.Geolocation
alias Bonfire.Files
alias Bonfire.Files.Media
alias Bonfire.Tag
alias Bonfire.Tag.Tagged

#### Flexto Stitching

## WARNING: This is the flaky magic bit. We use configuration to
## compile extra stuff into modules.  If you add new fields or
## relations to ecto models in a dependency, you must recompile that
## dependency for it to show up! You will probably find you need to
## `rm -Rf _build/*/lib/bonfire_data_*` a lot.

mixin = [foreign_key: :id, references: :id]

common_assocs = %{
  ### Mixins

  # A summary of an object that can appear in a feed.
  activity: quote(do: has_one(:activity, unquote(Activity), unquote(mixin))),
  # ActivityPub actor information
  actor: quote(do: has_one(:actor, unquote(Actor), unquote(mixin))),
  # Indicates the entity responsible for an activity. Sort of like creator, but transferrable. Used
  # during deletion - when the caretaker is deleted, all their stuff will be too.
  caretaker: quote(do: has_one(:caretaker, unquote(Caretaker), unquote(mixin))),
  # A Character has a unique username and some feeds.
  character: quote(do: has_one(:character, unquote(Character), unquote(mixin))),
  # Indicates the creator of an object
  created: quote(do: has_one(:created, unquote(Created), unquote(mixin))),
  # Used for non-textual interactions such as likes and follows to indicate the other object.
  edge: quote(do: has_one(:edge, unquote(Edge), unquote(mixin))),
  # Adds a name that can appear in the user interface for an object. e.g. for an ACL.
  named: quote(do: has_one(:named, unquote(Named), unquote(mixin))),
  # Information about the remote instance the object is from, if it is not local.
  peered: quote(do: has_one(:peered, unquote(Peered), unquote(mixin))),
  # Information about the content of posts, e.g. a scrubbed html body
  post_content: quote(do: has_one(:post_content, unquote(PostContent), unquote(mixin))),
  # Information about a user or other object that they wish to make available
  profile: quote(do: has_one(:profile, unquote(Profile), unquote(mixin))),
  # Threading information, for threaded discussions.
  replied: quote(do: has_one(:replied, unquote(Replied), unquote(mixin))),
  # Information that allows the system to identify special system-managed ACLS.
  stereotyped: quote(do: has_one(:stereotyped, unquote(Stereotyped), unquote(mixin))),

  ### Multimixins

  # Links to access control information for this object.
  controlled: quote(do: has_many(:controlled, unquote(Controlled), unquote(mixin))),
  # Inserts the object into selected feeds.
  feed_publishes: quote(do: has_many(:feed_publishes, unquote(FeedPublish), unquote(mixin))),

  # Information that this object has some files
  files: quote(do: has_many(:files, unquote(Files), unquote(mixin))),
  # The actual files
  media:
    quote(
      do:
        many_to_many(:media, unquote(Media),
          join_through: unquote(Files),
          unique: true,
          join_keys: [id: :id, media_id: :id],
          on_replace: :delete
        )
    ),

  # Information that this object tagged other objects.
  tagged: quote(do: has_many(:tagged, unquote(Tagged), unquote(mixin))),
  # The actual tags
  tags:
    quote(
      do:
        many_to_many(:tags, unquote(Pointer),
          join_through: unquote(Tagged),
          unique: true,
          join_keys: [id: :id, tag_id: :id],
          on_replace: :delete
        )
    ),

  ### Regular has_many associations

  # The objects which reply to this object.
  direct_replies:
    quote(do: has_many(:direct_replies, unquote(Replied), foreign_key: :reply_to_id)),
  # A recursive view of caretakers of caretakers of... used during deletion.
  care_closure: quote(do: has_many(:care_closure, unquote(CareClosure), foreign_key: :branch_id)),
  # Retrieves activities where we are the object. e.g. if we are a
  # post or a user, this could turn up activities from likes or follows.
  activities:
    quote(do: has_many(:activities, unquote(Activity), foreign_key: :object_id, references: :id)),

  ### Stuff I'm not sure how to categorise yet

  # Used currently only for requesting to follow a user, but more general
  request: quote(do: has_one(:request, unquote(Request), unquote(mixin)))
}

# retrieves a list of quoted forms suitable for use with unquote_splicing
common = fn names ->
  for name <- List.wrap(names) do
    with nil <- common_assocs[name],
         do:
           raise(RuntimeError, message: "Expected a common association name, got #{inspect(name)}")
  end
end

edge = common.([:controlled, :activities, :request, :created])

edges =
  common.([:controlled, :activities, :request, :created, :caretaker, :activity, :feed_publishes])

# first up, pointers could have all the mixins we're using. TODO

pointer_mixins =
  common.([
    :activity,
    :actor,
    :caretaker,
    :character,
    :created,
    :edge,
    :named,
    :peered,
    :post_content,
    :profile,
    :replied,
    :stereotyped
  ])

config :pointers, Pointer,
  code:
    (quote do
       @like_ulid "11KES11KET0BE11KEDY0VKN0WS"
       @boost_ulid "300STANN0VNCERESHARESH0VTS"
       @follow_ulid "70110WTHE1EADER1EADER1EADE"
       field(:dummy, :any, virtual: true)
       # pointables
       has_one(:circle, unquote(Circle), foreign_key: :id)
       has_one(:permitted, unquote(Permitted), foreign_key: :object_id)
       has_one(:user, unquote(User), foreign_key: :id)
       has_one(:post, unquote(Post), foreign_key: :id)
       has_one(:message, unquote(Message), foreign_key: :id)
       has_one(:category, unquote(Category), foreign_key: :id)
       has_one(:geolocation, unquote(Geolocation), foreign_key: :id)
       # mixins
       unquote_splicing(pointer_mixins)
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :files, :media]))
       # has_many
       unquote_splicing(common.([:activities, :care_closure, :direct_replies, :feed_publishes]))

       ## special things
       # these should go away in future and they should be populated by a single query.
       has_one(:like_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @like_ulid]
       )

       has_one(:boost_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @boost_ulid]
       )

       has_one(:follow_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @follow_ulid]
       )
     end)

config :pointers, Table, []

# now let's weave everything else together for convenience
# bonfire_data_access_control

config :bonfire_data_access_control, Acl,
  code:
    (quote do
       # mixins
       (unquote_splicing(common.([:caretaker, :named, :stereotyped])))

       # multimixins
       # unquote_splicing(common.([:controlled]))
     end)

config :bonfire_data_access_control, Circle,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:caretaker, :named, :stereotyped]))
       # multimixins
       unquote_splicing(common.([:controlled]))
     end)

config :bonfire_data_access_control, Controlled, []

config :bonfire_data_access_control, Encircle,
  code:
    (quote do
       has_one(:peer, unquote(Peer), foreign_key: :id, references: :subject_id)
     end)

config :bonfire_data_access_control, Grant,
  code:
    (quote do
       # mixins
       (unquote_splicing(common.([:caretaker])))

       # multimixins
       # unquote_splicing(common.([:controlled]))
     end)

config :bonfire_data_access_control, Verb, []

config :bonfire_boundaries, Stereotyped,
  code:
    (quote do
       # mixins
       (unquote_splicing(common.([:named])))
     end)

# bonfire_data_activity_pub

config :bonfire_data_activity_pub, Actor,
  code:
    (quote do
       # hacks
       belongs_to(:character, unquote(Character), foreign_key: :id, define_field: false)
       belongs_to(:user, unquote(User), foreign_key: :id, define_field: false)
       # mixins
       unquote_splicing(common.([:peered]))
       # multimixins
       unquote_splicing(common.([:controlled]))
     end)

config :bonfire_data_activity_pub, Peer, []
config :bonfire_data_activity_pub, Peered, []

# bonfire_data_identity

config :bonfire_data_identity, Account,
  code:
    (quote do
       has_one(:credential, unquote(Credential), foreign_key: :id)
       has_one(:email, unquote(Email), foreign_key: :id)
       has_one(:settings, unquote(Settings), foreign_key: :id)

       many_to_many(:users, unquote(User),
         join_through: Accounted,
         join_keys: [account_id: :id, id: :id]
       )

       # optional
       many_to_many(:shared_users, unquote(User),
         join_through: "bonfire_data_shared_user_accounts",
         join_keys: [account_id: :id, shared_user_id: :id]
       )
     end)

config :bonfire_data_identity, Accounted,
  code:
    (quote do
       belongs_to(:user, unquote(User), foreign_key: :id, define_field: false)
     end)

config :bonfire_data_identity, Caretaker,
  code:
    (quote do
       has_one(:user, unquote(User), foreign_key: :id, references: :caretaker_id)
       # mixins
       unquote_splicing(common.([:character, :profile]))
     end)

config :bonfire_data_identity, Character,
  code:
    (quote do
       @follow_ulid "70110WTHE1EADER1EADER1EADE"
       # mixins
       unquote_splicing(common.([:actor, :peered, :profile]))
       has_one(:user, unquote(User), unquote(mixin))
       has_one(:feed, unquote(Feed), unquote(mixin))
       has_many(:followers, unquote(Follow), foreign_key: :following_id, references: :id)
       has_many(:followed, unquote(Follow), foreign_key: :follower_id, references: :id)

       has_one(:follow_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @follow_ulid]
       )
     end)

config :bonfire_data_identity, Credential,
  code:
    (quote do
       belongs_to(:account, unquote(Account), foreign_key: :id, define_field: false)
     end)

config :bonfire_data_identity, Email,
  must_confirm: true,
  code:
    (quote do
       belongs_to(:account, unquote(Account), foreign_key: :id, define_field: false)
     end)

config :bonfire_data_identity, Self, []

config :bonfire_data_identity, User,
  code:
    (quote do
       @like_ulid "11KES11KET0BE11KEDY0VKN0WS"
       @boost_ulid "300STANN0VNCERESHARESH0VTS"
       @follow_ulid "70110WTHE1EADER1EADER1EADE"
       # mixins
       has_one(:accounted, unquote(Accounted), foreign_key: :id)
       has_one(:instance_admin, unquote(InstanceAdmin), foreign_key: :id, on_replace: :update)
       has_one(:self, unquote(Self), foreign_key: :id)
       has_one(:shared_user, unquote(Bonfire.Data.SharedUser), foreign_key: :id)
       has_one(:settings, unquote(Settings), foreign_key: :id)
       unquote_splicing(common.([:actor, :character, :created, :peered, :profile]))
       # multimixins
       unquote_splicing(common.([:controlled]))
       # manies
       has_many(:encircles, unquote(Encircle), foreign_key: :subject_id)
       # todo: stop through
       has_many(:creations, through: [:created, :pointer])
       # todo: stop through
       has_many(:posts, through: [:created, :post])

       has_many(:followers, unquote(Edge),
         foreign_key: :object_id,
         references: :id,
         where: [table_id: @follow_ulid]
       )

       has_many(:followed, unquote(Edge),
         foreign_key: :subject_id,
         references: :id,
         where: [table_id: @follow_ulid]
       )

       has_many(:user_activities, unquote(Activity), foreign_key: :subject_id, references: :id)

       has_many(:boost_activities, unquote(Edge),
         foreign_key: :subject_id,
         references: :id,
         where: [table_id: @boost_ulid]
       )

       has_many(:like_activities, unquote(Edge),
         foreign_key: :subject_id,
         references: :id,
         where: [table_id: @like_ulid]
       )

       many_to_many(:caretaker_accounts, unquote(Account),
         join_through: "bonfire_data_shared_user_accounts",
         join_keys: [shared_user_id: :id, account_id: :id]
       )

       # has_many :account, through: [:accounted, :account] # this is private info, do not expose
       # has_one :geolocation, Bonfire.Geolocate.Geolocation # enable if using Geolocate extension
     end)

### bonfire_data_social

config :bonfire_data_social, Activity,
  code:
    (quote do
       @like_ulid "11KES11KET0BE11KEDY0VKN0WS"
       @boost_ulid "300STANN0VNCERESHARESH0VTS"
       @follow_ulid "70110WTHE1EADER1EADER1EADE"
       has_many(:feed_publishes, unquote(FeedPublish), unquote(mixin))
       has_one(:seen, unquote(Edge), foreign_key: :id, references: :id)
       # ugly workaround needed for certain queries (TODO: check if still needed)
       has_one(:activity, unquote(Activity), foreign_key: :id, references: :id)
       # mixins linked to the object rather than the activity:
       has_one(:created, unquote(Created), foreign_key: :id, references: :object_id)
       has_one(:replied, unquote(Replied), foreign_key: :id, references: :object_id)
       field(:path, EctoMaterializedPath.ULIDs, virtual: true)

       has_one(:like_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :object_id,
         where: [table_id: @like_ulid]
       )

       has_one(:boost_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :object_id,
         where: [table_id: @boost_ulid]
       )

       has_one(:follow_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :object_id,
         where: [table_id: @follow_ulid]
       )

       has_many(:controlled, unquote(Controlled), foreign_key: :id, references: :object_id)
       has_many(:tagged, unquote(Tagged), foreign_key: :id, references: :object_id)

       many_to_many(:tags, unquote(Pointer),
         join_through: unquote(Tagged),
         unique: true,
         join_keys: [id: :object_id, tag_id: :id],
         on_replace: :delete
       )

       has_many(:files, unquote(Files), foreign_key: :id, references: :object_id)

       many_to_many(:media, unquote(Media),
         join_through: unquote(Files),
         unique: true,
         join_keys: [id: :object_id, media_id: :id],
         on_replace: :delete
       )
     end)

config :bonfire_data_social, APActivity,
  code:
    (quote do
       (unquote_splicing(common.([:activity, :caretaker])))
     end)

config :bonfire_data_edges, Edge,
  code:
    (quote do
       (unquote_splicing(edge))

       # TODO: requires composite foreign keys:
       # has_one :activity, unquote(Activity),
       #   foreign_key: [:table_id, :object_id], references: [:table_id, :object_id]
     end)

config :bonfire_data_social, Feed,
  code:
    (quote do
       # mixins
       (unquote_splicing(common.([:activity, :caretaker])))

       # belongs_to :character, unquote(Character), foreign_key: :id, define_field: false
       # belongs_to :user, unquote(User), foreign_key: :id, define_field: false
     end)

config :bonfire_data_social, FeedPublish,
  code:
    (quote do
       field(:dummy, :any, virtual: true)
       has_one(:activity, unquote(Activity), foreign_key: :object_id, references: :id)

       # belongs_to :character, unquote(Character), foreign_key: :id, define_field: false
       # belongs_to :user, unquote(User), foreign_key: :id, define_field: false
     end)

config :bonfire_data_social, Follow,
  code:
    (quote do
       (unquote_splicing(edges))
     end)

# belongs_to: [follower_character: {Character, foreign_key: :follower_id, define_field: false}],
# belongs_to: [follower_profile: {Profile, foreign_key: :follower_id, define_field: false}],
# belongs_to: [followed_character: {Character, foreign_key: :followed_id, define_field: false}],
# belongs_to: [followed_profile: {Profile, foreign_key: :followed_id, define_field: false}]

config :bonfire_data_social, Block,
  code:
    (quote do
       (unquote_splicing(edges))
     end)

config :bonfire_data_social, Boost,
  code:
    (quote do
       (unquote_splicing(edges))
     end)

# has_one:  [activity: {Activity, foreign_key: :object_id, references: :boosted_id}] # requires an ON clause

config :bonfire_data_social, Like,
  code:
    (quote do
       (unquote_splicing(edges))
     end)

# has_one:  [activity: {Activity, foreign_key: :object_id, references: :liked_id}] # requires an ON clause

config :bonfire_data_social, Flag,
  code:
    (quote do
       (unquote_splicing(edges))
     end)

config :bonfire_data_social, Request,
  code:
    (quote do
       (unquote_splicing(edges))
     end)

config :bonfire_data_social, Bookmark,
  code:
    (quote do
       (unquote_splicing(edges))
     end)

config :bonfire_data_social, Message,
  code:
    (quote do
       @like_ulid "11KES11KET0BE11KEDY0VKN0WS"
       @boost_ulid "300STANN0VNCERESHARESH0VTS"
       # mixins
       unquote_splicing(
         common.([:activity, :caretaker, :created, :peered, :post_content, :replied])
       )

       # multimixins
       unquote_splicing(common.([:controlled, :feed_publishes, :tagged, :tags, :files, :media]))
       # has
       unquote_splicing(common.([:direct_replies]))
       # special
       has_one(:like_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @like_ulid]
       )

       has_one(:boost_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @boost_ulid]
       )
     end)

config :bonfire_data_social, Mention, []
config :bonfire_data_social, Named, []

config :bonfire_data_social, Post,
  code:
    (quote do
       @like_ulid "11KES11KET0BE11KEDY0VKN0WS"
       @boost_ulid "300STANN0VNCERESHARESH0VTS"
       # mixins
       unquote_splicing(
         common.([:activities, :activity, :caretaker, :created, :peered, :post_content, :replied])
       )

       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :files, :media, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
       # special
       has_one(:permitted, unquote(Permitted), foreign_key: :object_id)
       # has_one:  [creator_user: {[through: [:created, :creator_user]]}],
       # has_one:  [creator_character: {[through: [:created, :creator_character]]}],
       # has_one:  [creator_profile: {[through: [:created, :creator_profile]]}],
       # has_one :activity, unquote(Activity), foreign_key: :object_id, references: :id # requires an ON clause
       # has_one:  [reply_to: {[through: [:replied, :reply_to]]}],
       # has_one:  [reply_to_post: {[through: [:replied, :reply_to_post]]}],
       # has_one:  [reply_to_post_content: {[through: [:replied, :reply_to_post_content]]}],
       # has_one:  [reply_to_creator_character: {[through: [:replied, :reply_to_creator_character]]}],
       # has_one:  [reply_to_creator_profile: {[through: [:replied, :reply_to_creator_profile]]}],
       # has_one:  [thread_post: {[through: [:replied, :thread_post]]}],
       # has_one:  [thread_post_content: {[through: [:replied, :thread_post_content]]}],
       has_one(:like_count, unquote(EdgeTotal),
         references: :id,
         foreign_key: :id,
         where: [table_id: @like_ulid]
       )

       has_one(:boost_count, unquote(EdgeTotal),
         references: :id,
         foreign_key: :id,
         where: [table_id: @boost_ulid]
       )
     end)

config :bonfire_data_social, PostContent,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:created]))
       # multimixins
       unquote_splicing(common.([:controlled]))
       # virtuals for changesets
       field(:hashtags, {:array, :any}, virtual: true)
       field(:mentions, {:array, :any}, virtual: true)
     end)

config :bonfire_data_social, Replied,
  code:
    (quote do
       # multimixins - shouldn't be here really
       unquote_splicing(common.([:controlled]))

       @like_ulid "11KES11KET0BE11KEDY0VKN0WS"
       @boost_ulid "300STANN0VNCERESHARESH0VTS"
       belongs_to(:post, unquote(Post), foreign_key: :id, define_field: false)
       belongs_to(:post_content, unquote(PostContent), foreign_key: :id, define_field: false)
       has_one(:activity, unquote(Activity), foreign_key: :object_id, references: :id)
       # used in changesets
       field(:replying_to, :map, virtual: true)
       has_one(:reply_to_post, unquote(Post), foreign_key: :id, references: :reply_to_id)

       has_one(:reply_to_post_content, unquote(PostContent),
         foreign_key: :id,
         references: :reply_to_id
       )

       has_one(:reply_to_created, unquote(Created), foreign_key: :id, references: :reply_to_id)
       # has_one  :reply_to_creator_user, through: [:reply_to_created, :creator_user]
       # has_one  :reply_to_creator_character, through: [:reply_to_created, :creator_character]
       # has_one  :reply_to_creator_profile, through: [:reply_to_created, :creator_profile]
       has_many(:direct_replies, unquote(Replied), foreign_key: :reply_to_id, references: :id)
       has_many(:thread_replies, unquote(Replied), foreign_key: :thread_id, references: :id)
       has_one(:thread_post, unquote(Post), foreign_key: :id, references: :thread_id)

       has_one(:thread_post_content, unquote(PostContent),
         foreign_key: :id,
         references: :thread_id
       )

       has_one(:like_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @like_ulid]
       )

       has_one(:boost_count, unquote(EdgeTotal),
         foreign_key: :id,
         references: :id,
         where: [table_id: @boost_ulid]
       )
     end)

config :bonfire_data_social, Created,
  code:
    (quote do
       belongs_to(:creator_user, unquote(User), foreign_key: :creator_id, define_field: false)

       belongs_to(:creator_character, unquote(Character),
         foreign_key: :creator_id,
         define_field: false
       )

       belongs_to(:creator_profile, unquote(Profile),
         foreign_key: :creator_id,
         define_field: false
       )

       # mixins - shouldn't be here really
       unquote_splicing(common.([:peered]))
       # huh?
       has_one(:post, unquote(Post), unquote(mixin))
     end)

config :bonfire_data_social, Profile,
  code:
    (quote do
       belongs_to(:user, unquote(User), foreign_key: :id, define_field: false)
       # multimixins - shouldn't be here really
       unquote_splicing(common.([:controlled]))
     end)

######### other extensions

config :bonfire_files, Media,
  code:
    (quote do
       field(:url, :string, virtual: true)
       # multimixins - shouldn't be here really
       unquote_splicing(common.([:controlled]))
     end)

config :bonfire_classify, Category,
  code:
    (quote do
       # mixins
       # TODO :caretaker
       unquote_splicing(common.([:activity, :created, :actor, :peered, :profile, :character]))
       # multimixins
       unquote_splicing(common.([:controlled, :feed_publishes]))

       # add references of tagged objects to any Category
       many_to_many(:tags, unquote(Pointer),
         join_through: unquote(Tagged),
         unique: true,
         join_keys: [tag_id: :id, id: :id],
         on_replace: :delete
       )
     end)

config :bonfire_geolocate, Bonfire.Geolocate.Geolocation,
  code:
    (quote do
       # mixins
       unquote_splicing(
         common.([:activity, :caretaker, :created, :actor, :peered, :profile, :character])
       )

       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
     end)

config :bonfire_valueflows, ValueFlows.EconomicEvent,
  code:
    (quote do
       # mixins
       # TODO :caretaker
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows, ValueFlows.EconomicResource,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows, ValueFlows.Knowledge.ResourceSpecification,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows, ValueFlows.Process,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows, ValueFlows.Knowledge.ProcessSpecification,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows, ValueFlows.Planning.Intent,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows, ValueFlows.Planning.Commitment,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows, ValueFlows.Proposal,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)

config :bonfire_valueflows_observe, ValueFlows.Observe.Observation,
  code:
    (quote do
       # mixins
       unquote_splicing(common.([:activity, :caretaker, :peered, :replied]))
       # multimixins
       unquote_splicing(common.([:controlled, :tagged, :tags, :feed_publishes]))
       # has
       unquote_splicing(common.([:direct_replies]))
     end)
