-- | Serialization of Pattern Subject to gram notation.
--
-- This module provides functions to convert Pattern Subject data structures
-- into gram notation text format. The serialization handles all aspects of
-- gram notation including:
{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
--
-- * Subject identity (symbols, quoted strings, numbers)
-- * Labels (single and multiple)
-- * Property records with all value types
-- * Pattern structure (nodes, relationships, nested patterns)
--
-- == Serialization Strategy
--
-- The serialization process converts Haskell data structures to gram notation:
--
-- * @Pattern Subject@ → gram notation string
-- * @Subject@ → gram attributes notation (identity, labels, properties)
-- * @Value@ types → gram value notation
--
-- == String Value Serialization
--
-- String values use different formats based on length:
--
-- * __Short strings__ (≤120 characters): Double-quoted with escapes
-- * __Long strings__ (>120 characters): Codefence format (triple-backtick)
--
-- Tagged strings follow the same threshold for inline vs codefence:
--
-- * __Short tagged__: @tag\`content\`@
-- * __Long tagged__: @\`\`\`tag\\ncontent\\n\`\`\`@
--
-- The threshold is defined by 'codefenceThreshold' (120 characters).
--
-- == Examples
--
-- Serializing a simple subject:
--
-- >>> import Pattern.Core (Pattern(..))
-- >>> import Subject.Core (Subject(..), Symbol(..))
-- >>> import Data.Set (Set)
-- >>> import qualified Data.Set as Set
-- >>> let s = Subject (Symbol "n") (Set.fromList ["Person"]) empty
-- >>> let p = Pattern { value = s, elements = [] }
-- >>> toGram p
-- "(n:Person)"
--
-- Serializing with properties:
--
-- >>> import Data.Map (fromList)
-- >>> import Subject.Value (VString)
-- >>> let s = Subject (Symbol "n") (Set.fromList ["Person"]) (fromList [("name", VString "Alice")])
-- >>> let p = Pattern { value = s, elements = [] }
-- >>> toGram p
-- "(n:Person {name:\"Alice\"})"
module Gram.Serialize
  ( toGram
  , codefenceThreshold
  ) where

import Pattern.Core (Pattern(..))
import Subject.Core (Subject(..), Symbol(..))
import Subject.Value (Value(..), RangeValue(..))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Char (isAlpha, isAlphaNum)

-- | Character threshold for codefence serialization.
--
-- Strings with length greater than this value will be serialized
-- using codefence format (triple-backticks). Length is measured as 
-- total character count including newline characters.
--
-- Strings of this length or fewer use standard quote-delimited format.
--
-- === Examples
--
-- >>> codefenceThreshold
-- 120
--
-- >>> length "short string" <= codefenceThreshold
-- True
codefenceThreshold :: Int
codefenceThreshold :: Int
codefenceThreshold = Int
120

-- | Escape special characters in strings for gram notation.
--
-- Escapes quotes, backslashes, and control characters in string values 
-- to ensure proper serialization in gram notation.
--
-- === Examples
--
-- >>> escapeString "Hello"
-- "Hello"
--
-- >>> escapeString "He said \"Hello\""
-- "He said \\\"Hello\\\""
--
-- >>> escapeString "Line 1\nLine 2"
-- "Line 1\\nLine 2"
escapeString :: String -> String
escapeString :: String -> String
escapeString = (Char -> String) -> String -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Char -> String
escapeChar
  where
    escapeChar :: Char -> String
escapeChar Char
'"' = String
"\\\""
    escapeChar Char
'\\' = String
"\\\\"
    escapeChar Char
'\n' = String
"\\n"
    escapeChar Char
'\r' = String
"\\r"
    escapeChar Char
'\t' = String
"\\t"
    escapeChar Char
c = [Char
c]

-- | Check if a string can safely use codefence format.
--
-- A string can use codefence format if:
--
-- 1. It exceeds the length threshold (120 characters)
-- 2. It does NOT contain the closing fence pattern (@\\n\`\`\`@)
--
-- If the string contains the closing fence pattern, using codefence
-- format would cause the parser to truncate content at that point,
-- violating round-trip preservation.
--
-- === Examples
--
-- >>> canUseCodefence (replicate 121 'x')
-- True
--
-- >>> canUseCodefence (replicate 100 'x' ++ "\n```" ++ replicate 50 'y')
-- False
canUseCodefence :: String -> Bool
canUseCodefence :: String -> Bool
canUseCodefence String
s = 
  String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
s Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
codefenceThreshold Bool -> Bool -> Bool
&& Bool -> Bool
not (String -> Bool
containsClosingFence String
s)
  where
    containsClosingFence :: String -> Bool
containsClosingFence String
str = String
"\n```" String -> String -> Bool
forall {a}. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
str
    isInfixOf :: [a] -> [a] -> Bool
isInfixOf [a]
needle [a]
haystack = ([a] -> Bool) -> [[a]] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ([a] -> [a] -> Bool
forall {a}. Eq a => [a] -> [a] -> Bool
isPrefixOf [a]
needle) ([a] -> [[a]]
forall {a}. [a] -> [[a]]
tails [a]
haystack)
    isPrefixOf :: [a] -> [a] -> Bool
isPrefixOf [] [a]
_ = Bool
True
    isPrefixOf [a]
_ [] = Bool
False
    isPrefixOf (a
x:[a]
xs) (a
y:[a]
ys) = a
x a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
y Bool -> Bool -> Bool
&& [a] -> [a] -> Bool
isPrefixOf [a]
xs [a]
ys
    tails :: [a] -> [[a]]
tails [] = [[]]
    tails xs :: [a]
xs@(a
_:[a]
xs') = [a]
xs [a] -> [[a]] -> [[a]]
forall a. a -> [a] -> [a]
: [a] -> [[a]]
tails [a]
xs'

-- | Escape content for backtick-delimited strings.
--
-- Escapes backticks and newlines in content that will be placed
-- inside a single-backtick delimited string (tag\`content\`).
--
-- === Examples
--
-- >>> escapeBacktickedContent "hello"
-- "hello"
--
-- >>> escapeBacktickedContent "a`b"
-- "a\\`b"
escapeBacktickedContent :: String -> String
escapeBacktickedContent :: String -> String
escapeBacktickedContent = (Char -> String) -> String -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Char -> String
escapeChar
  where
    escapeChar :: Char -> String
escapeChar Char
'`' = String
"\\`"
    escapeChar Char
'\n' = String
"\\n"
    escapeChar Char
'\r' = String
"\\r"
    escapeChar Char
'\\' = String
"\\\\"
    escapeChar Char
c = [Char
c]

-- | Serialize a string using codefence format for long strings.
--
-- Uses triple-backtick codefence format for strings exceeding the
-- threshold length. Content is preserved without escaping.
--
-- === Examples
--
-- >>> serializeCodefenceString "Long content..."
-- "```\nLong content...\n```"
serializeCodefenceString :: String -> String
serializeCodefenceString :: String -> String
serializeCodefenceString String
s = String
"```\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n```"

-- | Serialize a tagged string using codefence format for long content.
--
-- Uses triple-backtick codefence format with tag for tagged strings
-- whose content exceeds the threshold length.
--
-- === Examples
--
-- >>> serializeTaggedCodefenceString "md" "Long markdown..."
-- "```md\nLong markdown...\n```"
serializeTaggedCodefenceString :: String -> String -> String
serializeTaggedCodefenceString :: String -> String -> String
serializeTaggedCodefenceString String
tag String
content = String
"```" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
tag String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
content String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n```"

-- | Format a Symbol for gram notation.
--
-- Returns the symbol string, quoted with backticks if necessary.
-- Symbols in gram notation are unquoted identifiers if they start with
-- a letter/underscore and contain only alphanumeric/specified chars.
-- Otherwise they must be quoted.
--
-- === Examples
--
-- >>> quoteSymbol (Symbol "n")
-- "n"
--
-- >>> quoteSymbol (Symbol "my-id.1")
-- "my-id.1"
--
-- >>> quoteSymbol (Symbol "dear world")
-- "`dear world`"
quoteSymbol :: Symbol -> String
quoteSymbol :: Symbol -> String
quoteSymbol (Symbol String
s)
  | String -> Bool
needsQuoting String
s = String
"`" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
escapeBackticks String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"`"
  | Bool
otherwise = String
s
  where
    needsQuoting :: String -> Bool
needsQuoting String
"" = Bool
True
    needsQuoting (Char
c:String
cs) = Bool -> Bool
not (Char -> Bool
isIdStart Char
c) Bool -> Bool -> Bool
|| (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isIdChar) String
cs
    
    isIdStart :: Char -> Bool
isIdStart Char
c = Char -> Bool
isAlpha Char
c Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'_'
    isIdChar :: Char -> Bool
isIdChar Char
c = Char -> Bool
isAlphaNum Char
c Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'_' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'-' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'@'
    
    escapeBackticks :: String -> String
escapeBackticks = (Char -> String) -> String -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\Char
c -> if Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'`' then String
"\\`" else [Char
c])

-- | Serialize a Value to gram notation.
--
-- Converts a Value type to its gram notation string representation.
-- Handles all standard and extended value types.
--
-- === Examples
--
-- >>> serializeValue (VInteger 42)
-- "42"
--
-- >>> serializeValue (VString "Alice")
-- "\"Alice\""
--
-- >>> serializeValue (VArray [VInteger 1, VInteger 2])
-- "[1,2]"
serializeValue :: Value -> String
serializeValue :: Value -> String
serializeValue (VInteger Integer
i) = Integer -> String
forall a. Show a => a -> String
show Integer
i
serializeValue (VDecimal Double
d) = Double -> String
forall a. Show a => a -> String
show Double
d
serializeValue (VBoolean Bool
True) = String
"true"
serializeValue (VBoolean Bool
False) = String
"false"
serializeValue (VString String
s)
  | String -> Bool
canUseCodefence String
s = String -> String
serializeCodefenceString String
s
  | Bool
otherwise = String
"\"" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
escapeString String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\""
serializeValue (VSymbol String
sym) = String
sym
serializeValue (VTaggedString String
tag String
content)
  | String -> Bool
canUseCodefence String
content = String -> String -> String
serializeTaggedCodefenceString String
tag String
content
  | Bool
otherwise = String
tag String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"`" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
escapeBacktickedContent String
content String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"`"
serializeValue (VArray [Value]
vs) = String
"[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
intercalate String
"," ((Value -> String) -> [Value] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Value -> String
serializeValue [Value]
vs) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"]"
serializeValue (VMap Map String Value
m) = String
"{" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
intercalate String
"," (((String, Value) -> String) -> [(String, Value)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String, Value) -> String
serializeProperty (Map String Value -> [(String, Value)]
forall k a. Map k a -> [(k, a)]
Map.toList Map String Value
m)) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"}"
  where
    serializeProperty :: (String, Value) -> String
serializeProperty (String
k, Value
v) = String
k String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
":" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Value -> String
serializeValue Value
v
serializeValue (VRange (RangeValue (Just Double
lowerVal) (Just Double
upperVal))) = Double -> String
formatRangeDouble Double
lowerVal String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".." String -> String -> String
forall a. [a] -> [a] -> [a]
++ Double -> String
formatRangeDouble Double
upperVal
serializeValue (VRange (RangeValue (Just Double
lowerVal) Maybe Double
Nothing)) = Double -> String
formatRangeDouble Double
lowerVal String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"..."
serializeValue (VRange (RangeValue Maybe Double
Nothing (Just Double
upperVal))) = String
"..." String -> String -> String
forall a. [a] -> [a] -> [a]
++ Double -> String
formatRangeDouble Double
upperVal
serializeValue (VRange (RangeValue Maybe Double
Nothing Maybe Double
Nothing)) = String
"..."
serializeValue (VMeasurement String
unit Double
val) = Double -> String
forall a. Show a => a -> String
show Double
val String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
unit

-- | Format a Double for range serialization, showing as integer if whole number.
formatRangeDouble :: Double -> String
formatRangeDouble :: Double -> String
formatRangeDouble Double
d
  | Double
d Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Integer -> Double
forall a. Num a => Integer -> a
fromInteger (Double -> Integer
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round Double
d) = Integer -> String
forall a. Show a => a -> String
show (Double -> Integer
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round Double
d :: Integer)
  | Bool
otherwise = Double -> String
forall a. Show a => a -> String
show Double
d

-- Helper function for intercalate
intercalate :: String -> [String] -> String
intercalate :: String -> [String] -> String
intercalate String
_ [] = String
""
intercalate String
_ [String
x] = String
x
intercalate String
sep (String
x:[String]
xs) = String
x String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
sep String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
intercalate String
sep [String]
xs

-- | Serialize a property record to gram notation.
--
-- Converts a Map of property key-value pairs to gram notation property
-- record syntax: @{key1:value1,key2:value2}@
--
-- Note: Property order is not guaranteed as Map doesn't preserve insertion order.
--
-- === Examples
--
-- >>> serializePropertyRecord empty
-- ""
--
-- >>> serializePropertyRecord (fromList [("name", VString "Alice")])
-- "{name:\"Alice\"}"
serializePropertyRecord :: Map String Value -> String
serializePropertyRecord :: Map String Value -> String
serializePropertyRecord Map String Value
props
  | Map String Value -> Bool
forall k a. Map k a -> Bool
Map.null Map String Value
props = String
""
  | Bool
otherwise = String
" {" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
intercalate String
"," (((String, Value) -> String) -> [(String, Value)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String, Value) -> String
serializeProperty (Map String Value -> [(String, Value)]
forall k a. Map k a -> [(k, a)]
Map.toList Map String Value
props)) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"}"
  where
    serializeProperty :: (String, Value) -> String
serializeProperty (String
k, Value
v) = String
k String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
":" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Value -> String
serializeValue Value
v

-- | Serialize identity, labels, and properties (attributes part).
-- Used internally by toGram for both node and subject syntax.
serializeIdentity :: Symbol -> String
serializeIdentity :: Symbol -> String
serializeIdentity (Symbol String
"") = String
""  -- Anonymous subject
serializeIdentity Symbol
ident = Symbol -> String
quoteSymbol Symbol
ident

serializeLabels :: Set String -> String
serializeLabels :: Set String -> String
serializeLabels Set String
lbls
  | Set String -> Bool
forall a. Set a -> Bool
Set.null Set String
lbls = String
""
  | Bool
otherwise = String
":" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
intercalate String
":" (Set String -> [String]
forall a. Set a -> [a]
Set.toList Set String
lbls)

    -- | Serialize a Subject to gram notation (legacy function, kept for compatibility).
-- Note: This always uses node syntax. Use toGram for proper syntax selection.
-- NOTE: This function is currently unused but kept for potential future use.
{-# WARNING serializeSubject "This function is unused but kept for compatibility" #-}
serializeSubject :: Subject -> String
serializeSubject :: Subject -> String
serializeSubject (Subject Symbol
ident Set String
lbls Map String Value
props) =
  String
"(" String -> String -> String
forall a. [a] -> [a] -> [a]
++
  Symbol -> String
serializeIdentity Symbol
ident String -> String -> String
forall a. [a] -> [a] -> [a]
++
  Set String -> String
serializeLabels Set String
lbls String -> String -> String
forall a. [a] -> [a] -> [a]
++
  Map String Value -> String
serializePropertyRecord Map String Value
props

-- | Serialize pattern elements for implicit root (no brackets/pipe).
-- Used when serializing the top-level Gram container.
-- NOTE: This function is now inlined into toGram to handle properties.
-- Keeping signature for potential reuse or removing if unused.
-- serializeImplicitElements :: [Pattern Subject] -> String
-- serializeImplicitElements elems = 
--   intercalate "\n" (map toGram elems)

-- | Serialize pattern elements to gram notation.
--
-- Converts a list of Pattern Subject elements to gram notation.
-- For subjects with nested elements, elements are serialized after a pipe separator.
-- References (just identity, no labels/properties/elements) are serialized as just the symbol.
--
-- === Examples
--
-- >>> serializePatternElements []
-- ""
--
-- >>> serializePatternElements [Pattern (Subject (Symbol "a") Set.empty empty) []]
-- " a"
--
-- >>> serializePatternElements [Pattern (Subject (Symbol "a") (Set.fromList ["Person"]) empty) []]
-- " (a:Person)"
serializePatternElements :: [Pattern Subject] -> String
serializePatternElements :: [Pattern Subject] -> String
serializePatternElements [Pattern Subject]
elems
  | [Pattern Subject] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Pattern Subject]
elems = String
""
  | Bool
otherwise = 
      let serialized :: [String]
serialized = (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null) ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$ (Pattern Subject -> String) -> [Pattern Subject] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Pattern Subject -> String
serializeNestedElement [Pattern Subject]
elems
      in String -> [String] -> String
intercalate String
", " [String]
serialized
  where
    serializeNestedElement :: Pattern Subject -> String
    serializeNestedElement :: Pattern Subject -> String
serializeNestedElement (Pattern (Subject Symbol
ident Set String
lbls Map String Value
props) [Pattern Subject]
nested)
      -- If it's a named reference (just identity, no labels/properties/elements), serialize as just the symbol
      | Set String -> Bool
forall a. Set a -> Bool
Set.null Set String
lbls Bool -> Bool -> Bool
&& Map String Value -> Bool
forall k a. Map k a -> Bool
Map.null Map String Value
props Bool -> Bool -> Bool
&& [Pattern Subject] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Pattern Subject]
nested Bool -> Bool -> Bool
&& Symbol
ident Symbol -> Symbol -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> Symbol
Symbol String
"" =
          Symbol -> String
quoteSymbol Symbol
ident
      -- Otherwise serialize as a full pattern (recursive call to toGram)
      | Bool
otherwise = Pattern Subject -> String
toGram (Subject -> [Pattern Subject] -> Pattern Subject
forall v. v -> [Pattern v] -> Pattern v
Pattern (Symbol -> Set String -> Map String Value -> Subject
Subject Symbol
ident Set String
lbls Map String Value
props) [Pattern Subject]
nested)

-- | Serialize a Pattern Subject to gram notation.
--
-- Converts a Pattern Subject data structure into its gram notation
-- string representation. The output follows the gram notation specification:
-- - Patterns with elements use subject syntax: `[attributes | elements]`
-- - Patterns without elements use node syntax: `(attributes)`
--
-- === Examples
--
-- Simple node (no elements):
--
-- >>> import Pattern.Core (Pattern(..))
-- >>> import Subject.Core (Subject(..), Symbol(..))
-- >>> import Data.Set (Set)
-- >>> import qualified Data.Set as Set
-- >>> let s = Subject (Symbol "n") (Set.fromList ["Person"]) empty
-- >>> let p = Pattern { value = s, elements = [] }
-- >>> toGram p
-- "(n:Person)"
--
-- Node with properties (no elements):
--
-- >>> import Data.Map (fromList)
-- >>> import Subject.Value (VString)
-- >>> let s = Subject (Symbol "n") (Set.fromList ["Person"]) (fromList [("name", VString "Alice")])
-- >>> let p = Pattern { value = s, elements = [] }
-- >>> toGram p
-- "(n:Person {name:\"Alice\"})"
--
-- Subject with nested elements:
--
-- >>> let inner1 = Pattern (Subject (Symbol "a") Set.empty empty) []
-- >>> let inner2 = Pattern (Subject (Symbol "b") Set.empty empty) []
-- >>> let outer = Pattern (Subject (Symbol "g") Set.empty empty) [inner1, inner2]
-- >>> toGram outer
-- "[g | a, b]"
toGram :: Pattern Subject -> String
toGram :: Pattern Subject -> String
toGram p :: Pattern Subject
p@(Pattern Subject
subj [Pattern Subject]
elems)
  | Subject -> Bool
isImplicitRoot Subject
subj = Map String Value -> [Pattern Subject] -> String
serializeImplicitElements (Subject -> Map String Value
properties Subject
subj) [Pattern Subject]
elems -- Implicit root -> record + elements
  | [Pattern Subject] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Pattern Subject]
