package crashlog

import (
	"fmt"
	"log"
	"runtime"
	"strings"
	"sync"
	"time"
)

// ── Mutex with debug tracking ──

// DebugMutex wraps a sync.Mutex with lock-holder tracking.
type DebugMutex struct {
	mu       sync.Mutex
	holder   string
	lockedAt time.Time
}

// Lock acquires the mutex with caller tracking.
func (m *DebugMutex) Lock() {
	m.mu.Lock()
	_, file, line, _ := runtime.Caller(1)
	if idx := strings.LastIndex(file, "Cyrene/"); idx >= 0 {
		file = file[idx+len("Cyrene/"):]
	}
	m.holder = fmt.Sprintf("%s:%d", file, line)
	m.lockedAt = time.Now()
}

// Unlock releases the mutex.
func (m *DebugMutex) Unlock() {
	m.holder = ""
	m.mu.Unlock()
}

// TryLock attempts to acquire the lock with a timeout. Returns true if acquired.
func (m *DebugMutex) TryLock(timeout time.Duration) bool {
	done := make(chan struct{})
	go func() {
		m.mu.Lock()
		m.mu.Unlock()
		close(done)
	}()
	select {
	case <-done:
		return true
	case <-time.After(timeout):
		return false
	}
}

// Holder returns who holds the lock.
func (m *DebugMutex) Holder() (string, time.Time) {
	return m.holder, m.lockedAt
}

// ── Channel debug helpers ──

// ChSend sends to a channel with a timeout and logs if it blocks.
func ChSend[T any](name string, ch chan<- T, val T, timeout time.Duration) bool {
	select {
	case ch <- val:
		return true
	case <-time.After(timeout):
		log.Printf("[debug] ChSend timeout: %s after %v", name, timeout)
		return false
	}
}

// ── Goroutine lifecycle ──

// GoStart logs goroutine start and wraps fn with panic recovery.
func GoStart(name string, fn func()) {
	log.Printf("[debug] goroutine START: %s", name)
	go func() {
		defer func() {
			if r := recover(); r != nil {
				stack := make([]byte, 4096)
				n := runtime.Stack(stack, false)
				log.Printf("[debug] goroutine PANIC: %s panic=%v\n%s", name, r, stack[:n])
			}
			log.Printf("[debug] goroutine EXIT: %s", name)
		}()
		fn()
	}()
}

// ── Timing ──

// Since logs if a duration exceeds a threshold.
func Since(name string, start time.Time, threshold time.Duration) {
	elapsed := time.Since(start)
	if elapsed > threshold {
		log.Printf("[debug] SLOW: %s took %v (threshold=%v)", name, elapsed.Round(time.Millisecond), threshold)
	}
}
