<?php

/**
 * MIT License. This file is part of the Propel package.
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Propel\Generator\Builder\Om;

use Propel\Generator\Builder\Om\TableMapBuilder\TableMapBuilderValidation;
use Propel\Generator\Builder\Util\EntityObjectClassNames;
use Propel\Generator\Model\ForeignKey;
use Propel\Generator\Model\IdMethod;
use Propel\Generator\Model\PropelTypes;
use Propel\Generator\Model\Table;
use Propel\Generator\Platform\PlatformInterface;

/**
 * Generates the table map class for user object model (OM).
 *
 * @author Hans Lellelid <hans@xmpl.org>
 */
class TableMapBuilder extends AbstractOMBuilder
{
    /**
     * @var \Propel\Generator\Builder\Util\EntityObjectClassNames
     */
    protected EntityObjectClassNames $tableNames;

    /**
     * @param \Propel\Generator\Model\Table $table
     */
    public function __construct(Table $table)
    {
        parent::__construct($table);
        $this->tableNames = $this->referencedClasses->useEntityObjectClassNames($table);
    }

    /**
     * @return void
    */
    #[\Override]
    protected function validateModel(): void
    {
        parent::validateModel();
        $this->disallowImplicitCollectionReplacement();
    }

    /**
     * @return void
     */
    protected function disallowImplicitCollectionReplacement(): void
    {
        TableMapBuilderValidation::validate($this);
    }

    /**
     * Gets the package for the map builder classes.
     *
     * @return string
     */
    #[\Override]
    public function getPackage(): string
    {
        return parent::getPackage() . '.Map';
    }

    /**
     * @return string|null
     */
    #[\Override]
    public function getNamespace(): ?string
    {
        $namespace = parent::getNamespace();
        if (!$namespace) {
            return 'Map';
        }

        $namespaceMap = $this->getBuildProperty('generator.objectModel.namespaceMap');
        if (!$namespaceMap) {
            return $namespace . 'Map';
        }

        return "$namespace\\$namespaceMap";
    }

    /**
     * @return string
     */
    public function getBaseTableMapClassName(): string
    {
        return 'TableMap';
    }

    /**
     * Returns the name of the current class being built.
     *
     * @return string
     */
    #[\Override]
    public function getUnprefixedClassName(): string
    {
        return $this->getTable()->getPhpName() . 'TableMap';
    }

    /**
     * Adds class phpdoc comment and opening of class.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    #[\Override]
    protected function addClassOpen(string &$script): void
    {
        $table = $this->getTable();
        $script .= "

/**
 * This class defines the structure of the '" . $table->getName() . "' table.
 *
 *";
        if ($this->getBuildProperty('generator.objectModel.addTimeStamp')) {
            $now = gmdate('c');
            $script .= "
 * This class was autogenerated by Propel " . $this->getBuildProperty('general.version') . " on:
 *
 * $now
 *";
        }
        $script .= "
 *
 * This map class is used by Propel to do runtime db structure discovery.
 * For example, the createSelectSql() method checks the type of a given column used in an
 * ORDER BY clause to know whether it needs to apply SQL to make the ORDER BY case-insensitive
 * (i.e. if it's a text column type).
 */
class " . $this->getUnqualifiedClassName() . " extends TableMap
{
    use InstancePoolTrait;
    use TableMapTrait;

";
    }

    /**
     * Specifies the methods that are added as part of the map builder class.
     * This can be overridden by subclasses that wish to add more methods.
     *
     * @see ObjectBuilder::addClassBody()
     *
     * @param string $script
     *
     * @return void
     */
    #[\Override]
    protected function addClassBody(string &$script): void
    {
        $table = $this->getTable();

        $this->declareClasses(
            '\Propel\Runtime\ActiveQuery\ColumnResolver\ColumnExpression\LocalColumnExpression',
            '\Propel\Runtime\ActiveQuery\InstancePoolTrait',
            '\Propel\Runtime\Map\TableMap',
            '\Propel\Runtime\Map\TableMapTrait',
            '\Propel\Runtime\Map\RelationMap',
            '\Propel\Runtime\ActiveQuery\Criteria',
            '\Propel\Runtime\Connection\ConnectionInterface',
            '\Propel\Runtime\Exception\PropelException',
            '\Propel\Runtime\DataFetcher\DataFetcherInterface',
            '\Propel\Runtime\Propel',
        );

        $script .= $this->addConstants();

        $this->addInheritanceColumnConstants($script);
        if ($table->hasValueSetColumns()) {
            $this->addValueSetColumnConstants($script);
        }

        // apply behaviors
        $this->applyBehaviorModifier('staticConstants', $script, '    ');
        $this->applyBehaviorModifier('staticAttributes', $script, '    ');
        $this->applyBehaviorModifier('staticMethods', $script, '    ');

        $this->addAttributes($script);

        $script .= $this->addFieldsAttributes();
        $this->addNormalizedColumnNameMap($script);

        if ($table->hasValueSetColumns()) {
            $this->addValueSetColumnAttributes($script);
            $this->addGetValueSets($script);
            $this->addGetValueSet($script);
        }

        $this->addInitialize($script);
        $this->addBuildRelations($script);
        $this->addGetBehaviors($script);

        $script .= $this->addInstancePool();
        $script .= $this->addClearRelatedInstancePool();

        $this->addGetPrimaryKeyHash($script);
        $this->addGetPrimaryKeyFromRow($script);

        $this->addGetOMClassMethod($script);
        $this->addPopulateObject($script);
        $this->addPopulateObjects($script);

        if (!$table->isAlias()) {
            $this->addSelectMethods($script);
            $this->addGetTableMap($script);
        }

        $this->addDoDelete($script);
        $this->addDoDeleteAll($script);

        $this->addDoInsert($script);
    }