elems = Subject -> String
serializeSubjectAsNode Subject
subj  -- No elements -> node syntax
  | Just [Pattern Subject]
edges <- Pattern Subject -> Maybe [Pattern Subject]
isWalkPattern Pattern Subject
p = [Pattern Subject] -> String
serializeWalkPattern [Pattern Subject]
edges
  | Just (Subject
rel, Pattern Subject
left, Pattern Subject
right) <- Pattern Subject
-> Maybe (Subject, Pattern Subject, Pattern Subject)
isEdgePattern Pattern Subject
p = Subject -> Pattern Subject -> Pattern Subject -> String
serializeEdgePattern Subject
rel Pattern Subject
left Pattern Subject
right
  | Bool
otherwise = Subject -> [Pattern Subject] -> String
serializeSubjectAsSubject Subject
subj [Pattern Subject]
elems  -- Has elements -> subject syntax
  where
    -- | Check if subject is the Implicit Root
    -- Identified by "Gram.Root" label.
    isImplicitRoot :: Subject -> Bool
    isImplicitRoot :: Subject -> Bool
isImplicitRoot (Subject (Symbol String
"") Set String
lbls Map String Value
_) = String
"Gram.Root" String -> Set String -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set String
lbls
    isImplicitRoot Subject
_ = Bool
False

    -- | Serialize pattern elements for implicit root (record + elements).
    -- Note: We do NOT serialize the "Gram.Root" label itself, as it is implicit in the file structure.
    serializeImplicitElements :: Map String Value -> [Pattern Subject] -> String
    serializeImplicitElements :: Map String Value -> [Pattern Subject] -> String
