-- | Graph Lens: Interpretive view of Pattern structures as graphs.
--
-- This module provides the Graph Lens feature, which enables interpreting
-- Pattern structures as graph structures (nodes, relationships, walks) through
-- a minimal, elegant design based on a single predicate.
--
-- == Overview
--
-- A Graph Lens provides an interpretive view of a Pattern as a graph structure.
-- Rather than defining graph concepts (nodes, relationships, walks) as intrinsic
-- properties of Pattern, they emerge through the lens's interpretation. This
-- design enables multiple graph views of the same Pattern and supports
-- higher-order graphs where relationships or entire graphs become nodes.
--
-- == Core Design
--
-- The Graph Lens consists of:
--
-- * @scopePattern@: The Pattern that defines the boundary for all graph operations.
--   Only direct elements of this pattern are considered for graph structure.
-- * @testNode@: A predicate determining which direct elements are nodes.
--   All other graph concepts (relationships, walks) derive from this single predicate.
--
-- == Design Principles
--
-- 1. **Scope-bounded operations**: All graph operations only consider direct elements
--    of @scopePattern@, never descending into nested structures.
--
-- 2. **Single predicate foundation**: Only @testNode@ is required. All other graph
--    predicates (relationships, walks, etc.) are derived from this.
--
-- 3. **Context captured at construction**: If a predicate needs context, that context
--    must be captured when the predicate is created, not during evaluation.
--
-- 4. **Interpretation, not intrinsic**: Graph structure is not a property of Pattern
--    itself, but an interpretation through the lens.
--
-- == Categorical Interpretation
--
-- Graph Lens provides a functorial interpretation where Pattern structures are
-- transformed into graph interpretations. The transformation Pattern → Graph
-- interpretation is functorial in nature.
--
-- == GraphView
--
-- 'GraphView' is the universal transformation interface produced from a 'GraphLens'
-- (via 'toGraphView') or from a 'Pattern.PatternGraph.PatternGraph'
-- (via 'Pattern.PatternGraph.toGraphView'). It pairs a snapshot 'GraphQuery'
-- with a categorized, traversable list of graph elements, enabling lazy pipeline
-- transformations:
--
-- > pipeline :: PatternGraph Subject -> PatternGraph Subject
-- > pipeline graph =
-- >   materialize canonicalClassifier LastWriteWins
-- >   . mapWithContext canonicalClassifier enrich
-- >   . filterGraph canonicalClassifier isRelevant dissolve
-- >   . mapAllGraph updateTimestamp
-- >   . Pattern.PatternGraph.toGraphView canonicalClassifier
-- >   $ graph
--
-- Finalize a 'GraphView' pipeline back to a 'Pattern.PatternGraph.PatternGraph'
-- by calling 'Pattern.PatternGraph.materialize'.
--
-- == Example
--
-- >>> let graphPattern = pattern "graph" [point "a", point "b", pattern "r1" [point "a", point "b"]]
-- >>> let isAtomic (Pattern _ els) = null els
-- >>> let atomicLens = GraphLens graphPattern isAtomic
-- >>> nodes atomicLens
-- [Pattern "a" [],Pattern "b" []]
--
-- See @design/graph-lens.md@ and @specs/023-graph-lens/quickstart.md@ for
-- comprehensive examples and usage patterns.
{-# LANGUAGE TypeFamilies #-}
module Pattern.Graph
  ( -- * Graph Lens Type
    GraphLens(..)
  , mkGraphLens
    -- * Node Operations
  , nodes
  , isNode
    -- * Relationship Operations
  , isRelationship
  , relationships
  , source
  , target
  , reverseRel
    -- * Walk Operations
  , isWalk
  , walks
  , walkNodes
    -- * Navigation Operations
  , neighbors
  , incidentRels
  , degree
    -- * GraphQuery constructor
  , fromGraphLens
    -- * GraphView constructor
  , toGraphView
    -- * GraphView and Substitution (re-exported from Pattern.Graph.Types)
  , GraphView(..)
  , Substitution(..)
  ) where

import Pattern.Core (Pattern(..))
import Data.Maybe (mapMaybe)

import Pattern.Graph.GraphClassifier (GraphClass(..), GraphClassifier(..), GraphValue(..))
import Pattern.Graph.GraphQuery (GraphQuery(..))
import Pattern.Graph.Types (GraphView(..), Substitution(..))

-- | A Graph Lens provides an interpretive view of a Pattern as a graph structure.
-- 
-- The lens consists of:
-- * @scopePattern@: The Pattern that defines the boundary for all graph operations.
--   Only direct elements of this pattern are considered for graph structure.
-- * @testNode@: A predicate determining which direct elements are nodes.
--   All other graph concepts (relationships, walks) derive from this predicate.
--
-- == Categorical Interpretation
--
-- Graph Lens provides a functorial interpretation where Pattern structures are
-- transformed into graph interpretations. The transformation Pattern → Graph
-- interpretation is functorial in nature.
--
-- == Design Principles
--
-- 1. Scope-bounded: All operations only consider direct elements of scopePattern
-- 2. Single predicate foundation: Only testNode is required, all else derives
-- 3. Context at construction: Predicate context captured when lens is created
-- 4. Interpretation, not intrinsic: Graph structure is an interpretation, not
--    a property of Pattern itself
--
-- == Example
--
-- >>> let atomicLens = GraphLens pattern (\(Pattern _ els) -> null els)
-- >>> nodes atomicLens
-- [[a], [b], [c]]
data GraphLens v = GraphLens
  { forall v. GraphLens v -> Pattern v
scopePattern :: Pattern v
    -- ^ The Pattern that defines the graph scope
  , forall v. GraphLens v -> Pattern v -> Bool
testNode     :: Pattern v -> Bool
    -- ^ Predicate determining which elements are nodes
  }

-- | Construct a 'GraphLens' using a predicate to identify nodes.
--
-- This encapsulates the context for interpreting graph structure.
mkGraphLens :: Pattern v -> (Pattern v -> Bool) -> GraphLens v
mkGraphLens :: forall v. Pattern v -> (Pattern v -> Bool) -> GraphLens v
mkGraphLens = Pattern v -> (Pattern v -> Bool) -> GraphLens v
forall v. Pattern v -> (Pattern v -> Bool) -> GraphLens v
GraphLens

-- Helper to check walk validity under a specific node predicate
isValidWalk :: GraphValue v => (Pattern v -> Bool) -> [Pattern v] -> Bool
isValidWalk :: forall v.
GraphValue v =>
(Pattern v -> Bool) -> [Pattern v] -> Bool
isValidWalk Pattern v -> Bool
_ [] = Bool
False
isValidWalk Pattern v -> Bool
p [Pattern v]
rels = Bool -> Bool
not ([Pattern v] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (([Pattern v] -> Pattern v -> [Pattern v])
-> [Pattern v] -> [Pattern v] -> [Pattern v]
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl [Pattern v] -> Pattern v -> [Pattern v]
step [] [Pattern v]
rels))
  where
    step :: [Pattern v] -> Pattern v -> [Pattern v]
step [] (Pattern v
_ [Pattern v
a, Pattern v
b]) = if Pattern v -> Bool
p Pattern v
a Bool -> Bool -> Bool
&& Pattern v -> Bool
p Pattern v
b then [Pattern v
a, Pattern v
b] else []
    step [Pattern v]
active (Pattern v
_ [Pattern v
a, Pattern v
b]) =
      if Pattern v -> Bool
p Pattern v
a Bool -> Bool -> Bool
&& Pattern v -> Bool
p Pattern v
b then
        let fromA :: [Pattern v]
fromA = if (Pattern v -> Bool) -> [Pattern v] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Pattern v
x -> v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
a) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
x)) [Pattern v]
active then [Pattern v
b] else []
            fromB :: [Pattern v]