    /**
     * Adds the addSelectColumns(), doCount(), etc. methods.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addSelectMethods(string &$script): void
    {
        $this->addAddSelectColumns($script);
        $this->addRemoveSelectColumns($script);
    }

    /**
     * Adds any constants needed for this TableMap class.
     *
     * @return string
     */
    protected function addConstants(): string
    {
        $collectionBuilder = $this->builderFactory->createObjectCollectionBuilder($this->getTable());

        return $this->renderTemplate('tableMapConstants', [
            'className' => $this->getClasspath(),
            'dbName' => $this->getDatabase()->getName(),
            'tableName' => $this->getTable()->getName(),
            'tablePhpName' => $this->getTable()->getPhpName(),
            'omClassName' => $this->getTable()->isAbstract() ? '' : addslashes($this->getStubObjectBuilder()->getFullyQualifiedClassName()),
            'classPath' => $this->getStubObjectBuilder()->getClasspath(),
            'nbColumns' => $this->getTable()->getNumColumns(),
            'nbLazyLoadColumns' => $this->getTable()->getNumLazyLoadColumns(),
            'nbHydrateColumns' => $this->getTable()->getNumColumns() - $this->getTable()->getNumLazyLoadColumns(),
            'columns' => $this->getTable()->getColumns(),
            'stringFormat' => $this->getTable()->getDefaultStringFormat(),
            'objectCollectionClassName' => $collectionBuilder->resolveTableCollectionClassNameFq(),
            'objectCollectionType' => $collectionBuilder->resolveTableCollectionClassType(),
        ]);
    }

    /**
     * Adds the COLUMN_NAME constant to the class definition.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addColumnNameConstants(string &$script): void
    {
        $tableName = $this->getTable()->getName();

        foreach ($this->getTable()->getColumns() as $column) {
            $columnName = $column->getName();
            $columnConstant = $column->getConstantName();

            $script .= "
    /**
     * The column name for the $columnName field
     */
    public const {$columnConstant} = '$tableName.$columnName';\n";
        }
    }

    /**
     * Adds the valueSet constants for ENUM and SET columns.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addValueSetColumnConstants(string &$script): void
    {
        foreach ($this->getTable()->getColumns() as $column) {
            if (!$column->isValueSetType()) {
                continue;
            }
            $columnName = $column->getName();
            $columnConstant = $column->getConstantName();
            $script .= "
    /** The enumerated values for the $columnName field */";

            foreach ($column->getValueSet() as $value) {
                $valueSetConstant = $this->getValueSetConstant($value);
                $script .= "
    public const {$columnConstant}_{$valueSetConstant} = '$value';";
            }
            $script .= "\n";
        }
    }

    /**
     * Adds the valueSet attributes for ENUM columns.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addValueSetColumnAttributes(string &$script): void
    {
        $script .= "
    /**
     * The enumerated values for this table
     *
     * @var array<string, array<string>>
     */
    protected static \$enumValueSets = [";
        foreach ($this->getTable()->getColumns() as $col) {
            if (!$col->isValueSetType()) {
                continue;
            }
            $columnConstant = $col->getConstantName();
            $columnConstantFq = $col->getFQConstantName();
            $script .= "
        $columnConstantFq => [";

            foreach ($col->getValueSet() as $value) {
                $valueSetConstant = $this->getValueSetConstant($value);
                $script .= "
            self::{$columnConstant}_{$valueSetConstant},";
            }

            $script .= '
        ],';
        }
        $script .= "
    ];\n";
    }

    /**
     * Adds the getValueSets() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetValueSets(string &$script): void
    {
        $script .= "
    /**
     * Gets the list of values for all ENUM and SET columns
     *
     * @return array<string, array<string>>
     */
    public static function getValueSets(): array
    {
      return static::\$enumValueSets;
    }
";
    }

    /**
     * Adds the getValueSet() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetValueSet(string &$script): void
    {
        $script .= "
    /**
     * Gets the list of values for an ENUM or SET column
     *
     * @param string \$colname
     *
     * @return array<string> list of possible values for the column
     */
    public static function getValueSet(string \$colname): array
    {
        \$valueSets = self::getValueSets();

        return \$valueSets[\$colname];
    }
";
    }

    /**
     * Adds the CLASSKEY_* and CLASSNAME_* constants used for inheritance.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    public function addInheritanceColumnConstants(string &$script): void
    {
        $col = $this->getTable()->getChildrenColumn();

        if (!$col || !$col->isEnumeratedClasses()) {
            return;
        }

        $isNumericKey = $col->isNumericType() && $col->getType() !== PropelTypes::DECIMAL;

        foreach ($col->getChildren() as $child) {
            $childBuilder = $this->getMultiExtendObjectBuilder();
            $childBuilder->setChild($child);
            $fqcn = addslashes($childBuilder->getFullyQualifiedClassName());

            $suffix = $child->getConstantSuffix();
            $key = $isNumericKey ? $child->getKey() : "'" . $child->getKey() . "'";
            $script .= "
    /** A key representing a particular subclass */
    public const CLASSKEY_{$suffix} = $key;
";

            if (strtoupper($child->getClassName()) != $child->getConstantSuffix()) {
                $childClassLiteral = strtoupper($child->getClassName());
                $script .= "
    /** A key representing a particular subclass */
    public const CLASSKEY_{$childClassLiteral} = '$fqcn';
";
            }

            $script .= "
    /** A class that can be returned by this tableMap. */
    public const CLASSNAME_{$suffix} = '$fqcn';
