{{- /* Determine if Path for requested GVR is at /api or /apis based on emptiness of group */ -}}
{{- $prefix := (ternary "/api" (join "" "/apis/" $.GVR.Group) (not $.GVR.Group)) -}}

{{- /* Search both cluster-scoped and namespaced-scoped paths for the GVR to find its GVK */ -}}
{{- /* Also search for paths with {name} component in case the list path is missing */ -}}
{{- /* Looks for the following paths: */ -}}
{{- /* /apis/<group>/<version>/<resource> */ -}}
{{- /* /apis/<group>/<version>/<resource>/{name} */ -}}
{{- /* /apis/<group>/<version>/namespaces/{namespace}/<resource> */ -}}
{{- /* /apis/<group>/<version>/namespaces/{namespace}/<resource>/{name} */ -}}
{{- /* Also search for get verb paths in case list verb is missing */ -}}
{{- $clusterScopedSearchPath :=  join "/" $prefix $.GVR.Version $.GVR.Resource -}}
{{- $clusterScopedNameSearchPath :=  join "/" $prefix $.GVR.Version $.GVR.Resource "{name}" -}}
{{- $namespaceScopedSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource -}}
{{- $namespaceScopedNameSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource "{name}" -}}
{{- $gvk := "" -}}

{{- /* Pull GVK from operation */ -}}
{{- range $index, $searchPath := (list $clusterScopedSearchPath $clusterScopedNameSearchPath $namespaceScopedSearchPath $namespaceScopedNameSearchPath) -}}
    {{- with $resourcePathElement := index $.Document "paths" $searchPath -}}
        {{- range $methodIndex, $method := (list "get" "post" "put" "patch" "delete") -}}
            {{- with $resourceMethodPathElement := index $resourcePathElement $method -}}
                {{- with $gvk = index $resourceMethodPathElement "x-kubernetes-group-version-kind" -}}
                    {{- break -}}
                {{- end -}}
            {{- end -}}
        {{- end -}}
    {{- end -}}
{{- end -}}

{{- with $gvk -}}
    {{- if $gvk.group -}}
        GROUP:      {{ $gvk.group }}{{"\n" -}}
    {{- end -}}
    KIND:       {{ $gvk.kind}}{{"\n" -}}
    VERSION:    {{ $gvk.version }}{{"\n" -}}
    {{- "\n" -}}

    {{- with include "schema" (dict "gvk" $gvk "Document" $.Document "FieldPath" $.FieldPath "Recursive" $.Recursive) -}}
        {{- . -}}
    {{- else -}}
        {{- throw "error: GVK %v not found in OpenAPI schema" $gvk -}}
    {{- end -}}
{{- else -}}
    {{- throw "error: GVR (%v) not found in OpenAPI schema" $.GVR.String -}}
{{- end -}}
{{- "\n" -}}

{{- /*
Finds a schema with the given GVK and prints its explain output or empty string
if GVK was not found

Takes dictionary as argument with keys:
    gvk: openapiv3 JSON schema
    Document: entire doc
    FieldPath: field path to follow
    Recursive: print recursive
*/ -}}
{{- define "schema" -}}
    {{- /* Find definition with this GVK by filtering out the components/schema with the given x-kubernetes-group-version-kind */ -}}
    {{- range index $.Document "components" "schemas" -}}
        {{- if contains (index . "x-kubernetes-group-version-kind") $.gvk -}}
            {{- with include "output" (set $ "schema" .) -}}
                {{- . -}}
            {{- else -}}
                {{- $fieldName := (index $.FieldPath (sub (len $.FieldPath) 1)) -}}
                {{- throw "error: field \"%v\" does not exist" $fieldName}}
            {{- end -}}
            {{- break -}}
        {{- end -}}
    {{- end -}}
{{- end -}}

