<?php
/**
 * Plugin Name: Fea Audio Player (#76)
 * Description: Reproductor TTS compacto en la fila del autor (a la derecha) cuando
 *              existe el meta fea_audio_url (voz Nico, MiniMax HD). Se inyecta como
 *              último hijo del grupo flex del autor (template FSE) vía render_block.
 */
if (!defined('ABSPATH')) exit;

/** Devuelve el HTML del reproductor para el post actual, o '' si no hay audio. */
function fea_audio_player_html(): string {
    $id = get_the_ID();
    $url = get_post_meta($id, 'fea_audio_url', true);
    if (!$url) return '';
    $voice = get_post_meta($id, 'fea_audio_voice', true) ?: 'NicoFeadulta2026';
    $sha256 = get_post_meta($id, 'fea_audio_sha256', true);
    $label = '<span class="fea-audio-label">'
        . '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">'
        . '<path fill="currentColor" d="M3 10v4h4l5 5V5L7 10H3zm13.5 2a4.5 4.5 0 0 0-2.5-4.03v8.06A4.5 4.5 0 0 0 16.5 12zM14 3.23v2.06a7 7 0 0 1 0 13.42v2.06a9 9 0 0 0 0-17.54z"/>'
        . '</svg> Escucha</span>';
    // Para un asset con hash registrado, descarga y verifica el fichero completo
    // antes de delegar playback al navegador. Evita que un stream Range mezclado
    // por una caché reproduzca fragmentos de otro MP3.
    if (preg_match('/^[a-f0-9]{64}$/i', $sha256)) {
        return '<div class="fea-audio">' . $label
            . '<button type="button" class="fea-audio-verified-play" data-fea-audio-verified-play'
            . ' data-src="' . esc_url($url) . '" data-sha256="' . esc_attr(strtolower($sha256)) . '"'
            . ' data-post-id="' . esc_attr($id) . '" data-voice="' . esc_attr($voice) . '">Reproducir audio</button>'
            . '<audio controls preload="none" hidden data-fea-audio-track data-post-id="' . esc_attr($id) . '" data-voice="' . esc_attr($voice) . '"></audio>'
            . '<span class="fea-audio-status" role="status" aria-live="polite"></span></div>';
    }
    return '<div class="fea-audio">' . $label
        . '<audio controls preload="none" src="' . esc_url($url) . '"'
        . ' data-fea-audio-track data-post-id="' . esc_attr($id) . '" data-voice="' . esc_attr($voice) . '"></audio>'
        . '</div>';
}

// Inyecta el player como último hijo de la fila flex del autor (el grupo que
// contiene el core/avatar en la cabecera FSE del single).
add_filter('render_block', function ($html, $block) {
    if (!is_singular('post')) return $html;
    if (($block['blockName'] ?? '') !== 'core/group') return $html;
    $has_avatar = false;
    foreach ($block['innerBlocks'] ?? [] as $ib) {
        if (($ib['blockName'] ?? '') === 'core/avatar') { $has_avatar = true; break; }
    }
    if (!$has_avatar) return $html;
    $player = fea_audio_player_html();
    if (!$player) return $html;
    $pos = strrpos($html, '</div>');
    return $pos === false ? $html . $player : substr($html, 0, $pos) . $player . substr($html, $pos);
}, 10, 2);