serializeImplicitElements Map String Value
props' [Pattern Subject]
elems' = 
      let propsStr :: String
propsStr = if Map String Value -> Bool
forall k a. Map k a -> Bool
Map.null Map String Value
props' then String
"" else Map String Value -> String
serializePropertyRecord Map String Value
props'
          elemsStr :: String
elemsStr = String -> [String] -> String
intercalate String
"\n" ((Pattern Subject -> String) -> [Pattern Subject] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Pattern Subject -> String
toGram [Pattern Subject]
elems')
      in case (String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
propsStr, String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
elemsStr) of
           (Bool
True, Bool
True) -> String
"{}" -- Empty graph/root
           (Bool
False, Bool
True) -> String -> String
trimLeadingSpace String
propsStr -- Remove leading space from serializePropertyRecord
           (Bool
True, Bool
False) -> String
elemsStr
           (Bool
False, Bool
False) -> String -> String
trimLeadingSpace String
propsStr String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
elemsStr

    trimLeadingSpace :: String -> String
trimLeadingSpace (Char
' ':String
xs) = String
xs
    trimLeadingSpace String
xs = String
xs

    -- | Check if pattern is a Walk Pattern: [Gram.Walk | edge1, edge2, ...]
    isWalkPattern :: Pattern Subject -> Maybe [Pattern Subject]
    isWalkPattern :: Pattern Subject -> Maybe [Pattern Subject]
isWalkPattern (Pattern (Subject Symbol
_ Set String
lbls Map String Value
_) [Pattern Subject]
edges)
      | String
"Gram.Walk" String -> Set String -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set String
lbls = [Pattern Subject] -> Maybe [Pattern Subject]
forall a. a -> Maybe a
Just [Pattern Subject]
edges
      | Bool
otherwise = Maybe [Pattern Subject]
forall a. Maybe a
Nothing

    -- | Serialize a Walk Pattern as chained path: (a)->(b)->(c)
    -- Assumes edges are connected: (a)->(b), (b)->(c), etc.
    -- We serialize the first edge fully: (a)->(b)
    -- Then for subsequent edges, we only serialize the relationship and right node: ->(c)
    serializeWalkPattern :: [Pattern Subject] -> String
    serializeWalkPattern :: [Pattern Subject] -> String
serializeWalkPattern [] = String
""
    serializeWalkPattern [Pattern Subject
e] = Pattern Subject -> String
toGram Pattern Subject
e -- Fallback to normal serialization for single edge
    serializeWalkPattern (Pattern Subject
first:[Pattern Subject]
rest) = 
      Pattern Subject -> String
toGram Pattern Subject
first String -> String -> String
forall a. [a] -> [a] -> [a]
++ (Pattern Subject -> String) -> [Pattern Subject] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Pattern Subject -> String
serializeConnectedEdge [Pattern Subject]
rest

    -- | Helper to serialize subsequent edges in a walk: -[rel]->(right)
    -- We assume the left node of this edge matches the right node of the previous one,
    -- so we skip serializing the left node.
    serializeConnectedEdge :: Pattern Subject -> String
    serializeConnectedEdge :: Pattern Subject -> String
serializeConnectedEdge Pattern Subject
p' = 
      case Pattern Subject
-> Maybe (Subject, Pattern Subject, Pattern Subject)
isEdgePattern Pattern Subject
p' of
        Just (Subject
rel, Pattern Subject
_, Pattern Subject
right) -> String
"-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Subject -> String
serializeRelationship Subject
rel String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"->" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Pattern Subject -> String
toGram Pattern Subject
right
        Maybe (Subject, Pattern Subject, Pattern Subject)
Nothing -> String
" | " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Pattern Subject -> String
toGram Pattern Subject
p -- Fallback if walk contains non-edge (shouldn't happen in valid walks)

    -- | Check if pattern is an Edge Pattern: [rel | left, right]
    -- Note: This assumes the parser's convention where an edge is a pattern
    -- whose value is the relationship subject and has exactly two elements (nodes).
    -- We MUST verify that left and right are atomic (Nodes), otherwise we might
    -- falsely identify a list of 2 paths as an edge!
    isEdgePattern :: Pattern Subject -> Maybe (Subject, Pattern Subject, Pattern Subject)
    isEdgePattern :: Pattern Subject
-> Maybe (Subject, Pattern Subject, Pattern Subject)
isEdgePattern (Pattern Subject
r [l :: Pattern Subject
l@(Pattern Subject
_ []), r_node :: Pattern Subject
r_node@(Pattern Subject
_ [])]) = (Subject, Pattern Subject, Pattern Subject)
-> Maybe (Subject, Pattern Subject, Pattern Subject)
forall a. a -> Maybe a
Just (Subject
r, Pattern Subject
l, Pattern Subject
r_node)
    isEdgePattern Pattern Subject
_ = Maybe (Subject, Pattern Subject, Pattern Subject)
forall a. Maybe a
Nothing

    -- | Serialize an Edge Pattern as path syntax: (left)-[rel]->(right)
    serializeEdgePattern :: Subject -> Pattern Subject -> Pattern Subject -> String
    serializeEdgePattern :: Subject -> Pattern Subject -> Pattern Subject -> String
serializeEdgePattern Subject
rel Pattern Subject
left Pattern Subject
right =
      Pattern Subject -> String
toGram Pattern Subject
left String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Subject -> String
serializeRelationship Subject
rel String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"->" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Pattern Subject -> String
toGram Pattern Subject
right

    -- | Serialize relationship part: [rel] or just - if anonymous/empty
    serializeRelationship :: Subject -> String
    serializeRelationship :: Subject -> String
serializeRelationship (Subject (Symbol String
"") Set String
lbls Map String Value
props)
      | Set String -> Bool
forall a. Set a -> Bool
Set.null Set String
lbls Bool -> Bool -> Bool
&& Map String Value -> Bool
forall k a. Map k a -> Bool
Map.null Map String Value
props = String
""  -- Anonymous relationship: returns empty string so result is -- + "" + -> = -->
      | Bool
otherwise = String
"[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Set String -> String
serializeLabels Set String
lbls String -> String -> String
forall a. [a] -> [a] -> [a]
++ Map String Value -> String
serializePropertyRecord Map String Value
props String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"]"
    serializeRelationship (Subject Symbol
ident Set String
lbls Map String Value
props) =
      String
"[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Symbol -> String
serializeIdentity Symbol
ident String -> String -> String
forall a. [a] -> [a] -> [a]
++ Set String -> String
serializeLabels Set String
lbls String -> String -> String
forall a. [a] -> [a] -> [a]
++ Map String Value -> String
serializePropertyRecord Map String Value
props String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"]"

    serializeSubjectAsNode :: Subject -> String
    serializeSubjectAsNode :: Subject -> String
serializeSubjectAsNode (Subject Symbol
ident Set String
lbls Map String Value
props) =
      String
"(" String -> String -> String
forall a. [a] -> [a] -> [a]
++
      Symbol -> String
serializeIdentity Symbol
ident String -> String -> String
forall a. [a] -> [a] -> [a]
++
      Set String -> String
serializeLabels Set String
lbls String -> String -> String
forall a. [a] -> [a] -> [a]
++
      Map String Value -> String
serializePropertyRecord Map String Value
props String -> String -> String
forall a. [a] -> [a] -> [a]
++
      String
")"
    
    serializeSubjectAsSubject :: Subject -> [Pattern Subject] -> String
    serializeSubjectAsSubject :: Subject -> [Pattern Subject] -> String
serializeSubjectAsSubject (Subject Symbol
ident Set String
lbls Map String Value
props) [Pattern Subject]
nested =
      String
"[" String -> String -> String
forall a. [a] -> [a] -> [a]
++
      Symbol -> String
serializeIdentity Symbol
ident String -> String -> String
forall a. [a] -> [a] -> [a]
++
      Set String -> String
serializeLabels Set String
lbls String -> String -> String
forall a. [a] -> [a] -> [a]
++
      Map String Value -> String
serializePropertyRecord Map String Value
props String -> String -> String
forall a. [a] -> [a] -> [a]
++
      (if Bool -> Bool
not ([Pattern Subject] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Pattern Subject]
nested) then String
" | " else String
"") String -> String -> String
forall a. [a] -> [a] -> [a]
++
      [Pattern Subject] -> String
serializePatternElements [Pattern Subject]
nested String -> String -> String
forall a. [a] -> [a] -> [a]
++
      String
"]"