import Specimen.LazyRoseTree
import Specimen.LazyList
namespace SearchTree
-- An "anchor" is a hypothesis that does NOT commute freely with the hypothesis being
-- inserted — it shares variables that haven't been instantiated by a more-leftward
-- hypothesis. When inserting, we must consider positions both before and after each anchor,
-- splitting the search into chunks between anchor points.
structure Chunks' α where
  beforeAnchor : List α
  anchors : List (List α)
  numAnchors : Nat
  deriving Repr

private def getNeighbors {α v} [BEq α] [BEq v] (hyps : List (α × List v)) : List (α × List (α × List v)) :=
  hyps.map (fun (hyp, vars) =>
    let neighbors := hyps.filterMap (fun (otherHyp, otherVars) =>
      if hyp == otherHyp then .none else
      let vars' := vars.filter (otherVars.contains ·)
      if vars'.isEmpty then
        .none
      else
        .some (otherHyp, vars'))
    (hyp, neighbors))

private def splitIntoChunks {α} [BEq α] (order : List α) (anchors : List α) : Chunks' α :=
  let (beforeAnchor, rest) := order.span (!anchors.contains ·)
  let rec split (remaining : List α) (currentChunk : List α) (result : List (List α)) : List (List α) :=
    match remaining with
    | [] => currentChunk.reverse :: result |>.reverse
    | x :: xs =>
      if anchors.contains x then
        split xs [x] (currentChunk.reverse :: result)
      else
        split xs (x :: currentChunk) result
  let anchors :=
    match rest with
    | firstAnchor :: rest' => split rest' [firstAnchor] []
    | [] => []
  let numAnchors := anchors.length
  ⟨beforeAnchor, anchors, numAnchors⟩

-- Create lazy search tree for enumDependencySatisfyingOrderings
def enumDependencySatisfyingOrderingsTree {α v} [BEq α] [Repr α] [Repr v] [BEq v]
  (hyps : List (α × List v)) : LazyRoseTree (List α) :=
  let neighbors := getNeighbors hyps

  let rec buildTree (remaining : List (α × List (α × List v))) (currentOrder : List α) : LazyRoseTree (List α) :=
    -- dbg_trace s!"leaking info on {repr currentOrder}\n"
    match remaining with
    | [] => ⟨currentOrder, ⟨fun _ => []⟩⟩
    | (h, deps) :: rest =>
      let inOrder := Id.run do
        let mut inOrder := []
        let mut env := []
        for (h', vs) in deps do
          if !(vs.removeAll env).isEmpty then
            inOrder := h' :: inOrder
          env := vs ++ env
        return inOrder

      -- let inOrder := deps.filter (currentOrder.contains ·)
      let chunks := splitIntoChunks currentOrder inOrder
      let insertionPositions := List.range (chunks.numAnchors + 1)

      ⟨currentOrder, ⟨fun _ =>
        insertionPositions.map fun pos =>
        let newChunks := {chunks with anchors := chunks.anchors.insertIdx pos [h], numAnchors := chunks.numAnchors + 1}
        let newOrder := newChunks.beforeAnchor ++ newChunks.anchors.flatten
        buildTree rest newOrder⟩⟩

  buildTree neighbors []



-- Prune tree with global best score state
partial def pruneTreeWithScore {α σ} [LT σ] [Min σ] [DecidableRel (fun a b : σ => a < b)] (tree : LazyRoseTree α) (score : α → σ) (bestScore : σ) : Option (LazyRoseTree α × σ) :=

  let nodeScore := score tree.val
  if nodeScore > bestScore then none else
  let children := tree.children.get
  if children.isEmpty then
    -- Leaf node: check score and update best
    some (⟨tree.val, ⟨fun _ => []⟩⟩, nodeScore)
  else Id.run do
    -- Internal node: prune children with scores >= bestScore
    let mut prunedChildren := []
    let mut finalBest := bestScore
    let children := children.toArray.qsort (fun a b => score a.val < score b.val)
    for child in children do
      match pruneTreeWithScore child score finalBest with
      | none => continue
      | some (prunedChild, childBest) =>
        prunedChildren := prunedChild :: prunedChildren
        finalBest := min finalBest childBest

    if prunedChildren.isEmpty then none else
    some (⟨tree.val, ⟨fun _ => prunedChildren.reverse⟩⟩, finalBest)

-- Prune tree with global best score state
partial def minTreePruning {α σ} [LT σ] [Min σ] [DecidableRel (fun a b : σ => a < b)] (tree : LazyRoseTree α) (score : α → σ) (bestScore : σ) : Option (α × σ) :=

  let nodeScore := score tree.val
  if nodeScore > bestScore then none else
  let children := tree.children.get
  if children.isEmpty then
    -- Leaf node: check score and update best
    some (tree.val, nodeScore)
  else Id.run do
    -- Internal node: prune children with scores >= bestScore
    let mut bestChild := none
    let mut bestScore := bestScore
    let children := children.toArray.qsort (fun a b => score a.val < score b.val)
    for child in children do
      match minTreePruning child score bestScore with
      | none => continue
      | some (bestA, childBestScore) =>
        if childBestScore < bestScore then
          bestChild := some bestA
          bestScore := childBestScore

    bestChild.map (fun a => (a,bestScore))

-- Prune tree and tag each value with its score
def pruneTreeWithScoredValues {α : Type} {σ : Type} [LT σ] [Min σ] [Ord σ] [DecidableRel (fun a b : σ => a < b)] [Repr σ] [Inhabited (α × σ)]
                                      (tree : LazyRoseTree α) (score : α → σ) (bestScore : σ)
                                      : Option (LazyRoseTree (α × σ) × σ) :=
  pruneTreeWithScore (tree.map fun a => (a, score a)) (fun a => a.snd) bestScore

-- Original scoring function for simple variable lists
def scoreOrdering {α v} [BEq α] [BEq v] (hypVarMap : List (α × List v)) (ordering : List α) : Nat :=
  let rec go (remaining : List α) (env : List v) (checks : Nat) : Nat :=
    match remaining with
    | [] => checks
    | hyp :: rest =>
      match hypVarMap.find? (fun (h, _) => h == hyp) with
      | none => go rest env (checks + 1)  -- Unknown hyp = check
      | some (_, vars) =>
        if vars.all (env.contains ·) then
          go rest (vars ++ env) (checks + 1)  -- All vars bound = check
        else
          go rest (vars ++ env) checks  -- Generate vars, no check
  go ordering [] 0

-- Test with simple example
#guard_msgs(error, drop info) in
#eval
  let tree := enumDependencySatisfyingOrderingsTree [("H",[1]),("I",[1,2]),("J",[2])]
  tree



-- Lexicographic ordering for Nat × Nat
instance : LT (Nat × Nat) where
  lt a b := Prod.Lex (· < ·) (· < ·) a b

instance : DecidableRel (fun a b : Nat × Nat => a < b) := Prod.Lex.instDecidableRelOfDecidableEq

instance : Min (Nat × Nat) where
  min a b := if a < b then a else b

instance : Ord (Nat × Nat) where
  compare a b :=
    match compare a.1 b.1 with
    | Ordering.lt => Ordering.lt
    | Ordering.gt => Ordering.gt
    | Ordering.eq => compare a.2 b.2

abbrev LexNat := Nat × Nat

-- Variable expression types
inductive VarExpr (v : Type) where
| Var : v → VarExpr v  -- Single variable
| Ctor : List v → VarExpr v  -- Contains constructor, but not function
| Func : List v → VarExpr v  -- Contains function application
  deriving BEq, Repr

-- Extract all variables from a VarExpr
def extractVars {α} [BEq α] : VarExpr α → List α
| .Var v => [v]
| .Ctor args => args
| .Func args => args

-- Check if VarExpr contains function applications
partial def containsFunc : VarExpr α → Bool
| .Func .. => true
| _ => false

-- Extensible scoring function with constructor/function support
def scoreOrderingAdvanced {α v} [BEq α] [BEq v] (hypVarMap : List (α × List (VarExpr v))) (ordering : List α) : Nat :=
  let processed := ordering.filterMap (fun h => hypVarMap.find? (fun (h', _) => h == h'))
  let unprocessed := hypVarMap.filter (fun (h, _) => !ordering.contains h)
  let rec go (remaining : List (α × List (VarExpr v))) (env : List v) (arbitrary : List v) (checks : Nat) : Nat :=
    match remaining with
    | [] =>
      -- Add lower bound for remaining hypotheses that will be checks
      let forcedChecks := unprocessed.filter (fun (_, varExprs) =>
        varExprs.flatMap extractVars |>.all env.contains)
      -- Filter forced checks by whether they use any arbitrary variable
      let guaranteedChecks := forcedChecks.filter (fun (_, varExprs) =>
        (varExprs.flatMap extractVars).any (!arbitrary.contains ·))
      checks + guaranteedChecks.length
    | (_, varExprs) :: rest =>
      let producers := varExprs.filter (fun x => !containsFunc x && (extractVars x).all fun v => !env.contains v)
      let newVars := varExprs.flatMap extractVars
      let newArbitrary := newVars.filter (!env.contains ·)
      if producers.isEmpty then
        go rest (env ++ newVars) arbitrary (checks + 1)
      else
        go rest (env ++ newVars) (arbitrary ++ newArbitrary) checks
  go processed [] [] 0

-- Get current environment from partial ordering
def getCurrentEnv {α v} [BEq α] [BEq v] (currentOrder : List α) (hypVarMap : List (α × List v)) : List v :=
  let rec go (order : List α) (env : List v) : List v :=
    match order with
    | [] => env
    | hyp :: rest =>
      match hypVarMap.find? (fun (h, _) => h == hyp) with
      | some (_, vars) => go rest (vars ++ env)
      | none => go rest env
  go currentOrder []

-- Get variables that could be arbitrarily generated in current ordering
def getArbitraryVars {α v} [BEq α] [BEq v] (currentOrder : List α) (hypVarMap : List (α × List v)) : List v :=
  let rec go (order : List α) (env : List v) (arbitrary : List v) : List v :=
    match order with
    | [] => arbitrary
    | hyp :: rest =>
      match hypVarMap.find? (fun (h, _) => h == hyp) with
      | some (_, vars) =>
        let newVars := vars.filter (!env.contains ·)  -- Variables this hyp can generate
        go rest (vars ++ env) (newVars ++ arbitrary)
      | none => go rest env arbitrary
  go currentOrder [] []
-- Count guaranteed arbitrary variables
def countGuaranteedArbitraries {α v} [BEq α] [BEq v] (currentOrder : List α) (remaining : List (α × List v)) (hypVarMap : List (α × List v)) : Nat :=
  let remainingVars := remaining.flatMap (fun (_, vars) => vars)

  let rec collectArbitraryVars (order : List α) (env : List v) (arbitraryCount : Nat) : Nat :=
    match order with
    | [] => arbitraryCount
    | hyp :: rest =>
      match hypVarMap.find? (fun (h, _) => h == hyp) with
      | some (_, vars) =>
        let newArbitraries := vars.filter (fun v => !env.contains v && !remainingVars.contains v)
        let newEnv := vars ++ env
        collectArbitraryVars rest newEnv (arbitraryCount + newArbitraries.length)
      | none => collectArbitraryVars rest env arbitraryCount

  let arbitraryCount := collectArbitraryVars currentOrder [] 0
  max 0 (arbitraryCount - 1)

-- Lower bound score for internal nodes (primary: checks, secondary: -arbitraries)
def lowerBoundScore {α v} [BEq α] [BEq v] (currentOrder : List α) (remaining : List (α × List v)) (hypVarMap : List (α × List v)) : LexNat :=
  let currentScore := scoreOrdering hypVarMap currentOrder
  let currentEnv := getCurrentEnv currentOrder hypVarMap
  let arbitraryVars := getArbitraryVars currentOrder hypVarMap

  -- Count hypotheses that will definitely be checks
  let forcedChecks := remaining.filter (fun (_, vars) =>
    vars.all (currentEnv.contains ·))  -- All vars already bound

  let guaranteedChecks := forcedChecks.filter (fun (_, vars) =>
    let generatableVars := vars  -- For simple case, all vars are generatable
    generatableVars.any (!arbitraryVars.contains ·))  -- Can't be arbitrary

  let primaryScore := currentScore + guaranteedChecks.length
  let secondaryScore := countGuaranteedArbitraries currentOrder remaining hypVarMap
  (primaryScore, secondaryScore)

-- Lexicographic scoring: (primary: checks, secondary: -arbitraries)
def scoreOrderingLex {α v} [BEq α] [BEq v] (hypVarMap : List (α × List v)) (ordering : List α) : LexNat :=
  if ordering.length == hypVarMap.length then
    -- Complete ordering: count actual arbitrary variables
    let arbitraryVars := getArbitraryVars ordering hypVarMap
    (scoreOrdering hypVarMap ordering, arbitraryVars.length)
  else
    -- Partial ordering: use lower bound
    let remaining := hypVarMap.filter (fun (h, _) => !ordering.contains h)
    lowerBoundScore ordering remaining hypVarMap

-- Optimized scoring with cached computations
def scoreOrderingOptimized {α v} [BEq α] [BEq v] (hypVarMap : List (α × List v)) (ordering : List α) : LexNat :=
  if ordering.length == hypVarMap.length then
    -- Complete ordering: optimized computation
    let rec go (order : List α) (env : List v) (checks : Nat) (arbitraries : Nat) : Nat × Nat :=
      match order with
      | [] => (checks, arbitraries)
      | hyp :: rest =>
        match hypVarMap.find? (fun (h, _) => h == hyp) with
        | some (_, vars) =>
          let unboundVars := vars.filter (!env.contains ·)
          let newEnv := vars ++ env
          if unboundVars.isEmpty then
            go rest newEnv (checks + 1) arbitraries
          else
            go rest newEnv checks (arbitraries + unboundVars.length)
        | none => go rest env (checks + 1) arbitraries
    go ordering [] 0 0
  else
    -- Partial ordering: use optimized lower bound
    let remaining := hypVarMap.filter (fun (h, _) => !ordering.contains h)
    let currentScore := scoreOrdering hypVarMap ordering
    let secondaryScore := countGuaranteedArbitraries ordering remaining hypVarMap
    (currentScore, secondaryScore)

-- Augmented scoring function with lexicographic lower bounds
def scoreOrderingWithLowerBound {α v} [BEq α] [BEq v] (hypVarMap : List (α × List v)) (ordering : List α) : LexNat :=
  scoreOrderingOptimized hypVarMap ordering

-- Function to test pruning with hypothesis-variable mapping
def testPruning {α v} [BEq α] [Repr α] [Repr v] [BEq v] [Hashable v] (hypVarMap : List (α × List v)) (maxScore : Nat) :=
  let tree := enumDependencySatisfyingOrderingsTree hypVarMap
  let scoreFunc := scoreOrdering hypVarMap
  pruneTreeWithScore tree scoreFunc maxScore

-- Function to test pruning with scored values
def testPruningWithScores {α v} [BEq α] [Repr α] [Repr v] [BEq v] [Hashable v] (hypVarMap : List (α × List v)) (maxScore : Nat) :=
  let tree := enumDependencySatisfyingOrderingsTree hypVarMap
  let scoreFunc := scoreOrdering hypVarMap
  pruneTreeWithScoredValues tree scoreFunc maxScore

-- Function to test pruning with lexicographic scoring (lower bounds)
def testPruningWithLexScoring {α v} [BEq α] [Repr α] [Repr v] [BEq v] (hypVarMap : List (α × List v)) (maxScore : LexNat) :=
  let tree := enumDependencySatisfyingOrderingsTree hypVarMap
  let scoreFunc := scoreOrderingWithLowerBound hypVarMap
  pruneTreeWithScoredValues tree scoreFunc maxScore


#guard_msgs(error, drop info) in
#eval enumDependencySatisfyingOrderingsTree [("H", [1]), ("I", [1,2]), ("J", [2])]
-- Example 1: Simple case
#guard_msgs(error, drop info) in
#eval testPruning [("H", [1]), ("I", [1,2]), ("J", [2])] 5

-- Example 1b: Simple case with scored values
#guard_msgs(error, drop info) in
#eval testPruningWithScores [("H", [1]), ("I", [1,2]), ("J", [2])] 5

#guard_msgs(error, drop info) in
#eval testPruningWithLexScoring [("H", [1]), ("I", [1,2]), ("J", [2])] (2, 1)

-- Example 2: STLC TApp constructor with VarExpr
-- typing Γ e1 (Fun τ1 τ2) has constructor Fun constraining τ1, τ2
-- typing Γ e2 τ1 has simple variable τ1
#guard_msgs(error, drop info) in
#eval
  let hypMapVarExpr := [("typing_e1", [VarExpr.Var "e1", .Ctor ["tau1", "tau2"]]),
                        ("typing_e2", [.Var "e2", .Var "tau1"])]
  let hypMapFlat := [("typing_e1", ["e1", "tau1", "tau2"]), ("typing_e2", ["e2", "tau1"])]
  let tree := enumDependencySatisfyingOrderingsTree hypMapFlat
  let scoreFunc := scoreOrderingAdvanced hypMapVarExpr
  pruneTreeWithScoredValues tree scoreFunc 2

-- Count total nodes in a tree
partial def countNodes {α} (tree : LazyRoseTree α) : Nat :=
  1 + (tree.children.get.map countNodes).foldl (· + ·) 0

-- Count leaf nodes in a tree
partial def countLeaves {α} (tree : LazyRoseTree α) : Nat :=
  let children := tree.children.get
  if children.isEmpty then 1 else (children.map countLeaves).foldl (· + ·) 0

-- Analytical tree size calculation matching buildTree's dynamic anchor computation
def calculateTreeSize {α v} [BEq α] [BEq v] (hyps : List (α × List v)) : Nat × Nat :=
  let neighbors := getNeighbors hyps

  let rec countTree (remaining : List (α × List (α × List v))) (currentOrder : List α) : Nat × Nat :=
    match remaining with
    | [] => (1, 1)
    | (h, deps) :: rest =>
      let inOrder := Id.run do
        let mut inOrder := []
        let mut env := []
        for (h', vs) in deps do
          if !(vs.removeAll env).isEmpty then
            inOrder := h' :: inOrder
          env := vs ++ env
        return inOrder
      let chunks := splitIntoChunks currentOrder inOrder
      let insertionPositions := List.range (chunks.numAnchors + 1)
      insertionPositions.foldl (fun (accNodes, accLeaves) pos =>
        let newChunks := {chunks with anchors := chunks.anchors.insertIdx pos [h], numAnchors := chunks.numAnchors + 1}
        let newOrder := newChunks.beforeAnchor ++ newChunks.anchors.flatten
        let (childNodes, childLeaves) := countTree rest newOrder
        (accNodes + childNodes, accLeaves + childLeaves)
      ) (1, 0)

  countTree neighbors []

-- Measure pruning effectiveness
def measurePruning {α v} [BEq α] [Repr α] [Repr v] [BEq v] (hypVarMap : List (α × List v)) (maxScore : _) :=
  let (originalNodes, originalLeaves) := calculateTreeSize hypVarMap

  let originalTree := enumDependencySatisfyingOrderingsTree hypVarMap
  let scoreFunc := scoreOrderingLex hypVarMap
  let prunedResult := pruneTreeWithScore originalTree scoreFunc maxScore

  match prunedResult with
  | none => (originalNodes, originalLeaves, 0, 0, 100)  -- 100% pruned
  | some (prunedTree, _) =>
    let prunedNodes := countNodes prunedTree
    let prunedLeaves := countLeaves prunedTree
    let leafReduction := (originalLeaves - prunedLeaves) * 100 / originalLeaves
    (originalNodes, originalLeaves, prunedNodes, prunedLeaves, leafReduction)

-- Example 1: Simple case with pruning measurement
#guard_msgs(error, drop info) in
#eval measurePruning [("H", [1]), ("I", [1,2]), ("J", [2])] (6,0)

#guard_msgs(error, drop info) in
#eval testPruningWithScores [("H", [1]), ("I", [1,2]), ("J", [2])] 10

-- Example 1c: Simple case with lexicographic scoring
#guard_msgs(error, drop info) in
#eval testPruningWithLexScoring [("H", [1]), ("I", [1,2]), ("J", [2])] (2, 1)

-- Example 3: Many hypotheses with pruning measurement
#guard_msgs(error, drop info) in
#eval measurePruning [("H1", ["a"]), ("H2", ["a", "b"]), ("H3", ["b", "c"]),
                      ("H4", ["c", "d"]), ("H5", ["d", "e"]), ("H6", ["a", "e"])] (7,0)

#guard_msgs(error, drop info) in
#eval testPruningWithScores [("H1", ["a"]), ("H2", ["a", "b"]), ("H3", ["b", "c"]),
                      ("H4", ["c", "d"]), ("H5", ["d", "e"]), ("H6", ["a", "e"])] 3

-- Example 3b: Many hypotheses with lexicographic scoring
#guard_msgs(error, drop info) in
#eval testPruningWithLexScoring [("H1", ["a"]), ("H2", ["a", "b"]), ("H3", ["b", "c"]),
                      ("H4", ["c", "d"]), ("H5", ["d", "e"]), ("H6", ["a", "e"])] (4, 2)
def validTensorScalarOpHypotheses : List (String × List String) := [
  ("ValidHeader", ["header"]),
  ("ValidEvents", ["events"]),
  ("HasTensorScalarOpcode", ["header"]),
  ("TensorScalarValidOps", ["op0", "op1"]),
  ("TensorScalarValidTypes", ["header", "in_dtype", "out_dtype"]),
  ("TensorScalarImmediatesCheck", ["imm0_src", "imm1_src", "imm0", "imm1", "header", "in_dtype", "num_active_channels"]),
  ("TensorScalarShiftChk", ["op0", "op1", "in_dtype"]),
  ("TensorScalarTensorChk", ["src_mem_pattern", "in_dtype", "dst_mem_pattern", "out_dtype"]),
  ("TensorScalarReverseChk", []),
  ("S3d3TransposeCheck", ["header", "src_mem_pattern", "num_active_channels"]),
  ("ValidDtype_in", ["in_dtype"]),
  ("ValidDtype_out", ["out_dtype"]),
  ("ValidAluOp_op0", ["op0"]),
  ("ValidAluOp_op1", ["op1"]),
  ("HasZeroAccumCmdField", ["accumulator_cmd"]),
  ("HasValidActiveChannelRange", ["num_active_channels"]),
  ("StartAddrActiveChannels_src", ["src_mem_pattern", "num_active_channels"]),
  ("StartAddrActiveChannels_dst", ["dst_mem_pattern", "num_active_channels"]),
  ("Tensor3dValid_src", ["src_mem_pattern", "in_dtype"]),
  ("Tensor3dValid_dst", ["dst_mem_pattern", "out_dtype"])
]

-- #guard_msgs in
-- #eval measurePruning validTensorScalarOpHypotheses (9,14)

-- Lazy stream version that yields increasingly better values
partial def minTreePruningStream {α σ} [LT σ] [Min σ] [DecidableRel (fun a b : σ => a < b)] (tree : LazyRoseTree α) (score : α → σ) (bestScore : σ) : LazyList (α × σ) :=
  let nodeScore := score tree.val
  if nodeScore > bestScore then .lnil else
  let children := tree.children.get
  if children.isEmpty then
    pure (tree.val, nodeScore)
  else
    let children := children.toArray.qsort (fun a b => score a.val < score b.val)
    let rec processChildren (remaining : Array (LazyRoseTree α)) (currentBest : σ) : LazyList (α × σ) :=
      if h : 0 < remaining.size then
        let child := remaining[0]
        let rest := remaining.extract 1 remaining.size
        let childResults := minTreePruningStream child score currentBest
        match childResults with
        | .lnil => processChildren rest currentBest
        | .lcons (val, newScore) tail =>
          .lcons (val, newScore) ⟨fun _ =>
            let updatedBest := min currentBest newScore
            LazyList.append tail.get (processChildren rest updatedBest)⟩
      else .lnil
    processChildren children bestScore

-- Prune tree and tag each value with its score
def pruningStreamWithScoredValues {α : Type} {σ : Type} [LT σ] [Min σ] [Ord σ] [DecidableRel (fun a b : σ => a < b)] [Repr σ] [Inhabited (α × σ)]
                                      (tree : LazyRoseTree α) (score : α → σ) (bestScore : σ)
                                      : LazyList ((α × σ) × σ) :=
  minTreePruningStream (tree.map fun a => (a, score a)) (fun a => a.snd) bestScore

-- Enumerate dependency-satisfying orderings using pruning stream
def enumDependencySatisfyingOrderingsWithPruning {α v} [BEq α] [Repr α] [Repr v] [BEq v] [Hashable v] (hyps : List (α × List v)) : LazyList (List α) :=
  let tree := enumDependencySatisfyingOrderingsTree hyps
  let scoreFunc := scoreOrdering hyps
  minTreePruningStream tree scoreFunc (hyps.length + 1) |>.mapLazyList Prod.fst

-- Enumerate dependency-satisfying orderings using advanced scoring with VarExpr
def enumDependencySatisfyingOrderingsWithAdvancedPruning {α v} [BEq α] [Repr α] [Repr v] [BEq v] [Hashable v] (hyps : List (α × List v)) (toVarExpr : α → List (VarExpr v)) : LazyList (List α) :=
  let tree := enumDependencySatisfyingOrderingsTree hyps
  let hypMapVarExpr := hyps.map (fun (h, _) => (h, toVarExpr h))
  let scoreFunc := scoreOrderingAdvanced hypMapVarExpr
  minTreePruningStream tree scoreFunc (hyps.length + 1) |>.mapLazyList Prod.fst

/-- Monadic tree traversal with branch-and-bound pruning and early termination.
    Explores best-first (children sorted by score), prunes when partial score exceeds best.
    - `score`: monadic scoring function (may trigger on-demand dep derivation)
    - `isBetter`: returns true if first arg is strictly better than second
    - `done`: IO.Ref flag; consumer sets to `true` to stop exploration
    - `yield`: callback receiving (leaf value, its score) and current best; returns updated best -/
partial def minTreePruningM [Monad m] [MonadLiftT BaseIO m] {α σ : Type}
    (tree : LazyRoseTree α) (score : α → m σ) (isBetter : σ → σ → Bool) (bestScore : σ)
    (done : IO.Ref Bool) (yield : (α × σ) → σ → m σ) : m σ := do
  if ← liftM (m := BaseIO) done.get then return bestScore
  let nodeScore ← score tree.val
  if isBetter bestScore nodeScore then return bestScore
  let children := tree.children.get
  if children.isEmpty then
    yield (tree.val, nodeScore) bestScore
  else
    let mut scoredChildren : Array (LazyRoseTree α × σ) := #[]
    for child in children do
      let s ← score child.val
      scoredChildren := scoredChildren.push (child, s)
    let sorted := scoredChildren.qsort (fun a b => isBetter a.2 b.2)
    let mut currentBest := bestScore
    for (child, _) in sorted do
      if ← liftM (m := BaseIO) done.get then break
      currentBest ← minTreePruningM child score isBetter currentBest done yield
    return currentBest
