<?php
/**
 * Axia4 Database Layer
 *
 * Provides a PDO SQLite connection and a lightweight migration runner.
 * All application data previously stored as JSON files under /DATA is now
 * persisted in /DATA/axia4.sqlite.
 *
 * Usage:  db()  → returns the shared PDO instance (auto-migrates on first call).
 */

define('DB_PATH',        '/DATA/axia4.sqlite');
define('MIGRATIONS_DIR', __DIR__ . '/migrations');

// ── Connection ────────────────────────────────────────────────────────────────

function db(): PDO
{
    static $pdo = null;
    if ($pdo !== null) {
        return $pdo;
    }

    if (!is_dir('/DATA')) {
        mkdir('/DATA', 0755, true);
    }

    $pdo = new PDO('sqlite:' . DB_PATH);
    $pdo->setAttribute(PDO::ATTR_ERRMODE,            PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    $pdo->exec('PRAGMA journal_mode = WAL');
    $pdo->exec('PRAGMA foreign_keys = ON');
    $pdo->exec('PRAGMA synchronous   = NORMAL');

    db_migrate($pdo);

    return $pdo;
}

// ── Migration runner ──────────────────────────────────────────────────────────

function db_migrate(PDO $pdo): void
{
    $pdo->exec(
        'CREATE TABLE IF NOT EXISTS schema_migrations (
            version    INTEGER PRIMARY KEY,
            applied_at TEXT NOT NULL DEFAULT (datetime(\'now\'))
        )'
    );

    $applied = $pdo->query('SELECT version FROM schema_migrations ORDER BY version')
                   ->fetchAll(PDO::FETCH_COLUMN);

    $files = glob(MIGRATIONS_DIR . '/*.{sql,php}', GLOB_BRACE) ?: [];
    sort($files);

    foreach ($files as $file) {
        if (!preg_match('/^(\d+)/', basename($file), $m)) {
            continue;
        }
        $version = (int) $m[1];
        if (in_array($version, $applied, true)) {
            continue;
        }

        if (str_ends_with($file, '.sql')) {
            $pdo->exec((string) file_get_contents($file));
        } elseif (str_ends_with($file, '.php')) {
            // PHP migration receives the connection as $db
            $db = $pdo;
            require $file;
        }

        $pdo->prepare('INSERT INTO schema_migrations (version) VALUES (?)')->execute([$version]);
    }
}

// ── Config helpers ────────────────────────────────────────────────────────────

function db_get_config(string $key, $default = null)
{
    $stmt = db()->prepare('SELECT value FROM config WHERE key = ?');
    $stmt->execute([$key]);
    $row = $stmt->fetch();
    if ($row === false) {
        return $default;
    }
    $decoded = json_decode($row['value'], true);
    return $decoded !== null ? $decoded : $row['value'];
}

function db_set_config(string $key, $value): void
{
    db()->prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)')
       ->execute([$key, is_string($value) ? $value : json_encode($value)]);
}

function db_get_all_config(): array
{
    $rows   = db()->query('SELECT key, value FROM config')->fetchAll();
    $result = [];
    foreach ($rows as $row) {
        $decoded = json_decode($row['value'], true);
        $result[$row['key']] = ($decoded !== null) ? $decoded : $row['value'];
    }
    return $result;
}

// ── User helpers ──────────────────────────────────────────────────────────────

/** Find a user by username or email (always lower-cased). Returns DB row or null. */
function db_get_user(string $username): ?array
{
    if (str_contains($username, "@")) {
        $stmt = db()->prepare('SELECT * FROM users WHERE email = ?');
        $stmt->execute([strtolower($username)]);
        $row = $stmt->fetch();
        return $row !== false ? $row : null;
    }
    $stmt = db()->prepare('SELECT * FROM users WHERE username = ?');
    $stmt->execute([strtolower($username)]);
    $row = $stmt->fetch();
    return $row !== false ? $row : null;
}

/** Return all user rows ordered by username. */
function db_get_all_users(): array
{
    return db()->query('SELECT * FROM users ORDER BY username')->fetchAll();
}

/**
 * Build the auth_data session array from a DB user row.
 * Preserves the same format existing code expects:
 *   auth_data.permissions, auth_data.active_organizations auth_data.organizations.
 */
function db_build_auth_data(array $row): array
{
    $permissions = json_decode($row['permissions'] ?? '[]', true) ?: [];
    $meta        = json_decode($row['meta']        ?? '{}', true) ?: [];
    $ea          = [
        'organization' => '',
        'organizations' => [],
        'role' => '',
        'aulas' => [],
        'organizations_data' => [],
    ];

    // Fetch all organization assignments for this user
    $stmt = db()->prepare(
        'SELECT org_id, role, ea_aulas
           FROM user_orgs
          WHERE user_id = ?
          ORDER BY org_id'
    );
    $stmt->execute([$row['id']]);
    $org_rows = $stmt->fetchAll();
    $orgs = [];
    if (!empty($org_rows)) {
        $first        = $org_rows[0];
        foreach ($org_rows as $r) {
            $orgs[] = $r['org_id'];
        }
        $ea['organization'] = $first['org_id'];
        $ea['role']   = $first['role'];
        $ea['aulas']  = json_decode($first['ea_aulas'] ?? '[]', true) ?: [];
        $ea['organizations']      = $orgs;
        $ea['organizations_data'] = $org_rows;
    }

    $active_org = $ea['organization'] ?? '';
    $aulatek = [
        'organizacion' => $active_org,
        'organizaciones' => $orgs,
        'organization' => $active_org,
        'organizations' => $orgs,
        'centro' => $active_org,
        'centros' => $orgs,
        'role' => $ea['role'] ?? '',
        'aulas' => $ea['aulas'] ?? [],
    ];

    return array_merge($meta, [
        'display_name'  => $row['display_name'],
        'email'         => $row['email'],
        'password_hash' => $row['password_hash'],
        'permissions'   => $permissions,
        'orgs'          => $orgs,
        'organizations' => $orgs,
        'active_organization' => $active_org,
        'active_organizations' => $ea,
        'aulatek'       => $aulatek,
        'entreaulas'    => $aulatek,
        'google_auth'   => (bool) $row['google_auth'],
    ]);
}

/**
 * Create or update a user.
 * $data keys: username, display_name, email, password_hash, permissions[],
 *             google_auth, entreaulas{organizacion,organizaciones[],role,aulas[]}, + any extra meta.
 * Returns the user ID.
 */
function db_upsert_user(array $data): int
{
    $pdo      = db();
    $username = strtolower((string) ($data['username'] ?? ''));

    $existing = $pdo->prepare('SELECT id FROM users WHERE username = ?');
    $existing->execute([$username]);
    $existing_row = $existing->fetch();

    $permissions = json_encode($data['permissions'] ?? []);
    $meta_skip   = ['username', 'display_name', 'email', 'password_hash',
                    'permissions', 'entreaulas', 'google_auth',
                    'orgs', 'organizations', 'organization', 'organizacion',
                    'role', 'aulas'];
    $meta = [];
    foreach ($data as $k => $v) {
        if (!in_array($k, $meta_skip, true)) {
            $meta[$k] = $v;
        }
    }

    if ($existing_row) {
        $user_id = (int) $existing_row['id'];
        $upd = $pdo->prepare(
            "UPDATE users SET
                display_name  = ?,
                email         = ?,
                permissions   = ?,
                google_auth   = ?,
                meta          = ?,
                updated_at    = datetime('now')
             WHERE id = ?"
        );
        $upd->execute([
            $data['display_name'] ?? '',
            $data['email']        ?? '',
            $permissions,
            (int) ($data['google_auth'] ?? 0),
            json_encode($meta),
            $user_id,
        ]);
        if (!empty($data['password_hash'])) {
            $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
                ->execute([$data['password_hash'], $user_id]);
        }
    } else {
        $pdo->prepare(
            'INSERT INTO users (username, display_name, email, password_hash, permissions, google_auth, meta)
             VALUES (?, ?, ?, ?, ?, ?, ?)'
        )->execute([
            $username,
            $data['display_name'] ?? '',
            $data['email']        ?? '',
            $data['password_hash'] ?? '',
            $permissions,
            (int) ($data['google_auth'] ?? 0),
            json_encode($meta),
        ]);
        $user_id = (int) $pdo->lastInsertId();
    }

    // Update organization assignments if tenant data is provided.
    $has_org_payload = array_key_exists('entreaulas', $data)
        || array_key_exists('orgs', $data)
        || array_key_exists('organizations', $data)
        || array_key_exists('organization', $data)
        || array_key_exists('organizacion', $data);

    if ($has_org_payload) {
        $ea = $data['entreaulas'] ?? [];

        $organizations = [];
        $candidate_lists = [
            $data['organizations'] ?? null,
            $data['orgs'] ?? null,
            $ea['organizaciones'] ?? null,
            $ea['organizations'] ?? null,
            $ea['centros'] ?? null,
        ];
        foreach ($candidate_lists as $list) {
            if (is_array($list) && !empty($list)) {
                $organizations = $list;
                break;
            }
        }
        if (empty($organizations)) {
            foreach ([
                $data['organization'] ?? null,
                $data['organizacion'] ?? null,
                $ea['organizacion'] ?? null,
                $ea['organization'] ?? null,
                $ea['centro'] ?? null,
            ] as $single) {
                if (!empty($single)) {
                    $organizations = [$single];
                    break;
                }
            }
        }

        $organizations = array_values(array_unique(array_filter(array_map(
            static function ($value): string {
                return preg_replace('/[^a-zA-Z0-9._-]/', '', (string) $value);
            },
            $organizations
        ))));

        $role = (string) ($data['role'] ?? $ea['role'] ?? '');
        $aulas_payload = $data['aulas'] ?? $ea['aulas'] ?? [];
        if (!is_array($aulas_payload)) {
            $aulas_payload = [];
        }
        $aulas = json_encode($aulas_payload, JSON_UNESCAPED_UNICODE);

        $pdo->prepare('DELETE FROM user_orgs WHERE user_id = ?')->execute([$user_id]);

        $ins_org = $pdo->prepare('INSERT OR IGNORE INTO organizaciones (org_id, org_name) VALUES (?, ?)');
        $ins_uo = $pdo->prepare(
            'INSERT OR REPLACE INTO user_orgs (user_id, org_id, role, ea_aulas) VALUES (?, ?, ?, ?)'
        );
        foreach ($organizations as $org_id) {
            if ($org_id === '') {
                continue;
            }
            $ins_org->execute([$org_id, $org_id]);
            $ins_uo->execute([$user_id, $org_id, $role, $aulas]);
        }
    }

    return $user_id;
}

/** Delete a user and their organization assignments. */
function db_delete_user(string $username): void
{
    db()->prepare('DELETE FROM users WHERE username = ?')->execute([strtolower($username)]);
}

// ── Organization helpers ─────────────────────────────────────────────────────

function db_get_organizations(): array
{
    return db()->query('SELECT org_id, org_name FROM organizaciones ORDER BY org_id')->fetchAll();
}

function db_get_organization_ids(): array
{
    return db()->query('SELECT org_id FROM organizaciones ORDER BY org_id')->fetchAll(PDO::FETCH_COLUMN);
}

function db_get_organizaciones(): array
{
    return db_get_organizations();
}

function get_organizations(): array
{
    return db_get_organizations();
}

function db_get_centros(): array
{
    $rows = db_get_organizations();
    return array_map(static function (array $row): array {
        return [
            'centro_id' => $row['org_id'],
            'name' => $row['org_name'],
        ];
    }, $rows);
}

function db_get_centro_ids(): array
{
    return db_get_organization_ids();
}

// ── Aulario helpers ───────────────────────────────────────────────────────────

/** Get a single aulario config. Returns merged array (name, icon, + extra fields) or null. */
function db_get_aulario(string $centro_id, string $aulario_id): ?array
{
    $stmt = db()->prepare(
        'SELECT name, icon, extra FROM aularios WHERE org_id = ? AND aulario_id = ?'
    );
    $stmt->execute([$centro_id, $aulario_id]);
    $row = $stmt->fetch();
    if ($row === false) {
        return null;
    }
    $extra = json_decode($row['extra'] ?? '{}', true) ?: [];
    return array_merge($extra, ['name' => $row['name'], 'icon' => $row['icon']]);
}

/** Get all aularios for a centro as aulario_id → config array. */
function db_get_aularios(string $centro_id): array
{
    $stmt = db()->prepare(
        'SELECT aulario_id, name, icon, extra FROM aularios WHERE org_id = ? ORDER BY aulario_id'
    );
    $stmt->execute([$centro_id]);
    $result = [];
    foreach ($stmt->fetchAll() as $row) {
        $extra = json_decode($row['extra'] ?? '{}', true) ?: [];
        $result[$row['aulario_id']] = array_merge($extra, [
            'name' => $row['name'],
            'icon' => $row['icon'],
        ]);
    }
    return $result;
}

// ── SuperCafe helpers ─────────────────────────────────────────────────────────

function db_get_supercafe_menu(string $centro_id): array
{
    $stmt = db()->prepare('SELECT data FROM supercafe_menu WHERE org_id = ?');
    $stmt->execute([$centro_id]);
    $row = $stmt->fetch();
    if ($row === false) {
        return [];
    }
    return json_decode($row['data'], true) ?: [];
}

function db_set_supercafe_menu(string $centro_id, array $menu): void
{
    db()->prepare('INSERT OR REPLACE INTO supercafe_menu (org_id, data, updated_at) VALUES (?, ?, datetime(\'now\'))')
       ->execute([$centro_id, json_encode($menu, JSON_UNESCAPED_UNICODE)]);
}

/** Return all SC orders for a centro as an array of rows. */
function db_get_supercafe_orders(string $centro_id): array
{
    $stmt = db()->prepare(
        'SELECT * FROM supercafe_orders WHERE org_id = ? ORDER BY created_at DESC'
    );
    $stmt->execute([$centro_id]);
    return $stmt->fetchAll();
}

/** Return a single SC order by ref, or null. */
function db_get_supercafe_order(string $centro_id, string $order_ref): ?array
{
    $stmt = db()->prepare(
        'SELECT * FROM supercafe_orders WHERE org_id = ? AND order_ref = ?'
    );
    $stmt->execute([$centro_id, $order_ref]);
    $row = $stmt->fetch();
    return $row !== false ? $row : null;
}

/** Create or update an SC order. */
function db_upsert_supercafe_order(
    string $centro_id,
    string $order_ref,
    string $fecha,
    string $persona,
    string $comanda,
    string $notas,
    string $estado
): void {
    db()->prepare(
        'INSERT INTO supercafe_orders (org_id, order_ref, fecha, persona, comanda, notas, estado)
             VALUES (?, ?, ?, ?, ?, ?, ?)
         ON CONFLICT(org_id, order_ref) DO UPDATE SET
             fecha    = excluded.fecha,
             persona  = excluded.persona,
             comanda  = excluded.comanda,
             notas    = excluded.notas,
             estado   = excluded.estado'
    )->execute([$centro_id, $order_ref, $fecha, $persona, $comanda, $notas, $estado]);
}

/** Generate the next order_ref for a centro (sc001, sc002, …). */
function db_next_supercafe_ref(string $centro_id): string
{
    $stmt = db()->prepare(
        "SELECT order_ref FROM supercafe_orders WHERE org_id = ? ORDER BY id DESC LIMIT 1"
    );
    $stmt->execute([$centro_id]);
    $last = $stmt->fetchColumn();
    $n = 0;
    if ($last && preg_match('/^sc(\d+)$/', $last, $m)) {
        $n = (int) $m[1];
    }
    return 'sc' . str_pad($n + 1, 3, '0', STR_PAD_LEFT);
}

/** Count 'Deuda' orders for a persona in a centro. */
function db_supercafe_count_debts(string $centro_id, string $persona_key): int
{
    $stmt = db()->prepare(
        "SELECT COUNT(*) FROM supercafe_orders WHERE org_id = ? AND persona = ? AND estado = 'Deuda'"
    );
    $stmt->execute([$centro_id, $persona_key]);
    return (int) $stmt->fetchColumn();
}

// ── Comedor helpers ───────────────────────────────────────────────────────────

function db_get_comedor_menu_types(string $centro_id, string $aulario_id): array
{
    $stmt = db()->prepare(
        'SELECT data FROM comedor_menu_types WHERE org_id = ? AND aulario_id = ?'
    );
    $stmt->execute([$centro_id, $aulario_id]);
    $row = $stmt->fetch();
    if ($row === false) {
        return [];
    }
    return json_decode($row['data'], true) ?: [];
}

function db_set_comedor_menu_types(string $centro_id, string $aulario_id, array $types): void
{
    db()->prepare(
        'INSERT OR REPLACE INTO comedor_menu_types (org_id, aulario_id, data) VALUES (?, ?, ?)'
    )->execute([$centro_id, $aulario_id, json_encode($types, JSON_UNESCAPED_UNICODE)]);
}

function db_get_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day): array
{
    $stmt = db()->prepare(
        'SELECT data FROM comedor_entries WHERE org_id = ? AND aulario_id = ? AND year_month = ? AND day = ?'
    );
    $stmt->execute([$centro_id, $aulario_id, $ym, $day]);
    $row = $stmt->fetch();
    if ($row === false) {
        return [];
    }
    return json_decode($row['data'], true) ?: [];
}

