<?php
/**
 * includes/notifications/notifications-core.php
 *
 * ============================================================================
 * SISTEMA DE NOTIFICAÇÕES — NÚCLEO (1.5.182)
 * ============================================================================
 *
 * Arquitectura para mostrar notificações aos artistas e admins:
 *
 *   1. BROADCAST (target_telemovel = NULL)
 *      Mensagens do admin para todos os utilizadores ou para um role.
 *      Ex: "Manutenção programada", "Nova feature disponível".
 *
 *   2. ALERTAS ESPECÍFICOS (target_telemovel = '258...')
 *      Mensagens dirigidas a um utilizador específico.
 *      Ex: "Cansei foi devolvido", "Pedido SOMAS aprovado".
 *
 * 2 LOCAIS DE RENDERING:
 *
 *   A. BANNER no topo da Distribuição (página inicial do artista)
 *      — Mostra TODAS as severities (info/success/warning/danger)
 *      — Por baixo do header "Olá, X"
 *      — Cards empilhados se houver múltiplas
 *
 *   B. BARRA FIXA GLOBAL (todas as páginas do plugin onde aplicável)
 *      — Mostra APENAS severity warning/danger
 *      — Banner único (mais recente) — o resto fica para o banner topo
 *      — Compacto, dispensável
 *
 * DESIGN:
 *   - Dispensáveis (utilizador fecha → desaparece para sempre para ele,
 *     guardado em rmbz_notifications_dismissed)
 *   - Expiráveis (admin define data — depois não mostra mais para ninguém)
 *   - AJAX para dismiss (sem reload)
 *   - Idempotente (se a mesma notificação chegar 2x não duplica;
 *     usa metadata.dedup_key)
 *
 * CICLO DE VIDA:
 *   created → active → (dismissed por user OU expired) → archived
 *
 * ============================================================================
 */

if ( ! defined( 'ABSPATH' ) ) exit;

/* ============================================================================
 * 1. SCHEMA — instalação das tabelas via dbDelta (idempotente)
 * ============================================================================ */

/**
 * Versão do schema. Bump cada vez que a estrutura ou dados base mudarem
 * para forçar re-execução do dbDelta + migrações no próximo carregar.
 *
 * Histórico:
 *   1.0 (1.5.182) — schema inicial (rmbz_notifications, rmbz_notifications_dismissed)
 *   1.1 (1.5.186) — migração: corrigir action_url errados em notificações
 *                   pré-existentes ('distro_action=editar' → 'edit_release',
 *                   'distro_action=ver' → 'view_release')
 *   1.2 (1.5.190) — migração: corrigir parâmetro release_id → id em URLs
 *                   gravados. O wizard real lê $_GET['id'], não 'release_id'.
 *   1.3 (1.6.37)  — tabela rmbz_user_notification_prefs: preferências de push
 *                   por categoria (releases, submissions, money, license).
 */
const RMBZ_NOTIF_SCHEMA_VERSION = '1.3';

