use crate::{
    ColumnDef, Expr, IntoColumnDef, SchemaStatementBuilder, backend::SchemaBuilder, foreign_key::*,
    index::*, table::constraint::Check, types::*,
};

/// Create a table
///
/// # Examples
///
/// ```
/// use sea_query::{*, tests_cfg::*};
///
/// let table = Table::create()
///     .table(Char::Table)
///     .if_not_exists()
///     .comment("table's comment")
///     .col(ColumnDef::new(Char::Id).integer().not_null().auto_increment().primary_key())
///     .col(ColumnDef::new(Char::FontSize).integer().not_null().comment("font's size"))
///     .col(ColumnDef::new(Char::Character).string().not_null())
///     .col(ColumnDef::new(Char::SizeW).integer().not_null())
///     .col(ColumnDef::new(Char::SizeH).integer().not_null())
///     .col(ColumnDef::new(Char::FontId).integer().default(Value::Int(None)))
///     .foreign_key(
///         ForeignKey::create()
///             .name("FK_2e303c3a712662f1fc2a4d0aad6")
///             .from(Char::Table, Char::FontId)
///             .to(Font::Table, Font::Id)
///             .on_delete(ForeignKeyAction::Cascade)
///             .on_update(ForeignKeyAction::Cascade)
///     )
///     .to_owned();
///
/// assert_eq!(
///     table.to_string(MysqlQueryBuilder),
///     [
///         r#"CREATE TABLE IF NOT EXISTS `character` ("#,
///             r#"`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT,"#,
///             r#"`font_size` int NOT NULL COMMENT 'font\'s size',"#,
///             r#"`character` varchar(255) NOT NULL,"#,
///             r#"`size_w` int NOT NULL,"#,
///             r#"`size_h` int NOT NULL,"#,
///             r#"`font_id` int DEFAULT NULL,"#,
///             r#"CONSTRAINT `FK_2e303c3a712662f1fc2a4d0aad6`"#,
///                 r#"FOREIGN KEY (`font_id`) REFERENCES `font` (`id`)"#,
///                 r#"ON DELETE CASCADE ON UPDATE CASCADE"#,
///         r#") COMMENT 'table\'s comment'"#,
///     ].join(" ")
/// );
/// assert_eq!(
///     table.to_string(PostgresQueryBuilder),
///     [
///         r#"CREATE TABLE IF NOT EXISTS "character" ("#,
///             r#""id" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY,"#,
///             r#""font_size" integer NOT NULL,"#,
///             r#""character" varchar NOT NULL,"#,
///             r#""size_w" integer NOT NULL,"#,
///             r#""size_h" integer NOT NULL,"#,
///             r#""font_id" integer DEFAULT NULL,"#,
///             r#"CONSTRAINT "FK_2e303c3a712662f1fc2a4d0aad6""#,
///                 r#"FOREIGN KEY ("font_id") REFERENCES "font" ("id")"#,
///                 r#"ON DELETE CASCADE ON UPDATE CASCADE"#,
///         r#")"#,
///     ].join(" ")
/// );
/// assert_eq!(
///     table.to_string(SqliteQueryBuilder),
///     [
///        r#"CREATE TABLE IF NOT EXISTS "character" ("#,
///            r#""id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
///            r#""font_size" integer NOT NULL,"#,
///            r#""character" varchar NOT NULL,"#,
///            r#""size_w" integer NOT NULL,"#,
///            r#""size_h" integer NOT NULL,"#,
///            r#""font_id" integer DEFAULT NULL,"#,
///            r#"FOREIGN KEY ("font_id") REFERENCES "font" ("id") ON DELETE CASCADE ON UPDATE CASCADE"#,
///        r#")"#,
///     ].join(" ")
/// );
/// ```
#[derive(Default, Debug, Clone)]
pub struct TableCreateStatement {
    pub(crate) table: Option<TableRef>,
    pub(crate) columns: Vec<ColumnDef>,
    pub(crate) options: Vec<TableOpt>,
    pub(crate) partition_by: Option<PartitionBy>,
    pub(crate) partition_of: Option<TableRef>,
    pub(crate) partition_values: Option<PartitionValues>,
    pub(crate) partitions: Vec<PartitionDefinition>,
    pub(crate) indexes: Vec<IndexCreateStatement>,
    pub(crate) foreign_keys: Vec<ForeignKeyCreateStatement>,
    pub(crate) if_not_exists: bool,
    pub(crate) check: Vec<Check>,
    pub(crate) comment: Option<String>,
    pub(crate) extra: Option<String>,
    pub(crate) temporary: bool,
}

