{- |
Module                  : Summoner.Source
Copyright               : (c) 2017-2026 Kowainik
SPDX-License-Identifier : MPL-2.0
Maintainer              : Kowainik <xrom.xkov@gmail.com>
Stability               : Stable
Portability             : Portable

This module contains the 'Source' data that describes how to fetch custom files.
-}

module Summoner.Source
       ( Source (..)
       , sourceCodec
       , fetchSources
       , fetchSource
       ) where

import Colourista (errorMessage, infoMessage)
import Control.Exception (catch)
import System.Process (readProcess)
import Toml (TomlBiMapError (..), TomlCodec)

import Summoner.Mode (ConnectMode (..), isOffline)
import Summoner.Tree (TreeFs, pathToTree)

import qualified Data.Map.Strict as Map
import qualified Toml


-- | Type of the source resource.
data Source
    {- | URL link to the source file. Such files will be downloaded by URL. But
    they are ingored in the @offline@ mode.
    -}
    = Url !Text

    {- | File path to the local source file.
    -}
    | Local !FilePath

    {- | Raw file text content.
    -}
    | Raw !Text
    deriving stock (Show, Eq)

showSource :: Source -> Text
showSource = \case
    Url _ -> "Url"
    Local _ -> "Local"
    Raw _ -> "Raw"

-- TODO: return Maybe
matchUrl :: Source -> Either TomlBiMapError Text
matchUrl (Url url) = Right url
matchUrl e         = Left $ WrongConstructor "Url" $ showSource e

-- TODO: return Maybe
matchLocal :: Source -> Either TomlBiMapError FilePath
matchLocal (Local file) = Right file
matchLocal e            = Left $ WrongConstructor "Local" $ showSource e

-- TODO: return Maybe
matchRaw :: Source -> Either TomlBiMapError Text
matchRaw (Raw raw) = Right raw
matchRaw e         = Left $ WrongConstructor "Raw" $ showSource e

{- | This 'TomlCodec' is used in the @files@ field of config. It decodes
corresponding constructor from the top-level key.
-}
sourceCodec :: TomlCodec Source
sourceCodec = asum
    [ Toml.dimatch (rightToMaybe . matchUrl) Url (Toml.text "url")
    , Toml.dimatch (rightToMaybe . matchLocal) Local (Toml.string "local")
    , Toml.dimatch (rightToMaybe . matchRaw) Raw (Toml.text "raw")
    ]

{- | This function fetches contents of extra file sources.
-}
fetchSources :: ConnectMode -> Map FilePath Source -> IO [TreeFs]
fetchSources connectMode = mapMaybeM sourceToTree . Map.toList
  where
    sourceToTree :: (FilePath, Source) -> IO (Maybe TreeFs)
    sourceToTree (path, source) = do
        infoMessage $ "Fetching content of the extra file: " <> toText path
        fetchSource connectMode source >>= \case
            Nothing -> Nothing <$ errorMessage ("Error fetching: " <> toText path)
            Just content -> pure $ Just $ pathToTree path content

{- | Fetches content of the given extra file source.
Doesn't fetch 'Url' if the 'ConnectMode' is 'Offline'.
-}
fetchSource :: ConnectMode -> Source -> IO (Maybe Text)
fetchSource connectMode = \case
    Local path -> catch (Just . decodeUtf8 <$> readFileBS path) (localError path)
    Url url -> if isOffline connectMode
        then Nothing <$ infoMessage ("Ignoring fetching from URL in offline mode from source: " <> url)
        else fetchUrl url `catch` urlError url
    Raw raw -> pure $ Just raw
  where
    localError :: FilePath -> SomeException -> IO (Maybe Text)
    localError path err = do
        errorMessage ("Couldn't read file: " <> toText path)
        errorMessage $ toText $ displayException err
        pure Nothing

    urlError :: Text -> SomeException -> IO (Maybe Text)
    urlError url err = do
        errorMessage ("Couldn't get to link: " <> url)
        errorMessage $ toText $ displayException err
        pure Nothing

    fetchUrl :: Text -> IO (Maybe Text)
    fetchUrl url = Just . toText <$> readProcess "curl" [toString url, "--silent", "--fail"] ""
