-- | Constructor functions and property manipulation for Subject.
--
-- This module provides convenient functions for creating and manipulating
-- Subject instances, including constructor functions and property record
-- operations.
--
-- == Construction Functions
--
-- * @subject@ - Create an empty subject with default identity
-- * @subjectWith@ - Create a subject with all components specified
--
-- == Property Manipulation
--
-- * @addProperty@ - Add a property to a subject's property record
-- * @updateProperty@ - Update an existing property in a subject's property record
-- * @removeProperty@ - Remove a property from a subject's property record
-- * @hasProperty@ - Check if a subject has a specific property
--
-- == Examples
--
-- Creating an empty subject:
--
-- >>> emptySubj = subject
-- >>> identity emptySubj
-- Symbol ""
-- >>> labels emptySubj
-- fromList []
-- >>> properties emptySubj
-- fromList []
--
-- Creating a subject with all components:
--
-- >>> import Data.Map (fromList)
-- >>> import Subject.Value (VString, VInteger)
-- >>> s = subjectWith (Symbol "n") (Set.fromList ["Person"]) (fromList [("name", VString "Alice")])
--
-- Adding a property:
--
-- >>> s' = addProperty "age" (VInteger 30) s
module Subject.Construction
  ( subject
  , subjectWith
  , addProperty
  , updateProperty
  , removeProperty
  , hasProperty
  ) where

import Data.Map (delete, empty, insert, member)
import Data.Set (Set)
import qualified Data.Set as Set
import Subject.Core (Symbol (..), PropertyRecord, Subject (..))
import Subject.Value (Value)

-- | Create an empty subject with default identity, no labels, and no properties.
--
-- This function provides a convenient way to create an empty subject without
-- using verbose record syntax. The resulting subject uses a default identity
-- (Symbol "") and is functionally identical to @Subject (Symbol "") Set.empty empty@
-- or @mempty@.
--
-- === Examples
--
-- Empty subject:
--
-- >>> s = subject
-- >>> identity s
-- Symbol ""
-- >>> labels s
-- fromList []
-- >>> properties s
-- fromList []
--
-- Using as starting point for building subjects:
--
-- >>> s = addProperty "name" (VString "Alice") (subject { labels = Set.fromList ["Person"] })
subject :: Subject
subject :: Subject
subject = Symbol -> Set String -> PropertyRecord -> Subject
Subject (String -> Symbol
Symbol String
"") Set String
forall a. Set a
Set.empty PropertyRecord
forall k a. Map k a
empty

-- | Create a subject with all components specified.
--
-- This function provides a convenient way to create a subject with identity,
-- labels, and properties without using verbose record syntax. The resulting
-- subject is functionally identical to @Subject ident lbls props@.
--
-- === Examples
--
-- Subject with all components:
--
-- >>> import Data.Map (fromList)
-- >>> import Data.Set (Set)
-- >>> import qualified Data.Set as Set
-- >>> import Subject.Value (VString, VInteger)
-- >>> s = subjectWith (Symbol "n") (Set.fromList ["Person"]) (fromList [("name", VString "Alice"), ("age", VInteger 30)])
--
-- Subject with only labels:
--
-- >>> s = subjectWith (Symbol "n") (Set.fromList ["Person"]) empty
--
-- Subject with only identity:
--
-- >>> s = subjectWith (Symbol "n") Set.empty empty
subjectWith :: Symbol -> Set String -> PropertyRecord -> Subject
subjectWith :: Symbol -> Set String -> PropertyRecord -> Subject
subjectWith Symbol
ident Set String
lbls PropertyRecord
props = Symbol -> Set String -> PropertyRecord -> Subject
Subject Symbol
ident Set String
lbls PropertyRecord
props