function rmbz_notif_maybe_install_tables() {
    $current = get_option( 'rmbz_notifications_schema_v' );
    if ( $current === RMBZ_NOTIF_SCHEMA_VERSION ) return;

    if ( ! function_exists( 'dbDelta' ) ) {
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    }

    global $wpdb;
    $charset = $wpdb->get_charset_collate();

    // Tabela principal de notificações
    $sql_notifs = "CREATE TABLE {$wpdb->prefix}rmbz_notifications (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        type VARCHAR(64) NOT NULL DEFAULT 'general',
        severity VARCHAR(16) NOT NULL DEFAULT 'info',
        title VARCHAR(200) NOT NULL,
        body TEXT NULL,
        action_url VARCHAR(500) NULL,
        action_label VARCHAR(80) NULL,
        target_telemovel VARCHAR(20) NULL,
        target_role VARCHAR(32) NULL,
        dedup_key VARCHAR(120) NULL,
        metadata LONGTEXT NULL,
        created_at DATETIME NOT NULL,
        expires_at DATETIME NULL,
        created_by VARCHAR(64) NULL,
        is_archived TINYINT(1) NOT NULL DEFAULT 0,
        PRIMARY KEY (id),
        KEY idx_target_telemovel (target_telemovel),
        KEY idx_target_role (target_role),
        KEY idx_severity (severity),
        KEY idx_created_at (created_at),
        KEY idx_expires_at (expires_at),
        KEY idx_dedup (dedup_key),
        KEY idx_archived (is_archived)
    ) {$charset};";

    // Tabela de dismissals — separada para não actualizar o registo principal
    // cada vez que alguém fecha
    $sql_dismissed = "CREATE TABLE {$wpdb->prefix}rmbz_notifications_dismissed (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        notification_id BIGINT UNSIGNED NOT NULL,
        telemovel VARCHAR(20) NOT NULL,
        dismissed_at DATETIME NOT NULL,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_notif_user (notification_id, telemovel),
        KEY idx_notification (notification_id),
        KEY idx_telemovel (telemovel)
    ) {$charset};";

    // [1.6.37] Preferências de push por user e categoria.
    // Default: tudo ON. Quando o user desactiva, criamos um registo
    // com enabled=0. A lookup é por (telemovel, category).
    //
    // Categorias mapeadas em rmbz_notif_category_for_type():
    //   - releases:    release_*, match_review_*
    //   - submissions: submission_status_change
    //   - money:       withdrawal_*
    //   - license:     license_*, somas_*
    //
    // Este guard só silencia o PUSH — a notificação continua a ser criada
    // na BD e o user vê-la-á no sino quando abrir o app. Só o telemóvel
    // não toca.
    $sql_prefs = "CREATE TABLE {$wpdb->prefix}rmbz_user_notification_prefs (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        telemovel VARCHAR(20) NOT NULL,
        category VARCHAR(32) NOT NULL,
        enabled TINYINT(1) NOT NULL DEFAULT 1,
        updated_at DATETIME NOT NULL,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_user_cat (telemovel, category),
        KEY idx_telemovel (telemovel)
    ) {$charset};";

    dbDelta( $sql_notifs );
    dbDelta( $sql_dismissed );
    dbDelta( $sql_prefs );

    // [1.5.186] MIGRAÇÃO 1.0 → 1.1 — corrigir action_url errados gravados
    // antes do fix de URL routes.
    //
    // Antes da 1.5.186, os listeners gravavam URLs com slugs inválidos
    // (?distro_action=editar / =ver) que faziam o botão "Ver e corrigir"
    // não levar a lado nenhum — caía no dashboard.
    //
    // Esta migração corre UMA VEZ ao subir de schema 1.0 → 1.1 e corrige
    // todas as notificações pré-existentes na BD. Idempotente: se já não
    // houver URLs antigos (porque a 1.5.186 foi instalada de raiz) o
    // UPDATE não afecta linhas e não há side-effects.
    //
    // Aplicar sempre que $current < '1.1' (i.e. 1.0 ou ausente).
    if ( $current === '1.0' || $current === false || $current === '' ) {
        // editar → edit_release
        $wpdb->query(
            "UPDATE {$wpdb->prefix}rmbz_notifications
             SET action_url = REPLACE(action_url, 'distro_action=editar', 'distro_action=edit_release')
             WHERE action_url LIKE '%distro_action=editar%'"
        );
        // ver → view_release (cuidado: substring match, evitar falsos positivos)
        // O padrão real era '?distro_action=ver&release_id=X' — o '&' garante
        // que não apanha outras strings que tenham 'ver' como substring.
        $wpdb->query(
            "UPDATE {$wpdb->prefix}rmbz_notifications
             SET action_url = REPLACE(action_url, 'distro_action=ver&', 'distro_action=view_release&')
             WHERE action_url LIKE '%distro_action=ver&%'"
        );
    }

    // [1.5.190] MIGRAÇÃO 1.1 → 1.2 — corrigir parâmetro release_id → id
    // nos URLs gravados em rmbz_notifications.action_url.
    //
    // BUG: o wizard de release lê $_GET['id'], NÃO $_GET['release_id'].
    // Logo URLs como '?distro_action=edit_release&release_id=42' faziam
    // o wizard ignorar o ID e abrir como CRIAÇÃO de novo lançamento.
    //
    // Antes (errado):  ?distro_action=edit_release&release_id=42
    // Agora (correcto): ?distro_action=edit_release&id=42
    //
    // Idempotente: se URL já está correcto, REPLACE não afecta linhas.
    // Aplica-se a todas as instalações que estejam em 1.1 ou inferior
    // (porque o bug existiu desde a 1.5.184).
    if ( $current === '1.0' || $current === '1.1' || $current === false || $current === '' ) {
        // release_id= → id=
        // Usar REPLACE simples é seguro porque os outros parâmetros
        // do plugin que terminam em "_id" (track_id, request_id, etc.)
        // não estão nesta tabela. Mesmo que estivessem, o LIKE
        // '%release_id=%' filtra para só atingir os relevantes.
        $wpdb->query(
            "UPDATE {$wpdb->prefix}rmbz_notifications
             SET action_url = REPLACE(action_url, 'release_id=', 'id=')
             WHERE action_url LIKE '%release_id=%'"
        );
    }

    update_option( 'rmbz_notifications_schema_v', RMBZ_NOTIF_SCHEMA_VERSION );
}
add_action( 'plugins_loaded', 'rmbz_notif_maybe_install_tables', 5 );


/* ============================================================================
 * 2. API HELPERS — criar / obter / dispensar
 * ============================================================================ */

/**
 * Cria uma nova notificação. Retorna o ID criado, ou false em erro,
 * ou o ID existente se for um dedup hit.
 *
 * @param array $args {
 *     @type string  $type           Categoria (default 'general'). Ex: 'release_returned',
 *                                   'somas_approved', 'maintenance', 'system'.
 *     @type string  $severity       'info' | 'success' | 'warning' | 'danger'. Default 'info'.
 *     @type string  $title          Título curto (max 200).
 *     @type string  $body           Texto opcional.
 *     @type string  $action_url     URL opcional para acção.
 *     @type string  $action_label   Label do CTA (default 'Ver detalhes').
 *     @type string  $target_telemovel  Telemovel específico ou NULL para broadcast.
 *     @type string  $target_role    'artist' | 'admin' | NULL para todos.
 *     @type string  $dedup_key      Chave única — se existir notificação activa
 *                                   com a mesma chave, retorna o ID dela em vez
 *                                   de criar duplicada.
 *     @type array   $metadata       Array livre para JSON.
 *     @type string  $expires_at     Data MySQL ou NULL para sem expiração.
 *     @type string  $created_by     Quem criou (admin user, 'system', etc.).
 * }
 * @return int|false ID da notificação ou false em erro.
 */