";
        }
    }

    /**
     * @param string $value
     *
     * @return string
     */
    protected function getValueSetConstant(string $value): string
    {
        return strtoupper(preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $value));
    }

    /**
     * Adds any attributes needed for this TableMap class.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addAttributes(string &$script): void
    {
    }

    /**
     * @return string
     */
    protected function addFieldsAttributes(): string
    {
        $tableColumns = $this->getTable()->getColumns();

        $fieldNamesPhpName = '';
        $fieldNamesCamelCaseName = '';
        $fieldNamesColname = '';
        $fieldNamesRawColname = '';
        $fieldNamesFieldName = '';
        $fieldNamesNum = '';

        $fieldKeysPhpName = '';
        $fieldKeysCamelCaseName = '';
        $fieldKeysColname = '';
        $fieldKeysRawColname = '';
        $fieldKeysFieldName = '';
        $fieldKeysNum = '';

        foreach ($tableColumns as $num => $col) {
            $fieldNamesPhpName .= "'" . $col->getPhpName() . "', ";
            $fieldNamesCamelCaseName .= "'" . $col->getCamelCaseName() . "', ";
            $fieldNamesColname .= $this->getColumnConstant($col, $this->getTableMapClass()) . ', ';
            $fieldNamesRawColname .= "'" . $col->getConstantName() . "', ";
            $fieldNamesFieldName .= "'" . $col->getName() . "', ";
            $fieldNamesNum .= "$num, ";

            $fieldKeysPhpName .= "'" . $col->getPhpName() . "' => $num, ";
            $fieldKeysCamelCaseName .= "'" . $col->getCamelCaseName() . "' => $num, ";
            $fieldKeysColname .= $this->getColumnConstant($col, $this->getTableMapClass()) . " => $num, ";
            $fieldKeysRawColname .= "'" . $col->getConstantName() . "' => $num, ";
            $fieldKeysFieldName .= "'" . $col->getName() . "' => $num, ";
            $fieldKeysNum .= "$num, ";
        }

        return $this->renderTemplate('tableMapFields', [
                'fieldNamesPhpName' => $fieldNamesPhpName,
                'fieldNamesCamelCaseName' => $fieldNamesCamelCaseName,
                'fieldNamesColname' => $fieldNamesColname,
                'fieldNamesRawColname' => $fieldNamesRawColname,
                'fieldNamesFieldName' => $fieldNamesFieldName,
                'fieldNamesNum' => $fieldNamesNum,
                'fieldKeysPhpName' => $fieldKeysPhpName,
                'fieldKeysCamelCaseName' => $fieldKeysCamelCaseName,
                'fieldKeysColname' => $fieldKeysColname,
                'fieldKeysRawColname' => $fieldKeysRawColname,
                'fieldKeysFieldName' => $fieldKeysFieldName,
                'fieldKeysNum' => $fieldKeysNum,
        ]);
    }

    /**
     * @param string $script
     *
     * @return void
     */
    protected function addNormalizedColumnNameMap(string &$script): void
    {
        $table = $this->getTable();
        $tableColumns = $table->getColumns();

        $arrayString = '';
        foreach ($tableColumns as $column) {
            $variants = [
                $column->getPhpName(), // ColumnName => COLUMN_NAME
                $table->getPhpName() . '.' . $column->getPhpName(), // TableName.ColumnName => COLUMN_NAME
                $column->getCamelCaseName(), // columnName => COLUMN_NAME
                $table->getCamelCaseName() . '.' . $column->getCamelCaseName(), // tableName.columnName => COLUMN_NAME
                $this->getColumnConstant($column, $this->getTableMapClass()), // TableNameTableMap::COL_COLUMN_NAME => COLUMN_NAME
                $column->getConstantName(), // COL_COLUMN_NAME => COLUMN_NAME
                $column->getName(), // column_name => COLUMN_NAME
                $table->getName() . '.' . $column->getName(), // table_name.column_name => COLUMN_NAME
            ];

            $variants = array_unique($variants);

            $normalizedName = strtoupper($column->getName());
            array_walk($variants, static function ($variant) use (&$arrayString, $normalizedName): void {
                $arrayString .= PHP_EOL . "        '{$variant}' => '{$normalizedName}',";
            });
        }

        $script .= '
    /**
     * Holds a list of column names and their normalized version.
     *
     * @var array<string, string>
     */
    protected $normalizedColumnNameMap = [' . $arrayString . PHP_EOL
            . '    ];' . PHP_EOL;
    }

    /**
     * Closes class.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    #[\Override]
    protected function addClassClose(string &$script): void
    {
        $script .= "}\n";
        $this->applyBehaviorModifier('tableMapFilter', $script, '');
    }

    /**
     * Adds the addInitialize() method to the table map class.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addInitialize(string &$script): void
    {
        $table = $this->getTable();
        /** @var \Propel\Generator\Platform\DefaultPlatform $platform */
        $platform = $this->getPlatform();

        $script .= "
    /**
     * Initialize the table attributes and columns
     * Relations are not initialized by this method since they are lazy loaded
     *
     * @throws \Propel\Runtime\Exception\PropelException
     *
     * @return void
     */
    public function initialize(): void
    {
        // attributes
        \$this->setName('" . $table->getName() . "');
        \$this->setPhpName('" . $table->getPhpName() . "');
        \$this->setIdentifierQuoting(" . ($table->isIdentifierQuotingEnabled() ? 'true' : 'false') . ");
        \$this->setClassName('" . addslashes($this->getStubObjectBuilder()->getFullyQualifiedClassName()) . "');
        \$this->setPackage('" . parent::getPackage() . "');";
        if ($table->getIdMethod() === 'native') {
            $script .= "
        \$this->setUseIdGenerator(true);";
        } else {
            $script .= "
        \$this->setUseIdGenerator(false);";
        }

        if ($table->getIdMethodParameters()) {
            $params = $table->getIdMethodParameters();
            $imp = $params[0];
            $script .= "
        \$this->setPrimaryKeyMethodInfo('" . $imp->getValue() . "');";
        } elseif ($table->getIdMethod() == IdMethod::NATIVE && ($platform->getNativeIdMethod() == PlatformInterface::SEQUENCE || $platform->getNativeIdMethod() == PlatformInterface::SERIAL)) {
            $script .= "
        \$this->setPrimaryKeyMethodInfo('" . $platform->getSequenceName($table) . "');";
        }

        if ($this->getTable()->getChildrenColumn()) {
            $script .= "
        \$this->setSingleTableInheritance(true);";
        }

        if ($this->getTable()->getIsCrossRef()) {
            $script .= "
        \$this->setIsCrossRef(true);";
        }

        // Add columns to map
            $script .= "
        // columns";
        foreach ($table->getColumns() as $col) {
            $columnName = $col->getName();
            $phpName = $col->getPhpName();
            $size = $col->getSize() ?: 'null';
            $default = $col->getDefaultValueString();
            $columnType = $col->getType();
            $isNotNull = $col->isNotNull() ? 'true' : 'false';

            if (!$col->isForeignKey()) {
                $method = $col->isPrimaryKey() ? 'addPrimaryKey' : 'addColumn';
                $script .= "
        \$this->$method('$columnName', '$phpName', '$columnType', $isNotNull, $size, $default);";
            } else {
                $method = $col->isPrimaryKey() ? 'addForeignPrimaryKey' : 'addForeignKey';
                foreach ($col->getForeignKeys() as $fk) {
                    $foreignTableName = $fk->getForeignTableName();
                    $mappedForeignColumn = $fk->getMappedForeignColumn($col->getName());
                    $script .= "
        \$this->$method('$columnName', '$phpName', '$columnType', '$foreignTableName', '$mappedForeignColumn', $isNotNull, $size, $default);";
                }
            }

            if ($col->isValueSetType()) {
                $valueSet = '[\'' . implode('\', \'', $col->getValueSet()) . '\']';
                $script .= "
        \$this->getColumn('$columnName')->setValueSet($valueSet);";
            }

            if ($col->isPrimaryString()) {
                $script .= "
        \$this->getColumn('$columnName')->setPrimaryString(true);";
            }
        }

        $script .= "
    }
";
    }

    /**
     * Adds the method that build the RelationMap objects
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addBuildRelations(string &$script): void
    {
        $addRelationStatements = '';

        foreach ($this->getTable()->getForeignKeys() as $fkey) {
            $relationName = $fkey->getIdentifier();
            $addRelationStatements .= $this->buildAddRelationStatement($relationName, $fkey, 'RelationMap::MANY_TO_ONE', false, false, 'null');
        }

        foreach ($this->getTable()->getReferrers() as $fkey) {
            $relationName = $fkey->getIdentifierReversed();
            $cardinalityConstant = 'RelationMap::ONE_TO_' . ($fkey->isLocalPrimaryKey() ? 'ONE' : 'MANY');
            $pluralName = $fkey->isLocalPrimaryKey() ? 'null' : "'" . $this->getRefFKPhpNameAffix($fkey, true) . "'";
            $addRelationStatements .= $this->buildAddRelationStatement($relationName, $fkey, $cardinalityConstant, true, false, $pluralName);
        }

        foreach ($this->getTable()->getCrossRelations() as $crossFKs) {
            foreach ($crossFKs->getCrossForeignKeys() as $fk) {
                $relationName = $fk->getIdentifier();
                $pluralName = "'" . $this->getFKPhpNameAffix($fk, true) . "'";
                $addRelationStatements .= $this->buildAddRelationStatement($relationName, $fk, 'RelationMap::MANY_TO_MANY', false, true, $pluralName);
            }
        }

        $script .= "
    /**
     * Build the RelationMap objects for this table relationships
     *
     * @return void
     */
    public function buildRelations(): void
    {{$addRelationStatements}
    }
