<?php
/**
 * DokuWiki Plugin bez (Action Component)
 *
 */

// must be run within Dokuwiki

if(!defined('DOKU_INC')) die();

require_once(dirname(__DIR__) . '/meta/path_helper.php');

/**
 * Class action_plugin_bez_migration
 *
 * Handle migrations that need more than just SQL
 */
class action_plugin_bez_migration extends DokuWiki_Action_Plugin {
    /**
     * @inheritDoc
     */
    public function register(Doku_Event_Handler $controller) {
        $controller->register_hook('PLUGIN_SQLITE_DATABASE_UPGRADE', 'BEFORE', $this, 'handle_migrations');
    }

    /**
     * Call our custom migrations when defined
     *
     * @param Doku_Event $event
     * @param $param
     */
    public function handle_migrations(Doku_Event $event, $param) {
        if ($event->data['sqlite']->getAdapter()->getDbName() !== 'b3p') {
            return;
        }
        $to = $event->data['to'];

        if(is_callable(array($this, "migration$to"))) {
            $event->preventDefault();
            $event->result = call_user_func(array($this, "migration$to"), $event->data);
        }
    }

    protected function migration12($data) {
        global $INFO;

        $file = $data['file'];
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $sql = file_get_contents($file);
        if($sql === false) {
            throw new Exception('cannot open file ' . $file);
        }

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        $queries = $matches[0];

        $db = $sqlite->getAdapter()->getPdo();

//        $db->beginTransaction();   // translation already started
        foreach($queries as $query) {
            $res = $db->query($query);
            if($res === false) {
                $err = $db->errorInfo();
                msg($err[0] . ' ' . $err[1] . ' ' . $err[2] . ':<br /><pre>' . hsc($query) . '</pre>', -1);
                $db->rollBack();
                return false;
            }
        }
//        $db->commit();  // commit will be done inside SQLiteDB

        return true;
    }

    protected function migration3($data) {
        global $INFO;

        $file = $data['file'];
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $sql = file_get_contents($file);
        if($sql === false) {
            throw new Exception('cannot open file ' . $file);
        }

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        $queries = $matches[0];

        $db = $sqlite->getAdapter()->getPdo();

//        $db->beginTransaction();  // translation already started
        foreach($queries as $query) {
            $res = $db->query($query);
            if($res === false) {
                $err = $db->errorInfo();
                msg($err[0] . ' ' . $err[1] . ' ' . $err[2] . ':<br /><pre>' . hsc($query) . '</pre>', -1);
                $db->rollBack();
                return false;
            }
        }
//        $db->commit();  // commit will be done inside SQLiteDB

        return true;
    }

    /**
     * Executes Migration 1
     *
     * Add a latest column to all existing multi tables
     *
     * @param helper_plugin_sqlite $sqlite
     * @return bool
     */
    protected function migration1($data) {
        global $INFO;

        $file = $data['file'];
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $sql = file_get_contents($file);
        if ($sql === false) {
            throw new Exception('cannot open file '.$file);
        }

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        $queries = $matches[0];

        $db = $sqlite->getAdapter()->getPdo();

//        $db->beginTransaction(); // translation already started
        foreach ($queries as $query) {
            $res = $db->query($query);
            if($res === false) {
                $err = $db->errorInfo();
                msg($err[0].' '.$err[1].' '.$err[2].':<br /><pre>'.hsc($query).'</pre>', -1);
                $db->rollBack();
                return false;
            }
        }

        $bez_file = bez_resolve_path('data/meta/bez.sqlite3');
        if (!file_exists($bez_file)) {
//        $db->commit();  // commit will be done inside SQLiteDB
            return true;
        }

        //import from bez
        $bez = new \PDO('sqlite:' . $bez_file);

        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        $stmt = $bez->query('SELECT * FROM issuetypes');
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $sqlite->storeEntry('label', array('id' => $row['id'],
                                                            'name' => $row['pl'],
                                                            'added_by' => $INFO['client'],
                                                            'added_date' => date('c')));
        }

