// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package router

import (
	"go4.org/netipx"
	"tailscale.com/types/logger"
)

// ConsolidatingRoutes wraps a Router with logic that consolidates Routes
// whenever Set is called. It attempts to consolidate cfg.Routes into the
// smallest possible set.
func ConsolidatingRoutes(logf logger.Logf, router Router) Router {
	return &consolidatingRouter{Router: router, logf: logger.WithPrefix(logf, "router: ")}
}

type consolidatingRouter struct {
	Router
	logf logger.Logf
}

// Set implements Router and attempts to consolidate cfg.Routes into the
// smallest possible set.
func (cr *consolidatingRouter) Set(cfg *Config) error {
	return cr.Router.Set(cr.consolidateRoutes(cfg))
}

func (cr *consolidatingRouter) consolidateRoutes(cfg *Config) *Config {
	if cfg == nil {
		return nil
	}
	if len(cfg.Routes) < 2 {
		return cfg
	}
	if len(cfg.Routes) == 2 && cfg.Routes[0].Addr().Is4() != cfg.Routes[1].Addr().Is4() {
		return cfg
	}
	var builder netipx.IPSetBuilder
	for _, route := range cfg.Routes {
		builder.AddPrefix(route)
	}
	set, err := builder.IPSet()
	if err != nil {
		cr.logf("consolidateRoutes failed, keeping existing routes: %s", err)
		return cfg
	}
	newRoutes := set.Prefixes()
	oldLength := len(cfg.Routes)
	newLength := len(newRoutes)
	if oldLength == newLength {
		// Nothing consolidated, return as-is.
		return cfg
	}
	cr.logf("consolidated %d routes down to %d", oldLength, newLength)
	newCfg := *cfg
	newCfg.Routes = newRoutes
	return &newCfg
}
