content: -- | A crude implementation of the Nix store concept.
  --
  -- For anything fancier than this, it would be best to use FFI bindings instead,
  -- such as hercules-ci-cnix-store.
  module Nix.Diff.Store
    ( StorePath (..),
      toPhysicalPath,
      toText,
      doesFileExist,
      readFileUtf8Lenient,
    )
  where
  
  import Control.Monad ((<=<))
  import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
  import qualified Data.ByteString
  import Data.Data (Data)
  import Data.Functor ((<&>))
  import qualified Data.List as L
  import Data.Text (Text)
  import qualified Data.Text.Encoding
  import qualified Data.Text.Encoding.Error
  import qualified System.Directory as Directory
  import System.Environment (lookupEnv)
  import Test.QuickCheck (Arbitrary)
  import qualified Data.Text as T
  
  -- | A file path that may not exist on the true file system;
  -- needs to be looked up in a store, which may be relocated.
  --
  -- Unlike the (C++) Nix StorePath type, subpaths are allowed.
  newtype StorePath = StorePath
    { -- | If the store is relocated, its physical location is elsewhere, and this 'FilePath' won't resolve.
      -- Use 'toPhysicalPath'.
      unsafeStorePathFile :: FilePath
    }
    deriving (Data)
    deriving newtype (Show, Eq, Ord, ToJSON, FromJSON, ToJSONKey, FromJSONKey, Arbitrary)
  
  doesFileExist :: StorePath -> IO Bool
  doesFileExist =
    Directory.doesFileExist <=< toPhysicalPath
  
  readFileUtf8Lenient :: StorePath -> IO Text
  readFileUtf8Lenient sp = do
    file <- toPhysicalPath sp
    Data.Text.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode
      <$> Data.ByteString.readFile file
  
  toPhysicalPath :: StorePath -> IO FilePath
  toPhysicalPath (StorePath p) = do
    nixStoreDir <- lookupEnv "NIX_STORE_DIR" <&> maybe "/nix/store" stripSlash
    nixRemoteMaybe <- lookupEnv "NIX_REMOTE" <&> fmap stripSlash
    case nixRemoteMaybe of
      Just nixRemote | nixStoreDir `L.isPrefixOf` p -> do
        pure $ nixRemote <> "/" <> L.dropWhile (== '/') p
      _ -> pure p
  
  -- | Convert a 'StorePath' to a 'Text' for display purposes. The path may not exist at this physical location.
  toText :: StorePath -> Text
  toText (StorePath p) = T.pack p
  
  stripSlash :: FilePath -> FilePath
  stripSlash s | not ("/" `L.isSuffixOf` s) = s
  stripSlash s = doIt s
    where doIt = T.unpack . T.dropWhileEnd (== '/') . T.pack
  