{{- /*
Follows FieldPath until the FieldPath is empty. Then prints field name and field 
list of resultant schema. If field path is not found. Prints nothing.
Example output:

FIELD: spec

DESCRIPTION:
    <template "description">

FIELDS:
    <template "fieldList">

Takes dictionary as argument with keys:
    schema: openapiv3 JSON schema
    history: map[string]int
    Document: entire doc
    FieldPath: field path to follow
    Recursive: print recursive
*/ -}}
{{- define "output" -}}
    {{- $refString := or (index $.schema "$ref") "" -}}
    {{- $nextContext := set $ "history" (set $.history $refString 1) -}}
    {{- $resolved := or (resolveRef $refString $.Document) $.schema -}}
    {{- if not $.FieldPath -}}
        DESCRIPTION:{{- "\n" -}}
        {{- or (include "description" (dict "schema" $resolved "Document" $.Document)) "<empty>" | wrap 76 | indent 4 -}}{{- "\n" -}}
        {{- with include "fieldList" (dict "schema" $resolved "level" 1 "Document" $.Document "Recursive" $.Recursive) -}}
            FIELDS:{{- "\n" -}}
            {{- . -}}
        {{- end -}}
    {{- else if and $refString (index $.history $refString) -}}
        {{- /* Stop and do nothing. Hit a cycle */ -}}
    {{- else if and $resolved.properties (index $resolved.properties (first $.FieldPath)) -}}
        {{- /* Schema has this property directly. Traverse to next schema */ -}}
        {{- $subschema := index $resolved.properties (first $.FieldPath) -}}
        {{- if eq 1 (len $.FieldPath) -}}
            {{- /* TODO: The original explain would say RESOURCE instead of FIELD here under some circumstances */ -}}
            FIELD: {{first $.FieldPath}} <{{ template "typeGuess" (dict "schema" $subschema "Document" $.Document) }}>{{"\n"}}
            {{- template "extractEnum" (dict "schema" $subschema "Document" $.Document "isLongView" true "limit" -1) -}}{{"\n"}}
            {{- "\n" -}}
        {{- end -}}

        {{- template "output" (set $nextContext "history" (dict) "FieldPath" (slice $.FieldPath 1) "schema" $subschema ) -}}
    {{- else if $resolved.items -}}
        {{- /* If the schema is an array then traverse the array item fields */ -}}
        {{- template "output" (set $nextContext "schema" $resolved.items) -}}
    {{- else if $resolved.additionalProperties -}}
        {{- /* If the schema is a map then traverse the map item fields */ -}}
        {{- template "output" (set $nextContext "schema" $resolved.additionalProperties) -}}
    {{- else -}}
        {{- /* Last thing to try is all the alternatives in the allOf case */ -}}
        {{- /* Stop when one of the alternatives has an output at all */ -}}
        {{- range $index, $subschema := $resolved.allOf -}}
            {{- with include "output" (set $nextContext "schema" $subschema) -}}
                {{- . -}}
                {{- break -}}
            {{- end -}}
        {{- end -}}
    {{- end -}}
{{- end -}}

{{- /*
Prints list of fields of a given open api schema in following form:

field1 <type> -required-
    DESCRIPTION

field2 <type> -required-
    DESCRIPTION

or if Recursive is enabled:
field1 <type> -required-
    subfield1 <type>
        subsubfield1 <type>
        subsubfield2 <type>
    subfield2 <type>
field2 <type> -required-
    subfield1 <type>
    subfield2 <type>

Follows refs for field traversal. If there are cycles in the schema, they are
detected and traversal ends.

Takes dictionary as argument with keys:
    schema: openapiv3 JSON schema
    level: indentation level
    history: map[string]int containing all ref names so far
    Document: entire doc
    Recursive: print recursive
*/ -}}
{{- define "fieldList" -}}
    {{- /* Resolve schema if it is a ref */}}
    {{- /* If this is a ref seen before, then ignore it */}}
    {{- $refString := or (index $.schema "$ref") "" }}
    {{- if and $refString (index (or $.history (dict)) $refString) -}}
        {{- /* Do nothing for cycle */}}
    {{- else -}}
        {{- $nextContext := set $ "history" (set $.history $refString 1) -}}
        {{- $resolved := or (resolveRef $refString $.Document) $.schema -}}
        {{- range $k, $v := $resolved.properties -}}
            {{- template "fieldDetail" (dict "name" $k "schema" $resolved "short" $.Recursive "level" $.level "Document" $.Document) -}}
            {{- if $.Recursive -}}
                {{- /* Check if we already know about this element */}}
                {{- template "fieldList" (set $nextContext "schema" $v "level" (add $.level 1)) -}}
            {{- end -}}
        {{- end -}}
        {{- range $resolved.allOf -}}
            {{- template "fieldList" (set $nextContext "schema" .)}}
        {{- end -}}
        {{- if $resolved.items}}{{- template "fieldList" (set $nextContext "schema" $resolved.items)}}{{end}}
        {{- if $resolved.additionalProperties}}{{- template "fieldList" (set $nextContext "schema" $resolved.additionalProperties)}}{{end}}
    {{- end -}}
{{- end -}}


{{- /*

Prints a single field of the given schema
Optionally prints in a one-line style

Takes dictionary as argument with keys:
    schema: openapiv3 JSON schema which contains the field
    name: name of field
    short: limit printing to a single-line summary
    level: indentation amount
    Document: openapi document
*/ -}}
{{- define "fieldDetail" -}}
    {{- $level := or $.level 0 -}}
    {{- $indentAmount := mul $level 2 -}}
    {{- $fieldSchema := index $.schema.properties $.name -}}
    {{- $.name | indent $indentAmount -}}{{"\t"}}<{{ template "typeGuess" (dict "schema" $fieldSchema "Document" $.Document) }}>
    {{- if contains $.schema.required $.name}} -required-{{- end -}}
    {{- template "extractEnum" (dict "schema" $fieldSchema "Document" $.Document "isLongView" false "limit" 4 "indentAmount" $indentAmount) -}}
    {{- "\n" -}}
    {{- if not $.short -}}
        {{- or $fieldSchema.description "<no description>" | wrap (sub 78 $indentAmount) | indent (add $indentAmount 2) -}}{{- "\n" -}}
        {{- "\n" -}}
    {{- end -}}
{{- end -}}