function rmbz_notify_create( $args ) {
    global $wpdb;

    $args = wp_parse_args( $args, [
        'type'             => 'general',
        'severity'         => 'info',
        'title'            => '',
        'body'             => null,
        'action_url'       => null,
        'action_label'     => null,
        'target_telemovel' => null,
        'target_role'      => null,
        'dedup_key'        => null,
        'metadata'         => null,
        'expires_at'       => null,
        'created_by'       => 'system',
    ]);

    if ( empty( $args['title'] ) ) {
        return false;
    }

    // Validar severity
    $valid_severities = ['info', 'success', 'warning', 'danger'];
    if ( ! in_array( $args['severity'], $valid_severities, true ) ) {
        $args['severity'] = 'info';
    }

    // Dedup — se já existe notificação com a mesma key e ainda activa, retorna-a
    if ( ! empty( $args['dedup_key'] ) ) {
        $existing = $wpdb->get_var( $wpdb->prepare(
            "SELECT id FROM {$wpdb->prefix}rmbz_notifications
             WHERE dedup_key = %s
               AND is_archived = 0
               AND (expires_at IS NULL OR expires_at > %s)
             ORDER BY id DESC LIMIT 1",
            $args['dedup_key'],
            current_time( 'mysql' )
        ) );
        if ( $existing ) {
            return (int) $existing;
        }
    }

    $data = [
        'type'             => sanitize_key( $args['type'] ),
        'severity'         => $args['severity'],
        'title'            => mb_substr( wp_strip_all_tags( $args['title'] ), 0, 200 ),
        'body'             => $args['body'] ? wp_kses_post( $args['body'] ) : null,
        'action_url'       => $args['action_url'] ? esc_url_raw( $args['action_url'] ) : null,
        'action_label'     => $args['action_label'] ? mb_substr( sanitize_text_field( $args['action_label'] ), 0, 80 ) : null,
        'target_telemovel' => $args['target_telemovel'] ? preg_replace('/[^0-9+]/', '', $args['target_telemovel']) : null,
        'target_role'      => $args['target_role'] ? sanitize_key( $args['target_role'] ) : null,
        'dedup_key'        => $args['dedup_key'] ? mb_substr( sanitize_text_field( $args['dedup_key'] ), 0, 120 ) : null,
        'metadata'         => $args['metadata'] ? wp_json_encode( $args['metadata'] ) : null,
        'created_at'       => current_time( 'mysql' ),
        'expires_at'       => $args['expires_at'],
        'created_by'       => mb_substr( sanitize_text_field( $args['created_by'] ), 0, 64 ),
        'is_archived'      => 0,
    ];

    $result = $wpdb->insert( "{$wpdb->prefix}rmbz_notifications", $data );
    if ( $result === false ) {
        return false;
    }

    $new_id = (int) $wpdb->insert_id;

    // [FCM 1.6.6] Disparar action para o sistema de push notifications
    // poder enviar automaticamente. O hook está em fcm-auto-push.php.
    do_action( 'rmbz_notification_created', $new_id );

    return $new_id;
}


/**
 * Atalho para criar notificação para um utilizador específico.
 */
function rmbz_notify_user( $telemovel, $title, $body = null, $severity = 'info', $args = [] ) {
    return rmbz_notify_create( array_merge( $args, [
        'target_telemovel' => $telemovel,
        'title'            => $title,
        'body'             => $body,
        'severity'         => $severity,
    ]));
}


/**
 * Atalho para criar broadcast (todos os utilizadores ou um role).
 */
function rmbz_notify_broadcast( $title, $body = null, $severity = 'info', $args = [] ) {
    return rmbz_notify_create( array_merge( $args, [
        'target_telemovel' => null, // broadcast
        'title'            => $title,
        'body'             => $body,
        'severity'         => $severity,
    ]));
}


/**
 * Devolve as notificações activas para um utilizador.
 *
 * Inclui:
 *   - Notificações específicas dele (target_telemovel = $telemovel)
 *   - Broadcasts gerais (target_telemovel = NULL e target_role = NULL ou 'artist')
 *
 * Exclui:
 *   - Já dispensadas por ele
 *   - Expiradas
 *   - Arquivadas
 *
 * Ordem: severity (danger > warning > info > success), depois mais recente primeiro.
 *
 * @param string $telemovel
 * @param array  $opts {
 *     @type string $severity_filter  Se passado, só retorna desta severity ou superior.
 *                                    Valores: 'info' (todas), 'success', 'warning', 'danger'.
 *     @type int    $limit            Default 20.
 * }
 * @return array de objects da BD
 */
