//go:build !darwin || !cgo

package oomkiller

import (
	"context"

	"github.com/sagernet/sing-box/adapter"
	boxService "github.com/sagernet/sing-box/adapter/service"
	boxConstant "github.com/sagernet/sing-box/constant"
	"github.com/sagernet/sing-box/log"
	"github.com/sagernet/sing-box/option"
	E "github.com/sagernet/sing/common/exceptions"
	"github.com/sagernet/sing/common/memory"
	"github.com/sagernet/sing/service"
)

func RegisterService(registry *boxService.Registry) {
	boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService)
}

type Service struct {
	boxService.Adapter
	logger        log.ContextLogger
	router        adapter.Router
	adaptiveTimer *adaptiveTimer
	timerConfig   timerConfig
	hasTimerMode  bool
	useAvailable  bool
	memoryLimit   uint64
}

func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) {
	s := &Service{
		Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),
		logger:  logger,
		router:  service.FromContext[adapter.Router](ctx),
	}

	if options.MemoryLimit != nil {
		s.memoryLimit = options.MemoryLimit.Value()
	}
	if s.memoryLimit > 0 {
		s.hasTimerMode = true
	} else if memory.AvailableSupported() {
		s.useAvailable = true
		s.hasTimerMode = true
	}

	config, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable)
	if err != nil {
		return nil, err
	}
	s.timerConfig = config

	return s, nil
}

func (s *Service) Start(stage adapter.StartStage) error {
	if stage != adapter.StartStateStart {
		return nil
	}
	if !s.hasTimerMode {
		return E.New("memory pressure monitoring is not available on this platform without memory_limit")
	}
	s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
	s.adaptiveTimer.start(false)
	if s.useAvailable {
		s.logger.Info("started memory monitor with available memory detection")
	} else {
		s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
	}
	return nil
}

func (s *Service) Close() error {
	if s.adaptiveTimer != nil {
		s.adaptiveTimer.stop()
	}
	return nil
}