function db_set_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day, array $data): void
{
    db()->prepare(
        'INSERT OR REPLACE INTO comedor_entries (org_id, aulario_id, year_month, day, data) VALUES (?, ?, ?, ?, ?)'
    )->execute([$centro_id, $aulario_id, $ym, $day, json_encode($data, JSON_UNESCAPED_UNICODE)]);
}

// ── Diario helpers ────────────────────────────────────────────────────────────

function db_get_diario_entry(string $centro_id, string $aulario_id, string $entry_date): array
{
    $stmt = db()->prepare(
        'SELECT data FROM diario_entries WHERE org_id = ? AND aulario_id = ? AND entry_date = ?'
    );
    $stmt->execute([$centro_id, $aulario_id, $entry_date]);
    $row = $stmt->fetch();
    if ($row === false) {
        return [];
    }
    return json_decode($row['data'], true) ?: [];
}

function db_set_diario_entry(string $centro_id, string $aulario_id, string $entry_date, array $data): void
{
    db()->prepare(
        'INSERT OR REPLACE INTO diario_entries (org_id, aulario_id, entry_date, data) VALUES (?, ?, ?, ?)'
    )->execute([$centro_id, $aulario_id, $entry_date, json_encode($data, JSON_UNESCAPED_UNICODE)]);
}