function rmbz_notify_get_for_user( $telemovel, $opts = [] ) {
    global $wpdb;

    $opts = wp_parse_args( $opts, [
        'severity_filter' => 'info', // todas
        'limit'           => 20,
    ]);

    $telemovel_clean = preg_replace('/[^0-9+]/', '', $telemovel);
    if ( empty( $telemovel_clean ) ) return [];

    $now = current_time( 'mysql' );

    // Filtro de severity (warning/danger only para a barra fixa global)
    $severity_where = '';
    if ( $opts['severity_filter'] === 'warning' ) {
        $severity_where = "AND n.severity IN ('warning','danger')";
    } elseif ( $opts['severity_filter'] === 'danger' ) {
        $severity_where = "AND n.severity = 'danger'";
    }

    $sql = $wpdb->prepare(
        "SELECT n.*
         FROM {$wpdb->prefix}rmbz_notifications n
         LEFT JOIN {$wpdb->prefix}rmbz_notifications_dismissed d
                ON d.notification_id = n.id AND d.telemovel = %s
         WHERE n.is_archived = 0
           AND (n.expires_at IS NULL OR n.expires_at > %s)
           AND d.id IS NULL
           AND (
                n.target_telemovel = %s
             OR (n.target_telemovel IS NULL AND (n.target_role IS NULL OR n.target_role = 'artist'))
           )
           {$severity_where}
         ORDER BY
            CASE n.severity
                WHEN 'danger' THEN 1
                WHEN 'warning' THEN 2
                WHEN 'info' THEN 3
                WHEN 'success' THEN 4
                ELSE 5
            END,
            n.created_at DESC
         LIMIT %d",
        $telemovel_clean,
        $now,
        $telemovel_clean,
        (int) $opts['limit']
    );

    $rows = $wpdb->get_results( $sql );

    // [1.5.187] Filtrar notificações órfãs — referenciam release_id que já
    // não existe (release foi apagado pelo artista ou pelo admin).
    // Defesa em runtime que cobre casos onde o cleanup proactivo não correu
    // (ex: deletes feitos antes da 1.5.187, deletes via SQL directo, etc.).
    return rmbz_notify_filter_orphans( $rows );
}


/**
 * [1.5.187] Filtra notificações cujo recurso referenciado já não existe.
 *
 * Verifica:
 *   - metadata.release_id → existe na rmbz_distro_releases?
 *   - action_url contém release_id=X ou id=X → release X existe?
 *
 * Notificações órfãs são marcadas como is_archived=1 (cleanup oportunístico)
 * e removidas do resultado.
 *
 * @param array $notifications Resultados raw do query
 * @return array Filtrado
 */
function rmbz_notify_filter_orphans( $notifications ) {
    if ( empty( $notifications ) ) return $notifications;

    global $wpdb;

    // Recolher todos os release_ids referenciados (de metadata + action_url)
    $referenced_ids = [];
    foreach ( $notifications as $n ) {
        $rid = rmbz_notify_extract_release_id( $n );
        if ( $rid > 0 ) $referenced_ids[ $rid ] = true;
    }

    if ( empty( $referenced_ids ) ) return $notifications;

    // 1 query batch para verificar quais ainda existem
    $ids_placeholder = implode( ',', array_fill( 0, count( $referenced_ids ), '%d' ) );
    $existing_raw = $wpdb->get_col( $wpdb->prepare(
        "SELECT id FROM {$wpdb->prefix}rmbz_distro_releases
         WHERE id IN ({$ids_placeholder})",
        ...array_keys( $referenced_ids )
    ));
    $existing = array_flip( array_map( 'intval', $existing_raw ) );

    // Filtrar e marcar órfãs como arquivadas (cleanup oportunístico)
    $kept = [];
    $orphan_ids = [];
    foreach ( $notifications as $n ) {
        $rid = rmbz_notify_extract_release_id( $n );
        if ( $rid > 0 && ! isset( $existing[ $rid ] ) ) {
            // Órfã — marcar para arquivar
            $orphan_ids[] = (int) $n->id;
            continue;
        }
        $kept[] = $n;
    }

    // Arquivar órfãs em batch (não bloqueia render se falhar)
    if ( ! empty( $orphan_ids ) ) {
        $orphan_placeholder = implode( ',', array_fill( 0, count( $orphan_ids ), '%d' ) );
        $wpdb->query( $wpdb->prepare(
            "UPDATE {$wpdb->prefix}rmbz_notifications
             SET is_archived = 1
             WHERE id IN ({$orphan_placeholder})",
            ...$orphan_ids
        ));
    }

    return $kept;
}


/**
 * [1.5.187] Helper — extrai release_id de uma notificação a partir de:
 *   1. metadata JSON (chave 'release_id')
 *   2. action_url (parâmetro release_id=X ou id=X — ambos formatos
 *      suportados desde 1.5.190 para retro-compatibilidade)
 *
 * Retorna 0 se não há referência a release_id.
 */
function rmbz_notify_extract_release_id( $notification ) {
    // 1. Metadata JSON
    if ( ! empty( $notification->metadata ) ) {
        $meta = json_decode( $notification->metadata, true );
        if ( is_array( $meta ) && ! empty( $meta['release_id'] ) ) {
            return (int) $meta['release_id'];
        }
    }
    // 2. action_url query string
    if ( ! empty( $notification->action_url ) ) {
        // [1.5.190] Aceitar ambos os parâmetros:
        //   ?...&release_id=N (URLs antigos, pré-1.5.190)
        //   ?...&id=N (URLs novos, formato correcto do wizard)
        // Tentar release_id primeiro, depois id (com cuidado para não
        // apanhar release_id como id por overlap de substring).
        if ( preg_match( '/[?&]release_id=(\d+)/', $notification->action_url, $m ) ) {
            return (int) $m[1];
        }
        // Para o &id= temos de garantir que NÃO é &release_id= que matcha
        // por substring. O lookbehind negativo (?<!release_) garante que
        // o caractere antes de "id=" não é "_" (de "release_id"). Como
        // PCRE suporta lookbehinds fixos, isto é seguro.
        if ( preg_match( '/[?&](?<!_)id=(\d+)/', $notification->action_url, $m ) ) {
            return (int) $m[1];
        }
    }
    return 0;
}


/**
 * Marca uma notificação como dispensada por um utilizador.
 * Idempotente — chamar 2x não duplica linha (UNIQUE key).
 *
 * @return bool true em sucesso (ou se já estava dispensada).
 */