/// All available table options
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum TableOpt {
    Engine(String),
    Collate(String),
    CharacterSet(String),
}

/// All available table partition options
#[derive(Debug, Clone)]
pub enum PartitionBy {
    Range(Vec<DynIden>),
    List(Vec<DynIden>),
    Hash(Vec<DynIden>),
    Key(Vec<DynIden>),
}

#[derive(Debug, Clone)]
pub enum PartitionValues {
    In(Vec<Expr>),
    FromTo(Vec<Expr>, Vec<Expr>),
    LessThan(Vec<Expr>),
    With(u32, u32),
}

#[derive(Debug, Clone)]
pub(crate) struct PartitionDefinition {
    pub(crate) name: DynIden,
    pub(crate) values: Option<PartitionValues>,
}

impl TableCreateStatement {
    /// Construct create table statement
    pub fn new() -> Self {
        Self::default()
    }

    /// Create table if table not exists
    pub fn if_not_exists(&mut self) -> &mut Self {
        self.if_not_exists = true;
        self
    }

    /// Set table name
    pub fn table<T>(&mut self, table: T) -> &mut Self
    where
        T: IntoTableRef,
    {
        self.table = Some(table.into_table_ref());
        self
    }

    /// Set table comment
    pub fn comment<T>(&mut self, comment: T) -> &mut Self
    where
        T: Into<String>,
    {
        self.comment = Some(comment.into());
        self
    }

    /// Add a new table column
    pub fn col<C: IntoColumnDef>(&mut self, column: C) -> &mut Self {
        let mut column = column.into_column_def();
        column.table.clone_from(&self.table);
        self.columns.push(column);
        self
    }

    pub fn check<T>(&mut self, value: T) -> &mut Self
    where
        T: Into<Check>,
    {
        self.check.push(value.into());
        self
    }

    /// Add an index. MySQL only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Glyph::Table)
    ///         .col(ColumnDef::new(Glyph::Id).integer().not_null())
    ///         .index(Index::create().unique().name("idx-glyph-id").col(Glyph::Id))
    ///         .to_string(MysqlQueryBuilder),
    ///     [
    ///         "CREATE TABLE `glyph` (",
    ///         "`id` int NOT NULL,",
    ///         "UNIQUE KEY `idx-glyph-id` (`id`)",
    ///         ")",
    ///     ]
    ///     .join(" ")
    /// );
    /// ```
    pub fn index(&mut self, index: &mut IndexCreateStatement) -> &mut Self {
        self.indexes.push(index.take());
        self
    }

    /// Add an primary key.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// let mut statement = Table::create();
    /// statement
    ///     .table(Glyph::Table)
    ///     .col(ColumnDef::new(Glyph::Id).integer().not_null())
    ///     .col(ColumnDef::new(Glyph::Image).string().not_null())
    ///     .primary_key(Index::create().col(Glyph::Id).col(Glyph::Image));
    /// assert_eq!(
    ///     statement.to_string(MysqlQueryBuilder),
    ///     [
    ///         "CREATE TABLE `glyph` (",
    ///         "`id` int NOT NULL,",
    ///         "`image` varchar(255) NOT NULL,",
    ///         "PRIMARY KEY (`id`, `image`)",
    ///         ")",
    ///     ]
    ///     .join(" ")
    /// );
    /// assert_eq!(
    ///     statement.to_string(PostgresQueryBuilder),
    ///     [
    ///         "CREATE TABLE \"glyph\" (",
    ///         "\"id\" integer NOT NULL,",
    ///         "\"image\" varchar NOT NULL,",
    ///         "PRIMARY KEY (\"id\", \"image\")",
    ///         ")",
    ///     ]
    ///     .join(" ")
    /// );
    /// assert_eq!(
    ///     statement.to_string(SqliteQueryBuilder),
    ///     [
    ///         r#"CREATE TABLE "glyph" ("#,
    ///         r#""id" integer NOT NULL,"#,
    ///         r#""image" varchar NOT NULL,"#,
    ///         r#"PRIMARY KEY ("id", "image")"#,
    ///         r#")"#,
    ///     ]
    ///     .join(" ")
    /// );
    /// ```
    pub fn primary_key(&mut self, index: &mut IndexCreateStatement) -> &mut Self {
        let mut index = index.take();
        index.primary = true;
        self.indexes.push(index);
        self
    }

    /// Add a foreign key
    pub fn foreign_key(&mut self, foreign_key: &mut ForeignKeyCreateStatement) -> &mut Self {
        self.foreign_keys.push(foreign_key.take());
        self
    }