fromB = if (Pattern v -> Bool) -> [Pattern v] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Pattern v
x -> v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
b) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
x)) [Pattern v]
active then [Pattern v
a] else []
        in [Pattern v]
fromA [Pattern v] -> [Pattern v] -> [Pattern v]
forall a. [a] -> [a] -> [a]
++ [Pattern v]
fromB
      else []
    step [Pattern v]
_ Pattern v
_ = []

-- | Extract all nodes from the graph lens.
--
-- Nodes are direct elements of scopePattern that satisfy the testNode predicate.
--
-- == Time Complexity
-- O(n) where n is the number of direct elements in scopePattern
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> nodes lens
-- [[a], [b], [c]]
nodes :: GraphLens v -> [Pattern v]
nodes :: forall v. GraphLens v -> [Pattern v]
nodes lens :: GraphLens v
lens@(GraphLens (Pattern v
_ [Pattern v]
elems) Pattern v -> Bool
_) =
  (Pattern v -> Bool) -> [Pattern v] -> [Pattern v]
forall a. (a -> Bool) -> [a] -> [a]
filter (GraphLens v -> Pattern v -> Bool
forall v. GraphLens v -> Pattern v -> Bool
isNode GraphLens v
lens) [Pattern v]
elems

-- | Determine if a Pattern is a node according to the lens.
--
-- This is the context-aware version that uses the lens's testNode predicate.
-- The lens parameter provides the predicate context.
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> isNode lens (point "a")
-- True
-- >>> isNode lens (pattern "rel" [point "a", point "b"])
-- False
isNode :: GraphLens v -> Pattern v -> Bool
isNode :: forall v. GraphLens v -> Pattern v -> Bool
isNode (GraphLens Pattern v
_ Pattern v -> Bool
test) Pattern v
p = Pattern v -> Bool
test Pattern v
p