// ── Panel alumno helpers ──────────────────────────────────────────────────────

function db_get_panel_alumno(string $centro_id, string $aulario_id, string $alumno): array
{
    $stmt = db()->prepare(
        'SELECT data FROM panel_alumno WHERE org_id = ? AND aulario_id = ? AND alumno = ?'
    );
    $stmt->execute([$centro_id, $aulario_id, $alumno]);
    $row = $stmt->fetch();
    if ($row === false) {
        return [];
    }
    return json_decode($row['data'], true) ?: [];
}

function db_set_panel_alumno(string $centro_id, string $aulario_id, string $alumno, array $data): void
{
    db()->prepare(
        'INSERT OR REPLACE INTO panel_alumno (org_id, aulario_id, alumno, data) VALUES (?, ?, ?, ?)'
    )->execute([$centro_id, $aulario_id, $alumno, json_encode($data, JSON_UNESCAPED_UNICODE)]);
}

// ── Invitation helpers ────────────────────────────────────────────────────────

function db_get_all_invitations(): array
{
    return db()->query('SELECT * FROM invitations ORDER BY code')->fetchAll();
}

function db_get_invitation(string $code): ?array
{
    $stmt = db()->prepare('SELECT * FROM invitations WHERE code = ?');
    $stmt->execute([strtoupper($code)]);
    $row = $stmt->fetch();
    return $row !== false ? $row : null;
}

