<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use App\Shared\Infrastructure\Database\ColumnCommentsCatalog;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * M3 — Repertoire prestataires (ERP-132) : creation de toute la structure BDD
 * des prestataires sous le nouveau module Technique (jumeau du M2 fournisseur).
 *
 * Tables creees :
 *  - Table principale : provider (formulaire principal + Comptabilite + archive
 *    + soft-delete + Timestampable/Blamable). PAS d onglet Information.
 *  - M2M du formulaire principal : provider_category (RG-3.09),
 *    provider_site (sites du prestataire, RG-3.03 — NOUVEAU vs supplier).
 *  - Sous-collections : provider_contact (1:n), provider_address (1:n),
 *    provider_rib (1:n).
 *  - Jointures de provider_address : provider_address_site (RG-3.05),
 *    provider_address_contact, provider_address_category.
 *
 * Differences vs le M2 `supplier` (cf. spec M3 § 3.1) :
 *  - PAS d onglet Information : aucun champ description / competitors /
 *    founded_at / employees_count / revenue_amount / director_name /
 *    profit_amount / volume_forecast. Le provider est minimal : nom + compta.
 *  - AJOUT de provider_site (M2M) : sites rattaches au prestataire directement
 *    sur le formulaire principal (RG-3.03, >= 1). Sert aussi le cloisonnement
 *    par site (idx_provider_site_site, § 2.13).
 *  - provider_address SIMPLIFIEE : pas de address_type / bennes /
 *    triage_provider (specifiques fournisseur). Champs : country / postal_code
 *    / city / street / street_complement / position + M2M sites/contacts/categories.
 *
 * Referentiels comptables NON recrees : tva_mode / payment_delay / payment_type
 * / bank sont ceux du M1 (FK partagees, zero duplication — spec § 2.3).
 *
 * CategoryType PRESTATAIRE NON re-seede : il est cree par ERP-131
 * (Version20260612080000) avec ses categories de demonstration. Le M2M
 * provider_category / provider_address_category s appuie sur ce type existant.
 *
 * Namespace racine `DoctrineMigrations` (regle ABSOLUE Starseed n°11) et NON
 * `App\Module\Technique\...` : la migration cree un schema avec FK cross-module
 * (user, category, site, et les referentiels comptables M1). Avec plusieurs
 * migrations_paths, Doctrine Migrations 3.x trie par FQCN alphabetique — un
 * namespace modulaire s executerait avant la creation de user/category/site sur
 * base vide -> echec des FK. Le namespace racine garantit l ordre par timestamp.
 *
 * Style DDL aligne sur le M1/M2 (Version20260605130000) : `INT GENERATED BY
 * DEFAULT AS IDENTITY` (et non SERIAL), `TIMESTAMP(0) WITHOUT TIME ZONE` (et non
 * TIMESTAMPTZ, car le TimestampableBlamableTrait mappe `datetime_immutable`).
 * Garantit que `schema:update` restera un no-op quand les entites arriveront
 * (ticket ERP-133).
 *
 * Decision unicite (alignee Q4 M1 / § 2.6 M2) : unicite metier sur le NOM DE
 * SOCIETE uniquement (uq_provider_company_name_active, partiel). Pas d index
 * unique sur siren ni email.
 *
 * COMMENT ON COLUMN inline (regle ABSOLUE n°12) : chaque colonne metier porte sa
 * description ici-meme. Volontairement NON ajoutees a `ColumnCommentsCatalog` /
 * `makefile test-db-setup` a ce stade : tant que les entites Provider* n existent
 * pas (ERP-133), `schema:update --force` du setup de test droppe ces tables non
 * mappees — les referencer dans le catalogue ferait planter
 * `app:apply-column-comments`. Le catalogue + la ligne `dbal:run-sql`
 * (uq_provider_company_name_active) seront ajoutes au ticket entites (ERP-133),
 * exactement comme supplier (ERP-86) apres sa migration (ERP-85). Les 4 colonnes
 * Timestampable/Blamable reutilisent les textes standardises du catalogue
 * (`timestampableBlamableComments()`, simple tableau statique sans dependance DB).
 */