-- * Relationship Operations

-- | Determine if a Pattern is a relationship according to the lens.
--
-- A relationship is a non-node pattern with exactly two node elements.
--
-- == Properties
-- * Must not be a node (does not satisfy testNode predicate)
-- * Must have exactly two elements
-- * Both elements must be nodes (according to the lens)
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> let rel = pattern "knows" [point "Alice", point "Bob"]
-- >>> isRelationship lens rel
-- True
isRelationship :: GraphLens v -> Pattern v -> Bool
isRelationship :: forall v. GraphLens v -> Pattern v -> Bool
isRelationship lens :: GraphLens v
lens@(GraphLens Pattern v
_ Pattern v -> Bool
test) p :: Pattern v
p@(Pattern v
_ [Pattern v]
els) =
  Bool -> Bool
not (Pattern v -> Bool
test Pattern v
p) Bool -> Bool -> Bool
&& [Pattern v] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Pattern v]
els Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
2 Bool -> Bool -> Bool
&& (Pattern v -> Bool) -> [Pattern v] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Pattern v -> Bool
test [Pattern v]
els

-- | Extract all relationships from the graph lens.
--
-- Relationships are non-node patterns with exactly two node elements.
--
-- == Time Complexity
-- O(n) where n is the number of direct elements in scopePattern
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> relationships lens
-- [[knows | [Alice], [Bob]], [likes | [Bob], [Charlie]]]
relationships :: GraphLens v -> [Pattern v]
relationships :: forall v. GraphLens v -> [Pattern v]
relationships lens :: GraphLens v
lens@(GraphLens (Pattern v
_ [Pattern v]
elems) Pattern v -> Bool
_) =
  (Pattern v -> Bool) -> [Pattern v] -> [Pattern v]
forall a. (a -> Bool) -> [a] -> [a]
filter (GraphLens v -> Pattern v -> Bool
forall v. GraphLens v -> Pattern v -> Bool
isRelationship GraphLens v
lens) [Pattern v]
elems

-- | Extract the source node from a relationship.
--
-- For directed relationships, the source is the first element.
-- Returns Nothing if the pattern is not a relationship.
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> let rel = pattern "knows" [point "Alice", point "Bob"]
-- >>> source lens rel
-- Just (point "Alice")
source :: GraphLens v -> Pattern v -> Maybe (Pattern v)
source :: forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
source GraphLens v
lens p :: Pattern v
p@(Pattern v
_ (Pattern v
s:[Pattern v]
_))
  | GraphLens v -> Pattern v -> Bool
forall v. GraphLens v -> Pattern v -> Bool
isRelationship GraphLens v
lens Pattern v
p = Pattern v -> Maybe (Pattern v)
forall a. a -> Maybe a
Just Pattern v
s
  | Bool
otherwise = Maybe (Pattern v)
forall a. Maybe a
Nothing
source GraphLens v
_ Pattern v
_ = Maybe (Pattern v)
forall a. Maybe a
Nothing

-- | Extract the target node from a relationship.
--
-- For directed relationships, the target is the second element.
-- Returns Nothing if the pattern is not a relationship.
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> let rel = pattern "knows" [point "Alice", point "Bob"]
-- >>> target lens rel
-- Just (point "Bob")
target :: GraphLens v -> Pattern v -> Maybe (Pattern v)
target :: forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
target GraphLens v
lens p :: Pattern v
p@(Pattern v
_ [Pattern v
_, Pattern v
t])
  | GraphLens v -> Pattern v -> Bool