function db_upsert_invitation(string $code, bool $active, bool $single_use): void
{
    db()->prepare(
        'INSERT OR REPLACE INTO invitations (code, active, single_use) VALUES (?, ?, ?)'
    )->execute([strtoupper($code), (int) $active, (int) $single_use]);
}

function db_deactivate_invitation(string $code): void
{
    db()->prepare('UPDATE invitations SET active = 0 WHERE code = ?')->execute([strtoupper($code)]);
}

function db_delete_invitation(string $code): void
{
    db()->prepare('DELETE FROM invitations WHERE code = ?')->execute([strtoupper($code)]);
}

// ── Club helpers ──────────────────────────────────────────────────────────────

function db_get_club_config(): array
{
    $stmt = db()->query('SELECT data FROM club_config WHERE id = 1');
    $row  = $stmt->fetch();
    if ($row === false) {
        return [];
    }
    return json_decode($row['data'], true) ?: [];
}

function db_set_club_config(array $config): void
{
    db()->prepare('INSERT OR REPLACE INTO club_config (id, data) VALUES (1, ?)')
       ->execute([json_encode($config, JSON_UNESCAPED_UNICODE)]);
}

function db_get_all_club_events(): array
{
    return db()->query('SELECT date_ref, data FROM club_events ORDER BY date_ref DESC')->fetchAll();
}

