<?php

declare(strict_types=1);

namespace App\Doctrine\QuoteStrategy;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Internal\SQLResultCasing;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\JoinColumnMapping;
use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping;
use Doctrine\ORM\Mapping\QuoteStrategy;

use function array_map;
use function array_merge;
use function assert;
use function explode;
use function implode;

/**
 * Quote all identifiers to preserve camelCase column names in Postgres.
 */
final class AlwaysQuoteStrategy implements QuoteStrategy
{
    use SQLResultCasing;

    public function getColumnName(string $fieldName, ClassMetadata $class, AbstractPlatform $platform): string
    {
        return $platform->quoteSingleIdentifier($class->fieldMappings[$fieldName]->columnName);
    }

    public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string
    {
        $tableName = $platform->quoteSingleIdentifier($class->table['name']);

        if (!empty($class->table['schema'])) {
            return $platform->quoteSingleIdentifier($class->table['schema']).'.'.$tableName;
        }

        return $tableName;
    }

    public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
    {
        return implode('.', array_map(
            static fn (string $part) => $platform->quoteSingleIdentifier($part),
            explode('.', $definition['sequenceName']),
        ));
    }

    public function getJoinTableName(
        ManyToManyOwningSideMapping $association,
        ClassMetadata $class,
        AbstractPlatform $platform,
    ): string {
        $schema = '';

        if (isset($association->joinTable->schema)) {
            $schema = $platform->quoteSingleIdentifier($association->joinTable->schema).'.';
        }

        return $schema.$platform->quoteSingleIdentifier($association->joinTable->name);
    }

    public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string
    {
        return $platform->quoteSingleIdentifier($joinColumn->name);
    }

    public function getReferencedJoinColumnName(
        JoinColumnMapping $joinColumn,
        ClassMetadata $class,
        AbstractPlatform $platform,
    ): string {
        return $platform->quoteSingleIdentifier($joinColumn->referencedColumnName);
    }

    public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array
    {
        $quotedColumnNames = [];

        foreach ($class->identifier as $fieldName) {
            if (isset($class->fieldMappings[$fieldName])) {
                $quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform);

                continue;
            }

            $assoc = $class->associationMappings[$fieldName];
            assert($assoc->isToOneOwningSide());
            $joinColumns            = $assoc->joinColumns;
            $assocQuotedColumnNames = array_map(
                static fn (JoinColumnMapping $joinColumn) => $platform->quoteSingleIdentifier($joinColumn->name),
                $joinColumns,
            );

            $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
        }

        return $quotedColumnNames;
    }

    public function getColumnAlias(
        string $columnName,
        int $counter,
        AbstractPlatform $platform,
        ?ClassMetadata $class = null,
    ): string {
        return $this->getSQLResultCasing($platform, $columnName.'_'.$counter);
    }
}