";
    }

    /**
     * @param string $relationName
     * @param \Propel\Generator\Model\ForeignKey $fkey
     * @param string $cardinalityConstant
     * @param bool $isBack
     * @param bool $isCrossFk
     * @param string $pluralName
     *
     * @return string
     */
    protected function buildAddRelationStatement(
        string $relationName,
        ForeignKey $fkey,
        string $cardinalityConstant,
        bool $isBack,
        bool $isCrossFk,
        string $pluralName
    ): string {
        $table = $isBack ? $fkey->getTable() : $fkey->getForeignTable();
        $fkTableNameFq = addslashes($this->getNewStubObjectBuilder($table)->getFullyQualifiedClassName());
        $joinCondition = $isCrossFk ? '[]' : $this->arrayToString($fkey->getNormalizedMap($fkey->getMapping()));
        $onDelete = $fkey->hasOnDelete() ? "'{$fkey->getOnDelete()}'" : 'null';
        $onUpdate = $fkey->hasOnUpdate() ? "'{$fkey->getOnUpdate()}'" : 'null';
        $isPolymorphic = !$isCrossFk && $fkey->isPolymorphic() ? 'true' : 'false';

        return "
        \$this->addRelation(
            '$relationName',
            '$fkTableNameFq',
            $cardinalityConstant,
            $joinCondition,
            $onDelete,
            $onUpdate,
            $pluralName,
            $isPolymorphic
        );";
    }

    /**
     * Adds the behaviors getter
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetBehaviors(string &$script): void
    {
        $behaviors = $this->getTable()->getBehaviors();
        if (!$behaviors) {
            return;
        }

        $stringifiedBehaviors = [];
        foreach ($behaviors as $behavior) {
            $id = $behavior->getId();
            $params = $this->stringify($behavior->getParameters());
            $stringifiedBehaviors[] = "'$id' => $params,";
        }
        $itemsString = implode(PHP_EOL . '            ', $stringifiedBehaviors);

        $script .= "
    /**
     *
     * Gets the list of behaviors registered for this table
     *
     * @return array<string, array> Associative array (name => parameters) of behaviors
     */
    public function getBehaviors(): array
    {
        return [
            $itemsString
        ];
    }