add_action('wp_head', function () {
    if (!is_singular('post')) return;
    if (!get_post_meta(get_queried_object_id(), 'fea_audio_url', true)) return;
    ?>
    <style>
    /* El player se inyecta en el grupo flex del autor. En el single FSE ese
       contenedor es el wp-block-group (no .fea-byline, que solo existe en Astra),
       así que el wrap debe apuntar también a él o en móvil vertical el <audio>
       no encuentra ancho y no muestra controles (#142). */
    .fea-byline,
    .wp-block-group:has(> .wp-block-avatar):has(.wp-block-post-author-name){flex-wrap:wrap}
    .fea-audio{display:flex;align-items:center;gap:.5rem;margin-left:auto;
        padding:.3rem .55rem;background:#faf6f7;border:1px solid #e7d6da;
        border-left:3px solid #8b1a2e;border-radius:8px}
    .fea-audio-label{display:inline-flex;align-items:center;gap:.35rem;font-size:.78rem;
        font-weight:600;color:#8b1a2e;white-space:nowrap;line-height:1}
    .fea-audio audio{height:32px;width:230px;max-width:44vw}
    .fea-audio-verified-play{border:1px solid #8b1a2e;border-radius:6px;background:#fff;color:#8b1a2e;padding:.35rem .6rem;font:inherit;font-size:.82rem;font-weight:600;cursor:pointer}
    .fea-audio-verified-play:disabled{opacity:.65;cursor:wait}
    .fea-audio-status{font-size:.76rem;color:#6b5c60}
    @media(max-width:600px){
        .fea-audio{margin-left:0;width:100%;margin-top:.5rem}
        .fea-audio audio{flex:1 1 auto;width:auto;max-width:none}
    }
    </style>
    <?php
});

// Evento GA4 audio_play (issue tracking uso TTS). Un único evento por <audio>
// y carga de página, disparado en el primer 'play' (no en cada resume tras
// pausa/seek). gtag ya está definido por fea-analytics.php en wp_head prio 1,
// así que este script (footer) siempre lo encuentra disponible.
add_action('wp_footer', function () {
    if (!is_singular('post')) return;
    if (!get_post_meta(get_queried_object_id(), 'fea_audio_url', true)) return;
    ?>
    <script>
    (function () {
        function hex(buffer) {
            return Array.from(new Uint8Array(buffer)).map(function (n) {
                return n.toString(16).padStart(2, '0');
            }).join('');
        }
        document.querySelectorAll('[data-fea-audio-verified-play]').forEach(function (button) {
            button.addEventListener('click', async function () {
                var wrap = button.closest('.fea-audio');
                var audio = wrap.querySelector('audio[data-fea-audio-track]');
                var status = wrap.querySelector('.fea-audio-status');
                button.disabled = true;
                status.textContent = 'Verificando audio…';
                try {
                    // Se descarga el objeto entero una vez y se valida su hash. Sólo
                    // entonces se crea un blob local: el <audio> no hará Range contra
                    // CDN y jamás podrá mezclar fragmentos de otro MP3.
                    var response = await fetch(button.dataset.src, {cache: 'no-store'});
                    if (!response.ok) throw new Error('HTTP ' + response.status);
                    var bytes = await response.arrayBuffer();
                    var digest = hex(await crypto.subtle.digest('SHA-256', bytes));
                    if (digest !== button.dataset.sha256) throw new Error('La copia recibida no coincide con el audio verificado');
                    audio.src = URL.createObjectURL(new Blob([bytes], {type: 'audio/mpeg'}));
                    audio.hidden = false;
                    button.hidden = true;
                    status.textContent = '';
                    await audio.play();
                } catch (error) {
                    status.textContent = 'No se pudo verificar el audio. Inténtalo de nuevo.';
                    button.disabled = false;
                    console.error('Fe Adulta audio verification failed:', error);
                }
            });
        });
        document.querySelectorAll('audio[data-fea-audio-track]').forEach(function (audio) {
            var fired = false;
            audio.addEventListener('play', function () {
                // La verificación pudo fallar en el primer intento (p.ej. audio.play()
                // rechazado por política de autoplay tras el await de verificación) y
                // dejar el mensaje de error en pantalla. Si el audio realmente rompe a
                // reproducir -en este intento o en uno posterior desde los controles
                // nativos-, ese es el estado real: limpiar el aviso y ocultar el botón.
                var wrap = audio.closest('.fea-audio');
                if (wrap) {
                    var status = wrap.querySelector('.fea-audio-status');
                    if (status) status.textContent = '';
                    var button = wrap.querySelector('[data-fea-audio-verified-play]');
                    if (button) button.hidden = true;
                }
                if (fired || typeof gtag !== 'function') return;
                fired = true;
                gtag('event', 'audio_play', {
                    post_id: audio.dataset.postId,
                    voice: audio.dataset.voice
                });
            });
        });
    }());
    </script>
    <?php
});