-- | Add a property to a subject's property record.
--
-- If the property key already exists, the new value replaces the old value
-- (equivalent to @updateProperty@). The function returns a new subject with
-- the added/updated property.
--
-- === Examples
--
-- Adding a new property:
--
-- >>> s = subject { labels = Set.fromList ["Person"] }
-- >>> s' = addProperty "name" (VString "Alice") s
-- >>> properties s' !? "name"
-- Just (VString "Alice")
--
-- Adding a property that already exists (replaces old value):
--
-- >>> s = subject { properties = fromList [("name", VString "Bob")] }
-- >>> s' = addProperty "name" (VString "Alice") s
-- >>> properties s' !? "name"
-- Just (VString "Alice")
addProperty :: String -> Value -> Subject -> Subject
addProperty :: String -> Value -> Subject -> Subject
addProperty String
key Value
val (Subject Symbol
ident Set String
lbls PropertyRecord
props) =
  Symbol -> Set String -> PropertyRecord -> Subject
Subject Symbol
ident Set String
lbls (String -> Value -> PropertyRecord -> PropertyRecord
forall k a. Ord k => k -> a -> Map k a -> Map k a
insert String
key Value
val PropertyRecord
props)

-- | Update an existing property in a subject's property record.
--
-- If the property key does not exist, the function returns the subject unchanged.
-- Use @addProperty@ if you want to add a property regardless of whether it exists.
--
-- === Examples
--
-- Updating an existing property:
--
-- >>> s = subject { properties = fromList [("name", VString "Bob")] }
-- >>> s' = updateProperty "name" (VString "Alice") s
-- >>> properties s' !? "name"
-- Just (VString "Alice")
--
-- Updating a non-existent property (no change):
--
-- >>> s = subject { properties = fromList [("name", VString "Bob")] }
-- >>> s' = updateProperty "age" (VInteger 30) s
-- >>> properties s' !? "age"
-- Nothing
updateProperty :: String -> Value -> Subject -> Subject
updateProperty :: String -> Value -> Subject -> Subject
updateProperty String
key Value
val s :: Subject
s@(Subject Symbol
_ Set String
_ PropertyRecord
props) =
  if String -> PropertyRecord -> Bool
forall k a. Ord k => k -> Map k a -> Bool
member String
key PropertyRecord
props
    then String -> Value -> Subject -> Subject
addProperty String
key Value
val Subject
s
    else Subject
s

-- | Remove a property from a subject's property record.
--
-- If the property key does not exist, the function returns the subject unchanged.
-- The function returns a new subject with the property removed.
--
-- === Examples
--
-- Removing an existing property:
--
-- >>> s = subject { properties = fromList [("name", VString "Alice"), ("age", VInteger 30)] }
-- >>> s' = removeProperty "age" s
-- >>> properties s' !? "age"
-- Nothing
-- >>> properties s' !? "name"
-- Just (VString "Alice")
--
-- Removing a non-existent property (no change):
--
-- >>> s = subject { properties = fromList [("name", VString "Alice")] }
-- >>> s' = removeProperty "age" s
-- >>> properties s' == properties s
-- True
removeProperty :: String -> Subject -> Subject
removeProperty :: String -> Subject -> Subject
removeProperty String
key (Subject Symbol
ident Set String
lbls PropertyRecord
props) =
  Symbol -> Set String -> PropertyRecord -> Subject
Subject Symbol
ident Set String
lbls (String -> PropertyRecord -> PropertyRecord
forall k a. Ord k => k -> Map k a -> Map k a
delete String
key PropertyRecord
props)

-- | Check if a subject has a specific property.
--
-- Returns @True@ if the property key exists in the subject's property record,
-- @False@ otherwise.
--
-- === Examples
--
-- Checking for an existing property:
--
-- >>> s = subject { properties = fromList [("name", VString "Alice")] }
-- >>> hasProperty "name" s
-- True
--
-- Checking for a non-existent property:
--
-- >>> s = subject { properties = fromList [("name", VString "Alice")] }
-- >>> hasProperty "age" s
-- False
hasProperty :: String -> Subject -> Bool
hasProperty :: String -> Subject -> Bool
hasProperty String
key (Subject Symbol
_ Set String
_ PropertyRecord
props) = String -> PropertyRecord -> Bool
forall k a. Ord k => k -> Map k a -> Bool
member String
key PropertyRecord
props