";
    }

    /**
     * @param array|string|float|int|bool|null $value
     *
     * @return string
     */
    protected function stringify($value): string
    {
        if (!is_array($value)) {
            return var_export($value, true);
        }

        $items = [];
        foreach ($value as $key => $arrayValue) {
            $keyString = var_export($key, true);
            $valString = $this->stringify($arrayValue);
            $items[] = "$keyString => $valString";
        }
        $itemsCsv = implode(', ', $items);

        return "[$itemsCsv]";
    }

    /**
     * Adds the PHP code to return a instance pool key for the passed-in primary key variable names.
     *
     * @param array<string>|string $pkphp An array of PHP var names / method calls representing complete pk.
     *
     * @return string
     */
    public function getInstancePoolKeySnippet($pkphp): string
    {
        $pkphp = (array)$pkphp; // make it an array if it is not.
        $format = '%1$s === null || is_scalar(%1$s) || is_callable([%1$s, \'__toString\']) ? (string)%1$s : %1$s';
        $statements = array_map(fn ($keyLiteral) => sprintf($format, $keyLiteral), $pkphp);

        return (count($statements) === 1) ? $statements[0] : 'serialize([' . implode(', ', $statements) . '])';
    }

    /**
     * @return string
     */
    public function addInstancePool(): string
    {
        // No need to override instancePool if the PK is not composite
        if (!$this->getTable()->hasCompositePrimaryKey()) {
            return '';
        }

        $pks = $this->getTable()->getPrimaryKey();
        if (!count($pks)) {
            return '';
        }

        $add = [];
        $removeObjects = [];
        foreach ($pks as $column) {
            $columnName = $column->getPhpName();
            $add[] = "\$obj->get{$columnName}()";
            $removeObjects[] = "\$value->get{$columnName}()";
        }
        $addInstancePoolKeySnippet = $this->getInstancePoolKeySnippet($add);
        $removeInstancePoolKeySnippetObjects = $this->getInstancePoolKeySnippet($removeObjects);

        $removePks = [];
        $nbPks = count($pks);
        for ($i = 0; $i < $nbPks; $i++) {
            $removePks[] = "\$value[$i]";
        }
        $removeInstancePoolKeySnippetPks = $this->getInstancePoolKeySnippet($removePks);

        return $this->renderTemplate('tableMapInstancePool', [
                'modelClassName' => $this->tableNames->useObjectBaseClassName(),
                'modelClassNameFq' => $this->tableNames->useObjectBaseClassName(false),
                'addInstancePoolKeySnippet' => $addInstancePoolKeySnippet,
                'removeInstancePoolKeySnippetObjects' => $removeInstancePoolKeySnippetObjects,
                'removeInstancePoolKeySnippetPks' => $removeInstancePoolKeySnippetPks,
                'countPks' => count($pks),
        ]);
    }

    /**
     * @return string
     */
    public function addClearRelatedInstancePool(): string
    {
        $table = $this->getTable();
        $relatedClassNames = [];

        // Handle ON DELETE CASCADE for updating instance pool
        foreach ($table->getReferrers() as $fk) {
            // $fk is the foreign key in the other table, so localTableName will
            // actually be the table name of other table
            $tblFK = $fk->getTable();

            $joinedTableTableMapBuilder = $this->getNewTableMapBuilder($tblFK)->getTableMapBuilder();
            $tableMapClassName = $this->declareClassFromBuilder($joinedTableTableMapBuilder, true);

            if (!$tblFK->isForReferenceOnly()) {
                // we can't perform operations on tables that are
                // not within the schema (i.e. that we have no map for, etc.)

                if ($fk->getOnDelete() === ForeignKey::CASCADE || $fk->getOnDelete() === ForeignKey::SETNULL) {
                    $relatedClassNames[$tableMapClassName] = $tableMapClassName;
                }
            }
        }

        if (count($relatedClassNames) == 0) {
            return '';
        }

        return $this->renderTemplate('tableMapClearRelatedInstancePool', [
            'tableName' => $table->getName(),
            'relatedClassNames' => $relatedClassNames,
        ]);
    }

    /**
     * Checks whether any registered behavior on that table has a modifier for a hook
     *
     * @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
     * @param string $modifier
     *
     * @return bool
     */
    #[\Override]
    public function hasBehaviorModifier(string $hookName, string $modifier = ''): bool
    {
        return parent::hasBehaviorModifier($hookName, 'TableMapBuilderModifier');
    }

    /**
     * Checks whether any registered behavior on that table has a modifier for a hook
     *
     * @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
     * @param string $script The script will be modified in this method.
     * @param string $tab
     *
     * @return void
     */
    public function applyBehaviorModifier(string $hookName, string &$script, string $tab = '        '): void
    {
        $this->applyBehaviorModifierBase($hookName, 'TableMapBuilderModifier', $script, $tab);
    }

    /**
     * Adds method to get a version of the primary key that can be used as a unique key for identifier map.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetPrimaryKeyHash(string &$script): void
    {
        // We have to iterate through all the columns so that we know the offset of the primary
        // key columns.
        $n = 0;
        $pk = [];
        $cond = [];
        foreach ($this->getTable()->getColumns() as $col) {
            if (!$col->isLazyLoad()) {
                if ($col->isPrimaryKey()) {
                    $part = "\$row[TableMap::TYPE_NUM == \$indexType ? $n + \$offset : static::translateFieldName('{$col->getPhpName()}', TableMap::TYPE_PHPNAME, \$indexType)]";
                    $cond[] = $part . ' === null';
                    $pk[] = $part;
                }
                $n++;
            }
        }

        $script .= "
    /**
     * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table.
     *
     * For tables with a single-column primary key, that simple pkey value will be returned.  For tables with
     * a multi-column primary key, a serialize()d version of the primary key will be returned.
     *
     * @param array \$row Resultset row.
     * @param int \$offset The 0-based offset for reading from the resultset row.
     * @param string \$indexType One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME
     *                           TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM
     *
     * @return string|null The primary key hash of the row
     */
    public static function getPrimaryKeyHashFromRow(array \$row, int \$offset = 0, string \$indexType = TableMap::TYPE_NUM): ?string
    {";
        if (count($pk) > 0) {
            $script .= "
        // If the PK cannot be derived from the row, return NULL.
        if (" . implode(' && ', $cond) . ") {
            return null;
        }

        return " . $this->getInstancePoolKeySnippet($pk) . ";
    }
";
        } else {
            $script .= "
        return null;
    }