function db_get_club_event(string $date_ref): array
{
    $stmt = db()->prepare('SELECT data FROM club_events WHERE date_ref = ?');
    $stmt->execute([$date_ref]);
    $row = $stmt->fetch();
    if ($row === false) {
        return [];
    }
    return json_decode($row['data'], true) ?: [];
}

function db_set_club_event(string $date_ref, array $data): void
{
    db()->prepare('INSERT OR REPLACE INTO club_events (date_ref, data) VALUES (?, ?)')
       ->execute([$date_ref, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]);
}

// ── Multi-tenant helpers ──────────────────────────────────────────────────────

/** Return all organization IDs the authenticated user belongs to. */
function get_user_organizations(?array $auth_data = null): array
{
    $data = $auth_data ?? $_SESSION['auth_data'] ?? [];
    $orgs = $data['organizations']
        ?? $data['orgs']
        ?? $data['aulatek']['organizaciones']
        ?? $data['aulatek']['organizations']
        ?? $data['aulatek']['centros']
        ?? $data['entreaulas']['organizaciones']
        ?? $data['entreaulas']['organizations']
        ?? $data['entreaulas']['centros']
        ?? [];

    if (!empty($orgs) && is_array($orgs)) {
        return array_values(array_unique(array_filter($orgs, static function ($value): bool {
            return is_string($value) && $value !== '';
        })));
    }
    if (!empty($orgs)) {
        return [(string) $orgs];
    }

    foreach ([
        $data['active_organization'] ?? null,
        $data['aulatek']['organizacion'] ?? null,
        $data['aulatek']['organization'] ?? null,
        $data['aulatek']['centro'] ?? null,
        $data['entreaulas']['organizacion'] ?? null,
        $data['entreaulas']['organization'] ?? null,
        $data['entreaulas']['centro'] ?? null,
    ] as $single) {
        if (is_string($single) && $single !== '') {
            return [$single];
        }
    }

    return [];
}

