package hureg

import (
	"github.com/danielgtaylor/huma/v2"

	"github.com/cardinalby/hureg/pkg/huma/humaapi"
	"github.com/cardinalby/hureg/pkg/huma/op_handler"
)

type ExtraHumaApiInstance struct {
	humaAPI                huma.API
	bubblingRegMiddlewares RegMiddlewares
}

// APIGen is a core type of the library that wraps huma.API and stores RegMiddlewares that should be
// applied to operations before registration in Huma.
// It provides a fluent API to create derived APIGen instances with own set of actions/changes to an
// operation before its registration.
type APIGen struct {
	humaAPIWrapper         humaApiWrapper
	regMiddlewares         RegMiddlewares
	bubblingRegMiddlewares RegMiddlewares
	transformers           []huma.Transformer
	extraHumaAPIs          []ExtraHumaApiInstance
}

// NewAPIGen creates a new APIGen instance with the given huma.API.
func NewAPIGen(humaApi huma.API) APIGen {
	return APIGen{
		humaAPIWrapper: newHumaApiWrapper(humaApi),
	}
}

// ReplaceHumaAPI returns a new APIGen instance with replaced huma.API pointer.
// Can be useful for some tricky cases when you create huma.API instance based on another adapter but with
// the same config (and OpenAPI object pointer) to utilize router-specific middlewares for a group.
func (a APIGen) ReplaceHumaAPI(humaApi huma.API) APIGen {
	a.humaAPIWrapper = newHumaApiWrapper(humaApi)
	return a
}

// GetHumaAPI returns the wrapped huma.API.
func (a APIGen) GetHumaAPI() huma.API {
	return a.humaAPIWrapper
}

// AddRegMiddleware returns a new APIGen instance with the given RegMiddlewares added to the stored RegMiddlewares.
func (a APIGen) AddRegMiddleware(regMiddlewares ...RegMiddleware) APIGen {
	a.regMiddlewares = append(a.regMiddlewares, regMiddlewares...)
	return a
}

// GetRegMiddlewares returns the stored RegMiddlewares.
func (a APIGen) GetRegMiddlewares() RegMiddlewares {
	return a.regMiddlewares
}

// AddBubblingRegMiddleware returns a new APIGen instance with the given RegMiddlewares added to the
// stored bubbling RegMiddlewares. Bubbling RegMiddlewares will be applied to the operation in the opposite order
// after normal RegMiddlewares are done allowing you to observe changes made by all previous RegMiddlewares.
func (a APIGen) AddBubblingRegMiddleware(regMiddlewares ...RegMiddleware) APIGen {
	a.bubblingRegMiddlewares = append(a.bubblingRegMiddlewares, regMiddlewares...)
	return a
}

// GetBubblingRegMiddlewares returns the stored bubbling RegMiddlewares.
func (a APIGen) GetBubblingRegMiddlewares() RegMiddlewares {
	return a.bubblingRegMiddlewares
}

// AddOpHandler returns a new APIGen instance with the given OperationHandlers added to it.
func (a APIGen) AddOpHandler(handlers ...op_handler.OperationHandler) APIGen {
	for _, opHandler := range handlers {
		a.regMiddlewares = append(a.regMiddlewares, NewRegMiddleware(opHandler))
	}
	return a
}

// AddTransformers returns a new APIGen instance with the given transformers that will be applied
// to the responses of the handlers registered by this APIGen.
func (a APIGen) AddTransformers(transformers ...huma.Transformer) APIGen {
	a.transformers = append(a.transformers, transformers...)
	return a
}

// GetTransformers returns the stored transformers that will be applied to the responses of the handlers
// registered by this APIGen.
func (a APIGen) GetTransformers() []huma.Transformer {
	return a.transformers
}