";
        }
    }

    /**
     * Adds method to get the primary key from a row
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetPrimaryKeyFromRow(string &$script): void
    {
        $script .= "
    /**
     * Retrieves the primary key from the DB resultset row
     * For tables with a single-column primary key, that simple pkey value will be returned. 
     * For tables with a multi-column primary key, an array of the primary key columns will be returned.
     *
     * @param array \$row Resultset row.
     * @param int \$offset The 0-based offset for reading from the resultset row.
     * @param string \$indexType One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME
     *                           TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM
     *
     * @return mixed The primary key of the row
     */
    public static function getPrimaryKeyFromRow(array \$row, int \$offset = 0, string \$indexType = TableMap::TYPE_NUM)
    {";

        // We have to iterate through all the columns so that we
        // know the offset of the primary key columns.
        $table = $this->getTable();
        $n = 0;

        if ($table->hasCompositePrimaryKey()) {
            $script .= "
            \$pks = [];
            ";

            foreach ($table->getColumns() as $col) {
                if (!$col->isLazyLoad()) {
                    if ($col->isPrimaryKey()) {
                        $script .= '
        $pks[] = ' . ($col->isPhpObjectType() ? 'new ' . $col->getPhpType() . '(' : '(' . $col->getPhpType() . ') ') . "\$row[
            \$indexType == TableMap::TYPE_NUM
                ? $n + \$offset
                : self::translateFieldName('{$col->getPhpName()}', TableMap::TYPE_PHPNAME, \$indexType)
        ]" . ($col->isPhpObjectType() ? ')' : '') . ';';
                    }
                    $n++;
                }
            }

            $script .= "

        return \$pks;";
        } else {
            $pk = "''";
            foreach ($table->getColumns() as $col) {
                if ($col->isLazyLoad() || !$col->isPrimaryKey()) {
                    $n++;

                    continue;
                }

                $isObject = $col->isPhpObjectType();
                $phpType = $col->getPhpType();
                $columnName = $col->getPhpName();
                $varName = '$' . lcfirst($columnName) . 'Ix';
                $script .= "
        $varName = \$indexType === TableMap::TYPE_NUM
            ? $n + \$offset
            : self::translateFieldName('$columnName', TableMap::TYPE_PHPNAME, \$indexType);
                ";
                $rowAccess = "\$row[$varName]";
                $pk = $isObject ? "new $phpType ($rowAccess)" : "($phpType)$rowAccess";
                $n++;
            }

            $script .= "
        return $pk;";
        }

        $script .= "
    }
    ";
    }

    /**
     * Adds the correct getOMClass() method, depending on whether this table uses inheritance.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetOMClassMethod(string &$script): void
    {
        $table = $this->getTable();
        if ($table->getChildrenColumn()) {
            $this->addGetOMClassInheritance($script);
        } else {
            if ($table->isAbstract()) {
                $this->addGetOMClassNoInheritanceAbstract($script);
            } else {
                $this->addGetOMClassNoInheritance($script);
            }
        }
    }

    /**
     * Adds a getOMClass() for non-abstract tables that have inheritance.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetOMClassInheritance(string &$script): void
    {
        $col = $this->getTable()->getChildrenColumn();
        $script .= "
    /**
     * The returned Class will contain objects of the default type or
     * objects that inherit from the default.
     *
     * @param array \$row ConnectionInterface result row.
     * @param int \$colNum Column to examine for OM class information (first is 0).
     * @param bool \$withPrefix Whether to return the path with the class name
     *
     * @throws \Propel\Runtime\Exception\PropelException Any exceptions caught during processing will be
     *                         rethrown wrapped into a PropelException.
     *
     * @return string The OM class
     */
    public static function getOMClass(array \$row, int \$colNum, bool \$withPrefix = true): string
    {
        try {
";
        if ($col->isEnumeratedClasses()) {
            $script .= "
            \$omClass = null;
            \$classKey = \$row[\$colNum + " . ($col->getPosition() - 1) . "];

            switch (\$classKey) {
";
            foreach ($col->getChildren() as $child) {
                $script .= "
                case {$this->getTableMapClassName()}::CLASSKEY_" . $child->getConstantSuffix() . ":
                    \$omClass = {$this->getTableMapClassName()}::CLASSNAME_" . $child->getConstantSuffix() . ";
                    break;
";
            } /* foreach */
            $script .= "
                default:
                    \$omClass = \$withPrefix
                        ? {$this->getTableMapClassName()}::CLASS_DEFAULT
                        : {$this->getTableMapClassName()}::OM_CLASS;
";
            $script .= "
            } // switch
            if (!\$withPrefix) {
                \$omClass = preg_replace('#\.#', '\\\\', \$omClass);
            }
";
        } else { /* if not enumerated */
            $script .= "
            \$omClass = \$row[\$colNum + " . ($col->getPosition() - 1) . "];
            \$omClass = preg_replace('#\.#', '\\\\', '.'.\$omClass);
";
        }
        $script .= "
        } catch (\Exception \$e) {
            throw new PropelException('Unable to get OM class.', 0, \$e);
        }

        return \$omClass;
    }
";
    }

    /**
     * Adds a getOMClass() for non-abstract tables that do not use inheritance.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetOMClassNoInheritance(string &$script): void
    {
        $script .= "
    /**
     * The class that the tableMap will make instances of.
     *
     * If \$withPrefix is true, the returned path
     * uses a dot-path notation which is translated into a path
     * relative to a location on the PHP include_path.
     * (e.g. path.to.MyClass -> 'path/to/MyClass.php')
     *
     * @param bool \$withPrefix Whether to return the path with the class name
     *
     * @return string path.to.ClassName
     */
    public static function getOMClass(bool \$withPrefix = true): string
    {
        return \$withPrefix ? " . $this->getTableMapClass() . '::CLASS_DEFAULT : ' . $this->getTableMapClass() . "::OM_CLASS;
    }
";
    }

    /**
     * Adds a getOMClass() signature for abstract tables that do not have inheritance.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetOMClassNoInheritanceAbstract(string &$script): void
    {
        $objectClassName = $this->registerOwnClassIdentifier();

        $script .= "
    /**
     * The class that the tableMap will make instances of.
     *
     * This method must be overridden by the stub subclass, because
     * $objectClassName is declared abstract in the schema.
     *
     * @param bool \$withPrefix
     *
     * @return string
     */
    public static function getOMClass(bool \$withPrefix = true): string
    {
        throw new PropelException('$objectClassName is declared abstract, it cannot be instantiated.');
    }
";
    }

    /**
     * Adds the populateObject() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addPopulateObject(string &$script): void
    {
        $table = $this->getTable();
        $script .= "
    /**
     * Populates an object of the default type or an object that inherit from the default.
     *
     * @param array \$row Row returned by DataFetcher->fetch().
     * @param int \$offset The 0-based offset for reading from the resultset row.
     * @param string \$indexType The index type of \$row. Mostly DataFetcher->getIndexType().
     *                           One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME
     *                           TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM.
     *
     * @return array (" . $this->registerOwnClassIdentifier() . " object, last column rank)
     */
    public static function populateObject(array \$row, int \$offset = 0, string \$indexType = TableMap::TYPE_NUM): array
    {
        \$key = {$this->getTableMapClassName()}::getPrimaryKeyHashFromRow(\$row, \$offset, \$indexType);
        if ((\$obj = {$this->getTableMapClassName()}::getInstanceFromPool(\$key)) !== null) {
            // We no longer rehydrate the object, since this can cause data loss.
            // See http://www.propelorm.org/ticket/509
            // \$obj->hydrate(\$row, \$offset, true); // rehydrate
            \$col = \$offset + " . $this->getTableMapClass() . '::NUM_HYDRATE_COLUMNS;';
        if ($table->isAbstract()) {
            $script .= "
        } elseif (\$key === null) {
            // empty resultset, probably from a left join
            // since this table is abstract, we can't hydrate an empty object
            \$obj = null;
            \$col = \$offset + " . $this->getTableMapClass() . '::NUM_HYDRATE_COLUMNS;';
        }

        $clsLocation = $table->getChildrenColumn()
            ? 'static::getOMClass($row, $offset, false)'
            : $this->getTableMapClass() . '::OM_CLASS';

        $script .= "
        } else {
            \$cls = $clsLocation;
            /** @var {$this->registerOwnClassIdentifier()} \$obj */
            \$obj = new \$cls();
            \$col = \$obj->hydrate(\$row, \$offset, false, \$indexType);
            {$this->getTableMapClassName()}::addInstanceToPool(\$obj, \$key);
        }

        return [\$obj, \$col];
    }
