content: {-# LANGUAGE TemplateHaskell #-}
  -- For constraints on "append"
  {-# OPTIONS_GHC -Wno-redundant-constraints #-}
  
  #if defined(IS_WINDOWS)
  #define OS_NAME Windows
  #define OS_PATH WindowsPath
  #else
  #define OS_NAME Posix
  #define OS_PATH PosixPath
  #endif
  
  -- |
  -- Module      : Streamly.Internal.FileSystem.OS_PATH.Node
  -- Copyright   : (c) 2023 Composewell Technologies
  -- License     : BSD3
  -- Maintainer  : streamly@composewell.com
  -- Portability : GHC
  --
  -- This module provides a type safe path append operation by distinguishing
  -- paths between files and directories. Files are represented by the @File
  -- OS_PATH@ type and directories are represented by the @Dir OS_PATH@ type.
  --
  -- This distinction provides safety against appending a path to a file. Append
  -- operation allows appending to only 'Dir' types.
  --
  module Streamly.Internal.FileSystem.OS_PATH.Node
      (
      -- * Types
        File (..)
      , Dir (..)
      , IsNode
  
      -- * Statically Verified Path Literals
      -- | Quasiquoters.
      , dir
      , file
  
      -- * Statically Verified Path Strings
      -- | Template Haskell expression splices.
      , dirE
      , fileE
  
      -- * Operations
      , join
      )
  where
  
  import Control.Monad ((>=>))
  import Language.Haskell.TH (Q, Exp)
  import Language.Haskell.TH.Syntax (lift)
  import Language.Haskell.TH.Quote (QuasiQuoter)
  import Streamly.Internal.Data.Path (IsPath(..))
  import Streamly.Internal.FileSystem.Path.Common (OS(..), mkQ)
  import Streamly.Internal.FileSystem.OS_PATH (OS_PATH(..))
  
  import qualified Streamly.Internal.FileSystem.Path.Common as Common
  import qualified Streamly.Internal.FileSystem.OS_PATH as OsPath
  
  {- $setup
  >>> :m
  >>> :set -XQuasiQuotes
  
  For APIs that have not been released yet.
  
  >>> import Streamly.Internal.FileSystem.PosixPath (PosixPath)
  >>> import Streamly.Internal.FileSystem.PosixPath.Node (File, Dir, file, dir)
  >>> import qualified Streamly.Internal.FileSystem.PosixPath as Path
  >>> import qualified Streamly.Internal.FileSystem.PosixPath.Node as Node
  -}
  
  newtype File a = File a
  newtype Dir a = Dir a
  
  -- | Constraint to check if a type uses 'File' or 'Dir' as the outermost
  -- constructor.
  class IsNode a
  
  instance IsNode (File a)
  instance IsNode (Dir a)
  
  instance IsPath OS_PATH (File OS_PATH) where
      unsafeFromPath = File
  
      fromPath p@(OS_PATH arr) = do
          !_ <- Common.validateFile OS_NAME arr
          pure $ File p
  
      toPath (File p) = p
  
  instance IsPath OS_PATH (Dir OS_PATH) where
      unsafeFromPath = Dir
      fromPath p = pure (Dir p)
      toPath (Dir p) = p
  
  ------------------------------------------------------------------------------
  -- Statically Verified Strings
  ------------------------------------------------------------------------------
  
  -- XXX We can lift the array directly, ByteArray has a lift instance. Does that
  -- work better?
  
  liftDir :: Dir OS_PATH -> Q Exp
  liftDir (Dir p) =
      [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Dir OS_PATH |]
  
  liftFile :: File OS_PATH -> Q Exp
  liftFile (File p) =
      [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: File OS_PATH |]
  
  -- | Generates a Haskell expression of type @Dir OS_PATH@.
  --
  dirE :: String -> Q Exp
  dirE = either (error . show) liftDir . (OsPath.fromString >=> fromPath)
  
  -- | Generates a Haskell expression of type @File OS_PATH@.
  --
  fileE :: String -> Q Exp
  fileE = either (error . show) liftFile . (OsPath.fromString >=> fromPath)
  
  ------------------------------------------------------------------------------
  -- Statically Verified Literals
  ------------------------------------------------------------------------------
  
  -- XXX Define folds or parsers to parse the paths.
  -- XXX Build these on top of the str quasiquoter so that we get interpolation
  -- for free. Interpolated vars if any have to be of appropriate type depending
  -- on the context so that we can splice them safely.
  
  -- | Generates a @Dir OS_PATH@ type from a quoted literal.
  --
  -- >>> Path.toString (Path.toPath ([dir|usr|] :: Dir PosixPath))
  -- "usr"
  --
  dir :: QuasiQuoter
  dir = mkQ dirE
  
  -- | Generates a @File OS_PATH@ type from a quoted literal.
  --
  -- >>> Path.toString (Path.toPath ([file|usr|] :: File PosixPath))
  -- "usr"
  --
  file :: QuasiQuoter
  file = mkQ fileE
  
  -- The only safety we need for paths is: (1) The first path can only be a Dir
  -- type path, and (2) second path can only be a Seg path.
  
  -- | Append a 'Dir' or 'File' path to a 'Dir' path.
  --
  -- >>> Path.toString (Path.toPath (Node.join [dir|/usr|] [dir|bin|] :: Dir PosixPath))
  -- "/usr/bin"
  -- >>> Path.toString (Path.toPath (Node.join [dir|/usr|] [file|bin|] :: File PosixPath))
  -- "/usr/bin"
  --
  -- Fails if the second path is a specific location and not a path segment.
  --
  {-# INLINE join #-}
  join :: (IsPath OS_PATH (a OS_PATH), IsNode (a OS_PATH)) =>
      Dir OS_PATH -> a OS_PATH -> a OS_PATH
  join (Dir a) b =
      unsafeFromPath $ OsPath.unsafeJoin (toPath a) (toPath b)
  