from pathlib import Path
root=Path(__file__).resolve().parent
# Run in the isolated diagnostic worktree.
p=Path('boards/m5stack_stopwatch/board.cpp');s=p.read_text()
old='''    if (type.rfind("speech_",0)==0) {
        cJSON_Delete(root);return;
    }'''
new='''    if (type.rfind("speech_",0)==0) {
        const bool redraw=speech_message(root,type);
        cJSON_Delete(root);if(redraw) draw_status();return;
    }'''
assert s.count(old)==1;s=s.replace(old,new)
s=s.replace('if (s_approval.modal() || (s_input_modal!=InputModal::NONE && !(s_input_modal==InputModal::VOICE && s_voice_phase=="SENT")) || js(root,"target")!=selected_target()) {stop_speech();return true;}', 'if (s_approval.modal() || (s_input_modal!=InputModal::NONE && !(s_input_modal==InputModal::VOICE && s_voice_phase=="SENT")) || js(root,"target")!=selected_target()) {ESP_LOGW(TAG,"[DIAG_SPEECH] rejected_start approval=%d modal=%s target_match=%d",s_approval.modal(),modal_name(s_input_modal),js(root,"target")==selected_target());stop_speech();return true;}')
p.write_text(s)
p=Path('boards/m5stack_stopwatch/audio_backend.cpp');s=p.read_text();s=s.replace('#include "esp_heap_caps.h"','#include "esp_heap_caps.h"\n#include "mbedtls/sha256.h"')
s=s.replace('TickType_t s_speech_progress_at;', '''TickType_t s_speech_progress_at;
mbedtls_sha256_context s_diag_accepted;
size_t s_diag_accepted_bytes=0;unsigned s_diag_frames=0;
void diag_digest(mbedtls_sha256_context *ctx, char *hex) {
    unsigned char digest[32];mbedtls_sha256_finish(ctx,digest);
    static const char digits[]="0123456789abcdef";
    for(unsigned i=0;i<32;++i){hex[2*i]=digits[digest[i]>>4];hex[2*i+1]=digits[digest[i]&15];}hex[64]=0;
}''')
s=s.replace('''    auto *frame = new SpeechFrame{};
    const TickType_t idle_timeout''','''    auto *frame = new SpeechFrame{};
    mbedtls_sha256_context written_ctx;mbedtls_sha256_init(&written_ctx);
    uint32_t written_id=0;size_t written_bytes=0;unsigned written_frames=0;int64_t first_us=0;
    const TickType_t idle_timeout''')
s=s.replace('''        if (s_speech_id != frame->id || s_recording || s_approval_tone) continue;
        bool ok = true;''','''        if (s_speech_id != frame->id || s_recording || s_approval_tone) {
            ESP_LOGW(TAG,"[DIAG_SPEECH] drop generation=%u active=%u recording=%d approval=%d bytes=%u",frame->id,s_speech_id.load(),s_recording.load(),s_approval_tone.load(),unsigned(frame->size));continue;
        }
        if(written_id!=frame->id){written_id=frame->id;written_bytes=0;written_frames=0;first_us=esp_timer_get_time();mbedtls_sha256_starts(&written_ctx,0);ESP_LOGI(TAG,"[DIAG_SPEECH] first_write generation=%u device_us=%lld",written_id,(long long)first_us);}
        bool ok = true;''')
s=s.replace('''            if (esp_codec_dev_write(s_codec_dev, frame->pcm+offset, std::min<size_t>(512,frame->size-offset))!=ESP_OK) { ok=false; break; }''','''            const size_t bytes=std::min<size_t>(512,frame->size-offset);
            const int rc=esp_codec_dev_write(s_codec_dev,frame->pcm+offset,bytes);
            if(rc!=ESP_OK){ESP_LOGW(TAG,"[DIAG_SPEECH] codec_write_fail rc=%d total=%u",rc,unsigned(written_bytes));ok=false;break;}
            mbedtls_sha256_update(&written_ctx,frame->pcm+offset,bytes);written_bytes+=bytes;''')
s=s.replace('''        if (!ok || frame->end) {
            uint32_t expected=frame->id;''','''        if(frame->size)++written_frames;
        if(written_frames==1 || written_frames%10==0 || frame->end)ESP_LOGI(TAG,"[DIAG_SPEECH] written generation=%u frames=%u bytes=%u queue=%u",written_id,written_frames,unsigned(written_bytes),unsigned(uxQueueMessagesWaiting(s_speech_queue)));
        if (!ok || frame->end) {
            char hex[65];diag_digest(&written_ctx,hex);
            ESP_LOGI(TAG,"[DIAG_SPEECH] playback_end generation=%u reason=%s written_bytes=%u elapsed_ms=%lld sha256=%s",written_id,ok?"end":"codec-error",unsigned(written_bytes),(long long)((esp_timer_get_time()-first_us)/1000),hex);
            uint32_t expected=frame->id;''')
s=s.replace('''    if (!s_ready || !id || s_recording || s_approval_tone) return false;
    s_speech_id=0;''','''    if (!s_ready || !id || s_recording || s_approval_tone) {ESP_LOGW(TAG,"[DIAG_SPEECH] start_reject ready=%d recording=%d approval=%d",s_ready,s_recording.load(),s_approval_tone.load());return false;}
    mbedtls_sha256_init(&s_diag_accepted);mbedtls_sha256_starts(&s_diag_accepted,0);s_diag_accepted_bytes=0;s_diag_frames=0;
    ESP_LOGI(TAG,"[DIAG_SPEECH] backend_start generation=%u device_us=%lld recording=0 approval=0",id,(long long)esp_timer_get_time());
    s_speech_id=0;''')
s=s.replace('''    if (xQueueSend(s_speech_queue,&frame,0)!=pdTRUE) {
        s_speech_id=0;''','''    if (xQueueSend(s_speech_queue,&frame,0)!=pdTRUE) {
        ESP_LOGW(TAG,"[DIAG_SPEECH] queue_full generation=%u accepted=%u queue=%u",id,unsigned(s_diag_accepted_bytes),unsigned(uxQueueMessagesWaiting(s_speech_queue)));
        s_speech_id=0;''')
s=s.replace('''    s_speech_progress_at=xTaskGetTickCount();
    return true;
}
void audio_backend_speech_stop''','''    if(size){mbedtls_sha256_update(&s_diag_accepted,pcm,size);s_diag_accepted_bytes+=size;++s_diag_frames;}
    if(s_diag_frames==1 || s_diag_frames%10==0 || end)ESP_LOGI(TAG,"[DIAG_SPEECH] accepted generation=%u frames=%u bytes=%u queue=%u",id,s_diag_frames,unsigned(s_diag_accepted_bytes),unsigned(uxQueueMessagesWaiting(s_speech_queue)));
    if(end){char hex[65];diag_digest(&s_diag_accepted,hex);ESP_LOGI(TAG,"[DIAG_SPEECH] accepted_end generation=%u bytes=%u sha256=%s",id,unsigned(s_diag_accepted_bytes),hex);}
    s_speech_progress_at=xTaskGetTickCount();
    return true;
}
void audio_backend_speech_stop''')
p.write_text(s)
print('Diagnostic receiver enabled; byte/SHA256 logs added; hello capability untouched')