";
    }

    /**
     * Adds the populateObjects() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addPopulateObjects(string &$script): void
    {
        $table = $this->getTable();
        $script .= "
    /**
     * The returned array will contain objects of the default type or
     * objects that inherit from the default.
     *
     * @param \Propel\Runtime\DataFetcher\DataFetcherInterface \$dataFetcher
     *
     * @return array<object>
     */
    public static function populateObjects(DataFetcherInterface \$dataFetcher): array
    {
        \$results = [];
    ";
        if (!$table->getChildrenColumn()) {
            $script .= "
        // set the class once to avoid overhead in the loop
        \$cls = static::getOMClass(false);";
        }

        $script .= "
        // populate the object(s)
        while (\$row = \$dataFetcher->fetch()) {
            \$key = {$this->getTableMapClassName()}::getPrimaryKeyHashFromRow(\$row, 0, \$dataFetcher->getIndexType());
            if ((\$obj = {$this->getTableMapClassName()}::getInstanceFromPool(\$key)) !== null) {
                // We no longer rehydrate the object, since this can cause data loss.
                // See http://www.propelorm.org/ticket/509
                // \$obj->hydrate(\$row, 0, true); // rehydrate
                \$results[] = \$obj;
            } else {";

        if ($table->getChildrenColumn()) {
            $script .= "
                // class must be set each time from the record row
                \$cls = static::getOMClass(\$row, 0);
                \$cls = preg_replace('#\.#', '\\\\', \$cls);
                /** @var {$this->tableNames->useObjectStubClassName(false)} \$obj */
                " . $this->buildObjectInstanceCreationCode('$obj', '$cls') . "
                \$obj->hydrate(\$row);
                \$results[] = \$obj;
                {$this->getTableMapClassName()}::addInstanceToPool(\$obj, \$key);";
        } else {
            $script .= "
                \$obj = new {$this->tableNames->useObjectStubClassName()}();
                \$obj->hydrate(\$row);
                \$results[] = \$obj;
                {$this->getTableMapClassName()}::addInstanceToPool(\$obj, \$key);";
        }
        $script .= "
            } // if key exists
        }

        return \$results;
    }\n";
    }

    /**
     * Adds the addSelectColumns() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addAddSelectColumns(string &$script): void
    {
        $script .= "
    /**
     * Add all the columns needed to create a new object.
     *
     * Note: any columns that were marked with lazyLoad=\"true\" in the
     * XML schema will not be added to the select list and only loaded
     * on demand.
     *
     * @param \Propel\Runtime\ActiveQuery\Criteria \$criteria Object containing the columns to add.
     * @param string|null \$alias Optional table alias
     *
     * @return void
     */
    public static function addSelectColumns(Criteria \$criteria, ?string \$alias = null): void
    {
        \$tableMap = static::getTableMap();
        \$tableAlias = \$alias ?: '{$this->getTable()->getName()}';";
        foreach ($this->getTable()->getColumns() as $col) {
            if ($col->isLazyLoad()) {
                continue;
            }
            $normalizedColumnName = strtoupper($col->getName());
            $script .= "
        \$criteria->addSelectColumn(new LocalColumnExpression(\$criteria, \$tableAlias, \$tableMap->columns['$normalizedColumnName']));";
        }
        $script .= "
    }
";
    }

 // addAddSelectColumns()

    /**
     * Adds the removeSelectColumns() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addRemoveSelectColumns(string &$script): void
    {
        $script .= "
    /**
     * Remove all the columns needed to create a new object.
     *
     * Note: any columns that were marked with lazyLoad=\"true\" in the
     * XML schema will not be removed as they are only loaded on demand.
     *
     * @param \Propel\Runtime\ActiveQuery\Criteria \$criteria Object containing the columns to remove.
     * @param string|null \$alias Optional table alias
     *
     * @throws \Propel\Runtime\Exception\PropelException Any exceptions caught during processing will be
     *                         rethrown wrapped into a PropelException.
     *
     * @return void
     */
    public static function removeSelectColumns(Criteria \$criteria, ?string \$alias = null): void
    {
        if (\$alias === null) {";
        foreach ($this->getTable()->getColumns() as $col) {
            if (!$col->isLazyLoad()) {
                $script .= "
            \$criteria->removeSelectColumn({$col->getFQConstantName()});";
            } // if !col->isLazyLoad
        } // foreach
        $script .= "
        } else {";
        foreach ($this->getTable()->getColumns() as $col) {
            if (!$col->isLazyLoad()) {
                $script .= "
            \$criteria->removeSelectColumn(\$alias . '." . $col->getName() . "');";
            } // if !col->isLazyLoad
        } // foreach
        $script .= "
        }";
        $script .= "
    }
";
    }

 // addRemoveSelectColumns()

    /**
     * Adds the getTableMap() method which is a convenience method for apps to get DB metadata.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addGetTableMap(string &$script): void
    {
        $tableMapClass = $this->getTableMapClass();

        $script .= "
    /**
     * Returns the TableMap related to this object.
     * This method is not needed for general use but a specific application could have a need.
     *
     * @return static
     */
    public static function getTableMap(): TableMap
    {
        return Propel::getServiceContainer()->getDatabaseMap({$tableMapClass}::DATABASE_NAME)->getTable({$tableMapClass}::TABLE_NAME);
    }
";
    }

    /**
     * Adds the doDeleteAll() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addDoDeleteAll(string &$script): void
    {
        $table = $this->getTable();
        $script .= "
    /**
     * Deletes all rows from the " . $table->getName() . " table.
     *
     * @param \Propel\Runtime\Connection\ConnectionInterface|null \$con the connection to use
     *
     * @return int The number of affected rows (if supported by underlying database driver).
     */
    public static function doDeleteAll(?ConnectionInterface \$con = null): int
    {
        return " . $this->getQueryClassName() . "::create()->doDeleteAll(\$con);
    }
