content: module Axel.Eff.Error where
  
  import Axel.Prelude
  
  import Axel.Parse.AST (getAnn, toAxel)
  import qualified Axel.Sourcemap as SM
  import Axel.Utils.Text (Renderer)
  
  import Control.Monad ((>=>))
  
  import qualified Data.Text as T
  
  import qualified Effectful as Eff
  import qualified Effectful.Error.Static as Eff
  
  data Error where
    ConvertError :: FilePath -> Text -> Error
    MacroError :: FilePath -> SM.Expression -> Text -> Error
    NormalizeError :: FilePath -> Text -> [SM.Expression] -> Error
    ParseError :: FilePath -> Text -> Error
    ProjectError :: Text -> Error
  
  mkFileErrorMsg :: FilePath -> Text -> Text
  mkFileErrorMsg (FilePath filePath) err =
    "While compiling '" <> filePath <> "':\n\n" <> err
  
  renderErrorContext :: SM.Expression -> Text
  renderErrorContext expr =
    let sourcePosition =
          case getAnn expr of
            Just sp -> ", at " <> SM.renderSourcePosition sp
            Nothing -> ""
     in toAxel expr <> sourcePosition
  
  renderError :: Renderer Error
  renderError (ConvertError filePath err) = mkFileErrorMsg filePath err
  renderError (MacroError filePath ctxt err) =
    mkFileErrorMsg filePath $
    "While expanding " <> renderErrorContext ctxt <> ":\n\n" <> err
  renderError (NormalizeError filePath err context) =
    mkFileErrorMsg filePath $
    err <> "\n\n" <> "Context:\n" <> T.unlines (map renderErrorContext context)
  renderError (ParseError filePath err) = mkFileErrorMsg filePath err
  renderError (ProjectError err) = err
  
  -- | This should ONLY be used for truly impossible situations.
  fatal :: Text -> Text -> a
  fatal context message = error $ "[FATAL] " <> context <> " - " <> message
  
  unsafeRunError ::
       Renderer e -> Eff.Eff (Eff.Error e ': effs) a -> Eff.Eff effs a
  unsafeRunError render =
    Eff.runErrorNoCallStack >=> either (errorWithoutStackTrace . render) pure -- TODO Don't(?) use `error(WithoutStackTrace)` directly
  