forall v. GraphLens v -> Pattern v -> Bool
isRelationship GraphLens v
lens Pattern v
p = Pattern v -> Maybe (Pattern v)
forall a. a -> Maybe a
Just Pattern v
t
  | Bool
otherwise = Maybe (Pattern v)
forall a. Maybe a
Nothing
target GraphLens v
_ Pattern v
_ = Maybe (Pattern v)
forall a. Maybe a
Nothing

-- | Reverse the direction of a relationship pattern.
--
-- Swaps the first and second elements, effectively reversing the
-- relationship direction.
--
-- == Example
--
-- >>> let rel = pattern "knows" [point "Alice", point "Bob"]
-- >>> reverseRel rel
-- pattern "knows" [point "Bob", point "Alice"]
reverseRel :: Pattern v -> Pattern v
reverseRel :: forall v. Pattern v -> Pattern v
reverseRel (Pattern v
v [Pattern v
a, Pattern v
b]) = v -> [Pattern v] -> Pattern v
forall v. v -> [Pattern v] -> Pattern v
Pattern v
v [Pattern v
b, Pattern v
a]
reverseRel Pattern v
p = Pattern v
p  -- Return unchanged if not a 2-element pattern

-- * Walk Operations

-- | Check if a list of relationships are consecutively connected.
--
-- Relationships are consecutively connected if the target of one
-- equals the source of the next.
--
-- == Internal Function
-- This is an internal helper function used by isWalk.
consecutivelyConnected :: GraphValue v => GraphLens v -> [Pattern v] -> Bool
consecutivelyConnected :: forall v. GraphValue v => GraphLens v -> [Pattern v] -> Bool
consecutivelyConnected GraphLens v
lens [Pattern v]
rels =
  case [Pattern v]
rels of
    [] -> Bool
True
    [Pattern v
_] -> Bool
True
    (Pattern v
r1:Pattern v
r2:[Pattern v]
rest) ->
      case (GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
target GraphLens v
lens Pattern v
r1, GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
source GraphLens v
lens Pattern v
r2) of
        (Just Pattern v
t, Just Pattern v
s) -> v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
t) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
s) Bool -> Bool -> Bool
&& GraphLens v -> [Pattern v] -> Bool
forall v. GraphValue v => GraphLens v -> [Pattern v] -> Bool
consecutivelyConnected GraphLens v
lens (Pattern v
r2Pattern v -> [Pattern v] -> [Pattern v]
forall a. a -> [a] -> [a]
:[Pattern v]
rest)
        (Maybe (Pattern v), Maybe (Pattern v))
_ -> Bool
False

-- | Determine if a Pattern is a walk according to the lens.
--
-- A walk is a non-node pattern whose elements are all relationships,
-- where consecutive relationships share nodes (target of one equals
-- source of next).
--
-- == Properties
-- * Must not be a node (does not satisfy testNode predicate)
-- * All elements must be relationships (according to the lens)
-- * Consecutive relationships must be connected
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> let walk = pattern "path" [rel1, rel2, rel3]
-- >>> isWalk lens walk
-- True
isWalk :: GraphValue v => GraphLens v -> Pattern v -> Bool
isWalk :: forall v. GraphValue v => GraphLens v -> Pattern v -> Bool
isWalk lens :: GraphLens v
lens@(GraphLens Pattern v
_ Pattern v -> Bool
test) p :: Pattern v
p@(Pattern v
_ [Pattern v]
els) =
  Bool -> Bool
not (Pattern v -> Bool
test Pattern v
p) Bool -> Bool -> Bool
&& Bool -> Bool
not ([Pattern v] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Pattern v]
els)
  Bool -> Bool -> Bool
&& (Pattern v -> Bool) -> [Pattern v] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\Pattern v
e -> [Pattern v] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Pattern v -> [Pattern v]
forall v. Pattern v -> [Pattern v]
elements Pattern v
e) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
2 Bool -> Bool -> Bool
&& (Pattern v -> Bool) -> [Pattern v] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Pattern v -> Bool
test (Pattern v -> [Pattern v]
forall v. Pattern v -> [Pattern v]
elements Pattern v
e)) [Pattern v]
els
  Bool -> Bool -> Bool
