module IHP.IDE.SchemaDesigner.Controller.Tables where

import IHP.ControllerPrelude
import IHP.IDE.ToolServer.Types

import IHP.IDE.SchemaDesigner.View.Tables.New
import IHP.IDE.SchemaDesigner.View.Tables.Show
import IHP.IDE.SchemaDesigner.View.Tables.Index
import IHP.IDE.SchemaDesigner.View.Tables.Edit

import IHP.IDE.SchemaDesigner.Types
import IHP.IDE.SchemaDesigner.View.Layout (findStatementByName, replace, schemaDesignerLayout)
import qualified IHP.SchemaCompiler as SchemaCompiler
import IHP.IDE.SchemaDesigner.Controller.Helper
import IHP.IDE.SchemaDesigner.Controller.Validation
import IHP.IDE.SchemaDesigner.Controller.Columns (updateForeignKeyConstraint)
import qualified IHP.IDE.SchemaDesigner.SchemaOperations as SchemaOperations

instance Controller TablesController where
    beforeAction :: IO ()
beforeAction = (?context::ControllerContext) =>
((?context::ControllerContext) => Layout) -> IO ()
((?context::ControllerContext) => Layout) -> IO ()
setLayout (?context::ControllerContext) => Layout
Html -> Html
schemaDesignerLayout

    action :: TablesController -> IO ()
action TablesController
TablesAction = do
        [Statement]
statements <- IO [Statement]
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
IO [Statement]
readSchema

        let tableNames :: [Text]
tableNames = [Statement]
statements [Statement] -> ([Statement] -> [Text]) -> [Text]
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> (Statement -> Maybe Text) -> [Statement] -> [Text]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe \case
                StatementCreateTable CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name } -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
name
                Statement
otherwise -> Maybe Text
forall a. Maybe a
Nothing

        case [Text] -> Maybe Text
forall a. [a] -> Maybe a
headMay [Text]
tableNames of
            Just Text
tableName -> TablesController -> IO ()
forall action.
(?context::ControllerContext, HasPath action) =>
action -> IO ()
redirectTo ShowTableAction :: Text -> TablesController
ShowTableAction { Text
$sel:tableName:TablesAction :: Text
tableName :: Text
tableName }
            Maybe Text
otherwise -> IndexView -> IO ()
forall view.
(View view, ?context::ControllerContext) =>
view -> IO ()
render IndexView :: [Statement] -> IndexView
IndexView { [Statement]
$sel:statements:IndexView :: [Statement]
statements :: [Statement]
.. }

    action ShowTableAction { Text
tableName :: Text
$sel:tableName:TablesAction :: TablesController -> Text
tableName } = do
        let name :: Text
name = Text
tableName
        [Statement]
statements <- IO [Statement]
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
IO [Statement]
readSchema
        let (Just Statement
table) = Text -> [Statement] -> Maybe Statement
forall (t :: * -> *).
Foldable t =>
Text -> t Statement -> Maybe Statement
findStatementByName Text
name [Statement]
statements
        let generatedHaskellCode :: Text
generatedHaskellCode = [Statement] -> Statement -> Text
SchemaCompiler.compileStatementPreview [Statement]
statements Statement
table
        ShowView -> IO ()
forall view.
(View view, ?context::ControllerContext) =>
view -> IO ()
render ShowView :: [Statement] -> Text -> Statement -> ShowView
ShowView { [Statement]
Text
Statement
$sel:table:ShowView :: Statement
$sel:name:ShowView :: Text
$sel:statements:ShowView :: [Statement]
table :: Statement
statements :: [Statement]
name :: Text
.. }

    action TablesController
NewTableAction = do
        [Statement]
statements <- IO [Statement]
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
IO [Statement]
readSchema
        NewTableView -> IO ()
forall view.
(View view, ?context::ControllerContext) =>
view -> IO ()
render NewTableView :: [Statement] -> NewTableView
NewTableView { [Statement]
$sel:statements:NewTableView :: [Statement]
statements :: [Statement]
.. }

    action TablesController
CreateTableAction = do
        [Statement]
statements <- IO [Statement]
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
IO [Statement]
readSchema
        let tableName :: Text
tableName = ByteString -> Text
forall valueType.
(?context::ControllerContext, ParamReader valueType) =>
ByteString -> valueType
param ByteString
"tableName"
        let validationResult :: ValidatorResult
validationResult = Text
tableName Text -> (Text -> ValidatorResult) -> ValidatorResult
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> [Statement] -> Maybe Text -> Text -> ValidatorResult
validateTable [Statement]
statements Maybe Text
forall a. Maybe a
Nothing
        case ValidatorResult
validationResult of
            Failure Text
message -> do
                (?context::ControllerContext) => Text -> IO ()
Text -> IO ()
setErrorMessage Text
message
                TablesController -> IO ()
forall action.
(?context::ControllerContext, HasPath action) =>
action -> IO ()
redirectTo TablesController
TablesAction
            ValidatorResult
Success -> do
                ([Statement] -> [Statement]) -> IO ()
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
([Statement] -> [Statement]) -> IO ()
updateSchema (Text -> [Statement] -> [Statement]
SchemaOperations.addTable Text
tableName)
                TablesController -> IO ()
forall action.
(?context::ControllerContext, HasPath action) =>
action -> IO ()
redirectTo ShowTableAction :: Text -> TablesController
ShowTableAction { Text
tableName :: Text
$sel:tableName:TablesAction :: Text
.. }

    action EditTableAction { Int
Text
$sel:tableId:TablesAction :: TablesController -> Int
tableId :: Int
tableName :: Text
$sel:tableName:TablesAction :: TablesController -> Text
.. } = do
        [Statement]
statements <- IO [Statement]
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
IO [Statement]
readSchema
        let tableId :: Int
