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
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: JobConfig -> JobConfig -> Bool
$c/= :: JobConfig -> JobConfig -> Bool
== :: JobConfig -> JobConfig -> Bool
$c== :: JobConfig -> JobConfig -> Bool
Eq, Int -> JobConfig -> ShowS
[JobConfig] -> ShowS
JobConfig -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [JobConfig] -> ShowS
$cshowList :: [JobConfig] -> ShowS
show :: JobConfig -> String
$cshow :: JobConfig -> String
showsPrec :: Int -> JobConfig -> ShowS
$cshowsPrec :: Int -> 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 forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO Bool
Directory.doesFileExist (forall a b. ConvertibleStrings a b => a -> b
cs forall a b. (a -> b) -> a -> b
$ Text
applicationName forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs")
    if forall mono. MonoFoldable mono => mono -> Bool
null Text
jobName
        then forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a b. a -> Either a b
Left Text
"Job name cannot be empty"
        else do
            let jobConfig :: JobConfig
jobConfig = JobConfig
                    { Text
applicationName :: Text
$sel:applicationName:JobConfig :: Text
applicationName
                    , $sel:tableName:JobConfig :: Text
tableName = Text
jobName
                    , $sel:modelName:JobConfig :: Text
modelName = Text -> Text
tableNameToModelName Text
jobName
                    , Bool
isFirstJobInApplication :: Bool
$sel:isFirstJobInApplication:JobConfig :: Bool
isFirstJobInApplication
                    }
            forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a b. b -> Either a b
Right 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 =
    forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config forall a. Semigroup a => a -> a -> a
<> Text
".Job." forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
unqualifiedJobModuleName JobConfig
config

unqualifiedJobModuleName :: JobConfig -> Text
unqualifiedJobModuleName :: JobConfig -> Text
unqualifiedJobModuleName JobConfig
config = Text -> Text -> Text -> Text
Text.replace Text
"Job" Text
"" (forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "modelName" a => a
#modelName JobConfig
config)

buildPlan' :: JobConfig -> [GeneratorAction]
buildPlan' :: JobConfig -> [GeneratorAction]
buildPlan' JobConfig
config =
        let
            name :: Text
name = forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "modelName" a => a
#modelName JobConfig
config
            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 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 Text -> Text -> Text -> Text
Text.replace Text
"Job" Text
"" Text
name
                else Text
name --e.g. "TestJob" -> "Test""

            job :: Text
job =
                Text
""
                forall a. Semigroup a => a -> a -> a
<> Text
"module " forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
qualifiedJobModuleName JobConfig
config forall a. Semigroup a => a -> a -> a
<> Text
" where\n"
                forall a. Semigroup a => a -> a -> a
<> Text
"import " forall a. Semigroup a => a -> a -> a
<> forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config forall a. Semigroup a => a -> a -> a
<> Text
".Controller.Prelude\n"
                forall a. Semigroup a => a -> a -> a
<> Text
"\n"
                forall a. Semigroup a => a -> a -> a
<> Text
"instance Job " forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix forall a. Semigroup a => a -> a -> a
<> Text
" where\n"
                forall a. Semigroup a => a -> a -> a
<> Text
"    perform " forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix forall a. Semigroup a => a -> a -> a
<> Text
" { .. } = do\n"
                forall a. Semigroup a => a -> a -> a
<> Text
"        putStrLn \"Hello World!\"\n"

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


            emptyWorkerHs :: Text
            emptyWorkerHs :: Text
emptyWorkerHs =
                        let
                            applicationName :: Text
applicationName = forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config
                        in 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 = forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config 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 = forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config forall a. Semigroup a => a -> a -> a
<> Text
"/Job/" forall a. Semigroup a => a -> a -> a
<> Text
nameWithoutSuffix forall a. Semigroup a => a -> a -> a
<> Text
".hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
job }
            ]
            forall a. Semigroup a => a -> a -> a
<> if forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "isFirstJobInApplication" a => a
#isFirstJobInApplication JobConfig
config
                    then [ CreateFile { $sel:filePath:CreateFile :: Text
filePath = forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
emptyWorkerHs } ]
                    else
                        [ AddImport { $sel:filePath:CreateFile :: Text
filePath = forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
"import " 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 = forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get forall a. IsLabel "applicationName" a => a
#applicationName JobConfig
config forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
"        , worker @" forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix }
                        ]