        $stmt = $bez->query('SELECT * FROM tasktypes');
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $sqlite->storeEntry('task_program', array('id' => $row['id'],
                                                             'name' => $row['pl'],
                                                             'added_by' => $INFO['client'],
                                                             'added_date' => date('c')));
        }

        $stmt = $bez->query('SELECT *, (SELECT COUNT(*) FROM tasks
								WHERE tasks.cause = commcauses.id) AS task_count FROM commcauses');
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            if ($row['type'] == '0') {
                $type = 'comment';
            } elseif ($row['type'] == '1') {
                $type = 'cause_real';
            } elseif ($row['type'] == '2') {
                $type = 'cause_potential';
            }
            $sqlite->storeEntry('thread_comment',
                                       array('id' => $row['id'],
                                             'thread_id' => $row['issue'],
                                             'type' => $type,
                                             'author' => $row['reporter'],
                                             'create_date' => date('c', strtotime($row['datetime'])),
                                             'last_modification_date' => date('c', strtotime($row['datetime'])),
                                             'content' => $row['content'],
                                             'content_html' => $row['content_cache'],
                                             'task_count' => $row['task_count']));
        }

        $stmt = $bez->query('SELECT tasks.*, commcauses.type AS cause_type
                                          FROM tasks
                                          LEFT JOIN commcauses ON tasks.cause = commcauses.id');
        //thread_id => array('user_id' => 'user_id')
        $task_assignee = array();
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            if ($row['close_date'] != '') {
                $last_mod = date('c', (int) $row['close_date']);
            } else {
                $last_mod = date('c', (int) $row['date']);
            }

            $data =  array('id' => $row['id'],
                           'original_poster' => $row['reporter'],
                           'assignee' => $row['executor'],
                           'create_date' => date('c', (int) $row['date']),
                           'last_activity_date' => $last_mod,
                           'last_modification_date' => $last_mod,
                           'plan_date' => $row['plan_date'],
                           'all_day_event' => $row['all_day_event'],
                           'start_time' => $row['start_time'],
                           'finish_time' => $row['finish_time'],
                           'content' => $row['task'],
                           'content_html' => $row['task_cache'],
                           'thread_id' => $row['issue'],
                           'thread_comment_id' => $row['cause'],
                           'task_program_id' => $row['tasktype']
            );

            if ($data['thread_id'] != '') {
                if (!is_array($task_assignee[$data['thread_id']])) {
                    $task_assignee[$data['thread_id']] = array();
                }
                $task_assignee[$data['thread_id']][$row['executor']] = $row['executor'];
            }

            if ($row['cost'] != '0') {
                $data['cost'] = $row['cost'];
            }

            if ($row['issue'] == '') {
                $data['type'] = 'program';
            } elseif ($row['cause'] == '') {
                $data['type'] = 'correction';
            } elseif ($row['cause_type'] == '1') {
                $data['type'] = 'corrective';
            } else {
                $data['type'] = 'preventive';
            }

            if ($row['state'] == '0') {
                $data['state'] = 'opened';
            } elseif ($row['state'] == '1' || $row['state'] == '2') {
                $data['state'] = 'done';
                $data['closed_by'] = $row['executor'];
                $data['close_date'] = date('c', (int) $row['close_date']);

                if ($row['reason'] != '') {
                    $sqlite->storeEntry('task_comment',
                                               array('task_id' => $row['id'],
                                                     'author' => $row['executor'],
                                                     'create_date' => $data['close_date'],
                                                     'last_modification_date' => $data['close_date'],
                                                     'content' => $row['reason'],
                                                     'content_html' => $row['reason_cache']));
                }
            }

            //user_id => array()
            $participants = array();
            $subscribents = explode(',', $row['subscribents']);
            foreach ($subscribents as $user_id) {
                $participants[$user_id] = array('user_id' => $user_id, 'subscribent' => '1');
            }

            $op = $data['original_poster'];
            if (!isset($participants[$op])) {
                $participants[$op] = array('user_id' => $op, 'original_poster' => '1');
            } else {
                $participants[$op]['original_poster'] = '1';
            }

            $as = $data['assignee'];
            if (!isset($participants[$as])) {
                $participants[$as] = array('user_id' => $as, 'assignee' => '1');
            } else {
                $participants[$as]['assignee'] = '1';
            }

            foreach($participants as $part) {
                $part['task_id'] = $row['id'];
                $part['added_by'] = $INFO['client'];
                $part['added_date'] = date('c');

                $res = $sqlite->storeEntry('task_participant', $part);
                if ($res === false) {
                    throw new Exception($db->errorInfo());
                }
            }

            $sqlite->storeEntry('task', $data);
        }



        $stmt = $bez->query('SELECT *,
                                (SELECT COUNT(*) FROM tasks
								WHERE tasks.issue = issues.id) AS task_count,
								(SELECT COUNT(*) FROM tasks
								WHERE tasks.issue = issues.id AND tasks.state != 0) AS task_closed_count,
								(SELECT SUM(cost) FROM tasks
								WHERE tasks.issue = issues.id) AS task_sum_cost  
								FROM issues');

        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

            $data =  array('id' => $row['id'],
                           'original_poster' => $row['reporter'],
                           'coordinator' => $row['coordinator'],
                           'create_date' => date('c', (int) $row['date']),
                           'last_activity_date' => date('c', strtotime($row['last_activity'])),
                           'last_modification_date' => date('c', (int) $row['last_mod']),
                           'title' => $row['title'],
                           'content' => $row['description'],
                           'content_html' => $row['description_cache'],
                           'task_count' => $row['task_count'],
                           'task_count_closed' => $row['task_closed_count']
            );

            if ($row['task_sum_cost'] != '0') {
                $data['task_sum_cost'] = $row['task_sum_cost'];
            }

            if ($row['coordinator'] == '') {
                $data['state'] = 'proposal';
            } elseif ($row['state'] == '1') {
                $data['closed_by'] = $row['coordinator'];
                $data['state'] = 'closed';
                $data['close_date'] = $data['last_modification_date'];

                $sqlite->storeEntry('thread_comment',
                                           array('thread_id' => $row['id'],
                                                 'type' => 'comment',
                                                 'author' => $row['coordinator'],
                                                 'create_date' => $data['close_date'],
                                                 'last_modification_date' => $data['close_date'],
                                                 'content' => $row['opinion'],
                                                 'content_html' => $row['opinion_cache']));


            } elseif ($row['state'] == '2') {
                $data['closed_by'] = $row['coordinator'];
                $data['state'] = 'rejected';
                $data['close_date'] = $data['last_modification_date'];

                if ($row['opinion'] != '') {
                    $res = $sqlite->storeEntry('thread_comment',
                                               array('thread_id' => $row['id'],
                                                     'type' => 'comment',
                                                     'author' => $row['coordinator'],
                                                     'create_date' => $data['close_date'],
                                                     'last_modification_date' => $data['close_date'],
                                                     'content' => $row['opinion'],
                                                     'content_html' => $row['opinion_cache']));
                    if ($res === false) {
                        throw new Exception($db->errorInfo());
                    }
                }
            } else {
                $data['state'] = 'opened';
            }

            $sqlite->storeEntry('thread', $data);

            $sqlite->storeEntry('thread_label',
                                       array('thread_id' => $row['id'],
                                             'label_id' => $row['type']));

            //participants
            //user_id => array()
            $participants = array();
            $org_participants = array_filter(explode(',', $row['participants']));
            foreach ($org_participants as $user_id) {
                $participants[$user_id] = array('user_id' => $user_id);
            }

            $subscribents = array_filter(explode(',', $row['subscribents']));
            foreach ($subscribents as $user_id) {
                if (!isset($participants[$user_id])) {
                    $participants[$user_id] = array('user_id' => $user_id);
                }
                $participants[$user_id]['subscribent'] = '1';
            }

            $stmt_i = $bez->query('SELECT reporter FROM commcauses WHERE issue=' . $row['id']);
            while ($commcause = $stmt_i->fetch(\PDO::FETCH_ASSOC)) {
                $user_id = $commcause['reporter'];
                if (!isset($participants[$user_id])) {
                    $participants[$user_id] = array('user_id' => $user_id);
                }
                $participants[$user_id]['commentator'] = '1';
            }

            if (is_array($task_assignee[$row['id']])) foreach ($task_assignee[$row['id']] as $user_id) {
                if (!isset($participants[$user_id])) {
                    $participants[$user_id] = array('user_id' => $user_id);
                }
                $participants[$user_id]['task_assignee'] = '1';
            }

            $op = $data['original_poster'];
            if (!isset($participants[$op])) {
                $participants[$op] = array('user_id' => $op, 'original_poster' => '1');
            } else {
                $participants[$op]['original_poster'] = '1';
            }

            $cor = $data['coordinator'];
            if (!isset($participants[$cor])) {
                $participants[$cor] = array('user_id' => $cor, 'coordinator' => '1');
            } else {
                $participants[$cor]['coordinator'] = '1';
            }

            foreach($participants as $part) {
                $part['thread_id'] = $row['id'];
                $part['added_by'] = $INFO['client'];
                $part['added_date'] = date('c');

                $sqlite->storeEntry('thread_participant', $part);
            }

        }