function rmbz_notify_dismiss( $notification_id, $telemovel ) {
    global $wpdb;

    $telemovel_clean = preg_replace('/[^0-9+]/', '', $telemovel);
    if ( empty( $telemovel_clean ) ) return false;

    $notification_id = (int) $notification_id;
    if ( $notification_id <= 0 ) return false;

    // INSERT IGNORE não está em wpdb->insert directo — usar query com ON DUPLICATE
    $result = $wpdb->query( $wpdb->prepare(
        "INSERT INTO {$wpdb->prefix}rmbz_notifications_dismissed
         (notification_id, telemovel, dismissed_at)
         VALUES (%d, %s, %s)
         ON DUPLICATE KEY UPDATE dismissed_at = dismissed_at",
        $notification_id,
        $telemovel_clean,
        current_time( 'mysql' )
    ));

    return $result !== false;
}


/**
 * Apaga TODAS as notificações dispensadas por um utilizador (reset).
 * Útil para debug ou se o utilizador pedir "ver todas as notificações novamente".
 */
function rmbz_notify_reset_dismissed( $telemovel ) {
    global $wpdb;
    $telemovel_clean = preg_replace('/[^0-9+]/', '', $telemovel);
    if ( empty( $telemovel_clean ) ) return false;

    return $wpdb->delete(
        "{$wpdb->prefix}rmbz_notifications_dismissed",
        [ 'telemovel' => $telemovel_clean ]
    );
}


/* ============================================================================
 * 3. RENDERING — funções para imprimir HTML
 * ============================================================================ */

/**
 * Renderiza o banner do topo da Distribuição (TODAS as severities).
 * Para uso dentro do shortcode [1mz_distribution_dashboard].
 */
function rmbz_notif_render_dashboard_banner( $telemovel ) {
    if ( empty( $telemovel ) ) return;

    $notifs = rmbz_notify_get_for_user( $telemovel, [
        'severity_filter' => 'info', // todas
        'limit'           => 5,
    ]);

    if ( empty( $notifs ) ) return;
    ?>
    <div class="rmbz-notif-banner-stack">
        <?php foreach ( $notifs as $n ): ?>
            <div class="rmbz-notif-card rmbz-notif-card--<?php echo esc_attr( $n->severity ); ?>"
                 data-notif-id="<?php echo (int) $n->id; ?>">
                <div class="rmbz-notif-icon"><?php echo esc_html( rmbz_notif_icon_for_severity( $n->severity ) ); ?></div>
                <div class="rmbz-notif-body">
                    <div class="rmbz-notif-title"><?php echo esc_html( $n->title ); ?></div>
                    <?php if ( ! empty( $n->body ) ): ?>
                        <div class="rmbz-notif-text"><?php echo wp_kses_post( $n->body ); ?></div>
                    <?php endif; ?>
                    <?php if ( ! empty( $n->action_url ) ): ?>
                        <a href="<?php echo esc_url( $n->action_url ); ?>" class="rmbz-notif-action">
                            <?php echo esc_html( $n->action_label ?: 'Ver detalhes' ); ?> →
                        </a>
                    <?php endif; ?>
                </div>
                <button type="button" class="rmbz-notif-dismiss"
                        data-notif-id="<?php echo (int) $n->id; ?>"
                        aria-label="Dispensar"
                        title="Dispensar">×</button>
            </div>
        <?php endforeach; ?>
    </div>
    <?php rmbz_notif_render_inline_styles_once();
}


/**
 * Renderiza a barra fixa global (APENAS warning/danger, mais recente).
 * Para uso em shortcodes/páginas onde queremos um aviso persistente.
 */
function rmbz_notif_render_global_bar( $telemovel ) {
    if ( empty( $telemovel ) ) return;

    $notifs = rmbz_notify_get_for_user( $telemovel, [
        'severity_filter' => 'warning', // só warning/danger
        'limit'           => 1,
    ]);

    if ( empty( $notifs ) ) return;
    $n = $notifs[0];
    ?>
    <div class="rmbz-notif-bar rmbz-notif-bar--<?php echo esc_attr( $n->severity ); ?>"
         data-notif-id="<?php echo (int) $n->id; ?>">
        <span class="rmbz-notif-bar-icon"><?php echo esc_html( rmbz_notif_icon_for_severity( $n->severity ) ); ?></span>
        <span class="rmbz-notif-bar-title"><?php echo esc_html( $n->title ); ?></span>
        <?php if ( ! empty( $n->action_url ) ): ?>
            <a href="<?php echo esc_url( $n->action_url ); ?>" class="rmbz-notif-bar-link">
                <?php echo esc_html( $n->action_label ?: 'Ver' ); ?> →
            </a>
        <?php endif; ?>
        <button type="button" class="rmbz-notif-bar-dismiss"
                data-notif-id="<?php echo (int) $n->id; ?>"
                aria-label="Dispensar"
                title="Dispensar">×</button>
    </div>
    <?php rmbz_notif_render_inline_styles_once();
}


/**
 * Helper — emoji por severity.
 */
function rmbz_notif_icon_for_severity( $sev ) {
    switch ( $sev ) {
        case 'success': return '✓';
        case 'warning': return '!';
        case 'danger':  return '⚠';
        case 'info':
        default:        return 'ℹ';
    }
}


/**
 * Imprime CSS + JS inline UMA VEZ por página (mesmo se chamarmos os
 * 2 renders banner+bar). Usa flag estática.
 */