/** Spanish alias used by pre-body.php menu rendering. */
function get_user_organizaciones(?array $auth_data = null): array
{
    $org_ids = get_user_organizations($auth_data);
    if (empty($org_ids)) {
        return [];
    }
    $name_by_id = [];
    foreach (db_get_organizations() as $org_row) {
        $name_by_id[$org_row['org_id']] = $org_row['org_name'];
    }

    $result = [];
    foreach ($org_ids as $org_id) {
        $result[$org_id] = $name_by_id[$org_id] ?? $org_id;
    }
    return $result;
}

function get_user_centros(?array $auth_data = null): array
{
    return get_user_organizations($auth_data);
}

/** Ensure active organization session keys are set and mirrored for legacy code. */
function init_active_org(?array $auth_data = null): void
{
    $organizations = get_user_organizations($auth_data);
    if (empty($organizations)) {
        $_SESSION['active_organization'] = null;
        $_SESSION['active_organizacion'] = null;
        $_SESSION['active_centro'] = null;
        return;
    }

    $current = $_SESSION['active_organization']
        ?? $_SESSION['active_organizacion']
        ?? $_SESSION['active_centro']
        ?? null;

    if (!is_string($current) || !in_array($current, $organizations, true)) {
        $current = $organizations[0];
    }

    $_SESSION['active_organization'] = $current;
    $_SESSION['active_organizacion'] = $current;
    $_SESSION['active_centro'] = $current;

    if (!isset($_SESSION['auth_data']) || !is_array($_SESSION['auth_data'])) {
        $_SESSION['auth_data'] = [];
    }
    $_SESSION['auth_data']['active_organization'] = $current;
    if (!isset($_SESSION['auth_data']['aulatek']) || !is_array($_SESSION['auth_data']['aulatek'])) {
        $_SESSION['auth_data']['aulatek'] = [];
    }
    $_SESSION['auth_data']['aulatek']['organizacion'] = $current;
    $_SESSION['auth_data']['aulatek']['organization'] = $current;
    $_SESSION['auth_data']['aulatek']['centro'] = $current;
    if (!isset($_SESSION['auth_data']['entreaulas']) || !is_array($_SESSION['auth_data']['entreaulas'])) {
        $_SESSION['auth_data']['entreaulas'] = [];
    }
    $_SESSION['auth_data']['entreaulas']['organizacion'] = $current;
    $_SESSION['auth_data']['entreaulas']['organization'] = $current;
    $_SESSION['auth_data']['entreaulas']['centro'] = $current;
}