&& (Pattern v -> Bool) -> [Pattern v] -> Bool
forall v.
GraphValue v =>
(Pattern v -> Bool) -> [Pattern v] -> Bool
isValidWalk Pattern v -> Bool
test [Pattern v]
els

-- | Extract all walks from the graph lens.
--
-- Walks are non-node patterns whose elements are all relationships,
-- where consecutive relationships share nodes.
--
-- == Time Complexity
-- O(n) where n is the number of direct elements in scopePattern
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> walks lens
-- [[path | [rel1], [rel2], [rel3]]]
walks :: GraphValue v => GraphLens v -> [Pattern v]
walks :: forall v. GraphValue v => GraphLens v -> [Pattern v]
walks lens :: GraphLens v
lens@(GraphLens (Pattern v
_ [Pattern v]
elems) Pattern v -> Bool
_) =
  (Pattern v -> Bool) -> [Pattern v] -> [Pattern v]
forall a. (a -> Bool) -> [a] -> [a]
filter (GraphLens v -> Pattern v -> Bool
forall v. GraphValue v => GraphLens v -> Pattern v -> Bool
isWalk GraphLens v
lens) [Pattern v]
elems

-- | Extract nodes from a walk in traversal order.
--
-- Returns the source of the first relationship, followed by the targets
-- of subsequent relationships. Returns empty list if the pattern is not
-- a valid walk.
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> let walk = pattern "path" [rel1, rel2]
-- >>> walkNodes lens walk
-- [pattern "A", pattern "B", pattern "C"]
walkNodes :: GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
walkNodes :: forall v. GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
walkNodes GraphLens v
lens p :: Pattern v
p@(Pattern v
_ [Pattern v]
rels)
  | GraphLens v -> Pattern v -> Bool
forall v. GraphValue v => GraphLens v -> Pattern v -> Bool
isWalk GraphLens v
lens Pattern v
p = case [Pattern v]
rels of
      [] -> []
      (Pattern v
r:[Pattern v]
rest) -> case GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
source GraphLens v
lens Pattern v
r of
        Just Pattern v
s -> Pattern v
s Pattern v -> [Pattern v] -> [Pattern v]
forall a. a -> [a] -> [a]
: (Pattern v -> Maybe (Pattern v)) -> [Pattern v] -> [Pattern v]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
target GraphLens v
lens) (Pattern v
rPattern v -> [Pattern v] -> [Pattern v]
forall a. a -> [a] -> [a]
:[Pattern v]
rest)
        Maybe (Pattern v)
Nothing -> []
  | Bool
otherwise = []

-- * Navigation Operations

-- | Find all neighbors of a node.
--
-- Neighbors are nodes connected to the given node via relationships
-- (either as source or target).
--
-- == Time Complexity
-- O(r) where r is the number of relationships in the graph
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> neighbors lens (point "Alice")
-- [point "Bob", pattern "Charlie"]
neighbors :: GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
neighbors :: forall v. GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
neighbors GraphLens v
lens Pattern v
node =
  let rels :: [Pattern v]
rels = GraphLens v -> [Pattern v]
forall v. GraphLens v -> [Pattern v]
relationships GraphLens v
lens
      nodeId :: Id v
nodeId = v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
node)
      connectedNodes :: [Pattern v]
connectedNodes = (Pattern v -> [Pattern v]) -> [Pattern v] -> [Pattern v]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\Pattern v
r ->
        case (GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
source GraphLens v
lens Pattern v
r, GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
target GraphLens v
lens Pattern v
r) of
          (Just Pattern v
s, Just Pattern v
t) | v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
s) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
nodeId -> [Pattern v
t]
                           | v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
t) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
nodeId -> [Pattern v
s]
          (Maybe (Pattern v), Maybe (Pattern v))
_ -> []
        ) [Pattern v]
rels
  in [Pattern v]
connectedNodes

-- | Find all relationships involving a node.
--
-- Returns relationships where the node is either source or target.
--
-- == Time Complexity
-- O(r) where r is the number of relationships in the graph
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> incidentRels lens (point "Alice")
-- [[knows | [Alice], [Bob]], [likes | [Charlie], [Alice]]]
incidentRels :: GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
incidentRels :: forall v. GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
incidentRels GraphLens v
lens Pattern v
node =
  let nodeId :: Id v
nodeId = v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
node)
  in (Pattern v -> Bool) -> [Pattern v] -> [Pattern v]
