package codegen

import (
	"fmt"
	"strings"
)

// ensureExternalRefsInRequestBodyDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInRequestBodyDefinitions(defs *[]RequestBodyDefinition, ref string) {
	if ref == "" {
		return
	}

	for i, rbd := range *defs {
		ensureExternalRefsInSchema(&rbd.Schema, ref)

		// make sure we then update it in-place
		(*defs)[i] = rbd
	}
}

// ensureExternalRefsInResponseDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInResponseDefinitions(defs *[]ResponseDefinition, ref string) {
	if ref == "" {
		return
	}

	for i, rd := range *defs {
		for j, rcd := range rd.Contents {
			ensureExternalRefsInSchema(&rcd.Schema, ref)

			// make sure we then update it in-place
			rd.Contents[j] = rcd
		}

		// make sure we then update it in-place
		(*defs)[i] = rd
	}
}

// ensureExternalRefsInParameterDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInParameterDefinitions(defs *[]ParameterDefinition, ref string) {
	if ref == "" {
		return
	}

	for i, pd := range *defs {
		ensureExternalRefsInSchema(&pd.Schema, ref)

		// make sure we then update it in-place
		(*defs)[i] = pd
	}
}

// externalPackageFor returns the imported Go package name for a `$ref` that
// targets a file outside the current spec. Returns an empty string when the
// ref is empty, points within the current spec, or has no import-mapping.
func externalPackageFor(ref string) string {
	if ref == "" {
		return ""
	}
	parts := strings.SplitN(ref, "#", 2)
	if pack, ok := globalState.importMapping[parts[0]]; ok {
		return pack.Name
	}
	return ""
}

// ensureExternalRefsInSchema ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
//
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
//
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInSchema(schema *Schema, ref string) {
	if ref == "" {
		return
	}

	parts := strings.SplitN(ref, "#", 2)
	pack, ok := globalState.importMapping[parts[0]]
	if !ok {
		return
	}

	// if this is already defined as the start of a struct, we shouldn't inject **??**
	if !strings.HasPrefix(schema.GoType, "struct {") {
		schema.RefType = fmt.Sprintf("%s.%s", pack.Name, schema.GoType)
	}

	// Qualify union branch types. Each UnionElement was resolved as a
	// local reference during generateUnion (e.g. "Bionicle"); when the
	// enclosing schema came from an externally-ref'd file the branches
	// actually live in the imported package, so the As/From/Merge
	// methods generated by union.tmpl must use "<pkg>.Bionicle" instead.
	for i, elem := range schema.UnionElements {
		s := string(elem)
		if !strings.Contains(s, ".") {
			schema.UnionElements[i] = UnionElement(fmt.Sprintf("%s.%s", pack.Name, s))
		}
	}
}
