-- |
-- Module      : Data.Ollama.List
-- Copyright   : (c) 2025 Tushar Adhatrao
-- License     : MIT
-- Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
-- Stability   : experimental
-- Description : Functionality for listing available models in the Ollama client.
--
-- This module provides functions to retrieve a list of models available on the Ollama server.
-- It includes both an IO-based function ('list') and a monadic version ('listM') for use in
-- 'MonadIO' contexts. The list operation is performed via a GET request to the "/api//tags" endpoint,
-- returning a 'Models' type containing a list of 'ModelInfo' records with details about each model.
--
-- Example:
--
-- >>> list Nothing
-- Right (Models [ModelInfo ...])
--
{-# LANGUAGE OverloadedStrings #-}

module Data.Ollama.List
  ( -- * List Models API
    list,
    listM,

    -- * Model Types
    Models (..),
    ModelInfo (..)
  ) where

import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.Aeson
import Data.Ollama.Common.Config (OllamaConfig)
import Data.Ollama.Common.Error (OllamaError)
import Data.Ollama.Common.Types as CT
import Data.Ollama.Common.Utils as CU
import Data.Text (Text)
import Data.Time
import GHC.Int (Int64)

-- | A wrapper type containing a list of available models.
--
newtype Models = Models [ModelInfo]
  -- ^ List of 'ModelInfo' records describing available models.
  deriving (Models -> Models -> Bool
(Models -> Models -> Bool)
-> (Models -> Models -> Bool) -> Eq Models
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Models -> Models -> Bool
== :: Models -> Models -> Bool
$c/= :: Models -> Models -> Bool
/= :: Models -> Models -> Bool
Eq, Int -> Models -> ShowS
[Models] -> ShowS
Models -> String
(Int -> Models -> ShowS)
-> (Models -> String) -> ([Models] -> ShowS) -> Show Models
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Models -> ShowS
showsPrec :: Int -> Models -> ShowS
$cshow :: Models -> String
show :: Models -> String
$cshowList :: [Models] -> ShowS
showList :: [Models] -> ShowS
Show)

-- | Details about a specific model.
--
data ModelInfo = ModelInfo
  { ModelInfo -> Text
name :: !Text
    -- ^ The name of the model.
  , ModelInfo -> UTCTime
modifiedAt :: !UTCTime
    -- ^ The timestamp when the model was last modified.
  , ModelInfo -> Int64
size :: !Int64
    -- ^ The size of the model in bytes.
  , ModelInfo -> Text
digest :: !Text
    -- ^ The digest (hash) of the model.
  , ModelInfo -> ModelDetails
details :: !ModelDetails
    -- ^ Additional details about the model (e.g., format, family, parameters).
  } deriving (ModelInfo -> ModelInfo -> Bool
(ModelInfo -> ModelInfo -> Bool)
-> (ModelInfo -> ModelInfo -> Bool) -> Eq ModelInfo
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ModelInfo -> ModelInfo -> Bool
== :: ModelInfo -> ModelInfo -> Bool
$c/= :: ModelInfo -> ModelInfo -> Bool
/= :: ModelInfo -> ModelInfo -> Bool
Eq, Int -> ModelInfo -> ShowS
[ModelInfo] -> ShowS
ModelInfo -> String
(Int -> ModelInfo -> ShowS)
-> (ModelInfo -> String)
-> ([ModelInfo] -> ShowS)
-> Show ModelInfo
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ModelInfo -> ShowS
showsPrec :: Int -> ModelInfo -> ShowS
$cshow :: ModelInfo -> String
show :: ModelInfo -> String
$cshowList :: [ModelInfo] -> ShowS
showList :: [ModelInfo] -> ShowS
Show)

-- | JSON parsing instance for 'Models'.
instance FromJSON Models where
  parseJSON :: Value -> Parser Models
parseJSON = String -> (Object -> Parser Models) -> Value -> Parser Models
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Models" ((Object -> Parser Models) -> Value -> Parser Models)
-> (Object -> Parser Models) -> Value -> Parser Models
forall a b. (a -> b) -> a -> b
$ \Object
v -> [ModelInfo] -> Models
Models ([ModelInfo] -> Models) -> Parser [ModelInfo] -> Parser Models
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
v Object -> Key -> Parser [ModelInfo]
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"models"

-- | JSON parsing instance for 'ModelInfo'.
instance FromJSON ModelInfo where
  parseJSON :: Value -> Parser ModelInfo
parseJSON = String -> (Object -> Parser ModelInfo) -> Value -> Parser ModelInfo
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"ModelInfo" ((Object -> Parser ModelInfo) -> Value -> Parser ModelInfo)
-> (Object -> Parser ModelInfo) -> Value -> Parser ModelInfo
forall a b. (a -> b) -> a -> b
$ \Object
v ->
    Text -> UTCTime -> Int64 -> Text -> ModelDetails -> ModelInfo
ModelInfo
      (Text -> UTCTime -> Int64 -> Text -> ModelDetails -> ModelInfo)
-> Parser Text
-> Parser (UTCTime -> Int64 -> Text -> ModelDetails -> ModelInfo)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
v Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"name"
      Parser (UTCTime -> Int64 -> Text -> ModelDetails -> ModelInfo)
-> Parser UTCTime
-> Parser (Int64 -> Text -> ModelDetails -> ModelInfo)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
v Object -> Key -> Parser UTCTime
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"modified_at"
      Parser (Int64 -> Text -> ModelDetails -> ModelInfo)
-> Parser Int64 -> Parser (Text -> ModelDetails -> ModelInfo)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
v Object -> Key -> Parser Int64
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"size"
      Parser (Text -> ModelDetails -> ModelInfo)
-> Parser Text -> Parser (ModelDetails -> ModelInfo)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
v Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"digest"
      Parser (ModelDetails -> ModelInfo)
-> Parser ModelDetails -> Parser ModelInfo
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
v Object -> Key -> Parser ModelDetails
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"details"

-- | Retrieves a list of available models from the Ollama server.
--
-- Sends a GET request to the "/api//tags" endpoint to fetch the list of models.
-- Returns 'Right' with a 'Models' containing the list of 'ModelInfo' on success,
-- or 'Left' with an 'OllamaError' on failure.
list ::
  Maybe OllamaConfig -> -- ^ Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig' if 'Nothing')
  IO (Either OllamaError Models)
list :: Maybe OllamaConfig -> IO (Either OllamaError Models)
list Maybe OllamaConfig
mbConfig = do
  Text
-> ByteString
-> Maybe Value
-> Maybe OllamaConfig
-> (Response BodyReader -> IO (Either OllamaError Models))
-> IO (Either OllamaError Models)
forall payload response.
ToJSON payload =>
Text
-> ByteString
-> Maybe payload
-> Maybe OllamaConfig
-> (Response BodyReader -> IO (Either OllamaError response))
-> IO (Either OllamaError response)
withOllamaRequest
    Text
"/api//tags"
    ByteString
"GET"
    (Maybe Value
forall a. Maybe a
Nothing :: Maybe Value)
    Maybe OllamaConfig
mbConfig
    Response BodyReader -> IO (Either OllamaError Models)
forall a.
FromJSON a =>
Response BodyReader -> IO (Either OllamaError a)
commonNonStreamingHandler

-- | MonadIO version of 'list' for use in monadic contexts.
--
-- Lifts the 'list' function into a 'MonadIO' context, allowing it to be used in monadic computations.
--
-- Example:
--
-- >>> import Control.Monad.IO.Class
-- >>> runReaderT (listM Nothing) someContext
-- Right (Models [ModelInfo ...])
--
listM :: MonadIO m => Maybe OllamaConfig -> m (Either OllamaError Models)
listM :: forall (m :: * -> *).
MonadIO m =>
Maybe OllamaConfig -> m (Either OllamaError Models)
listM Maybe OllamaConfig
mbCfg = IO (Either OllamaError Models) -> m (Either OllamaError Models)
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either OllamaError Models) -> m (Either OllamaError Models))
-> IO (Either OllamaError Models) -> m (Either OllamaError Models)
forall a b. (a -> b) -> a -> b
$ Maybe OllamaConfig -> IO (Either OllamaError Models)
list Maybe OllamaConfig
mbCfg