    /// Set database engine. MySQL only.
    pub fn engine<T>(&mut self, string: T) -> &mut Self
    where
        T: Into<String>,
    {
        self.opt(TableOpt::Engine(string.into()));
        self
    }

    /// Set database collate. MySQL only.
    pub fn collate<T>(&mut self, string: T) -> &mut Self
    where
        T: Into<String>,
    {
        self.opt(TableOpt::Collate(string.into()));
        self
    }

    /// Set database character set. MySQL only.
    pub fn character_set<T>(&mut self, name: T) -> &mut Self
    where
        T: Into<String>,
    {
        self.opt(TableOpt::CharacterSet(name.into()));
        self
    }

    fn opt(&mut self, option: TableOpt) -> &mut Self {
        self.options.push(option);
        self
    }

    #[allow(dead_code)]
    fn partition(&mut self, partition: PartitionDefinition) -> &mut Self {
        self.partitions.push(partition);
        self
    }

    /// Set partition by range. Postgres and MySQL only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Glyph::Table)
    ///         .col(ColumnDef::new(Glyph::Id).integer().not_null())
    ///         .partition_by_range([Glyph::Id])
    ///         .to_string(PostgresQueryBuilder),
    ///     r#"CREATE TABLE "glyph" ( "id" integer NOT NULL ) PARTITION BY RANGE ("id")"#
    /// );
    /// ```
    pub fn partition_by_range<I, T>(&mut self, cols: I) -> &mut Self
    where
        I: IntoIterator<Item = T>,
        T: IntoIden,
    {
        self.partition_by = Some(PartitionBy::Range(
            cols.into_iter().map(|c| c.into_iden()).collect(),
        ));
        self
    }

    /// Set partition by list. Postgres and MySQL only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Glyph::Table)
    ///         .col(ColumnDef::new(Glyph::Id).integer().not_null())
    ///         .partition_by_list([Glyph::Id])
    ///         .to_string(PostgresQueryBuilder),
    ///     r#"CREATE TABLE "glyph" ( "id" integer NOT NULL ) PARTITION BY LIST ("id")"#
    /// );
    /// ```
    pub fn partition_by_list<I, T>(&mut self, cols: I) -> &mut Self
    where
        I: IntoIterator<Item = T>,
        T: IntoIden,
    {
        self.partition_by = Some(PartitionBy::List(
            cols.into_iter().map(|c| c.into_iden()).collect(),
        ));
        self
    }

    /// Set partition by hash. Postgres and MySQL only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Glyph::Table)
    ///         .col(ColumnDef::new(Glyph::Id).integer().not_null())
    ///         .partition_by_hash([Glyph::Id])
    ///         .to_string(PostgresQueryBuilder),
    ///     r#"CREATE TABLE "glyph" ( "id" integer NOT NULL ) PARTITION BY HASH ("id")"#
    /// );
    /// ```
    pub fn partition_by_hash<I, T>(&mut self, cols: I) -> &mut Self
    where
        I: IntoIterator<Item = T>,
        T: IntoIden,
    {
        self.partition_by = Some(PartitionBy::Hash(
            cols.into_iter().map(|c| c.into_iden()).collect(),
        ));
        self
    }

    /// Set partition by key. MySQL only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Glyph::Table)
    ///         .col(ColumnDef::new(Glyph::Id).integer().not_null())
    ///         .partition_by_key([Glyph::Id])
    ///         .to_string(MysqlQueryBuilder),
    ///     "CREATE TABLE `glyph` ( `id` int NOT NULL ) PARTITION BY KEY (`id`)"
    /// );
    /// ```
    pub fn partition_by_key<I, T>(&mut self, cols: I) -> &mut Self
    where
        I: IntoIterator<Item = T>,
        T: IntoIden,
    {
        self.partition_by = Some(PartitionBy::Key(
            cols.into_iter().map(|c| c.into_iden()).collect(),
        ));
        self
    }

    /// Set partition of table. Postgres only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Alias::new("glyph_1"))
    ///         .partition_of(Glyph::Table)
    ///         .values_from_to([1], [10])
    ///         .to_string(PostgresQueryBuilder),
    ///     r#"CREATE TABLE "glyph_1" PARTITION OF "glyph" FOR VALUES FROM (1) TO (10)"#
    /// );
    /// ```
    pub fn partition_of<T>(&mut self, table: T) -> &mut Self
    where
        T: IntoTableRef,
    {
        self.partition_of = Some(table.into_table_ref());
        self
    }

