module IHP.IDE.CodeGen.JobGenerator (buildPlan, buildPlan', JobConfig (..)) where

import IHP.Prelude
import qualified Data.Text as Text
import IHP.IDE.CodeGen.Types
import qualified System.Directory as Directory

data JobConfig = JobConfig
    { JobConfig -> Text
applicationName :: Text
    , JobConfig -> Text
tableName :: Text -- E.g. create_container_jobs
    , JobConfig -> Text
modelName :: Text -- E.g. CreateContainerJob
    , JobConfig -> Bool
isFirstJobInApplication :: Bool -- If true, creates Worker.hs in application directory
    } deriving (JobConfig -> JobConfig -> Bool
(JobConfig -> JobConfig -> Bool)
-> (JobConfig -> JobConfig -> Bool) -> Eq JobConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: JobConfig -> JobConfig -> Bool
== :: JobConfig -> JobConfig -> Bool
$c/= :: JobConfig -> JobConfig -> Bool
/= :: JobConfig -> JobConfig -> Bool
Eq, Int -> JobConfig -> ShowS
[JobConfig] -> ShowS
JobConfig -> String
(Int -> JobConfig -> ShowS)
-> (JobConfig -> String)
-> ([JobConfig] -> ShowS)
-> Show JobConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> JobConfig -> ShowS
showsPrec :: Int -> JobConfig -> ShowS
$cshow :: JobConfig -> String
show :: JobConfig -> String
$cshowList :: [JobConfig] -> ShowS
showList :: [JobConfig] -> ShowS
Show)

buildPlan :: Text -> Text -> IO (Either Text [GeneratorAction])
buildPlan :: Text -> Text -> IO (Either Text [GeneratorAction])
buildPlan Text
jobName Text
applicationName = do
    Bool
isFirstJobInApplication <- Bool -> Bool
not (Bool -> Bool) -> IO Bool -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO Bool
Directory.doesFileExist (Text -> String
forall a b. ConvertibleStrings a b => a -> b
cs (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Text
applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs")
    if Text -> Bool
forall mono. MonoFoldable mono => mono -> Bool
null Text
jobName
        then Either Text [GeneratorAction] -> IO (Either Text [GeneratorAction])
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either Text [GeneratorAction]
 -> IO (Either Text [GeneratorAction]))
-> Either Text [GeneratorAction]
-> IO (Either Text [GeneratorAction])
forall a b. (a -> b) -> a -> b
$ Text -> Either Text [GeneratorAction]
forall a b. a -> Either a b
Left Text
"Job name cannot be empty"
        else do
            let jobConfig :: JobConfig
jobConfig = JobConfig
                    { Text
$sel:applicationName:JobConfig :: Text
applicationName :: Text
applicationName
                    , $sel:tableName:JobConfig :: Text
tableName = Text
jobName
                    , $sel:modelName:JobConfig :: Text
modelName = Text -> Text
tableNameToModelName Text
jobName
                    , Bool
$sel:isFirstJobInApplication:JobConfig :: Bool
isFirstJobInApplication :: Bool
isFirstJobInApplication
                    }
            Either Text [GeneratorAction] -> IO (Either Text [GeneratorAction])
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either Text [GeneratorAction]
 -> IO (Either Text [GeneratorAction]))
-> Either Text [GeneratorAction]
-> IO (Either Text [GeneratorAction])
forall a b. (a -> b) -> a -> b
$ [GeneratorAction] -> Either Text [GeneratorAction]
forall a b. b -> Either a b
Right ([GeneratorAction] -> Either Text [GeneratorAction])
-> [GeneratorAction] -> Either Text [GeneratorAction]
forall a b. (a -> b) -> a -> b
$ JobConfig -> [GeneratorAction]
buildPlan' JobConfig
jobConfig

-- E.g. qualifiedMailModuleName config "Confirmation" == "Web.Mail.Users.Confirmation"
qualifiedJobModuleName :: JobConfig -> Text
qualifiedJobModuleName :: JobConfig -> Text
qualifiedJobModuleName JobConfig
config =
    JobConfig
config.applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".Job." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
unqualifiedJobModuleName JobConfig
config

unqualifiedJobModuleName :: JobConfig -> Text
unqualifiedJobModuleName :: JobConfig -> Text
unqualifiedJobModuleName JobConfig
config = HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace Text
"Job" Text
"" (JobConfig
config.modelName)

buildPlan' :: JobConfig -> [GeneratorAction]
buildPlan' :: JobConfig -> [GeneratorAction]
buildPlan' JobConfig
config =
        let
            name :: Text
name = JobConfig
config.modelName
            tableName :: Text
tableName = Text -> Text
modelNameToTableName Text
nameWithSuffix
            nameWithSuffix :: Text
nameWithSuffix = if Text
"Job" Text -> Text -> Bool
`isSuffixOf` Text
name
                then Text
name
                else Text
name Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"Job" --e.g. "Test" -> "TestJob"
            nameWithoutSuffix :: Text
nameWithoutSuffix = if Text
"Job" Text -> Text -> Bool
`isSuffixOf` Text
name
                then HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace Text
"Job" Text
"" Text
name
                else Text
name --e.g. "TestJob" -> "Test""

            job :: Text
job =
                Text
""
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"module " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
qualifiedJobModuleName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" where\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"import " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> JobConfig
config.applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".Controller.Prelude\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"instance Job " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" where\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    perform " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" { .. } = do\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"        putStrLn \"Hello World!\"\n"

            schemaSql :: Text
schemaSql =
                Text
""
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"CREATE TABLE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    status JOB_STATUS DEFAULT 'job_status_not_started' NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    last_error TEXT DEFAULT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    attempts_count INT DEFAULT 0 NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    locked_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    locked_by UUID DEFAULT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    run_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
");\n"


            emptyWorkerHs :: Text
            emptyWorkerHs :: Text
emptyWorkerHs =
                        let
                            applicationName :: Text
applicationName = JobConfig
config.applicationName
                        in String -> Text
forall a b. ConvertibleStrings a b => a -> b
cs [plain|module #{applicationName}.Worker where

import IHP.Prelude
import #{applicationName}.Types
import Generated.Types
import IHP.Job.Runner
import IHP.Job.Types

import #{qualifiedJobModuleName config}

instance Worker #{applicationName}Application where
    workers _ =
        [ worker @#{nameWithSuffix}
        -- Generator Marker
        ]
|]
        in
            [ EnsureDirectory { $sel:directory:CreateFile :: Text
directory = JobConfig
config.applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Job" }
            , AppendToFile { $sel:filePath:CreateFile :: Text
filePath = Text
"Application/Schema.sql", $sel:fileContent:CreateFile :: Text
fileContent = Text
schemaSql }
            , CreateFile { $sel:filePath:CreateFile :: Text
filePath = JobConfig
config.applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Job/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithoutSuffix Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
job }
            ]
            [GeneratorAction] -> [GeneratorAction] -> [GeneratorAction]
forall a. Semigroup a => a -> a -> a
<> if JobConfig
config.isFirstJobInApplication
                    then [ CreateFile { $sel:filePath:CreateFile :: Text
filePath = JobConfig
config.applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
emptyWorkerHs } ]
                    else
                        [ AddImport { $sel:filePath:CreateFile :: Text
filePath = JobConfig
config.applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
"import " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
qualifiedJobModuleName JobConfig
config }
                        , AppendToMarker { $sel:marker:CreateFile :: Text
marker = Text
"-- Generator Marker", $sel:filePath:CreateFile :: Text
filePath = JobConfig
config.applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
"        , worker @" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix }
                        ]