final class Version20260612100000 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'ERP-132 (M3) : tables provider + sous-collections + jointures M2M (referentiels comptables et CategoryType PRESTATAIRE reutilises).';
    }

    public function up(Schema $schema): void
    {
        $this->createProviderTable();
        $this->createProviderCategory();
        $this->createProviderSite();
        $this->createProviderContact();
        $this->createProviderAddress();
        $this->createProviderAddressJoinTables();
        $this->createProviderRib();
    }

    public function down(Schema $schema): void
    {
        // Ordre inverse des dependances FK : jointures et sous-collections
        // d abord, puis provider. Les referentiels comptables et le
        // CategoryType PRESTATAIRE ne sont pas touches (crees ailleurs).
        $this->addSql('DROP TABLE IF EXISTS provider_address_category');
        $this->addSql('DROP TABLE IF EXISTS provider_address_contact');
        $this->addSql('DROP TABLE IF EXISTS provider_address_site');
        $this->addSql('DROP TABLE IF EXISTS provider_rib');
        $this->addSql('DROP TABLE IF EXISTS provider_address');
        $this->addSql('DROP TABLE IF EXISTS provider_contact');
        $this->addSql('DROP TABLE IF EXISTS provider_site');
        $this->addSql('DROP TABLE IF EXISTS provider_category');
        $this->addSql('DROP TABLE IF EXISTS provider');
    }

    // =================================================================
    // Table principale `provider`
    // =================================================================

    private function createProviderTable(): void
    {
        $this->addSql(<<<'SQL'
            CREATE TABLE provider (
                id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
                company_name VARCHAR(180) NOT NULL,
                siren VARCHAR(20) DEFAULT NULL,
                account_number VARCHAR(40) DEFAULT NULL,
                tva_mode_id INT DEFAULT NULL,
                n_tva VARCHAR(40) DEFAULT NULL,
                payment_delay_id INT DEFAULT NULL,
                payment_type_id INT DEFAULT NULL,
                bank_id INT DEFAULT NULL,
                is_archived BOOLEAN DEFAULT FALSE NOT NULL,
                archived_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
                deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
                created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                created_by INT DEFAULT NULL,
                updated_by INT DEFAULT NULL,
                PRIMARY KEY (id),
                CONSTRAINT fk_provider_tva_mode
                    FOREIGN KEY (tva_mode_id) REFERENCES tva_mode (id) ON DELETE RESTRICT,
                CONSTRAINT fk_provider_payment_delay
                    FOREIGN KEY (payment_delay_id) REFERENCES payment_delay (id) ON DELETE RESTRICT,
                CONSTRAINT fk_provider_payment_type
                    FOREIGN KEY (payment_type_id) REFERENCES payment_type (id) ON DELETE RESTRICT,
                CONSTRAINT fk_provider_bank
                    FOREIGN KEY (bank_id) REFERENCES bank (id) ON DELETE RESTRICT,
                CONSTRAINT fk_provider_created_by
                    FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
                CONSTRAINT fk_provider_updated_by
                    FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
            )
            SQL);

        $this->addSql('CREATE INDEX idx_provider_is_archived ON provider (is_archived)');
        $this->addSql('CREATE INDEX idx_provider_deleted_at ON provider (deleted_at)');
        $this->addSql('CREATE INDEX idx_provider_created_by ON provider (created_by)');
        $this->addSql('CREATE INDEX idx_provider_updated_by ON provider (updated_by)');

        // Index sur les FK des referentiels comptables (Postgres n indexe pas
        // automatiquement les colonnes portant une FOREIGN KEY).
        $this->addSql('CREATE INDEX idx_provider_tva_mode_id ON provider (tva_mode_id)');
        $this->addSql('CREATE INDEX idx_provider_payment_delay_id ON provider (payment_delay_id)');
        $this->addSql('CREATE INDEX idx_provider_payment_type_id ON provider (payment_type_id)');
        $this->addSql('CREATE INDEX idx_provider_bank_id ON provider (bank_id)');

        // Unicite metier partielle : nom de societe insensible a la casse, parmi
        // les non-archives ET non soft-deletes uniquement (RG-3.10). Pas d index
        // unique sur siren ni email.
        $this->addSql(<<<'SQL'
            CREATE UNIQUE INDEX uq_provider_company_name_active
            ON provider (LOWER(company_name))
            WHERE is_archived = FALSE AND deleted_at IS NULL
            SQL);

        $this->comment('provider', '_table', 'Repertoire prestataires (M3 Technique) — entites archivables (is_archived) et soft-deletables (deleted_at, HP M4). Pas d onglet Information (≠ supplier).');
        $this->comment('provider', 'id', 'Identifiant interne auto-incremente.');
        $this->comment('provider', 'company_name', 'Raison sociale du prestataire (stockee en MAJUSCULES). Unique case-insensitive parmi les actifs non archives/non supprimes (uq_provider_company_name_active, RG-3.10).');
        $this->comment('provider', 'siren', 'Onglet Comptabilite : SIREN (9 chiffres attendus). NON unique — peut etre partage entre etablissements (RG-3.10).');
        $this->comment('provider', 'account_number', 'Onglet Comptabilite : numero de compte comptable du prestataire.');
        $this->comment('provider', 'tva_mode_id', 'Onglet Comptabilite : mode de TVA applique — FK -> tva_mode.id (referentiel partage M1), ON DELETE RESTRICT.');
        $this->comment('provider', 'n_tva', 'Onglet Comptabilite : numero de TVA intracommunautaire.');
        $this->comment('provider', 'payment_delay_id', 'Onglet Comptabilite : delai de reglement — FK -> payment_delay.id (M1), ON DELETE RESTRICT.');
        $this->comment('provider', 'payment_type_id', 'Onglet Comptabilite : type de reglement — FK -> payment_type.id (M1), ON DELETE RESTRICT. Pilote RG-3.07 (Banque si VIREMENT) et RG-3.08 (RIB).');
        $this->comment('provider', 'bank_id', 'Onglet Comptabilite : banque — FK -> bank.id (M1), ON DELETE RESTRICT. Obligatoire ssi payment_type = VIREMENT (RG-3.07), null sinon.');
        $this->comment('provider', 'is_archived', 'Drapeau fonctionnel d archivage — masque par defaut dans la liste. Bascule via permission technique.providers.archive.');
        $this->comment('provider', 'archived_at', 'Horodatage de l archivage — pose quand is_archived passe a vrai, remis a null a la restauration.');
        $this->comment('provider', 'deleted_at', 'Horodatage du soft-delete technique (HP M4) — non expose par l API au M3. Null = ligne active.');
        $this->addTimestampableBlamableComments('provider');
    }

    // =================================================================
    // M2M provider <-> category (type PRESTATAIRE — RG-3.09)
    // =================================================================

    private function createProviderCategory(): void
    {
        $this->addSql(<<<'SQL'
            CREATE TABLE provider_category (
                provider_id INT NOT NULL,
                category_id INT NOT NULL,
                PRIMARY KEY (provider_id, category_id),
                CONSTRAINT fk_provider_category_provider
                    FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_category_category
                    FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE RESTRICT
            )
            SQL);
        $this->addSql('CREATE INDEX idx_provider_category_category ON provider_category (category_id)');

        $this->comment('provider_category', '_table', 'Jointure M2M provider <-> category (Catalog) — categories de type PRESTATAIRE du prestataire, au moins une obligatoire (RG-3.09).');
        $this->comment('provider_category', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire porteur de la categorie.');
        $this->comment('provider_category', 'category_id', 'FK -> category.id, ON DELETE RESTRICT — categorie de type PRESTATAIRE rattachee au prestataire (RG-3.09).');
    }

    // =================================================================
    // M2M provider <-> site (formulaire principal — RG-3.03)
    // =================================================================

    private function createProviderSite(): void
    {
        $this->addSql(<<<'SQL'
            CREATE TABLE provider_site (
                provider_id INT NOT NULL,
                site_id INT NOT NULL,
                PRIMARY KEY (provider_id, site_id),
                CONSTRAINT fk_provider_site_provider
                    FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_site_site
                    FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE RESTRICT
            )
            SQL);
        // Index sur site_id : sert le filtre de cloisonnement par site
        // (WHERE site = :currentSite, § 2.13).
        $this->addSql('CREATE INDEX idx_provider_site_site ON provider_site (site_id)');

        $this->comment('provider_site', '_table', 'Jointure M2M provider <-> site (Sites) — sites du prestataire, selecteur du formulaire principal, au moins un obligatoire (RG-3.03). Sert le cloisonnement par site (§ 2.13).');
        $this->comment('provider_site', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire porteur du site.');
        $this->comment('provider_site', 'site_id', 'FK -> site.id, ON DELETE RESTRICT — site rattache au prestataire (RG-3.03, idx_provider_site_site).');
    }

    // =================================================================
    // Sous-collection : contacts (1:n)
    // =================================================================

    private function createProviderContact(): void
    {
        $this->addSql(<<<'SQL'
            CREATE TABLE provider_contact (
                id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
                provider_id INT NOT NULL,
                first_name VARCHAR(120) DEFAULT NULL,
                last_name VARCHAR(120) DEFAULT NULL,
                job_title VARCHAR(120) DEFAULT NULL,
                phone_primary VARCHAR(20) DEFAULT NULL,
                phone_secondary VARCHAR(20) DEFAULT NULL,
                email VARCHAR(180) DEFAULT NULL,
                position INT DEFAULT 0 NOT NULL,
                created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                created_by INT DEFAULT NULL,
                updated_by INT DEFAULT NULL,
                PRIMARY KEY (id),
                CONSTRAINT chk_provider_contact_name
                    CHECK (first_name IS NOT NULL OR last_name IS NOT NULL OR job_title IS NOT NULL OR phone_primary IS NOT NULL OR email IS NOT NULL),
                CONSTRAINT fk_provider_contact_provider
                    FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_contact_created_by
                    FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
                CONSTRAINT fk_provider_contact_updated_by
                    FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
            )
            SQL);
        $this->addSql('CREATE INDEX idx_provider_contact_provider ON provider_contact (provider_id)');

        $this->comment('provider_contact', '_table', 'Contacts d un prestataire (1:n) — au moins un champ rempli parmi prenom/nom/fonction/telephone/email (RG-3.04, chk_provider_contact_name).');
        $this->comment('provider_contact', 'id', 'Identifiant interne auto-incremente.');
        $this->comment('provider_contact', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire proprietaire du contact.');
        $this->comment('provider_contact', 'first_name', 'Prenom du contact (capitalise serveur). Au moins un champ du contact requis (RG-3.04, chk_provider_contact_name).');
        $this->comment('provider_contact', 'last_name', 'Nom du contact (capitalise serveur). Au moins un champ du contact requis (RG-3.04, chk_provider_contact_name).');
        $this->comment('provider_contact', 'job_title', 'Fonction / intitule de poste du contact (≤ 120 caracteres). Au moins un champ du contact requis (RG-3.04, chk_provider_contact_name).');
        $this->comment('provider_contact', 'phone_primary', 'Telephone principal du contact — chiffres uniquement (normalisation serveur).');
        $this->comment('provider_contact', 'phone_secondary', 'Telephone secondaire du contact — chiffres uniquement (normalisation serveur).');
        $this->comment('provider_contact', 'email', 'Email du contact (lowercase serveur).');
        $this->comment('provider_contact', 'position', 'Ordre d affichage du contact dans la liste du prestataire (croissant).');
        $this->addTimestampableBlamableComments('provider_contact');
    }

    // =================================================================
    // Sous-collection : adresses (1:n) — SANS address_type / bennes / triage
    // =================================================================

    private function createProviderAddress(): void
    {
        $this->addSql(<<<'SQL'
            CREATE TABLE provider_address (
                id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
                provider_id INT NOT NULL,
                country VARCHAR(80) DEFAULT 'France' NOT NULL,
                postal_code VARCHAR(20) NOT NULL,
                city VARCHAR(120) NOT NULL,
                street VARCHAR(255) NOT NULL,
                street_complement VARCHAR(255) DEFAULT NULL,
                position INT DEFAULT 0 NOT NULL,
                created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                created_by INT DEFAULT NULL,
                updated_by INT DEFAULT NULL,
                PRIMARY KEY (id),
                CONSTRAINT fk_provider_address_provider
                    FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_address_created_by
                    FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
                CONSTRAINT fk_provider_address_updated_by
                    FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
            )
            SQL);
        $this->addSql('CREATE INDEX idx_provider_address_provider ON provider_address (provider_id)');

        $this->comment('provider_address', '_table', 'Adresses d un prestataire (1:n) — >= 1 site rattache (RG-3.05). SANS address_type / bennes / triage_provider (specifiques fournisseur).');
        $this->comment('provider_address', 'id', 'Identifiant interne auto-incremente.');
        $this->comment('provider_address', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire proprietaire de l adresse.');
        $this->comment('provider_address', 'country', 'Pays de l adresse — defaut France.');
        $this->comment('provider_address', 'postal_code', 'Code postal (4-5 chiffres attendus) — declenche l autocompletion ville via l API BAN cote front (RG-3.06).');
        $this->comment('provider_address', 'city', 'Ville — preremplie depuis le code postal via API BAN cote front.');
        $this->comment('provider_address', 'street', 'Numero et voie de l adresse.');
        $this->comment('provider_address', 'street_complement', 'Complement d adresse (etage, batiment...) — optionnel.');
        $this->comment('provider_address', 'position', 'Ordre d affichage de l adresse dans la liste du prestataire (croissant).');
        $this->addTimestampableBlamableComments('provider_address');
    }

    // =================================================================
    // Jointures de provider_address (M2M)
    // =================================================================

    private function createProviderAddressJoinTables(): void
    {
        $this->addSql(<<<'SQL'
            CREATE TABLE provider_address_site (
                provider_address_id INT NOT NULL,
                site_id INT NOT NULL,
                PRIMARY KEY (provider_address_id, site_id),
                CONSTRAINT fk_provider_address_site_address
                    FOREIGN KEY (provider_address_id) REFERENCES provider_address (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_address_site_site
                    FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE RESTRICT
            )
            SQL);
        $this->comment('provider_address_site', '_table', 'Jointure M2M provider_address <-> site (Sites) — sites rattaches a l adresse (>= 1 obligatoire, RG-3.05).');
        $this->comment('provider_address_site', 'provider_address_id', 'FK -> provider_address.id, ON DELETE CASCADE — adresse concernee.');
        $this->comment('provider_address_site', 'site_id', 'FK -> site.id, ON DELETE RESTRICT — site rattache a l adresse.');

        $this->addSql(<<<'SQL'
            CREATE TABLE provider_address_contact (
                provider_address_id INT NOT NULL,
                provider_contact_id INT NOT NULL,
                PRIMARY KEY (provider_address_id, provider_contact_id),
                CONSTRAINT fk_provider_address_contact_address
                    FOREIGN KEY (provider_address_id) REFERENCES provider_address (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_address_contact_contact
                    FOREIGN KEY (provider_contact_id) REFERENCES provider_contact (id) ON DELETE CASCADE
            )
            SQL);
        $this->comment('provider_address_contact', '_table', 'Jointure M2M provider_address <-> provider_contact — contacts associes a une adresse.');
        $this->comment('provider_address_contact', 'provider_address_id', 'FK -> provider_address.id, ON DELETE CASCADE — adresse concernee.');
        $this->comment('provider_address_contact', 'provider_contact_id', 'FK -> provider_contact.id, ON DELETE CASCADE — contact associe a l adresse.');

        $this->addSql(<<<'SQL'
            CREATE TABLE provider_address_category (
                provider_address_id INT NOT NULL,
                category_id INT NOT NULL,
                PRIMARY KEY (provider_address_id, category_id),
                CONSTRAINT fk_provider_address_category_address
                    FOREIGN KEY (provider_address_id) REFERENCES provider_address (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_address_category_category
                    FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE RESTRICT
            )
            SQL);
        $this->comment('provider_address_category', '_table', 'Jointure M2M provider_address <-> category — categories d adresse de type PRESTATAIRE (RG-3.09).');
        $this->comment('provider_address_category', 'provider_address_id', 'FK -> provider_address.id, ON DELETE CASCADE — adresse concernee.');
        $this->comment('provider_address_category', 'category_id', 'FK -> category.id, ON DELETE RESTRICT — categorie d adresse de type PRESTATAIRE (RG-3.09).');
    }

    // =================================================================
    // Sous-collection : RIB (1:n)
    // =================================================================

    private function createProviderRib(): void
    {
        $this->addSql(<<<'SQL'
            CREATE TABLE provider_rib (
                id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
                provider_id INT NOT NULL,
                label VARCHAR(120) NOT NULL,
                bic VARCHAR(20) NOT NULL,
                iban VARCHAR(34) NOT NULL,
                position INT DEFAULT 0 NOT NULL,
                created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
                created_by INT DEFAULT NULL,
                updated_by INT DEFAULT NULL,
                PRIMARY KEY (id),
                CONSTRAINT fk_provider_rib_provider
                    FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
                CONSTRAINT fk_provider_rib_created_by
                    FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
                CONSTRAINT fk_provider_rib_updated_by
                    FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
            )
            SQL);
        $this->addSql('CREATE INDEX idx_provider_rib_provider ON provider_rib (provider_id)');

        $this->comment('provider_rib', '_table', 'Coordonnees bancaires d un prestataire (1:n) — >= 1 RIB attendu selon le type de reglement (RG-3.08). Tous les champs audites (pas d AuditIgnore).');
        $this->comment('provider_rib', 'id', 'Identifiant interne auto-incremente.');
        $this->comment('provider_rib', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire proprietaire du RIB.');
        $this->comment('provider_rib', 'label', 'Libelle du RIB (ex: compte principal).');
        $this->comment('provider_rib', 'bic', 'Code BIC/SWIFT de la banque (8 ou 11 caracteres).');
        $this->comment('provider_rib', 'iban', 'IBAN du compte (≤ 34 caracteres).');
        $this->comment('provider_rib', 'position', 'Ordre d affichage du RIB dans la liste du prestataire (croissant).');
        $this->addTimestampableBlamableComments('provider_rib');
    }

    // =================================================================
    // Helpers
    // =================================================================

    /**
     * Pose les 4 commentaires standardises Timestampable/Blamable sur une table,
     * en reutilisant le catalogue partage (source unique, cf. ERP-67). Seul le
     * tableau statique des textes est reutilise — aucune dependance a l etat DB.
     */
    private function addTimestampableBlamableComments(string $table): void
    {
        foreach (ColumnCommentsCatalog::timestampableBlamableComments() as $column => $description) {
            $this->comment($table, $column, $description);
        }
    }

    /**
     * Emet un `COMMENT ON TABLE` (colonne speciale `_table`) ou
     * `COMMENT ON COLUMN` en dollar-quoting Postgres ($_$...$_$) pour eviter
     * tout echappement d apostrophe.
     */
    private function comment(string $table, string $column, string $description): void
    {
        $quotedTable = '"'.str_replace('"', '""', $table).'"';

        if ('_table' === $column) {
            $this->addSql(sprintf('COMMENT ON TABLE %s IS $_$%s$_$', $quotedTable, $description));

            return;
        }

        $this->addSql(sprintf(
            'COMMENT ON COLUMN %s.%s IS $_$%s$_$',
            $quotedTable,
            '"'.str_replace('"', '""', $column).'"',
            $description,
        ));
    }
}