forall a. (a -> Bool) -> [a] -> [a]
filter (\Pattern v
r ->
    case (GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
source GraphLens v
lens Pattern v
r, GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
target GraphLens v
lens Pattern v
r) of
      (Just Pattern v
s, Maybe (Pattern v)
_) | v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
s) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
nodeId -> Bool
True
      (Maybe (Pattern v)
_, Just Pattern v
t) | v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
t) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
nodeId -> Bool
True
      (Maybe (Pattern v), Maybe (Pattern v))
_ -> Bool
False
    ) (GraphLens v -> [Pattern v]
forall v. GraphLens v -> [Pattern v]
relationships GraphLens v
lens)

-- | Compute the degree of a node (number of incident relationships).
--
-- The degree is the count of relationships where the node is either
-- source or target.
--
-- == Time Complexity
-- O(r) where r is the number of relationships in the graph
--
-- == Example
--
-- >>> let lens = GraphLens pattern isAtomic
-- >>> degree lens (point "Alice")
-- 3
degree :: GraphValue v => GraphLens v -> Pattern v -> Int
degree :: forall v. GraphValue v => GraphLens v -> Pattern v -> Int
degree GraphLens v
lens Pattern v
node = [Pattern v] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (GraphLens v -> Pattern v -> [Pattern v]
forall v. GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
incidentRels GraphLens v
lens Pattern v
node)

-- ============================================================================
-- GraphQuery constructor
-- ============================================================================

-- | Construct a 'GraphQuery' from a 'GraphLens'.
--
-- All fields are derived from existing 'Pattern.Graph' functions.
-- 'queryNodeById' and 'queryRelationshipById' perform O(n) \/ O(r) scans
-- (no index available from 'GraphLens'). 'queryContainers' scans relationships
-- and walks.
--
-- Note: defined here (not in "Pattern.Graph.GraphQuery") to avoid a circular
-- import between GraphQuery and Graph.
fromGraphLens :: (GraphValue v, Eq v) => GraphLens v -> GraphQuery v
fromGraphLens :: forall v. (GraphValue v, Eq v) => GraphLens v -> GraphQuery v
fromGraphLens GraphLens v
lens = GraphQuery
  { queryNodes :: [Pattern v]
queryNodes            = GraphLens v -> [Pattern v]
forall v. GraphLens v -> [Pattern v]
nodes GraphLens v
lens
  , queryRelationships :: [Pattern v]
queryRelationships    = GraphLens v -> [Pattern v]
forall v. GraphLens v -> [Pattern v]
relationships GraphLens v
lens
  , queryIncidentRels :: Pattern v -> [Pattern v]
queryIncidentRels     = GraphLens v -> Pattern v -> [Pattern v]
forall v. GraphValue v => GraphLens v -> Pattern v -> [Pattern v]
incidentRels GraphLens v
lens
  , querySource :: Pattern v -> Maybe (Pattern v)
querySource           = GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
source GraphLens v
lens
  , queryTarget :: Pattern v -> Maybe (Pattern v)
queryTarget           = GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
target GraphLens v
lens
  , queryDegree :: Pattern v -> Int
queryDegree           = GraphLens v -> Pattern v -> Int
forall v. GraphValue v => GraphLens v -> Pattern v -> Int
degree GraphLens v
lens
  , queryNodeById :: Id v -> Maybe (Pattern v)
queryNodeById         = \Id v
i ->
      (Pattern v -> Maybe (Pattern v) -> Maybe (Pattern v))
-> Maybe (Pattern v) -> [Pattern v] -> Maybe (Pattern v)
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (\Pattern v
n Maybe (Pattern v)
acc -> if v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
n) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
i then Pattern v -> Maybe (Pattern v)
forall a. a -> Maybe a
Just Pattern v
n else Maybe (Pattern v)
acc)
            Maybe (Pattern v)
forall a. Maybe a
Nothing
            (GraphLens v -> [Pattern v]
forall v. GraphLens v -> [Pattern v]
nodes GraphLens v
lens)
  , queryRelationshipById :: Id v -> Maybe (Pattern v)
queryRelationshipById = \Id v
i ->
      (Pattern v -> Maybe (Pattern v) -> Maybe (Pattern v))