";
    }

    /**
     * Adds the doDelete() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addDoDelete(string &$script): void
    {
        $table = $this->getTable();
        $modelClassName = $this->tableNames->useObjectStubClassName();

        $script .= "
    /**
     * @deprecated Delete via model or {$this->getQueryClassName()}.
     *
     * Performs a DELETE on the database, given a " . $this->registerOwnClassIdentifier() . " or Criteria object OR a primary key value.
     *
     * @param mixed \$values Criteria or " . $this->registerOwnClassIdentifier() . " object or primary key or array of primary keys
     *              which is used to create the DELETE statement
     * @param \Propel\Runtime\Connection\ConnectionInterface|null \$con the connection to use
     *
     * @throws \Propel\Runtime\Exception\PropelException Any exceptions caught during processing will be
     *                         rethrown wrapped into a PropelException.
     *
     * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows
     *                         if supported by native driver or if emulated using Propel.
     */
    public static function doDelete(\$values, ?ConnectionInterface \$con = null): int
    {
        trigger_deprecation('Propel', '2.0', 'TableMap::doDelete() should not be used anymore, delete via model or {$this->getQueryClassName()}');

        if (\$con === null) {
            \$con = Propel::getServiceContainer()->getWriteConnection(" . $this->getTableMapClass() . "::DATABASE_NAME);
        }

        if (\$values instanceof Criteria) {";
        $script .= "
            \$criteria = \$values;
        } elseif (\$values instanceof $modelClassName) { // it's a model object";
        if (count($table->getPrimaryKey()) > 0) {
            $script .= "
            \$criteria = \$values->buildPkeyCriteria();";
        } else {
            $script .= "
            // create criteria based on pk value
            \$criteria = \$values->buildCriteria();";
        }

        $script .= "
        } else { // it's a primary key, or an array of pks";

        if (!$table->getPrimaryKey()) {
            $class = $this->getObjectName();
            $this->declareClass('Propel\\Runtime\\Exception\\LogicException');
            $script .= "
            throw new LogicException('The $class object has no primary key');";
        } else {
            $script .= "
            \$criteria = new Criteria(" . $this->getTableMapClass() . '::DATABASE_NAME);';

            if (count($table->getPrimaryKey()) === 1) {
                $pkey = $table->getPrimaryKey();
                $col = array_shift($pkey);
                $script .= "
            \$criteria->addAnd(" . $this->getColumnConstant($col) . ', (array)$values, Criteria::IN);';
            } else {
                $script .= "
            // primary key is composite; we therefore, expect
            // the primary key passed to be an array of pkey values
            if (count(\$values) == count(\$values, COUNT_RECURSIVE)) {
                // array is not multi-dimensional
                \$values = [\$values];
            }
            foreach (\$values as \$value) {";
                $i = 0;
                foreach ($table->getPrimaryKey() as $col) {
                    if ($i === 0) {
                        $script .= "
                \$criterion = \$criteria->getNewCriterion(" . $this->getColumnConstant($col) . ", \$value[$i]);";
                    } else {
                        $script .= "
                \$criterion->addAnd(\$criteria->getNewCriterion(" . $this->getColumnConstant($col) . ", \$value[$i]));";
                    }
                    $i++;
                }
                $script .= "
                \$criteria->addOr(\$criterion);";
                $script .= "
            }";
            } /* if count(table->getPrimaryKeys()) */
        }

        $script .= "
        }

        \$query = " . $this->getQueryClassName() . "::create()->mergeWith(\$criteria);

        if (\$values instanceof Criteria) {
            {$this->getTableMapClassName()}::clearInstancePool();
        } elseif (!is_object(\$values)) { // it's a primary key, or an array of pks
            foreach ((array)\$values as \$singleval) {
                {$this->getTableMapClassName()}::removeInstanceFromPool(\$singleval);
            }
        }

        return \$query->delete(\$con);
    }
";
    }

    /**
     * Adds the doInsert() method.
     *
     * @param string $script The script will be modified in this method.
     *
     * @return void
     */
    protected function addDoInsert(string &$script): void
    {
        $table = $this->getTable();
        $tableMapClass = $this->getTableMapClass();
        $stubObjectName = $this->tableNames->useObjectStubClassName();
        $stubObjectNameFq = $this->tableNames->useObjectStubClassName(false);

        $script .= "
    /**
     * Performs an INSERT on the database, given a $stubObjectName or Criteria object.
     *
     * @param \Propel\Runtime\ActiveQuery\Criteria|$stubObjectNameFq \$criteria
     * @param \Propel\Runtime\Connection\ConnectionInterface|null \$con
     *
     * @throws \Propel\Runtime\Exception\PropelException Any exceptions caught during processing will be
     *                         rethrown wrapped into a PropelException.
     *
     * @return mixed The new primary key.
     */
    public static function doInsert(\$criteria, ?ConnectionInterface \$con = null)
    {
        if (\$con === null) {
            \$con = Propel::getServiceContainer()->getWriteConnection(" . $tableMapClass . "::DATABASE_NAME);
        }

        if (\$criteria instanceof Criteria) {
            \$criteria = clone \$criteria;
            \$criteria->turnFiltersToUpdateValues();
        } else {
            \$criteria = \$criteria->buildCriteria(); // build Criteria from $stubObjectName object
        }
";

        foreach ($table->getColumns() as $col) {
            if (
                $col->isPrimaryKey()
                && $col->isAutoIncrement()
                && $table->getIdMethod() !== 'none'
                && !$table->isAllowPkInsert()
            ) {
                $columnConstant = $this->getColumnConstant($col);
                $script .= "
        if (\$criteria->hasUpdateValue($columnConstant)) {
            throw new PropelException('Cannot insert a value for auto-increment primary key ($columnConstant)');
        }
";
                if (!$this->getPlatform()->supportsInsertNullPk()) {
                    $script .= "
        // remove pkey col since this table uses auto-increment and passing a null value for it is not valid
        \$criteria->remove(" . $this->getColumnConstant($col) . ");
";
                }
            } elseif (
                $col->isPrimaryKey()
                && $col->isAutoIncrement()
                && $table->getIdMethod() !== 'none'
                && $table->isAllowPkInsert()
                && !$this->getPlatform()->supportsInsertNullPk()
            ) {
                $script .= "
        // remove pkey col if it is null since this table does not accept that
        if (\$criteria->containsKey(" . $this->getColumnConstant($col) . ') && !$criteria->hasUpdateValue(' . $this->getColumnConstant($col) . ") ) {
            \$criteria->remove(" . $this->getColumnConstant($col) . ");
        }
";
            }
        }

        $script .= "
        // Set the correct dbName
        \$query = " . $this->getQueryClassName() . "::create()->mergeWith(\$criteria);

        // use transaction because \$criteria could contain info
        // for more than one table (I guess, conceivably)
        return \$con->transaction(function () use (\$con, \$query) {
            return \$query->doInsert(\$con);
        });
    }
";
    }
}