function init_active_centro(?array $auth_data = null): void
{
    init_active_org($auth_data);
}

// ── User session helpers (Dispositivos conectados) ────────────────────────────

/**
 * Register a new session record in user_sessions.
 * $remember_token_hash is the SHA-256 hash of the raw remember-me cookie value.
 * Stores a SHA-256 hash of the PHP session_id so the raw token never reaches the DB.
 */
function db_register_session(string $username, string $remember_token_hash = ''): void
{
    $token = hash('sha256', session_id());
    $ip    = $_SERVER['HTTP_X_FORWARDED_FOR']
           ?? $_SERVER['REMOTE_ADDR']
           ?? '';
    $ip = trim(explode(',', $ip)[0]);
    $ua = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 512);

    db()->prepare(
        "INSERT INTO user_sessions (session_token, username, ip_address, user_agent, remember_token_hash)
         VALUES (?, ?, ?, ?, ?)
         ON CONFLICT(session_token) DO UPDATE SET
             last_active         = datetime('now'),
             remember_token_hash = COALESCE(excluded.remember_token_hash, remember_token_hash)"
    )->execute([$token, strtolower($username), $ip, $ua, $remember_token_hash ?: null]);
}

/**
 * Restore a session from a remember-me token hash.
 * Updates the session_token to the current PHP session_id so revocation
 * continues to work after the PHP session was re-created.
 * Returns the session row (including username) on success, or null if not found.
 */
function db_restore_session_by_remember_token(string $token_hash): ?array
{
    $pdo  = db();
    $stmt = $pdo->prepare(
        'SELECT * FROM user_sessions WHERE remember_token_hash = ? LIMIT 1'
    );
    $stmt->execute([$token_hash]);
    $row = $stmt->fetch();
    if (!$row) {
        return null;
    }
    // Migrate the row to the new PHP session_id
    $new_session_token = hash('sha256', session_id());
    $pdo->prepare(
        "UPDATE user_sessions
            SET session_token = ?, last_active = datetime('now')
          WHERE remember_token_hash = ?"
    )->execute([$new_session_token, $token_hash]);
    return $row;
}

/** Update last_active for the current session. */
function db_touch_session(): void
{
    $token = hash('sha256', session_id());
    db()->prepare(
        "UPDATE user_sessions SET last_active = datetime('now') WHERE session_token = ?"
    )->execute([$token]);
}

/** Delete the current session record from the DB. */
function db_delete_session(): void
{
    $token = hash('sha256', session_id());
    db()->prepare('DELETE FROM user_sessions WHERE session_token = ?')->execute([$token]);
}

/**
 * Delete a specific session by token for a given user.
 * Enforces that the session belongs to the requesting user.
 */
function db_revoke_session(string $token, string $username): void
{
    db()->prepare('DELETE FROM user_sessions WHERE session_token = ? AND username = ?')
        ->execute([$token, strtolower($username)]);
}

/** Delete all session records for a user (e.g. on password change or account wipe). */
function db_delete_user_sessions(string $username): void
{
    db()->prepare('DELETE FROM user_sessions WHERE username = ?')
        ->execute([strtolower($username)]);
}

/** Return all session rows for a user, newest first. */
function db_get_user_sessions(string $username): array
{
    $stmt = db()->prepare(
        'SELECT session_token, ip_address, user_agent, created_at, last_active
           FROM user_sessions
          WHERE username = ?
          ORDER BY last_active DESC'
    );
    $stmt->execute([strtolower($username)]);
    return $stmt->fetchAll();
}

/**
 * Check whether the current PHP session has a valid record in user_sessions.
 * Returns false if the session was revoked or was never registered.
 */
function db_session_is_valid(string $username): bool
{
    $token = hash('sha256', session_id());
    $stmt  = db()->prepare(
        'SELECT 1 FROM user_sessions WHERE session_token = ? AND username = ? LIMIT 1'
    );
    $stmt->execute([$token, strtolower($username)]);
    return $stmt->fetchColumn() !== false;
}