function rmbz_notif_render_inline_styles_once() {
    static $printed = false;
    if ( $printed ) return;
    $printed = true;
    $ajax_url = esc_url( admin_url( 'admin-ajax.php' ) );
    $nonce    = wp_create_nonce( 'rmbz_notif_dismiss' );
    ?>
    <style>
    /* [1.5.182] Sistema de notificações — cores hardcoded para light theme
       e dark theme (lição da 1.5.176/177/181: tokens DS de superfície
       podem trocar com tema). Cada severity tem o seu par bg/text/border
       fixo. */

    /* === STACK DE BANNERS NO TOPO DA DISTRIBUIÇÃO === */
    .rmbz-notif-banner-stack {
        display: flex;
        flex-direction: column;
        gap: 10px;
        margin: 0 0 24px 0;
        font-family: var(--rmbz-font-body, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
    }
    .rmbz-notif-card {
        display: flex;
        gap: 12px;
        padding: 14px 18px;
        border-radius: 10px;
        border-left-width: 3px;
        border-left-style: solid;
        align-items: flex-start;
        animation: rmbz-notif-in 0.25s ease;
    }
    .rmbz-notif-card.is-dismissing {
        opacity: 0;
        transform: translateY(-4px);
        transition: all 0.2s ease;
    }
    @keyframes rmbz-notif-in {
        from { opacity: 0; transform: translateY(-4px); }
        to   { opacity: 1; transform: translateY(0); }
    }
    .rmbz-notif-card--info {
        background: #e8f3fc;
        border-color: #2563eb;
        color: #1e3a8a;
    }
    .rmbz-notif-card--success {
        background: #d4edda;
        border-color: #059669;
        color: #14532d;
    }
    .rmbz-notif-card--warning {
        background: #fff3cd;
        border-color: #d97706;
        color: #78350f;
    }
    .rmbz-notif-card--danger {
        background: #fde4e2;
        border-color: #dc2626;
        color: #7f1d1d;
    }
    .rmbz-notif-icon {
        font-size: 18px;
        line-height: 1;
        margin-top: 1px;
        flex-shrink: 0;
        width: 22px;
        text-align: center;
        font-weight: 600;
    }
    .rmbz-notif-body { flex: 1; min-width: 0; }
    .rmbz-notif-title {
        font-size: 14px;
        font-weight: 600;
        margin-bottom: 4px;
        line-height: 1.4;
    }
    .rmbz-notif-text {
        font-size: 13px;
        line-height: 1.5;
        opacity: 0.9;
    }
    .rmbz-notif-action {
        display: inline-block;
        margin-top: 8px;
        font-size: 13px;
        font-weight: 500;
        text-decoration: underline;
        color: inherit;
    }
    .rmbz-notif-action:hover { opacity: 0.8; }
    .rmbz-notif-dismiss {
        background: transparent;
        border: none;
        cursor: pointer;
        color: inherit;
        font-size: 20px;
        line-height: 1;
        padding: 0 4px;
        margin: -2px -6px 0 0;
        opacity: 0.6;
        flex-shrink: 0;
    }
    .rmbz-notif-dismiss:hover { opacity: 1; }

    /* === BARRA FIXA GLOBAL (warning/danger apenas) === */
    .rmbz-notif-bar {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 10px 16px;
        font-size: 13px;
        font-family: var(--rmbz-font-body, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
        margin: 0 0 12px 0;
        border-radius: 6px;
        border-left-width: 3px;
        border-left-style: solid;
        animation: rmbz-notif-in 0.25s ease;
    }
    .rmbz-notif-bar.is-dismissing {
        opacity: 0;
        transform: translateY(-4px);
        transition: all 0.2s ease;
    }
    .rmbz-notif-bar--warning {
        background: #fff3cd;
        border-color: #d97706;
        color: #78350f;
    }
    .rmbz-notif-bar--danger {
        background: #fde4e2;
        border-color: #dc2626;
        color: #7f1d1d;
    }
    .rmbz-notif-bar-icon {
        font-weight: 600;
        font-size: 14px;
        line-height: 1;
        width: 18px;
        text-align: center;
    }
    .rmbz-notif-bar-title { flex: 1; font-weight: 500; }
    .rmbz-notif-bar-link {
        color: inherit;
        text-decoration: underline;
        font-weight: 500;
        white-space: nowrap;
    }
    .rmbz-notif-bar-link:hover { opacity: 0.8; }
    .rmbz-notif-bar-dismiss {
        background: transparent;
        border: none;
        cursor: pointer;
        color: inherit;
        font-size: 16px;
        line-height: 1;
        padding: 0 4px;
        opacity: 0.6;
        flex-shrink: 0;
    }
    .rmbz-notif-bar-dismiss:hover { opacity: 1; }

    @media (max-width: 640px) {
        .rmbz-notif-bar { flex-wrap: wrap; }
        .rmbz-notif-bar-link { width: 100%; margin-top: 4px; }
    }
    </style>

    <script>
    (function(){
        var ajaxUrl = '<?php echo $ajax_url; ?>';
        var nonce   = '<?php echo $nonce; ?>';

        // Marcar dismissed no servidor. `keepalive:true` garante que a request
        // completa mesmo se o utilizador navegar para fora da página entretanto
        // (ex: ao clicar num link "Ver detalhes →").
        function sendDismiss(id) {
            if (!id) return;
            var fd = new FormData();
            fd.append('action', 'rmbz_notif_dismiss');
            fd.append('notification_id', String(id));
            fd.append('_nonce', nonce);
            try {
                fetch(ajaxUrl, {
                    method: 'POST',
                    body: fd,
                    credentials: 'same-origin',
                    keepalive: true   // [1.5.169] sobrevive a navigate
                });
            } catch(e) {
                // Fallback para sendBeacon se o browser não suporta keepalive
                if ( navigator.sendBeacon ) {
                    try { navigator.sendBeacon( ajaxUrl, fd ); } catch(_) {}
                }
            }
        }

        function dismiss(id, el) {
            if (!id || !el) return;
            // animação de saída
            el.classList.add('is-dismissing');
            setTimeout(function(){ el.parentNode && el.parentNode.removeChild(el); }, 220);
            sendDismiss(id);
        }

        document.addEventListener('click', function(e) {
            // Click em botão × (dismiss explícito)
            var btn = e.target.closest('.rmbz-notif-dismiss, .rmbz-notif-bar-dismiss');
            if (btn) {
                e.preventDefault();
                var id = btn.getAttribute('data-notif-id');
                var card = btn.closest('.rmbz-notif-card, .rmbz-notif-bar');
                dismiss(id, card);
                return;
            }

            // [1.5.169] Click no link "Ver detalhes →" / "Ver analytics →" /
            // "Ver →" — marcar como dismissed antes de navegar.
            // O utilizador clicou claramente porque viu a notificação e quer
            // ir lá; já não faz sentido voltar a aparecer no próximo refresh.
            // NÃO fazemos preventDefault — deixamos o link navegar normalmente.
            var link = e.target.closest('.rmbz-notif-action, .rmbz-notif-bar-link');
            if (link) {
                var card2 = link.closest('.rmbz-notif-card, .rmbz-notif-bar');
                if (card2) {
                    var nid = card2.getAttribute('data-notif-id');
                    if (nid) sendDismiss(nid);
                }
                // SEM preventDefault — link navega normalmente
            }
        }, false);
    })();
    </script>
    <?php
}


/* ============================================================================
 * 4. AJAX HANDLER — para o botão dismiss
 * ============================================================================ */

function rmbz_notif_ajax_dismiss() {
    // Nonce
    if ( empty( $_POST['_nonce'] ) || ! wp_verify_nonce( $_POST['_nonce'], 'rmbz_notif_dismiss' ) ) {
        wp_send_json_error( [ 'msg' => 'nonce_invalid' ], 403 );
    }

    // [1.5.168] FIX: usar a função real de auth do plugin.
    // ANTES: rmbz_get_logged_in_telemovel() — função NÃO EXISTE no codebase.
    // Resultado: 500 silencioso → card sumia visualmente mas dismissal nunca
    // era gravado em rmbz_notifications_dismissed → notificação voltava a
    // aparecer no próximo reload da página.
    if ( ! function_exists( 'rmbz_get_current_user_telemovel' ) ) {
        wp_send_json_error( [ 'msg' => 'no_session_helper' ], 500 );
    }
    $telemovel = rmbz_get_current_user_telemovel();
    if ( empty( $telemovel ) ) {
        wp_send_json_error( [ 'msg' => 'not_logged_in' ], 401 );
    }

    $notification_id = isset( $_POST['notification_id'] ) ? (int) $_POST['notification_id'] : 0;
    if ( $notification_id <= 0 ) {
        wp_send_json_error( [ 'msg' => 'invalid_id' ], 400 );
    }

    $ok = rmbz_notify_dismiss( $notification_id, $telemovel );
    if ( $ok ) {
        wp_send_json_success( [ 'dismissed' => $notification_id ] );
    }
    wp_send_json_error( [ 'msg' => 'db_error' ], 500 );
}
add_action( 'wp_ajax_rmbz_notif_dismiss',         'rmbz_notif_ajax_dismiss' );
add_action( 'wp_ajax_nopriv_rmbz_notif_dismiss',  'rmbz_notif_ajax_dismiss' );


/* ============================================================================
 * 5. ARCHIVE EXPIRED — limpeza periódica via WP cron
 * ============================================================================ */

/**
 * Marca como arquivadas todas as notificações já expiradas (não as apaga,
 * para auditoria — fica no histórico). Roda 1x por dia.
 */
function rmbz_notif_archive_expired() {
    global $wpdb;
    $now = current_time( 'mysql' );
    $wpdb->query( $wpdb->prepare(
        "UPDATE {$wpdb->prefix}rmbz_notifications
         SET is_archived = 1
         WHERE is_archived = 0
           AND expires_at IS NOT NULL
           AND expires_at <= %s",
        $now
    ));
}
add_action( 'rmbz_notif_daily_cleanup', 'rmbz_notif_archive_expired' );

// Schedule do cron
add_action( 'init', function() {
    if ( ! wp_next_scheduled( 'rmbz_notif_daily_cleanup' ) ) {
        wp_schedule_event( time() + 3600, 'daily', 'rmbz_notif_daily_cleanup' );
    }
});


/* ============================================================================
 * 6. CLI HELPER — para criar notificações via PHP de teste
 * ============================================================================
 *
 * Como criar uma notificação de teste manualmente (até a UI admin existir):
 *
 * Coloca no functions.php temporariamente:
 *
 *     add_action( 'init', function() {
 *         if ( isset($_GET['rmbz_test_notif']) && current_user_can('manage_options') ) {
 *             rmbz_notify_broadcast(
 *                 'Manutenção programada — Sábado 10/05',
 *                 'Distribuição offline durante ~30 min para upgrade.',
 *                 'info'
 *             );
 *             rmbz_notify_user(
 *                 '258861640807',
 *                 '"Cansei" foi devolvido pela curadoria',
 *                 'Motivo: capa não tem o título legível. Por favor corrija e reenvie.',
 *                 'danger',
 *                 [ 'action_url' => '?distro_action=edit_release&id=9', 'action_label' => 'Ver e corrigir' ]
 *             );
 *             wp_die('Notificações criadas.');
 *         }
 *     });
 *
 * Depois visita a Distribuição com o telemovel 258861640807 logado.
 * ============================================================================
 */

/* ============================================================================
 * [1.6.37] PREFERÊNCIAS DE PUSH POR CATEGORIA
 * ============================================================================
 *
 * O sistema de push (rmbz_fcm_auto_push) consulta estas funções antes de
 * enviar push notifications. Se a categoria estiver desactivada para o
 * user, o push é silenciado mas a notificação continua a ser criada na BD
 * (visível no sino do app).
 *
 * Categorias actuais:
 *   - releases    : release_live, release_rejected, release_returned,
 *                   match_review_*
 *   - submissions : submission_status_change (Site / Redes Sociais)
 *   - money       : withdrawal_approved, withdrawal_rejected
 *   - license     : license_approved, license_rejected, somas_*
 *   - other       : qualquer outro tipo (fallback)
 *
 * Para adicionar uma nova categoria, basta:
 *   1. Mapear os tipos novos em rmbz_notif_category_for_type()
 *   2. Adicionar o constante em RMBZ_NOTIF_CATEGORIES
 *   3. Adicionar label no controller da API
 */

/** Categorias suportadas — usadas para validação e UI. */
const RMBZ_NOTIF_CATEGORIES = [ 'releases', 'submissions', 'money', 'license', 'other' ];

/**
 * Mapeia um type de notificação para a sua categoria.
 *
 * @param string $type
 * @return string  Uma das constantes em RMBZ_NOTIF_CATEGORIES.
 */
function rmbz_notif_category_for_type( $type ) {
    $type = (string) $type;
    // Nota: usamos strpos === 0 em vez de str_starts_with por compat
    // com hosting em PHP 7.x (str_starts_with é PHP 8.0+).
    if ( strpos( $type, 'release_' ) === 0 || strpos( $type, 'match_review_' ) === 0 ) {
        return 'releases';
    }
    if ( strpos( $type, 'submission_' ) === 0 ) {
        return 'submissions';
    }
    if ( strpos( $type, 'withdrawal_' ) === 0 ) {
        return 'money';
    }
    if ( strpos( $type, 'license_' ) === 0 || strpos( $type, 'somas_' ) === 0 ) {
        return 'license';
    }
    return 'other';
}

/**
 * Verifica se uma categoria está activada para um telemóvel.
 * Default = true (se não há registo, o user não desactivou nada).
 *
 * @param string $telemovel
 * @param string $category
 * @return bool
 */
function rmbz_notif_is_category_enabled( $telemovel, $category ) {
    global $wpdb;
    $tel = preg_replace( '/[^0-9+]/', '', (string) $telemovel );
    if ( empty( $tel ) ) return true;

    $val = $wpdb->get_var( $wpdb->prepare(
        "SELECT enabled FROM {$wpdb->prefix}rmbz_user_notification_prefs
         WHERE telemovel = %s AND category = %s LIMIT 1",
        $tel, $category
    ) );

    // NULL = sem registo = default ON
    if ( $val === null ) return true;
    return (int) $val === 1;
}

/**
 * Devolve TODAS as preferências do user, com defaults (tudo activado).
 *
 * @param string $telemovel
 * @return array  Map { category => bool }
 */
function rmbz_notif_get_user_prefs( $telemovel ) {
    global $wpdb;
    $tel = preg_replace( '/[^0-9+]/', '', (string) $telemovel );

    // Defaults: tudo on
    $result = [];
    foreach ( RMBZ_NOTIF_CATEGORIES as $cat ) {
        $result[ $cat ] = true;
    }

    if ( empty( $tel ) ) return $result;

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT category, enabled FROM {$wpdb->prefix}rmbz_user_notification_prefs
         WHERE telemovel = %s",
        $tel
    ) );

    foreach ( $rows as $row ) {
        if ( isset( $result[ $row->category ] ) ) {
            $result[ $row->category ] = (int) $row->enabled === 1;
        }
    }

    return $result;
}

/**
 * Define o estado de UMA categoria. Cria registo se não existe.
 *
 * @param string $telemovel
 * @param string $category
 * @param bool   $enabled
 * @return bool
 */
function rmbz_notif_set_user_pref( $telemovel, $category, $enabled ) {
    global $wpdb;
    $tel = preg_replace( '/[^0-9+]/', '', (string) $telemovel );
    if ( empty( $tel ) ) return false;
    if ( ! in_array( $category, RMBZ_NOTIF_CATEGORIES, true ) ) return false;

    $table = $wpdb->prefix . 'rmbz_user_notification_prefs';
    $enabled = $enabled ? 1 : 0;

    // INSERT ... ON DUPLICATE KEY UPDATE (chave UNIQUE em telemovel+category)
    $sql = $wpdb->prepare(
        "INSERT INTO $table (telemovel, category, enabled, updated_at)
         VALUES (%s, %s, %d, %s)
         ON DUPLICATE KEY UPDATE enabled = %d, updated_at = %s",
        $tel, $category, $enabled, current_time( 'mysql' ),
        $enabled, current_time( 'mysql' )
    );
    return $wpdb->query( $sql ) !== false;
}
