ain :: IO ()
main = do
    props <- subcommandProps
    runSubcommand props \ExecParams{..} -> \case
        List ->
            listCommands protocol config ListOptions{showDescription = True}

        Tree ->
            showCommandTree protocol config TreeOptions
                { delimiter = '.'
                , showDescription = True
                }

        DryRun ->
            getExecutableCommand protocol config commandAndItsArguments
                >>= printAsDhall protocol

        DryRunCompletion idx shell -> do
            getCompletionCommand protocol config shell idx commandAndItsArguments
                >>= printAsDhall protocol

        Run monitor ->
            getExecutableCommand protocol config commandAndItsArguments
                >>= executeAndMonitorCommand protocol MonitorOptions
                        { monitor
                        , notificationMessage = "Action "
                            <> maybe "" (\s -> "'" <> fromString s <> "'")
                                (headMay commandAndItsArguments)
                        }

        RunDhall monitor expression ->
            runDhall protocol config
                MonitorOptions
                    { monitor
                    , notificationMessage = "Action "
                    }
                RunDhallOptions
                    { expression

                    -- There is no command name, only arguments.
                    , arguments = fromString <$> commandAndItsArguments
                    }
  where
    subcommandProps :: IO (SubcommandProps ExecParams Action)
    subcommandProps = do
        protocol <- subcommandParams
        arguments <- getArgs
        possiblyConfig <- inputConfig Dhall.auto protocol

        pure SubcommandProps
            { preprocess = \ep args ->
                let (opts, commandAndItsArguments) = Options.splitArguments args
                in (ep{commandAndItsArguments}, opts)
            , doCompletion
            , helpMsg
            , actionOptions = Endo . fmap <$> parseOptions
            , defaultAction = Just defAction
            , params = ExecParams
                { protocol
                , config = fromMaybe defConfig possiblyConfig
                , commandAndItsArguments = []
                }
            , arguments
            }

    getExecutableCommand :: Params -> Config -> [String] -> IO Command
    getExecutableCommand params Config{commands} commandAndItsArguments =
        case fromString <$> commandAndItsArguments of
            [] ->
                dieWith params stderr 1 "COMMAND: Missing argument."

            name : arguments ->
                getCommand params commands name arguments

    getCompletionCommand
        :: Params
        -> Config
        -> Shell
        -> Word
        -> [String]
        -> IO (Maybe Command)
    getCompletionCommand params config shell index = \case
        [] ->
            dieWith params stderr 1 "COMMAND: Missing argument."

        name : arguments -> do
            mkCmd <- getCompletion params config name
            pure $ mkCmd
                <*> pure shell
                <*> pure (fromIntegral index)
                <*> pure (fromString <$> arguments)

