content: module IHP.IDE.SchemaDesigner.Controller.Columns where
  
  import IHP.ControllerPrelude
  import IHP.IDE.ToolServer.Types
  
  import IHP.IDE.SchemaDesigner.View.Columns.New
  import IHP.IDE.SchemaDesigner.View.Columns.Edit
  import IHP.IDE.SchemaDesigner.View.Columns.NewForeignKey
  import IHP.IDE.SchemaDesigner.View.Columns.EditForeignKey
  
  import IHP.IDE.SchemaDesigner.Types
  import IHP.IDE.SchemaDesigner.View.Layout (schemaDesignerLayout, findStatementByName, replace)
  import IHP.IDE.SchemaDesigner.Controller.Helper
  import IHP.IDE.SchemaDesigner.Controller.Validation
  
  
  import qualified IHP.IDE.SchemaDesigner.SchemaOperations as SchemaOperations
  
  instance Controller ColumnsController where
      beforeAction = setLayout schemaDesignerLayout
  
      action NewColumnAction { tableName } = do
          statements <- readSchema
          let (Just table) = findStatementByName tableName statements
          let tableNames = nameList (getCreateTable statements)
          let enumNames = nameList (getCreateEnum statements)
          render NewColumnView { .. }
  
      action CreateColumnAction = do
          let tableName = param "tableName"
          let columnName = param "name"
          let validationResult = columnName |> validateColumn
          case validationResult of
              Failure message ->
                  setErrorMessage message
              Success -> do
                  let options = SchemaOperations.AddColumnOptions
                          { tableName
                          , columnName
                          , columnType = param "columnType"
                          , defaultValue = param "defaultValue"
                          , isArray = param "isArray"
                          , allowNull = param "allowNull"
                          , isUnique = param "isUnique"
                          , isReference = param "isReference"
                          , referenceTable = paramOrNothing "referenceTable"
                          , primaryKey = param "primaryKey"
                          , withIndex = paramOrDefault False "withIndex"
                          , autoPolicy = paramOrDefault False "autoPolicy"
                          }
                  updateSchema $ SchemaOperations.addColumn options
  
          redirectTo ShowTableAction { .. }
  
      action EditColumnAction { .. } = do
          let columnId = param "columnId"
          let name = tableName
          statements <- readSchema
          let (Just table) = findStatementByName name statements
          let table = findStatementByName tableName statements
          let columns = maybe [] ((.columns) . unsafeGetCreateTable) table
          let column = columns !! columnId
          let enumNames = nameList (getCreateEnum statements)
          render EditColumnView { .. }
  
      action UpdateColumnAction = do
          statements <- readSchema
          let tableName = param "tableName"
          let columnName = param "name"
          let validationResult = columnName |> validateColumn
  
          case validationResult of
              Failure message ->
                  setErrorMessage message
              Success -> do
                  let options = SchemaOperations.UpdateColumnOptions
                          { tableName
                          , columnName
                          , columnId = param "columnId"
                          , defaultValue = param "defaultValue"
                          , isArray = param "isArray"
                          , columnType = param "columnType"
                          , allowNull = param "allowNull"
                          , isUnique = param "isUnique"
                          , primaryKey = param "primaryKey"
                          }
  
                  updateSchema $ SchemaOperations.updateColumn options
          redirectTo ShowTableAction { .. }
  
      action DeleteColumnAction { .. } = do
          statements <- readSchema
          let tableName = param "tableName"
          let columnId = param "columnId"
          let columnName = param "columnName"
  
          let options = SchemaOperations.DeleteColumnOptions
                  { tableName
                  , columnName
                  , columnId
                  }
  
          updateSchema $ SchemaOperations.deleteColumn options
  
          redirectTo ShowTableAction { .. }
  
      action ToggleColumnUniqueAction { .. } = do
          let tableName = param "tableName"
          let columnId = param "columnId"
          updateSchema (map (toggleUniqueInColumn tableName columnId))
          redirectTo ShowTableAction { .. }
  
      -- FOREIGN KEYS
      action NewForeignKeyAction { tableName, columnName } = do
          let name = tableName
          statements <- readSchema
          let tableNames = nameList (getCreateTable statements)
          render NewForeignKeyView { .. }
  
      action CreateForeignKeyAction = do
          let tableName = param "tableName"
          let columnName = param "columnName"
          let constraintName = param "constraintName"
          let referenceTable = param "referenceTable"
          let onDelete = NoAction
          updateSchema (SchemaOperations.addForeignKeyConstraint tableName columnName constraintName referenceTable onDelete)
          redirectTo ShowTableAction { .. }
  
      action EditForeignKeyAction { tableName, columnName, constraintName, referenceTable } = do
          let name = tableName
          statements <- readSchema
          let tableNames = nameList (getCreateTable statements)
          let (Just statement) = find (\statement -> statement == AddConstraint { tableName = tableName, deferrable = Nothing, deferrableType = Nothing, constraint = ForeignKeyConstraint { name = Just constraintName, columnName = columnName, referenceTable = referenceTable, referenceColumn = "id", onDelete = statement.constraint.onDelete }}) statements
          onDelete <- case statement.constraint.onDelete of
              Just NoAction -> do pure "NoAction"
              Just Restrict -> do pure "Restrict"
              Just SetNull -> do pure "SetNull"
              Just SetDefault -> do pure "SetDefault"
              Just Cascade -> do pure "Cascade"
              Nothing -> do pure "NoAction"
          render EditForeignKeyView { .. }
  
      action UpdateForeignKeyAction = do
          statements <- readSchema
          let tableName = param "tableName"
          let columnName = param "columnName"
          let constraintName = param "constraintName"
          let referenceTable = param "referenceTable"
          let constraintId = statements
                  |> findIndex \case
                      AddConstraint { tableName = fkTable, constraint = ForeignKeyConstraint { columnName = fkColumnName } } -> tableName == fkTable && columnName == fkColumnName
                      otherwise -> False
          let onDeleteParam = param @Text "onDelete"
          let onDelete = case onDeleteParam of
                  "Restrict" -> Restrict
                  "SetNull" -> SetNull
                  "SetDefault" -> SetDefault
                  "Cascade" -> Cascade
                  _ -> NoAction
          case constraintId of
              Just constraintId -> updateSchema (updateForeignKeyConstraint tableName columnName constraintName referenceTable onDelete constraintId)
              Nothing -> putStrLn ("Error")
          redirectTo ShowTableAction { .. }
  
      action DeleteForeignKeyAction { constraintName, tableName } = do
          statements <- readSchema
          updateSchema (deleteForeignKeyConstraint constraintName)
          redirectTo ShowTableAction { .. }
  
  toggleUniqueInColumn :: Text -> Int -> Statement -> Statement
  toggleUniqueInColumn tableName columnId (StatementCreateTable table@CreateTable { name, columns })
      | name == tableName = StatementCreateTable $
          table { columns = (replace columnId ((columns !! columnId) { isUnique = (not (columns !! columnId).isUnique) }) columns) }
  toggleUniqueInColumn tableName columnId statement = statement
  
  deleteColumnInTable :: Text -> Int -> Statement -> Statement
  deleteColumnInTable tableName columnId (StatementCreateTable table@CreateTable { name, columns })
      | name == tableName = StatementCreateTable $
          table { columns = delete (columns !! columnId) columns}
  deleteColumnInTable tableName columnId statement = statement
  
  
  updateForeignKeyConstraint :: Text -> Text -> Text -> Text -> OnDelete -> Int -> [Statement] -> [Statement]
  updateForeignKeyConstraint tableName columnName constraintName referenceTable onDelete constraintId list = replace constraintId AddConstraint { tableName = tableName, deferrable = Nothing, deferrableType = Nothing, constraint = ForeignKeyConstraint { name = Just constraintName, columnName = columnName, referenceTable = referenceTable, referenceColumn = "id", onDelete = (Just onDelete) } } list
  
  deleteForeignKeyConstraint :: Text -> [Statement] -> [Statement]
  deleteForeignKeyConstraint constraintName = filter \case
      AddConstraint { constraint } | constraint.name == Just constraintName -> False
      otherwise -> True
  
  
  deleteTableIndex :: Text -> [Statement] -> [Statement]
  deleteTableIndex indexName list =
      list
      |> filter \case
          CreateIndex { indexName = indexName' } -> indexName' /= indexName
          otherwise -> True
  
  getCreateTable :: [Statement] -> [CreateTable]
  getCreateTable statements = foldr step [] statements
    where
      step (StatementCreateTable createTable) createTables = createTable : createTables
      step _ createTables = createTables
  
  getCreateEnum statements = filter isCreateEnumType statements
  isCreateEnumType CreateEnumType {} = True
  isCreateEnumType _ = False
  
  nameList statements = map (.name) statements
  
  
  validateColumn :: Validator Text
  validateColumn = validateNameInSchema "column name" [] Nothing
  
  referencingColumnForeignKeyConstraints tableName columnName =
      find \case
          AddConstraint { tableName = constraintTable, constraint = ForeignKeyConstraint { columnName = fkColumnName  }  } -> constraintTable == tableName && fkColumnName == columnName
          otherwise -> False
  