    /// Set partition values IN. Postgres partition tables only.
    ///
    /// MySQL partition definitions can use [`PartitionValues::In`] with
    /// [`Self::add_partition`].
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Alias::new("glyph_1"))
    ///         .partition_of(Glyph::Table)
    ///         .values_in([1, 2, 3])
    ///         .to_string(PostgresQueryBuilder),
    ///     r#"CREATE TABLE "glyph_1" PARTITION OF "glyph" FOR VALUES IN (1, 2, 3)"#
    /// );
    /// ```
    pub fn values_in<I, T>(&mut self, values: I) -> &mut Self
    where
        I: IntoIterator<Item = T>,
        T: Into<Expr>,
    {
        self.partition_values = Some(PartitionValues::In(
            values.into_iter().map(|v| v.into()).collect(),
        ));
        self
    }

    /// Set partition values FROM ... TO .... Postgres only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Alias::new("glyph_1"))
    ///         .partition_of(Glyph::Table)
    ///         .values_from_to([1], [10])
    ///         .to_string(PostgresQueryBuilder),
    ///     r#"CREATE TABLE "glyph_1" PARTITION OF "glyph" FOR VALUES FROM (1) TO (10)"#
    /// );
    /// ```
    pub fn values_from_to<I, T, J, U>(&mut self, from: I, to: J) -> &mut Self
    where
        I: IntoIterator<Item = T>,
        T: Into<Expr>,
        J: IntoIterator<Item = U>,
        U: Into<Expr>,
    {
        self.partition_values = Some(PartitionValues::FromTo(
            from.into_iter().map(|v| v.into()).collect(),
            to.into_iter().map(|v| v.into()).collect(),
        ));
        self
    }

    /// Set partition values LESS THAN. MySQL partition definitions only.
    ///
    /// Use [`PartitionValues::LessThan`] with [`Self::add_partition`] for MySQL
    /// table partition definitions.
    pub fn values_less_than<I, T>(&mut self, values: I) -> &mut Self
    where
        I: IntoIterator<Item = T>,
        T: Into<Expr>,
    {
        self.partition_values = Some(PartitionValues::LessThan(
            values.into_iter().map(|v| v.into()).collect(),
        ));
        self
    }

    /// Set partition values WITH (modulus, remainder). Postgres only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Alias::new("glyph_p1"))
    ///         .partition_of(Glyph::Table)
    ///         .values_with(4, 0)
    ///         .to_string(PostgresQueryBuilder),
    ///     r#"CREATE TABLE "glyph_p1" PARTITION OF "glyph" FOR VALUES WITH (MODULUS 4, REMAINDER 0)"#
    /// );
    /// ```
    pub fn values_with(&mut self, modulus: u32, remainder: u32) -> &mut Self {
        self.partition_values = Some(PartitionValues::With(modulus, remainder));
        self
    }

    /// Add a partition definition. MySQL only.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// assert_eq!(
    ///     Table::create()
    ///         .table(Glyph::Table)
    ///         .col(ColumnDef::new(Glyph::Id).integer().not_null())
    ///         .partition_by_range([Glyph::Id])
    ///         .add_partition(
    ///             Alias::new("p0"),
    ///             Some(PartitionValues::LessThan(vec![10.into()]))
    ///         )
    ///         .to_string(MysqlQueryBuilder),
    ///     "CREATE TABLE `glyph` ( `id` int NOT NULL ) PARTITION BY RANGE (`id`) ( PARTITION `p0` VALUES LESS THAN (10) )"
    /// );
    /// ```
    pub fn add_partition<T>(&mut self, name: T, values: Option<PartitionValues>) -> &mut Self
    where
        T: IntoIden,
    {
        self.partitions.push(PartitionDefinition {
            name: name.into_iden(),
            values,
        });
        self
    }

    pub fn get_table_name(&self) -> Option<&TableRef> {
        self.table.as_ref()
    }

    pub fn get_columns(&self) -> &Vec<ColumnDef> {
        self.columns.as_ref()
    }

    pub fn get_comment(&self) -> Option<&String> {
        self.comment.as_ref()
    }

    pub fn get_foreign_key_create_stmts(&self) -> &Vec<ForeignKeyCreateStatement> {
        self.foreign_keys.as_ref()
    }

    pub fn get_indexes(&self) -> &Vec<IndexCreateStatement> {
        self.indexes.as_ref()
    }

    /// Rewriting extra param. You should take care self about concat extra params. Add extra after options.
    /// Example for PostgresSQL [Citus](https://github.com/citusdata/citus) extension:
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    /// let table = Table::create()
    ///     .table(Char::Table)
    ///     .col(
    ///         ColumnDef::new(Char::Id)
    ///             .uuid()
    ///             .extra("DEFAULT uuid_generate_v4()")
    ///             .primary_key()
    ///             .not_null(),
    ///     )
    ///     .col(
    ///         ColumnDef::new(Char::CreatedAt)
    ///             .timestamp_with_time_zone()
    ///             .extra("DEFAULT NOW()")
    ///             .not_null(),
    ///     )
    ///     .col(ColumnDef::new(Char::UserData).json_binary().not_null())
    ///     .extra("USING columnar")
    ///     .take();
    /// assert_eq!(
    ///     table.to_string(PostgresQueryBuilder),
    ///     [
    ///         r#"CREATE TABLE "character" ("#,
    ///         r#""id" uuid NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),"#,
    ///         r#""created_at" timestamp with time zone NOT NULL DEFAULT NOW(),"#,
    ///         r#""user_data" jsonb NOT NULL"#,
    ///         r#") USING columnar"#,
    ///     ]
    ///     .join(" ")
    /// );
    /// ```
    pub fn extra<T>(&mut self, extra: T) -> &mut Self
    where
        T: Into<String>,
    {
        self.extra = Some(extra.into());
        self
    }

    pub fn get_extra(&self) -> Option<&String> {
        self.extra.as_ref()
    }

    /// Create temporary table
    ///
    /// Ref:
    /// - PostgreSQL: <https://www.postgresql.org/docs/17/sql-createtable.html#SQL-CREATETABLE-TEMPORARY>
    /// - MySQL: <https://dev.mysql.com/doc/refman/9.2/en/create-temporary-table.html>
    /// - MariaDB: <https://mariadb.com/kb/en/create-table/#create-temporary-table>
    /// - SQLite: <https://sqlite.org/lang_createtable.html>
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// let statement = Table::create()
    ///     .table(Font::Table)
    ///     .temporary()
    ///     .col(
    ///         ColumnDef::new(Font::Id)
    ///             .integer()
    ///             .not_null()
    ///             .primary_key()
    ///             .auto_increment(),
    ///     )
    ///     .col(ColumnDef::new(Font::Name).string().not_null())
    ///     .take();
    ///
    /// assert_eq!(
    ///     statement.to_string(MysqlQueryBuilder),
    ///     [
    ///         "CREATE TEMPORARY TABLE `font` (",
    ///         "`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT,",
    ///         "`name` varchar(255) NOT NULL",
    ///         ")",
    ///     ]
    ///     .join(" ")
    /// );
    /// assert_eq!(
    ///     statement.to_string(PostgresQueryBuilder),
    ///     [
    ///         r#"CREATE TEMPORARY TABLE "font" ("#,
    ///         r#""id" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY,"#,
    ///         r#""name" varchar NOT NULL"#,
    ///         r#")"#,
    ///     ]
    ///     .join(" ")
    /// );
    /// assert_eq!(
    ///     statement.to_string(SqliteQueryBuilder),
    ///     [
    ///         r#"CREATE TEMPORARY TABLE "font" ("#,
    ///         r#""id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
    ///         r#""name" varchar NOT NULL"#,
    ///         r#")"#,
    ///     ]
    ///     .join(" ")
    /// );
    /// ```
    pub fn temporary(&mut self) -> &mut Self {
        self.temporary = true;
        self
    }

    pub fn take(&mut self) -> Self {
        Self {
            table: self.table.take(),
            columns: std::mem::take(&mut self.columns),
            options: std::mem::take(&mut self.options),
            partition_by: self.partition_by.take(),
            partition_of: self.partition_of.take(),
            partition_values: self.partition_values.take(),
            partitions: std::mem::take(&mut self.partitions),
            indexes: std::mem::take(&mut self.indexes),
            foreign_keys: std::mem::take(&mut self.foreign_keys),
            if_not_exists: self.if_not_exists,
            check: std::mem::take(&mut self.check),
            comment: std::mem::take(&mut self.comment),
            extra: std::mem::take(&mut self.extra),
            temporary: self.temporary,
        }
    }
}

impl SchemaStatementBuilder for TableCreateStatement {
    fn build<T: SchemaBuilder>(&self, schema_builder: T) -> String {
        let mut sql = String::with_capacity(256);
        schema_builder.prepare_table_create_statement(self, &mut sql);
        sql
    }
}

impl TableCreateStatement {
    pub fn build<T: SchemaBuilder>(&self, schema_builder: T) -> String {
        <Self as SchemaStatementBuilder>::build(self, schema_builder)
    }

    pub fn to_string<T: SchemaBuilder>(&self, schema_builder: T) -> String {
        <Self as SchemaStatementBuilder>::to_string(self, schema_builder)
    }
}