//        $db->commit();  // commit will be done inside SQLiteDB

        return true;
    }

    protected function migration8($data) {
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $db = $sqlite->getAdapter()->getPdo();
        $db->commit(); // stop current transatction
        $db->query('PRAGMA journal_mode=WAL');
        $db->beginTransaction(); // recreate transaction
        return true;
    }

    /**
     * Migration 15: Soft delete - agregar columna deleted_at y actualizar vista
     */
    protected function migration15($data) {
        $file = $data['file'];
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $sql = file_get_contents($file);
        if($sql === false) {
            throw new Exception('cannot open file ' . $file);
        }

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        $queries = $matches[0];

        $db = $sqlite->getAdapter()->getPdo();

        foreach($queries as $query) {
            $query = trim($query);
            if (empty($query)) continue;

            $res = $db->query($query);
            if($res === false) {
                $err = $db->errorInfo();
                msg($err[0] . ' ' . $err[1] . ' ' . $err[2] . ':<br /><pre>' . hsc($query) . '</pre>', -1);
                $db->rollBack();
                return false;
            }
        }

        return true;
    }

    /**
     * Migration 16: Tabla de secuencia para control de consecutivos
     */
    protected function migration16($data) {
        $file = $data['file'];
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $sql = file_get_contents($file);
        if($sql === false) {
            throw new Exception('cannot open file ' . $file);
        }

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        $queries = $matches[0];

        $db = $sqlite->getAdapter()->getPdo();

        foreach($queries as $query) {
            $query = trim($query);
            if (empty($query)) continue;

            $res = $db->query($query);
            if($res === false) {
                $err = $db->errorInfo();
                msg($err[0] . ' ' . $err[1] . ' ' . $err[2] . ':<br /><pre>' . hsc($query) . '</pre>', -1);
                $db->rollBack();
                return false;
            }
        }

        return true;
    }

    /**
     * Migration 24: Columna year para año de operación del proyecto
     */
    protected function migration24($data) {
        $file = $data['file'];
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $sql = file_get_contents($file);
        if($sql === false) {
            throw new Exception('cannot open file ' . $file);
        }

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        $queries = $matches[0];

        $db = $sqlite->getAdapter()->getPdo();

        foreach($queries as $query) {
            $query = trim($query);
            if (empty($query)) continue;

            $res = $db->query($query);
            if($res === false) {
                $err = $db->errorInfo();
                msg($err[0] . ' ' . $err[1] . ' ' . $err[2] . ':<br /><pre>' . hsc($query) . '</pre>', -1);
                $db->rollBack();
                return false;
            }
        }

        return true;
    }

    /**
     * Migration 25: Tabla de preferencia de proyecto administrativo
     * Permite marcar un proyecto como preferido (solo uno a la vez, global)
     */
    protected function migration25($data) {
        $file = $data['file'];
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];

        $sql = file_get_contents($file);
        if($sql === false) {
            throw new Exception('cannot open file ' . $file);
        }

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        $queries = $matches[0];

        $db = $sqlite->getAdapter()->getPdo();

        foreach($queries as $query) {
            $query = trim($query);
            if (empty($query)) continue;

            $res = $db->query($query);
            if($res === false) {
                $err = $db->errorInfo();
                msg($err[0] . ' ' . $err[1] . ' ' . $err[2] . ':<br /><pre>' . hsc($query) . '</pre>', -1);
                $db->rollBack();
                return false;
            }
        }

        return true;
    }

    /**
     * Migration 22: Columnas para reversion de cargas masivas
     */
    protected function migration22($data) {
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];
        $db = $sqlite->getAdapter()->getPdo();

        // Crear tabla si no existe
        $db->query("CREATE TABLE IF NOT EXISTS bulk_task_log (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id INTEGER DEFAULT 0,
            filename TEXT NOT NULL,
            tasks_created INTEGER DEFAULT 0,
            tasks_skipped INTEGER DEFAULT 0,
            errors INTEGER DEFAULT 0,
            uploaded_by TEXT NOT NULL,
            uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            task_ids TEXT DEFAULT NULL,
            reverted INTEGER DEFAULT 0,
            reverted_at DATETIME DEFAULT NULL,
            reverted_by TEXT DEFAULT NULL
        )");

        // Verificar columnas existentes
        $stmt = $db->query("PRAGMA table_info(bulk_task_log)");
        $existingCols = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $existingCols[] = $row['name'];
        }

        // Agregar columnas faltantes
        $columnsToAdd = [
            'task_ids' => 'TEXT DEFAULT NULL',
            'reverted' => 'INTEGER DEFAULT 0',
            'reverted_at' => 'TEXT DEFAULT NULL',
            'reverted_by' => 'TEXT DEFAULT NULL'
        ];

        foreach ($columnsToAdd as $col => $type) {
            if (!in_array($col, $existingCols)) {
                try {
                    $db->query("ALTER TABLE bulk_task_log ADD COLUMN {$col} {$type}");
                } catch (\Exception $e) {
                    // Ignore - column might already exist
                }
            }
        }

        return true;
    }

    /**
     * Migration 27: CREATE TABLE IF NOT EXISTS — seguro, ejecutar SQL directo.
     */
    protected function migration27($data) {
        $file = $data['file'];
        $sqlite = $data['sqlite'];
        $db = $sqlite->getAdapter()->getPdo();

        $sql = file_get_contents($file);
        if ($sql === false) return false;

        $matches = array();
        preg_match_all('/.*?(?(?=BEGIN)BEGIN.*?END)\s*;/is', $sql, $matches);
        foreach ($matches[0] as $query) {
            $query = trim($query);
            if (empty($query)) continue;
            $db->query($query);
        }
        return true;
    }

    /**
     * Migration 28: ALTER TABLE ADD COLUMN — puede fallar si columna ya existe.
     * Maneja 'duplicate column name' silenciosamente.
     */
    protected function migration28($data) {
        $sqlite = $data['sqlite'];
        $db = $sqlite->getAdapter()->getPdo();

        // Verificar si la columna ya existe antes de intentar agregarla
        $stmt = $db->query("PRAGMA table_info(tax_calendar_master)");
        $columns = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $columns[] = $row['name'];
        }

        if (!in_array('is_active', $columns)) {
            $db->query("ALTER TABLE tax_calendar_master ADD COLUMN is_active INTEGER DEFAULT 1");
        }

        return true;
    }

    /**
     * Migration 29: Agregar campo source a tareas + backfill.
     * 1. Ejecuta el SQL (ALTER TABLE + backfill calendar desde tax_task_log)
     * 2. Parsea bulk_task_log.task_ids (JSON) para marcar tareas masivas
     */
    protected function migration29($data) {
        /** @var helper_plugin_sqlite $sqlite */
        $sqlite = $data['sqlite'];
        $db = $sqlite->getAdapter()->getPdo();

        // Paso 1: Agregar columna source si no existe
        $stmt = $db->query("PRAGMA table_info(task)");
        $columns = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $columns[] = $row['name'];
        }
        if (!in_array('source', $columns)) {
            $db->query("ALTER TABLE task ADD COLUMN source TEXT DEFAULT 'manual'");
        }

        // Backfill tareas del calendario tributario
        try {
            $db->query("UPDATE task SET source = 'calendar' WHERE source = 'manual' AND id IN (SELECT task_id FROM tax_task_log WHERE task_id IS NOT NULL)");
        } catch (\Exception $e) {
            // tax_task_log puede no existir — ignorar
        }

        // Paso 2: Backfill tareas masivas desde bulk_task_log (JSON)
        try {
            $stmt = $db->query("SELECT id, task_ids FROM bulk_task_log WHERE task_ids IS NOT NULL AND task_ids != '' AND (reverted IS NULL OR reverted = 0)");
        } catch (\Exception $e) {
            // Tabla no existe — no hay tareas masivas que migrar
            return true;
        }

        $updateStmt = $db->prepare("UPDATE task SET source = 'bulk' WHERE id = ? AND source = 'manual'");
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $taskIds = @json_decode($row['task_ids'], true);
            if (!is_array($taskIds) || empty($taskIds)) continue;

            foreach ($taskIds as $taskId) {
                $taskId = intval($taskId);
                if ($taskId > 0) {
                    $updateStmt->execute([$taskId]);
                }
            }
        }

        return true;
    }

    /**
     * Migration 31: Editor rico en tareas/comentarios, clasificación por sección, edición y respuestas.
     * Verifica columnas con PRAGMA antes de ALTER TABLE para evitar "duplicate column".
     */
    protected function migration31($data) {
        $sqlite = $data['sqlite'];
        $db = $sqlite->getAdapter()->getPdo();

        // Columnas nuevas para admin_thread_task
        $stmt = $db->query("PRAGMA table_info(admin_thread_task)");
        $taskCols = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $taskCols[] = $row['name']; }

        if (!in_array('content_html', $taskCols)) {
            $db->query("ALTER TABLE admin_thread_task ADD COLUMN content_html TEXT");
        }
        if (!in_array('seccion_id', $taskCols)) {
            $db->query("ALTER TABLE admin_thread_task ADD COLUMN seccion_id INTEGER DEFAULT NULL");
        }

        // Columnas nuevas para admin_thread_comment
        $stmt = $db->query("PRAGMA table_info(admin_thread_comment)");
        $commentCols = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $commentCols[] = $row['name']; }

        if (!in_array('updated_at', $commentCols)) {
            $db->query("ALTER TABLE admin_thread_comment ADD COLUMN updated_at DATETIME DEFAULT NULL");
        }
        if (!in_array('updated_by', $commentCols)) {
            $db->query("ALTER TABLE admin_thread_comment ADD COLUMN updated_by TEXT DEFAULT NULL");
        }
        if (!in_array('parent_id', $commentCols)) {
            $db->query("ALTER TABLE admin_thread_comment ADD COLUMN parent_id INTEGER DEFAULT NULL");
        }

        // Índices
        $db->query("CREATE INDEX IF NOT EXISTS idx_admin_comment_parent ON admin_thread_comment(parent_id)");
        $db->query("CREATE INDEX IF NOT EXISTS idx_admin_task_seccion ON admin_thread_task(seccion_id)");

        return true;
    }

}