// AddBasePath returns a new APIGen instance that will add the given basePath segment to an operation`s Path.
// It will append `basePath` to the previously added base paths if any.
// Adding a base path also re-generates the OperationID and Summary fields of an operation if it wasn't explicitly set.
// It's an alternative to Route, Group methods of go routes.
func (a APIGen) AddBasePath(basePath string) APIGen {
	return a.AddOpHandler(
		op_handler.AddBasePath(basePath),
		op_handler.UpdateOperationID(func(op *huma.Operation) string {
			// Unlike fan-out, adding base path doesn't create multiple operation registrations
			// and doesn't require mandatory operation ID update if it's explicitly provided,
			// but generated operation IDs will be updated
			return op.OperationID
		}),
		op_handler.UpdateGeneratedSummary,
	)
}

// AddMultiBasePaths returns a new APIGen instance that will register the same operation with multiple base paths.
// It respects base paths added before and after this method call.
// Since it leads to multiple registrations of the same operation, it requires OperationID to be updated to
// avoid registration of multiple operations with the same OperationID.
// If an operation has generated OperationID, it will be re-generated by default Huma operation ID builder.
// The same is true for the Summary field.
// For the case of explicitly set OperationID, you can provide a custom `explicitOpIDBuilder` builder.
// It will receive an operation with modified Path and metadata.KeyBasePath and should return a new OperationID.
// If `explicitOpIDBuilder` is nil, the built-in approach will be used. It will take metadata.KeyBasePath as
// a prefix for the OperationID, turning it into kebab-case and will append it to the metadata.KeyInitOperationID
// (that stores the initial user-provided OperationID).
func (a APIGen) AddMultiBasePaths(
	explicitOpIDBuilder func(*huma.Operation) string,
	basePaths ...string,
) APIGen {
	basePathsRegMiddlewares := make(RegMiddlewares, len(basePaths))
	for i, basePath := range basePaths {
		basePathsRegMiddlewares[i] = NewRegMiddleware(
			op_handler.AddBasePath(basePath),
		)
	}

	return a.
		AddRegMiddleware(basePathsRegMiddlewares.FanOut()).
		AddOpHandler(
			op_handler.UpdateOperationID(explicitOpIDBuilder),
			op_handler.UpdateGeneratedSummary,
		)
}

// AddMiddlewares returns a new APIGen instance that will add the given middlewares to the operation.
func (a APIGen) AddMiddlewares(middlewares ...func(huma.Context, func(huma.Context))) APIGen {
	return a.AddOpHandler(op_handler.AddMiddlewares(middlewares...))
}

// AddExtraHumaAPI returns a new APIGen instance with the given huma.API added to the extraHumaAPIs.
// All operations registered with this APIGen instance or derived instances will be passed to the given `api`
// additionally to the main huma.API. Optional bubbling RegMiddlewares will be applied to the operations before
// the registration in the `api`.
func (a APIGen) AddExtraHumaAPI(api huma.API, bubblingRegMiddlewares ...RegMiddleware) APIGen {
	a.extraHumaAPIs = append(a.extraHumaAPIs, ExtraHumaApiInstance{
		humaAPI:                api,
		bubblingRegMiddlewares: bubblingRegMiddlewares,
	})
	return a
}

// GetExtraHumaAPIs returns the stored extraHumaAPIs.
func (a APIGen) GetExtraHumaAPIs() []ExtraHumaApiInstance {
	return a.extraHumaAPIs
}

// AddOwnOpenAPI is a shortcut function to create a new APIGen instance and a OpenAPI object instance together.
// All operations registered with this APIGen instance or derived instances will be added to the OpenAPI object.
// `bubbleRegMiddlewares` will be additionally applied to the operations before the registration in the OpenAPI object.
// It allows you to have separate OpenAPI spec that contains only operations registered with this APIGen instance
// or derived instances.
// Use it in combination with `pkg/huma/oapi_handlers` package to serve the created OpenAPI spec.
func (a APIGen) AddOwnOpenAPI(apiConfig huma.Config, bubblingRegMiddlewares ...RegMiddleware) (APIGen, *huma.OpenAPI) {
	humaApi := humaapi.NewDummyHumaAPI(apiConfig)
	return a.AddExtraHumaAPI(humaApi, bubblingRegMiddlewares...), humaApi.OpenAPI()
}
