// Package extsvc holds shared plumbing for the two independent external
// services (Atomization, Work Performer): a small circuit breaker and a
// retrying JSON HTTP helper. The services themselves stay fully independent
// (§5) — each gets its own breaker instance, base URL, and token.
package extsvc

import (
	"sync"
	"time"
)

// Breaker implements §11.16: open after 3 consecutive failures, half-open
// probe every 60 s.
type Breaker struct {
	mu        sync.Mutex
	fails     int
	openedAt  time.Time
	probing   bool
	now       func() time.Time
	threshold int
	cooldown  time.Duration
}

func NewBreaker() *Breaker {
	return &Breaker{now: time.Now, threshold: 3, cooldown: 60 * time.Second}
}

// Allow reports whether a call may proceed right now. In the open state one
// probe is allowed per cooldown window (half-open).
func (b *Breaker) Allow() bool {
	b.mu.Lock()
	defer b.mu.Unlock()
	if b.fails < b.threshold {
		return true
	}
	if b.probing {
		return false
	}
	if b.now().Sub(b.openedAt) >= b.cooldown {
		b.probing = true // half-open: exactly one probe in flight
		return true
	}
	return false
}

// Record reports a call outcome. Success closes the breaker; failure counts
// toward (or re-arms) the open state.
func (b *Breaker) Record(err error) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.probing = false
	if err == nil {
		b.fails = 0
		return
	}
	b.fails++
	if b.fails >= b.threshold {
		b.openedAt = b.now()
	}
}

// State returns closed | open | half-open for status panels.
func (b *Breaker) State() string {
	b.mu.Lock()
	defer b.mu.Unlock()
	if b.fails < b.threshold {
		return "closed"
	}
	if b.probing || b.now().Sub(b.openedAt) >= b.cooldown {
		return "half-open"
	}
	return "open"
}