-> Maybe (Pattern v) -> [Pattern v] -> Maybe (Pattern v)
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (\Pattern v
r Maybe (Pattern v)
acc -> if v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
r) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
i then Pattern v -> Maybe (Pattern v)
forall a. a -> Maybe a
Just Pattern v
r else Maybe (Pattern v)
acc)
            Maybe (Pattern v)
forall a. Maybe a
Nothing
            (GraphLens v -> [Pattern v]
forall v. GraphLens v -> [Pattern v]
relationships GraphLens v
lens)
  , queryContainers :: Pattern v -> [Pattern v]
queryContainers       = \Pattern v
p ->
      let nodeId :: Id v
nodeId = v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
p)
          inRel :: Pattern v -> Bool
inRel Pattern v
r = case (GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
source GraphLens v
lens Pattern v
r, GraphLens v -> Pattern v -> Maybe (Pattern v)
forall v. GraphLens v -> Pattern v -> Maybe (Pattern v)
target GraphLens v
lens Pattern v
r) of
            (Just Pattern v
s, Maybe (Pattern v)
_) | v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
s) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
nodeId -> Bool
True
            (Maybe (Pattern v)
_, Just Pattern v
t) | v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
t) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
nodeId -> Bool
True
            (Maybe (Pattern v), Maybe (Pattern v))
_ -> Bool
False
          containingRels :: [Pattern v]
containingRels  = (Pattern v -> Bool) -> [Pattern v] -> [Pattern v]
forall a. (a -> Bool) -> [a] -> [a]
filter Pattern v -> Bool
inRel (GraphLens v -> [Pattern v]
forall v. GraphLens v -> [Pattern v]
relationships GraphLens v
lens)
          containingWalks :: [Pattern v]
containingWalks = (Pattern v -> Bool) -> [Pattern v] -> [Pattern v]
forall a. (a -> Bool) -> [a] -> [a]
filter
            (\Pattern v
w -> (Pattern v -> Bool) -> [Pattern v] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Pattern v
r -> v -> Id v
forall v. GraphValue v => v -> Id v
identify (Pattern v -> v
forall v. Pattern v -> v
value Pattern v
r) Id v -> Id v -> Bool
forall a. Eq a => a -> a -> Bool
== Id v
nodeId) (Pattern v -> [Pattern v]
forall v. Pattern v -> [Pattern v]
elements Pattern v
w))
            (GraphLens v -> [Pattern v]
forall v. GraphValue v => GraphLens v -> [Pattern v]
walks GraphLens v
lens)
      in [Pattern v]
containingRels [Pattern v] -> [Pattern v] -> [Pattern v]
forall a. [a] -> [a] -> [a]
++ [Pattern v]
containingWalks
  }

-- ============================================================================
-- GraphView constructor from GraphLens
-- ============================================================================

-- | Construct a 'GraphView' from a 'GraphLens' and a 'GraphClassifier'.
--
-- The classifier assigns a 'GraphClass' tag to each element in the lens scope.
-- 'viewQuery' is the snapshot query built from the same lens.
--
-- Note: defined here (not in "Pattern.Graph.GraphQuery") to avoid a circular
-- import — GraphQuery cannot import Pattern.Graph.
toGraphView
  :: (GraphValue v, Eq v)
  => GraphClassifier extra v
  -> GraphLens v
  -> GraphView extra v
toGraphView :: forall v extra.
(GraphValue v, Eq v) =>
GraphClassifier extra v -> GraphLens v -> GraphView extra v
toGraphView GraphClassifier extra v
classifier GraphLens v
lens =
  GraphView
    { viewQuery :: GraphQuery v
viewQuery    = GraphLens v -> GraphQuery v
forall v. (GraphValue v, Eq v) => GraphLens v -> GraphQuery v
fromGraphLens GraphLens v
lens
    , viewElements :: [(GraphClass extra, Pattern v)]
viewElements = (Pattern v -> (GraphClass extra, Pattern v))
-> [Pattern v] -> [(GraphClass extra, Pattern v)]
forall a b. (a -> b) -> [a] -> [b]
map (\Pattern v
p -> (GraphClassifier extra v -> Pattern v -> GraphClass extra
forall extra v.
GraphClassifier extra v -> Pattern v -> GraphClass extra
classify GraphClassifier extra v
classifier Pattern v
p, Pattern v
p)) [Pattern v]
scopeElems
    }
  where
    GraphLens (Pattern v
_ [Pattern v]
scopeElems) Pattern v -> Bool
_ = GraphLens v
lens