main :: IO ()
main = do
    props <- subcommandProps
    runSubcommand props \ExecParams{..} -> \case
        List ->
            listCommands protocol config ListOptions{showDescription = True}

        Tree ->
            showCommandTree protocol config TreeOptions
                { delimiter = '.'
                , showDescription = True
                }

        DryRun ->
            getExecutableCommand protocol config commandAndItsArguments
                >>= printAsDhall protocol

        DryRunCompletion idx shell -> do
            getCompletionCommand protocol config shell idx commandAndItsArguments
                >>= printAsDhall protocol

        Run monitor ->
            getExecutableCommand protocol config commandAndItsArguments
                >>= executeAndMonitorCommand protocol MonitorOptions
                        { monitor
                        , notificationMessage = "Action "
                            <> maybe "" (\s -> "'" <> fromString s <> "'")
                                (headMay commandAndItsArguments)
                        }

        RunDhall monitor expression ->
            runDhall protocol config
                MonitorOptions
                    { monitor
                    , notificationMessage = "Action "
                    }
                RunDhallOptions
                    { expression

                    -- There is no command name, only arguments.
                    , arguments = fromString <$> commandAndItsArguments
                    }
  where
    subcommandProps :: IO (SubcommandProps ExecParams Action)
    subcommandProps = do
        protocol <- subcommandParams
        arguments <- getArgs
        possiblyConfig <- inputConfig Dhall.auto protocol

        pure SubcommandProps
            { preprocess = \ep args ->
                let (opts, commandAndItsArguments) = Options.splitArguments args
                in (ep{commandAndItsArguments}, opts)
            , doCompletion
            , helpMsg
            , actionOptions = Endo . fmap <$> parseOptions
            , defaultAction = Just defAction
            , params = ExecParams
                { protocol
                , config = fromMaybe defConfig possiblyConfig
                , commandAndItsArguments = []
                }
            , arguments
            }

    getExecutableCommand :: Params -> Config -> [String] -> IO Command
    getExecutableCommand params Config{commands} commandAndItsArguments =
        case fromString <$> commandAndItsArguments of
            [] ->
                dieWith params stderr 1 "COMMAND: Missing argument."

            name : arguments ->
                getCommand params commands name arguments

    getCompletionCommand
        :: Params
        -> Config
        -> Shell
        -> Word
        -> [String]
        -> IO (Maybe Command)
    getCompletionCommand params config shell index = \case
        [] ->
            dieWith params stderr 1 "COMMAND: Missing argument."

        name : arguments -> do
            mkCmd <- getCompletion params config name
            pure $ mkCmd
                <*> pure shell
                <*> pure (fromIntegral index)
                <*> pure (fromString <$> arguments)
main = do
    props <- subcommandProps
    runSubcommand props \ExecParams{..} -> \case
        List ->
            listCommands protocol config ListOptions{showDescription = True}

        Tree ->
            showCommandTree protocol config TreeOptions
                { delimiter = '.'
                , showDescription = True
                }

        DryRun ->
            getExecutableCommand protocol config commandAndItsArguments
                >>= printAsDhall protocol

        DryRunCompletion idx shell -> do
            getCompletionCommand protocol config shell idx commandAndItsArguments
                >>= printAsDhall protocol

        Run monitor ->
            getExecutableCommand protocol config commandAndItsArguments
                >>= executeAndMonitorCommand protocol MonitorOptions
                        { monitor
                        , notificationMessage = "Action "
                            <> maybe "" (\s -> "'" <> fromString s <> "'")
                                (headMay commandAndItsArguments)
                        }

        RunDhall monitor expression ->
            runDhall protocol config
                MonitorOptions
                    { monitor
                    , notificationMessage = "Action "
                    }
                RunDhallOptions
                    { expression

                    -- There is no command name, only arguments.
                    , arguments = fromString <$> commandAndItsArguments
                    }
  where
    subcommandProps :: IO (SubcommandProps ExecParams Action)
    subcommandProps = do
        protocol <- subcommandParams
        arguments <- getArgs
        possiblyConfig <- inputConfig Dhall.auto protocol

        pure SubcommandProps
            { preprocess = \ep args ->
                let (opts, commandAndItsArguments) = Options.splitArguments args
                in (ep{commandAndItsArguments}, opts)
            , doCompletion
            , helpMsg
            , actionOptions = Endo . fmap <$> parseOptions
            , defaultAction = Just defAction
            , params = ExecParams
                { protocol
                , config = fromMaybe defConfig possiblyConfig
                , commandAndItsArguments = []
                }
            , arguments
            }

    getExecutableCommand :: Params -> Config -> [String] -> IO Command
    getExecutableCommand params Config{commands} commandAndItsArguments =
        case fromString <$> commandAndItsArguments of
            [] ->
                dieWith params stderr 1 "COMMAND: Missing argument."

            name : arguments ->
                getCommand params commands name arguments

    getCompletionCommand
        :: Params
        -> Config
        -> Shell
        -> Word
        -> [String]
        -> IO (Maybe Command)
    getCompletionCommand params config shell index = \case
        [] ->
            dieWith params stderr 1 "COMMAND: Missing argument."

        name : arguments -> do
            mkCmd <- getCompletion params config name
            pure $ mkCmd
                <*> pure shell
                <*> pure (fromIntegral index)
                <*> pure (fromString <$> arguments)