// SPDX-License-Identifier: GPL-3.0-or-later

// Package term provides signal handling utilities.
package term

import (
	"context"
	"os"
	"os/signal"
	"syscall"
)

// SignalHandler handles OS signals.
type SignalHandler struct {
	signals chan os.Signal
	done    chan struct{}
}

// NewSignalHandler creates a new signal handler.
func NewSignalHandler() *SignalHandler {
	return &SignalHandler{
		signals: make(chan os.Signal, 1),
		done:    make(chan struct{}),
	}
}

// Setup sets up signal handling for graceful shutdown.
// Returns a context that is cancelled when SIGINT or SIGTERM is received.
func (h *SignalHandler) Setup() context.Context {
	ctx, cancel := context.WithCancel(context.Background())

	signal.Notify(h.signals, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		select {
		case <-h.signals:
			cancel()
		case <-h.done:
			cancel()
		}
		signal.Stop(h.signals)
	}()

	return ctx
}

// SetupWithContext sets up signal handling with an existing context.
// Returns a context that is cancelled when SIGINT/SIGTERM is received
// or when the parent context is cancelled.
func (h *SignalHandler) SetupWithContext(parent context.Context) context.Context {
	ctx, cancel := context.WithCancel(parent)

	signal.Notify(h.signals, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		select {
		case <-h.signals:
			cancel()
		case <-parent.Done():
			cancel()
		case <-h.done:
			cancel()
		}
		signal.Stop(h.signals)
	}()

	return ctx
}

// Stop stops the signal handler.
func (h *SignalHandler) Stop() {
	close(h.done)
}

// Wait waits for a signal or context cancellation.
// Returns true if a signal was received, false if context was cancelled.
func (h *SignalHandler) Wait(ctx context.Context) bool {
	select {
	case <-h.signals:
		return true
	case <-ctx.Done():
		return false
	case <-h.done:
		return false
	}
}

// OnInterrupt sets up a callback for SIGINT (Ctrl+C).
// Returns a cleanup function that should be called when done.
func OnInterrupt(callback func()) func() {
	signals := make(chan os.Signal, 1)
	done := make(chan struct{})

	signal.Notify(signals, syscall.SIGINT)

	go func() {
		select {
		case <-signals:
			callback()
		case <-done:
		}
		signal.Stop(signals)
	}()

	return func() {
		close(done)
	}
}

// WithInterruptContext returns a context that is cancelled on SIGINT.
// Also returns a cleanup function that should be deferred.
func WithInterruptContext() (context.Context, func()) {
	ctx, cancel := context.WithCancel(context.Background())
	signals := make(chan os.Signal, 1)

	signal.Notify(signals, syscall.SIGINT)

	go func() {
		<-signals
		cancel()
		signal.Stop(signals)
	}()

	return ctx, func() {
		cancel()
		signal.Stop(signals)
	}
}
