{-# LANGUAGE RankNTypes #-}

module Typst.Bind (destructuringBind) where

import Control.Monad.State
import qualified Data.Map.Ordered as OM
import Data.Maybe (fromMaybe)
import qualified Data.Vector as V
import Typst.Syntax
import Typst.Types

destructuringBind ::
  Monad m =>
  (forall m'. Monad m' => Identifier -> Val -> MP m' ()) ->
  [BindPart] ->
  Val ->
  MP m ()
destructuringBind setIdentifier parts val = do
  let isSink Sink {} = True
      isSink _ = False
  let (fronts, rest) = break isSink parts
  let (sinks, backs) = span isSink rest
  mbsink <- case sinks of
    [Sink s] -> pure s
    [] -> pure Nothing
    _ -> fail "Bind cannot contain multiple sinks"
  case val of
    VDict m ->
      evalStateT (destructureDict setIdentifier fronts backs mbsink) m
    VArray v ->
      evalStateT (destructureArray setIdentifier fronts backs mbsink) v
    _ -> fail "Only Array or Dictionary values can be destructured"

destructureDict ::
  Monad m =>
  (forall m'. Monad m' => Identifier -> Val -> MP m' ()) ->
  [BindPart] ->
  [BindPart] ->
  Maybe Identifier ->
  StateT (OM.OMap Identifier Val) (MP m) ()
destructureDict setIdentifier fronts backs mbsink = do
  mapM_ handleDictBind (fronts ++ backs)
  case mbsink of
    Just i -> get >>= lift . setIdentifier i . VDict
    Nothing -> pure ()
  where
    handleDictBind :: Monad m => BindPart -> StateT (OM.OMap Identifier Val) (MP m) ()
    handleDictBind (Sink {}) = fail "Bind cannot contain multiple sinks"
    handleDictBind (Simple Nothing) = pure ()
    handleDictBind (Simple (Just i)) = do
      m <- get
      case OM.lookup i m of
        Nothing ->
          fail $ "Destructuring key not found in dictionary: " <> show i
        Just v -> do
          put $ OM.delete i m
          lift $ setIdentifier i v
    handleDictBind (WithKey key mbident) = do
      m <- get
      case OM.lookup key m of
        Nothing ->
          fail $ "Destructuring key not found in dictionary: " <> show key
        Just v -> do
          put $ OM.delete key m
          lift $ setIdentifier (fromMaybe key mbident) v

destructureArray ::
  Monad m =>
  (forall m'. Monad m' => Identifier -> Val -> MP m' ()) ->
  [BindPart] ->
  [BindPart] ->
  Maybe Identifier ->
  StateT (V.Vector Val) (MP m) ()
destructureArray setIdentifier fronts backs mbsink = do
  mapM_ handleFrontBind fronts
  mapM_ handleBackBind (reverse backs)
  case mbsink of
    Just i -> get >>= lift . setIdentifier i . VArray
    Nothing -> pure ()
  where
    handleFrontBind :: Monad m => BindPart -> StateT (V.Vector Val) (MP m) ()
    handleFrontBind (Sink {}) = fail "Bind cannot contain multiple sinks"
    handleFrontBind (WithKey {}) = fail "Cannot destructure array with key"
    handleFrontBind (Simple mbi) = do
      v <- get
      case V.uncons v of
        Nothing -> fail "Array does not contain enough elements to destructure"
        Just (x, v') -> do
          put v'
          case mbi of
            Nothing -> pure ()
            Just i -> lift $ setIdentifier i x

    handleBackBind :: Monad m => BindPart -> StateT (V.Vector Val) (MP m) ()
    handleBackBind (Sink {}) = fail "Bind cannot contain multiple sinks"
    handleBackBind (WithKey {}) = fail "Cannot destructure array with key"
    handleBackBind (Simple mbi) = do
      v <- get
      case V.unsnoc v of
        Nothing -> fail "Array does not contain enough elements to destructure"
        Just (v', x) -> do
          put v'
          case mbi of
            Nothing -> pure ()
            Just i -> lift $ setIdentifier i x
