content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE QuasiQuotes #-}
  {-# LANGUAGE RecordWildCards #-}
  
  {-| transpiler to Typescript.
  
  This module transpiles a given natural4 ruleset to Typescript, for
  use as a web "rule engine" called by some web application runtime.
  The entry-point of the TS code will be called with a "user input"
  object containing a (partial) set of known terms; the Typescript
  code computes an answer and returns an output object. The
  computation is given by the Natural4 ruleset.
  
  The code generated by this module follows conventional Typescript
  style by producing, as far as possible, Typescript class
  definitions, decision functions, and variable instances to reflect
  the input ruleset.
  
  * Architecture Decsion Records
  
  We considered : https://hackage.haskell.org/package/aeson-typescript
  not suitable because L4 allows users to define their own types which
  are not known to Haskell's type system.
  
  * Application Context: Input and Output
  
  It is expected that this typescript will be part of an end-user
  facing runtime. The web interface will construct a JSON object
  representing user input. The typescript runtime will be expected to
  run against that input and produce output state. The user input is
  aligned with the JSON schema generated by other parts of the
  system, as is the output object. That output state will inform the
  display of the user-visible components in the UI.
  
  So, for example, if the end-user input looks something like
  
  @
     const userInput = { "the moon is out": true
                       , "the sky": "cloudy"
                       }
  @
  
  We we run feed that into the rule engine component written in typescript
  which contains the "business logic" derived from natural4,
  and get back out
  
  @
     const ruleOutput = { "happy": true }
  @
  
  * Application Internals: Computation
  
  The box in the middle would be something like
  @
     export function ruleEngine ( userInput : UserInput ) : RuleOutput {
        var ruleOutput = {};
        ruleOutput["happy"] =    userInput["the moon is out"]
                              && userInput["the sky"] == "cloudy";
        return ruleOutput;
     }
  @
  
  The computations in the middle need to be topologically sorted, so that
  intermediate computations are evaluated in correct order.
  
  * Application Internals: OOP style
  
  The computations could become more complicated to reflect an OOP style, where
  certain computations live inside class methods:
  
  @
     class MyClass {
       constructor(public value1 : number,
                   public value2 : number) { super(); }
       value3() { return this.value1 + this.value2 }
     }
     class UserInput {
       myObject : MyClass;
     }
  
     // the caller sets us up with user input
     // const uI = { myObject : new MyClass (100, 200),
     //            , "the sky": "cloudy" }
  
     export function ruleEngine ( userInput : UserInput ) : RuleOutput {
        var ruleOutput = {};
        ruleOutput["happy"] =    userInput.myObject.value3()
                              && userInput["the sky"] == "cloudy";
        return ruleOutput;
     }
  @
  
  The advantage of this style is that the code generator doesn't
  need to fully topo-sort all the computation; the "backward chaining"
  style of computation is implicit in the method-call approach.
  
  -}
  
  module LS.XPile.Typescript (asTypescript) where
  
  -- import Debug.Trace
  import AnyAll (BoolStruct (All, Any, Leaf, Not))
  -- import L4.Syntax as CoreL4
  -- import L4.Annotation
  -- import Data.Functor ( (<&>) )
  
  import Data.Graph.Inductive (Graph (labNodes), outdeg)
  import Data.HashMap.Strict qualified as Map
  import Data.List (nub, partition)
  import Data.List.Extra (groupSort)
  import Data.List.NonEmpty qualified as NE
  import Data.Maybe (isJust, isNothing, maybeToList)
  -- JsonLogic
  
  import Data.String.Interpolate (i)
  import Data.Text qualified as T
  import Data.Text.Lazy qualified as DTL
  import Data.Tree qualified as DT
  import Debug.Trace (trace)
  import LS.Interpreter
    ( attrType,
      classRoots,
      exposedRoots,
      extractEnums,
      getCTkeys,
      globalFacts,
      isAnEnum,
      toObjectStr,
      topsortedClasses,
    )
  import LS.PrettyPrinter
    ( commentWith,
      snake_case,
      snake_inner,
      vvsep,
      (<//>),
      (</>),
    )
  import LS.PrettyPrinter.TypeSig
    ( ParamText4 (PT4, PT5),
      prettyMaybeType,
      prettySimpleType,
    )
  import LS.Interpreter (Interpreted(..))
  import LS.Rule as SFL4R
    ( Rule (..),
      ValuePredicate (..),
      isFact,
      ruleLabelName,
      ruleName,
    )
  import LS.Types as SFL4
    ( BoolStructR (..),
      ClsTab (CT),
      EntityType,
      HornClause (HC, hHead),
      HornClause2,
      MTExpr (MTT),
      MultiTerm,
      ParamText,
      ParamType (TOne, TOptional),
      RPRel (..),
      RelationalPredicate
        ( RPBoolStructR,
          RPConstraint,
          RPMT,
          RPParamText,
          RPnary
        ),
      TypeSig (InlineEnum, SimpleType),
      clsParent,
      getSymType,
      mt2text,
      rp2text,
    )
  import LS.XPile.Logging
    ( XPileLog,
      mutter,
      mutterd,
      mutterdhsf,
      mutters,
      pShowNoColorS,
      xpLog,
    )
  import Prettyprinter
    ( Doc,
      Pretty (pretty),
      braces,
      colon,
      comma,
      dquotes,
      emptyDoc,
      encloseSep,
      equals,
      indent,
      lbrace,
      line,
      list,
      nest,
      parens,
      rbrace,
      semi,
      space,
      viaShow,
      vsep,
      (<+>),
    )
  import Prettyprinter.Interpolate (di)
  import Text.JSON (Result (Error, Ok), decode, encode)
  import Text.Pretty.Simple (pShowNoColor)
  
  -- | top-level function
  asTypescript :: Interpreted -> XPileLog (Doc ann)
  asTypescript l4i = do
    vvsep <$> sequenceA [                              tsPrelude   l4i
                       , pure "// tsEnums",           tsEnums     l4i
                       , pure "// tsClasses",         tsClasses   l4i
                       , pure "// globalDefinitions [commented out]" --, globalDefinitions l4i
                       , pure "// jsInstances",       jsInstances l4i
                       , pure "// tsRuleEngine",      tsRuleEngine l4i
                       , pure "// toJsonLogic",       toJsonLogic l4i
                       , pure "// toPlainTS",         toPlainTS   l4i
                       ]
  
  
  -- | The entry-point for the runtime is the `tsRuleEngine`.
  --
  -- 1. It is given a @userInput@ object, whose attributes conform to
  --    the ontology defined in the ruleset.
  --
  -- 2. Top-level elements which are not part of any class hierarchy
  --     will be top-level attributes in @userInput@.
  --
  -- 3. The business logic runs based on the logical relations defined in the L4.
  --
  -- 4. The output gets placed in ruleOutput, perhaps with optional annotations showing
  --    the justification tree.
  --
  -- *Language Output Strategy*
  --
  -- In the initial sketch of this module we write TS by hand.
  -- But in future we may want to consider using [language-javascript](https://hackage.haskell.org/package/language-javascript-0.7.1.0/docs/Language-JavaScript-Parser-AST.html)
  --
  -- [TODO] we should refine the treatment of RPConstraint X IS Y
  -- where Y is in an enum; then we test for X == Y rather than stringifying to IS
  
  tsRuleEngine :: Interpreted -> XPileLog (Doc ann)
  tsRuleEngine l4i = do
  
    eRout <- exposedRoots l4i
  
    let globalRuleheads  = [ (n,r)
                           | (n,r) <- labNodes (ruleGraph l4i)
                           , r `notElem` eRout
                           , not (isFact r)
                           ]
  
        ( globalRuleParents , globalRuleLeaves) =
          partition (\(n,r) -> outdeg (ruleGraph l4i) n /= 0) globalRuleheads
  
        declaredClasses = classRoots (classtable l4i)
  
    mutterdhsf 3 "globalRuleheads should have excluded facts; we divide them into two halves." pShowNoColorS globalRuleheads
    mutterd 3 "globalRuleLeaves is one half."
    mutters ["These are the things we need to ask the user."
            ,"Enum cases are expanded, but can be converged farther down the operational chain."
            ,"We can do that because we should be able to type-infer that the LHS of the value assignment is type-annotated as an enum."
            ,"Fortunately we still have the original rule available at this point in the pipeline."]
    mutters [ "- " <> show (n,ruleName r) | (n,r) <- globalRuleLeaves ]
    mutterd 3 "globalRuleParents is the other half."
    mutters ["These are things we can compute for ourselves if the leaves above are filled out."
            ,"However, if the end-user wants to speed things up and answer parent nodes directly \"without proof\", they can do that."
            ]
    mutters [ "- " <> show (n,ruleName r) | (n,r) <- globalRuleParents ]
  
    return $ vsep
      [ "interface UserInput {"
  
         -- basically the ontology as we understand it, we're expecting that in the input.
         -- classes are top-level attributes in this interface;
         -- and top-level decision rules that are not classes also become top-level attributes.
         --
         -- [TODO] it'd be nice to infer some more type information so we can distinguish
         -- between booleans and numebrs for some of these top level decision values.
  
      , indent 2 $ vsep $
        "// heads of rules which are not class methods of some sort, excluding output roots below." :
        "// first we give the leaf nodes, which are ground terms not already known as facts" :
        [ prettyRuleName r | (nodeN,r) <- globalRuleLeaves ]
      , ""
      , indent 2 $ vsep $
        "// then we give the intermediate parent nodes" :
        [ prettyRuleName r | (nodeN,r) <- globalRuleParents ]
      , ""
      , indent 2 $ vsep $
        "// instances of DECLAREd classes" :
        [ ( className <> "Instance") <+> "?" <> colon <+> className <> semi
        | (classNameT, typedClass) <- declaredClasses
        , let className = snake_case [MTT classNameT]
        ]
  
      , "}"
      , ""
      , "interface OutputState {"
      , indent 2 $ vsep $
        "// roots of the rulegraph" :
        [ prettyRuleName exposedRoot
        | exposedRoot <- nub eRout ] -- not sure why nubbing doesn't seem to work, some methods still get dumped.
      , ""
      , indent 2 $ vsep $
        "// intermediate parent computations" :
        [ prettyRuleName r | (nodeN,r) <- globalRuleParents ]
      , "}"
      , "export function tsRuleEngine(userInput : UserInput) : OutputState {"
      , indent 2 $ vsep $
        "// we express each of the exposedRoot graphs;" :
        "// first rewritten as AOtrees, then dumped as AnyAll in TS" :
        [ "return { }" ]
      , "}"
      ]
    where
      prettyRuleName r = [di|"#{mt2text $ ruleName r}" ?: any;|]
        -- dquotes (pretty . mt2text $ ruleName r) <+> "?" <> colon <+> "any" <> semi
  
  -- | a convention for preserving the original strings and data structures for downstream use
  tsPrelude :: Interpreted -> XPileLog (Doc ann)
  tsPrelude l4i = pure $
    vsep [ "// tsPrelude"
         , "export const L4Orig = { enums: { }, classes: { } }"
         ]
  
  -- | output class declarations.
  --
  -- [TODO] -- add class methods here
  --
  -- We may need to upgrade all plain attributes to be methods -- because if bob's MyValue is 2 on tuesdays and 14 on wednesdays,
  -- the appropriate way to model that is as a method; but then MyValue has type `() => number`. And alice, whose MyValue is always 40,
  -- ends up with a @const 40@. That's why attributes may need to be upgraded to methods.
  
  tsClasses :: Interpreted -> XPileLog (Doc ann)
  tsClasses l4i = do
    let ct@(CT ch) = classtable l4i
    vvsep <$> sequenceA
      [ do
          mutterdhsf 3 "tsClasses / className" pShowNoColorS className
          mutterdhsf 3 "tsClasses / children" pShowNoColorS children
          pure $ [di|class #{snake_case [MTT className]}#{classExtension}|]
            -- attributes of the class are shown as decls
            <+> lbrace
        --    <//> "  //" <+> viaShow csuper
            <//> "  // using prettySimpleType (old code path)"
            <//> indent 2 ( vsep [ snakeAttr <> pretty (T.replicate padSpaces " ") <>
                                   case attrType children attrname of
                                     Just t@(SimpleType TOptional _) -> " () : null | " <+> prettySimpleType "ts" (snake_inner . MTT) t <+> braces (methodFor l4i className (Just t))
                                     Just t@(SimpleType TOne      sc)
                                       | sc /= "DefaultSuperClass"   -> " () : "        <+> prettySimpleType "ts" (snake_inner . MTT) t <+> braces (methodFor l4i className (Just t))
                                       | otherwise                   -> " () "          <+>                                                 braces (methodFor l4i className (Just t))
                                     Just t@(InlineEnum TOne      _) -> " () : "        <+> snake_case [MTT attrname] <> "Enum"         <+> braces (methodFor l4i className (Just t))
                                     Just t                          -> " () : "        <+> prettySimpleType "ts" (snake_inner . MTT) t <+> braces (methodFor l4i className (Just t))
                                     Nothing -> "// tsClasses nothing case"
                                   <> semi
                                 | attrname <- getCTkeys children
                                 , let snakeAttr = snake_case [MTT attrname]
                                       padSpaces = maxAttrLen - T.length attrname
                                 ]
                          )
  
            <//> "  //" <+> "using `methods` (new code path)"
            <//> let myMethods = concat [ methods l4i [MTT attrname]
                                        | attrname <- getCTkeys children
                                        ]
                 in if not $ null myMethods
                    then indent 2 ( encloseSep (lbrace <> line) (line <> rbrace) (comma <> space)
                                    myMethods
                                  )
                    else emptyDoc
  
            -- [TODO] finish out the attribute definition -- particularly tricky if it's a DECIDE, but we'll use the methods approach for that.
  
            <//> rbrace
          | className <- reverse $ topsortedClasses ct
          , (csuper, children) <- maybeToList $ Map.lookup className ch
          , case csuper of
              (Just (SimpleType _ _), _) -> True
              (Just (InlineEnum _ _), _) -> False -- we deal with enums separately below
              _                          -> True
          , let classExtension =
                  case clsParent ct className of
                    Nothing       -> mempty
                    Just "DefaultSuperClass" -> mempty
                    Just parent              -> " extends" <+> pretty parent
                maxAttrLen = maximum $ T.length <$> getCTkeys children
          ]
  
  -- | define a class method by searching through the l4i for two kinds of class method definition styles:
  --
  -- first,   DECIDE   MyClass's  MyValue  IS  such and such
  --
  -- second,   GIVEN   mc             IS  A  MyClass
  --                   someArgument   IS  A  number
  --           DECIDE  mc's  MyValue  IS  2 * someArgument
  --
  -- both cases result in the same method definition that looks something like
  -- @
  --    class MyClass { MyValue = (someArgument) => { return 2 * someArgument } }
  --
  
  methodFor :: Interpreted -> EntityType -> Maybe TypeSig -> Doc ann
  methodFor l4i entype = defaultMethod
  
  -- | if we can't find a decision rule defining this particular attribute as a method, we return a default method
  defaultMethod :: Maybe TypeSig -> Doc ann
  defaultMethod (Just (SimpleType TOne "string")) = " return \"\" "
  defaultMethod (Just (SimpleType TOne "number")) = " return 0 "
  defaultMethod _                                 = " return undefined "
  
  -- | output enum declarations
  tsEnums :: Interpreted -> XPileLog (Doc ann)
  tsEnums l4i = pure $
    vvsep ( showEnum <$> extractEnums l4i )
    where
      showEnum r@TypeDecl{super=Just (InlineEnum TOne enumNEList)} =
        let className = ruleLabelName r
        in
          "enum" <+> snake_case className <> "Enum" <+> lbrace
          <//> indent 2 ( vsep [ snake_case [enumStr] <> comma
                               | (enumMultiTerm, _) <- NE.toList enumNEList
                               , enumStr <- NE.toList enumMultiTerm
                               ] )
          <//> rbrace
  
            -- but we also want to preserve the original strings for downstream use
            -- so we use the L4Orig convention
          <//> "L4Orig.enums['" <> snake_case className <> "']" <+> equals <+> lbrace
          <//> indent 2 ( vsep [ snake_case [enumStr] <> colon <+> dquotes (pretty (mt2text [enumStr])) <> comma
                               | (enumMultiTerm, _) <- NE.toList enumNEList
                               , enumStr <- NE.toList enumMultiTerm
                               ] )
          <//> rbrace
  
  -- | all the instance symbols we know about, including those that show up in GIVEN, DEFINE, and DECIDE.
  -- This is probably not the best way to do it because we're commingling local and global variables here.
  -- Happy to rethink all of this.
  -- Also, not yet done, classes and variables need to be topologically sorted because typescript is picky about that.
  jsInstances :: Interpreted -> XPileLog (Doc ann)
  jsInstances l4i = pure $
    let sctabs = scopetable l4i
    in
    vvsep [ "//" <+> "scope" <+> scopenameStr scopename <//>
            "// symtab' = " <+> commentWith "// " [DTL.toStrict (pShowNoColor symtab')] <//>
            vvsep [ "const" <+> snake_case mt <+> prettyMaybeType "ts" (snake_inner . MTT) (getSymType symtype) <+> equals <+> nest 2 value
  --                  </> "// symtype = " <+> viaShow symtype
  --                  </> "// val = " <+> viaShow val
                  | (mt, (symtype, vals)) <- Map.toList symtab'
                  , symtype /= (Nothing,[])
                  , val <- nub vals
                  , value <- case val of
                                -- what we should do is gather all the paramtexts and join them in a single dictionary,
                                -- rather than assume that all the HC2 are paramtexts.
                               -- We could do with an additional test against the keyword, should be a Define.
  
                               -- also, we should preorganize all the constContents into a canonical data structure hierarchy, rather than try to fit things into the sctabs
                                HC { hHead = RPParamText {} } ->
                                  let constContents = asValuePT l4i vals <> methods l4i mt
                                  in [ encloseSep (lbrace <> line) (line <> rbrace) (comma <> space) constContents ]
                                _ ->
                                  ["// hc2ts" <> line, hc2ts l4i val]
                   ]
          | (scopename , symtab') <- Map.toList sctabs
          ]
    where
      scopenameStr [] = "globals"
      scopenameStr x  = snake_case x
  
  -- | This was originallyh written as instanceMethods and used in jsInstances. Next we are experimenting with expanding it to work for tsClasses as well.
  --
  -- If the list of valpreds ever gets very big, we can optimize the quadratic algo here using a map.
  methods :: Interpreted -> SFL4.MultiTerm -> [Doc ann]
  methods l4i mt =
    let marshalled = groupSort [ (attrName, vp)
                               | vp@ValPred{..} <- valuePreds l4i
                               , attrRel == Just RPis
                               , objPath == [mt2text mt]
                               ]
    in
    [ line <> ("// methods go here; attrName = " <> viaShow attrName')
      </> snake_inner (MTT attrName') <+> equals <+> parens emptyDoc <+> "=>"
      <+> braces ( line <> indent 2 (
                   vsep [ vpTS <> semi
                        | vp@ValPred{..} <- vps
                        , let vpTS = vpToTS l4i vp
                        ] ) <> line
                 )
    | (attrName', vps) <- marshalled
    ]
  
  -- | convert the @attrVal :- attrCond@ part of a ValuePredicate to typescript syntax
  vpToTS :: Interpreted -> ValuePredicate -> Doc ann
  vpToTS l4i ValPred{..}
    | attrCond == Just (Leaf (RPMT [MTT "OTHERWISE"]))
      || isNothing attrCond                = [di|return #{attrVal}|]
    | isJust    attrCond && isJust attrVal = [di|if (#{rp2ts attrCond}) { return #{attrVal}}|]
    | otherwise                            = "return null // [TODO]"
    where
      rp2ts :: Maybe BoolStructR -> Doc ann
      rp2ts Nothing = "true"
      rp2ts (Just bsr) = bsr2ts bsr
  
      bsr2ts :: BoolStructR -> Doc ann
  
      bsr2ts (Leaf (RPConstraint mt1 rprel mt2) ) =
        let rp = mt2objStr mt1
        in -- "// vpToTS case 1" <> line <>
           -- "// GIVENs in the original rule" <> viaShow (given <$> origRule) <> line <>
           [di|#{rp} #{renderRPrel rprel :: Doc ann} #{handleEnum [last mt1] mt2}|]
  
      -- special case, but we need to handle the general case better below
      bsr2ts (Leaf (RPnary RPis [RPMT mt1, RPMT mt2])) =
        let rp = mt2objStr mt1
        in "// vpToTS case 2" <> line <>
           pretty rp <+> renderRPrel RPis <+> handleEnum [last mt1] mt2
  
      -- general case not fully handled WRT enums.
      -- to properly do this we need to walk the type graph to figure out for ceratin
      -- what type the attribute has.
      bsr2ts (Leaf (RPnary RPis [RPMT mt1, rp2])) =
        let rp = mt2objStr mt1
        in "// vpToTS case 3" <> line <>
           pretty rp <+> renderRPrel RPis <+> pretty (rp2text rp2)
  
      bsr2ts (Leaf (RPnary RPsum rps)) = "sum" <> list (pretty . rp2text <$> rps)
      bsr2ts (Any pp rps) = "any" <> parens (list (bsr2ts <$> rps))
      bsr2ts (All pp rps) = "all" <> parens (list (bsr2ts <$> rps))
      bsr2ts (Not rp) = "not" <> parens (bsr2ts rp)
      bsr2ts _ = trace "bsr2ts needs more cases" ""
  
      mt2objStr mt =
        let (outStr, outLog) = xpLog $ toObjectStr mt
        in case outStr of
             Left err -> [i|// error encountered in bsr2ts conversion of ValuePredicate to Typescript code: #{err}|]
             Right rp -> T.unpack rp
  
      renderRPrel RPis = "=="
      renderRPrel RPgt = ">"
      renderRPrel RPlt = "<"
      renderRPrel _    = trace "add a renderRPrel in Typescript.hs" ""
  
      -- upgrade a value Monday to a DaysEnum.Monday
      handleEnum :: MultiTerm -> MultiTerm -> Doc ann
      handleEnum mt1 mt2
        | isAnEnum l4i (given =<< origRule) mt1 =
          [i|#{snake_case mt1}Enum.#{snake_case mt2}|]
        | otherwise = pretty $ mt2text mt2
  
  -- | top-level DEFINEs, going rule by rule
  -- just the facts.
  globalDefinitions :: Interpreted -> XPileLog (Doc ann)
  globalDefinitions l4i = pure $
    vvsep [ "const" <+> dumpNestedClass l4i f
          | f <- globalFacts l4i
          ]
  
  -- | What's going on with nested classes?
  -- See the discussion in localvars.org
  
  dumpNestedClass :: Interpreted -> DT.Tree ParamText -> Doc ann
  dumpNestedClass l4i (DT.Node pt children)
    | not $ null children =
      -- "// pt:"       <+> viaShow pt <//>
      -- "// children:" <+> viaShow children <//>
      pretty (PT5 pt l4i) <> nest 2 (encloseSep (line <> lbrace <> space) (line <> rbrace) (comma <> space) ( dumpNestedClass l4i <$> children ))
    | otherwise =
      -- "// pt:"       <+> viaShow pt <//>
      pretty (PT4 pt l4i)
  
  -- | convert the decision logic into functions that do the right thing.
  --
  -- There are several sources of decision logic. In the L4 source, we have a ruleset of multiple rules, some of which are DECIDE rules.
  --
  -- The Interpreter contains a bunch of gnarly code that tries to do expansion of one boolstruct into another.
  --
  -- Going beyond the propositional domain, however, it becomes difficult to force everything to be inlined. This is why named variables and let bindings were invented!
  --
  -- Now, let us assume we have a mathlang-like DSL of some sort that is implemented in something like Typescript or Purescript, which allows traced evaluation.
  --
  -- Our task now is to organize the rulesets into a graph of computations and data flow; then we compile that graph down to the evaluation DSL.
  --
  -- We have a couple candidates for the mathlang DSL:
  -- - https://jsonlogic.com/operations.html
  -- - our own mathlang
  -- - raw Typescript
  --
  -- Let's try the raw Typescript approach first, so we can be sure we know what we're doing.
  --
  -- We'll bypass most of what is provided by the Interpreter, and work with the raw rules themselves.
  --
  -- We might rely on the Interpreter to provide some of the data flow graphing, so we can trace variable expansion.
  --
  -- In this initial experiment, we do not implement scoped variables. All variables live in global scope.
  --
  -- Without further ado: [TODO] this is a work in progress
  hc2ts :: Interpreted -> HornClause2 -> Doc ann
  hc2ts _l4i  hc2@HC { hHead = RPMT        _ }                 = "value" <+> colon <+> dquotes (pretty (hHead hc2))
  hc2ts _l4i _hc2@HC { hHead = RPConstraint  mt1 _rprel mt2 }  = snake_case mt1 <+> colon <+> dquotes (snake_case mt2) <+> "// hc2ts RPConstraint"
  hc2ts _l4i _hc2@HC { hHead = RPBoolStructR mt1 _rprel _bsr } = snake_case mt1 <+> colon <+> "(some => lambda)"
  hc2ts  l4i _hc2@HC { hHead = RPParamText pt }                = pretty (PT4 pt l4i) <+> "// hc2ts RPParamText"
  hc2ts  l4i  hc2@HC { hHead = RPnary      _rprel [] }         = error "TypeScript: headless RPnary encountered"
  hc2ts  l4i  hc2@HC { hHead = RPnary      _rprel rps }        = hc2ts l4i hc2 {hHead = head rps} <+> "// hc2ts RPnary"
  
  -- | for debugging at the moment only.
  toPlainTS :: Interpreted -> XPileLog (Doc ann)
  toPlainTS l4i = do
    pure $ vvsep [ [di|// #{valpred}|]
                   | valpred <- valuePreds l4i
                   ]
  
  toJsonLogic :: Interpreted -> XPileLog (Doc ann)
  toJsonLogic l4i = do
    mutterd 1 "toJsonLogic"
    -- 2 + 2
    varData <- case decode "{ \"foo\" : \"bar\" }" of
                 Ok jsVarData -> pure jsVarData
                 Error err    -> mutterd 2 "error while decoding" >> mutter err
  
    pure [di|const varData = #{encode varData}|]
  
  -- dump only the paramtexts
  asValuePT :: Interpreted -> [HornClause2] -> [Doc ann]
  asValuePT l4i hc2s = -- trace ("asValuePT: " <> show hc2s) $
    [ [di|// hc2s: #{hc2s} $ PT4 pt l4i|]
    | HC { hHead = RPParamText pt } <- hc2s ]
  