{{- /*

Prints the description of the given OpenAPI v3 schema. Also walks through all
sibling schemas to the provided schema and prints the description of those schemas
too

Takes dictionary as argument with keys:
    schema: openapiv3 JSON schema
    Document: document to resolve refs within
*/ -}}
{{- define "description" -}}
    {{- with or (resolveRef (index $.schema "$ref") $.Document) $.schema -}}
        {{- if .description -}}
            {{- .description -}}
            {{- "\n" -}}
        {{- end -}}
        {{- range .allOf -}}
            {{- template "description" (set $ "schema" .)}}
        {{- end -}}
        {{- if .items -}}
            {{- template "description" (set $ "schema" .items) -}}
        {{- end -}}
        {{- if .additionalProperties -}}
            {{- template "description" (set $ "schema" .additionalProperties) -}}
        {{- end -}}
    {{- end -}}
{{- end -}}

{{- /* Renders a shortstring representing an interpretation of what is the "type"
        of a subschema e.g.:

        `string` `number`, `Object`, `[]Object`, `map[string]string`, etc.

    Serves as a more helpful type hint than raw typical openapi `type` field

Takes dictionary as argument with keys:
    schema: openapiv3 JSON schema
    Document: openapi document
*/ -}}
{{- define "typeGuess" -}}
    {{- with $.schema -}}
        {{- if .items -}}
            []{{template "typeGuess" (set $ "schema" .items)}}
        {{- else if .additionalProperties -}}
            map[string]{{template "typeGuess" (set $ "schema" .additionalProperties)}}
        {{- else if and .allOf (not .properties) (eq (len .allOf) 1) -}}
            {{- /* If allOf has a single element and there are no direct
                properties on the schema, defer to the allOf */ -}}
            {{- template "typeGuess" (set $ "schema" (first .allOf)) -}}
        {{- else if index . "$ref"}}
            {{- /* Parse the #!/components/schemas/io.k8s.Kind string into just the Kind name */ -}}
            {{- $ref := index . "$ref" -}}
            {{- /* Look up ref schema to see primitive type. Only put the ref type name if it is an object. */ -}}
            {{- $refSchema := resolveRef $ref $.Document -}}
            {{- if (or (not $refSchema) (or (not $refSchema.type) (eq $refSchema.type "object"))) -}}
                {{- $name := split $ref "/" | last -}}
                {{- or (split $name "." | last) "Object" -}}
            {{- else if $refSchema.type -}}
                {{- or $refSchema.type "Object" -}}
            {{- else -}}
                {{- or .type "Object" -}}
            {{- end -}}
        {{- else -}}
            {{/* Old explain used capitalized "Object". Just follow suit */}}
            {{- if eq .type "object" -}}Object
            {{- else -}}{{- or .type "Object" -}}{{- end -}}
        {{- end -}}
    {{- else -}}
        {{- fail "expected schema argument to subtemplate 'typeguess'" -}}
    {{- end -}}
{{- end -}}

{{- /* Check if there is any enum returns it in this format e.g.:

    ENUM: "", BlockDevice, CharDevice, Directory
    enum: "", BlockDevice, CharDevice, Directory

    Can change the style of enum in future by modifying this function

Takes dictionary as argument with keys:
    schema: openapiv3 JSON schema
    Document: openapi document
    isLongView: (boolean) Simple view: long list of all fields. Long view: all details of one field
    limit: (int) truncate the amount of enums that can be printed in simple view, -1 means all items
    indentAmount: intent of the beginning enum line in longform view
*/ -}}
{{- define "extractEnum" -}}
    {{- with $.schema -}}
        {{- if .enum -}}
            {{- $enumLen := len .enum -}}
            {{- $limit := or $.limit -1 -}}
            {{- if eq $.isLongView false -}}
                {{- "\n" -}}
                {{- "" | indent $.indentAmount -}}
                {{- "enum: " -}}
            {{- else -}}
                {{- "ENUM:" -}}
            {{- end -}}
            {{- range $index, $element := .enum -}}
                {{- /* Prints , ....  and return the range when it goes over the limit */ -}}
                {{- if and (gt $limit -1) (ge $index $limit) -}}
                    {{- ", ...." -}}
                    {{- break -}}
                {{- end -}}
                {{- /* Use to reflect "" when we see empty string */ -}}
                {{- $elementType := printf "%T" $element -}}
                {{- /* Print out either `, ` or `\n    ` based of the view */ -}}
                {{- /* Simple view */ -}}
                {{- if and (gt $index 0) (eq $.isLongView false) -}}
                    {{- ", " -}}
                {{- /* Long view */ -}}
                {{- else if eq $.isLongView true -}}
                    {{- "\n" -}}{{- "" | indent 4 -}}
                {{- end -}}
                {{- /* Convert empty string to `""` for more clarification */ -}}
                {{- if and (eq "string" $elementType) (eq $element "") -}}
                    {{- `""` -}}
                {{- else -}}
                    {{- $element -}}
                {{- end -}}
        {{- end -}}
        {{- end -}}
    {{- end -}}
{{- end -}}