package asr

import (
	"context"
	"sync"

	"dudu/server/pkg/protocol"
)

// MockProvider 测试/开发用：按收到的音频量逐字"识别"出预置句子，
// 行为模拟 Gummy：每帧产出 partial，句读处产出 final。
type MockProvider struct {
	// Script 轮换使用的识别文本
	Script []string
	mu     sync.Mutex
	idx    int
}

func NewMock() *MockProvider {
	return &MockProvider{Script: []string{
		"帮我把这份周报整理一下，重点突出本周的进展。",
		"好的，我马上把文件发给你，大概十分钟之内。",
	}}
}

func (m *MockProvider) Name() string { return "mock" }

func (m *MockProvider) StartSession(ctx context.Context, cfg SessionConfig) (Session, error) {
	m.mu.Lock()
	sentence := []rune(m.Script[m.idx%len(m.Script)])
	m.idx++
	m.mu.Unlock()
	s := &mockSession{
		sentence: sentence,
		results:  make(chan Result, 64),
		done:     make(chan struct{}),
	}
	return s, nil
}

type mockSession struct {
	sentence  []rune
	results   chan Result
	pos       int // 已"识别"的字数
	committed int // 已 final 的字数
	bytes     int
	closeOnce sync.Once
	done      chan struct{}
	mu        sync.Mutex
}

// 每帧（100ms 音频）识别出 1 个字；遇句读（，。）将其前内容定稿为 final。
func (s *mockSession) SendAudio(pcm []byte) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	select {
	case <-s.done:
		return nil
	default:
	}
	s.bytes += len(pcm)
	for s.pos < len(s.sentence) && s.bytes >= (s.pos+1)*protocol.FrameBytes {
		s.pos++
		ch := s.sentence[s.pos-1]
		endMs := int64(s.bytes) * 1000 / 32000 // 模拟 provider 侧时间戳=实收音频时长
		if ch == '，' || ch == '。' {
			s.emit(Result{Text: string(s.sentence[s.committed:s.pos]), IsFinal: true, EndTimeMs: endMs})
			s.committed = s.pos
		} else {
			s.emit(Result{Text: string(s.sentence[s.committed:s.pos]), EndTimeMs: endMs})
		}
	}
	return nil
}

func (s *mockSession) emit(r Result) {
	select {
	case s.results <- r:
	default: // 测试场景下不阻塞
	}
}

func (s *mockSession) Results() <-chan Result { return s.results }

// Close flush 未定稿部分为 final 并关闭通道。
func (s *mockSession) Close() error {
	s.closeOnce.Do(func() {
		s.mu.Lock()
		if s.pos > s.committed {
			s.emit(Result{Text: string(s.sentence[s.committed:s.pos]), IsFinal: true, EndTimeMs: int64(s.bytes) * 1000 / 32000})
			s.committed = s.pos
		}
		close(s.done)
		close(s.results)
		s.mu.Unlock()
	})
	return nil
}