tableId = ByteString -> Int
forall valueType.
(?context::ControllerContext, ParamReader valueType) =>
ByteString -> valueType
param ByteString
"tableId"
        EditTableView -> IO ()
forall view.
(View view, ?context::ControllerContext) =>
view -> IO ()
render EditTableView :: [Statement] -> Text -> Int -> EditTableView
EditTableView { Int
[Statement]
Text
$sel:tableId:EditTableView :: Int
$sel:tableName:EditTableView :: Text
$sel:statements:EditTableView :: [Statement]
tableId :: Int
statements :: [Statement]
tableName :: Text
.. }

    action TablesController
UpdateTableAction = do
        [Statement]
statements <- IO [Statement]
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
IO [Statement]
readSchema
        let tableName :: Text
tableName = ByteString -> Text
forall valueType.
(?context::ControllerContext, ParamReader valueType) =>
ByteString -> valueType
param ByteString
"tableName"
        let tableId :: Int
tableId = ByteString -> Int
forall valueType.
(?context::ControllerContext, ParamReader valueType) =>
ByteString -> valueType
param ByteString
"tableId"

        let oldTableName :: Text
oldTableName = (Proxy "name" -> CreateTable -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "name" (Proxy "name")
Proxy "name"
#name (CreateTable -> Text)
-> (Statement -> CreateTable) -> Statement -> Text
forall k (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Statement -> CreateTable
unsafeGetCreateTable) ([Statement]
statements [Statement] -> Int -> Statement
forall a. [a] -> Int -> a
!! Int
tableId)
        let validationResult :: ValidatorResult
validationResult = Text
tableName Text -> (Text -> ValidatorResult) -> ValidatorResult
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> [Statement] -> Maybe Text -> Text -> ValidatorResult
validateTable [Statement]
statements (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
oldTableName)
        case ValidatorResult
validationResult of
            Failure Text
message -> do
                (?context::ControllerContext) => Text -> IO ()
Text -> IO ()
setErrorMessage Text
message
                TablesController -> IO ()
forall action.
(?context::ControllerContext, HasPath action) =>
action -> IO ()
redirectTo ShowTableAction :: Text -> TablesController
ShowTableAction { $sel:tableName:TablesAction :: Text
tableName = Text
oldTableName }
            ValidatorResult
Success -> do
                ([Statement] -> [Statement]) -> IO ()
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
([Statement] -> [Statement]) -> IO ()
updateSchema (Int -> Text -> [Statement] -> [Statement]
SchemaOperations.updateTable Int
tableId Text
tableName)
                TablesController -> IO ()
forall action.
(?context::ControllerContext, HasPath action) =>
action -> IO ()
redirectTo ShowTableAction :: Text -> TablesController
ShowTableAction { Text
tableName :: Text
$sel:tableName:TablesAction :: Text
.. }

    action DeleteTableAction { Int
Text
tableName :: Text
tableId :: Int
$sel:tableId:TablesAction :: TablesController -> Int
$sel:tableName:TablesAction :: TablesController -> Text
.. } = do
        let tableName :: Text
tableName = ByteString -> Text
forall valueType.
(?context::ControllerContext, ParamReader valueType) =>
ByteString -> valueType
param ByteString
"tableName"

        ([Statement] -> [Statement]) -> IO ()
forall controller.
(?context::ControllerContext, ?modelContext::ModelContext,
 ?theAction::controller) =>
([Statement] -> [Statement]) -> IO ()
updateSchema (Text -> [Statement] -> [Statement]
SchemaOperations.deleteTable Text
tableName)
        TablesController -> IO ()
forall action.
(?context::ControllerContext, HasPath action) =>
action -> IO ()
redirectTo TablesController
TablesAction

validateTable :: [Statement] -> Maybe Text -> Validator Text
validateTable :: [Statement] -> Maybe Text -> Text -> ValidatorResult
validateTable [Statement]
statements = Text -> [Text] -> Maybe Text -> Text -> ValidatorResult
validateNameInSchema Text
"table name" ([Statement] -> [Text]
getAllObjectNames [Statement]
statements)

updateReferenceTableOfForeignKeyConstraint :: a -> Text -> [a] -> [Statement] -> [Statement]
updateReferenceTableOfForeignKeyConstraint a
constraint Text
newTableName [a]
statements =
    let Just Int
constraintId = a -> [a] -> Maybe Int
forall a. Eq a => a -> [a] -> Maybe Int
elemIndex a
constraint [a]
statements
        tableName :: Text
tableName = Proxy "tableName" -> a -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "tableName" (Proxy "tableName")
Proxy "tableName"
#tableName a
constraint
        columnName :: Text
columnName = Proxy "columnName" -> model -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "columnName" (Proxy "columnName")
Proxy "columnName"
#columnName (Proxy "constraint" -> a -> model
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "constraint" (Proxy "constraint")
Proxy "constraint"
#constraint a
constraint)
        constraintName :: Text
constraintName = Proxy "constraintName" -> a -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "constraintName" (Proxy "constraintName")
Proxy "constraintName"
#constraintName a
constraint
        referenceTable :: Text
referenceTable = Text
newTableName
        Just OnDelete
onDelete = Proxy "onDelete" -> model -> Maybe OnDelete
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "onDelete" (Proxy "onDelete")
Proxy "onDelete"
#onDelete (Proxy "constraint" -> a -> model
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "constraint" (Proxy "constraint")
Proxy "constraint"
#constraint a
constraint)
    in Text
-> Text
-> Text
-> Text
-> OnDelete
-> Int
-> [Statement]
-> [Statement]
updateForeignKeyConstraint Text
tableName Text
columnName Text
constraintName Text
referenceTable OnDelete
onDelete Int
constraintId