{# ========================== #}
{# TypeScript/Hono テンプレート #}
{# ========================== #}
{# OpenAPI仕様からTypeScript/Honoサーバコードを生成する単一ファイルテンプレート #}

{# HTTPメソッド一覧 #}
{%- set methods = ["get","put","post","delete","options","head","patch","trace"] %}

{# ===== スキーママクロ ===== #}
{# OpenAPIのスキーマ定義からTypeScriptの型定義を生成する #}

{# インライン匿名スキーマ名を生成するマクロ #}
{# Named schemas ($ref to components/schemas) はPhase 1 + ref_name フィルタで処理する #}
{%- macro SCHEMA_NAME(pointer) %}
{%- if schema_push(pointer, none) %}
{%- do schema_push(pointer, IDENTIFIED_SCHEMA(pointer, pointer|include_pointer)) %}
{%- endif %}{{pointer|replace("#/components/schemas/","")|decode|to_pascal_case}}
{%- endmacro %}

{# OpenAPIのスキーマ定義を対応するTypeScript型に変換するマクロ #}
{# $ref → ref_name フィルタで型名取得（Phase 1でその型が既に生成済み）#}
{# object → 型名（インライン匿名はSCHEMA_NAMEキャッシュ経由）#}
{%- macro SCHEMA(pointer,schema) -%}
{%- if schema["$ref"] %}{{schema["$ref"]|ref_name}}
{%- elif schema.type -%}
{%- if schema.type == "object" -%}
{%- if schema.additionalProperties %}{ [key: string]: {{SCHEMA(pointer+"/additionalProperties", schema.additionalProperties)}} }
{%- else %}{{SCHEMA_NAME(pointer)}}
{%- endif %}
{%- elif schema.type == "array" %}{{SCHEMA(pointer+"/items",schema.items)}}[]
{%- elif schema.type == "integer" %}number
{%- elif schema.type == "string" %}
{%- if schema.format == "binary" %}ArrayBuffer
{%- else %}string
{%- endif %}
{%- elif schema.type == "number" %}number
{%- elif schema.type == "boolean" %}boolean
{%- else -%}
{%- endif %}
{%- elif schema.allOf -%}{{SCHEMA(pointer+"/allOf/0", schema.allOf[0])}}
{%- elif schema.anyOf -%}{%- for item in schema.anyOf -%}{{SCHEMA(pointer+"/anyOf/"+loop.index0|string, item)}}{%- if not loop.last %} | {%- endif %}{%- endfor %}
{%- else -%}
unknown
{%- endif %}
{%- endmacro -%}

{# 名前付きスキーマ定義を生成するマクロ #}
{# objectの場合はtype定義、それ以外はtype aliasを生成する #}
{%- macro IDENTIFIED_SCHEMA(pointer, schema) %}
{%- if schema.type == "object" and schema.discriminator %}
type {{SCHEMA_NAME(pointer)}} =
{%- for disc_key, disc_ref in schema.discriminator.mapping|items %}
	| {{disc_ref|ref_name}}
{%- endfor %};
{%- elif schema.type == "object" %}
type {{SCHEMA_NAME(pointer)}} = {
{%- for property_key, property in schema.properties|default({})|items %}
{%- with inner=SCHEMA(pointer+"/properties/"+property_key, property) %}
	{{property_key}}:{%- if schema.required and property_key in schema.required %}{{inner}}{%- else %}{{inner}}|undefined{%- endif %},
{%- endwith %}
{%- endfor %}
}
{%- elif schema.anyOf %}
type {{SCHEMA_NAME(pointer)}} = {%- for item in schema.anyOf -%}{{SCHEMA(pointer+"/anyOf/"+loop.index0|string, item)}}{%- if not loop.last %} | {%- endif %}{%- endfor %};
{%- else %}
type {{SCHEMA_NAME(pointer)}}={{SCHEMA(pointer, schema)}};
{%- endif %}
{%- endmacro %}

{# キャッシュされたインライン匿名スキーマ定義を出力するマクロ #}
{# Named schemasはPhase 1で出力済みなのでここには含まれない #}
{%- macro IDENTIFIED_SCHEMA_DRAIN() %}
{%- for pointer, output in schema_drain()|items %}
{{output}}
{%- endfor %}
{%- endmacro %}

{# ===== オペレーションマクロ ===== #}

{# オペレーション名をキャメルケースで返すマクロ（TypeScript規約） #}
{%- macro OPERATION_NAME(pointer,operation) -%}
{%- if operation.operationId -%}
{{operation.operationId|to_camel_case}}
{%- else -%}
{{pointer|decode|to_camel_case}}
{%- endif -%}
{%- endmacro -%}

{# リクエスト型名をパスカルケースで返すマクロ #}
{%- macro OPERATION_REQUEST_NAME(pointer,operation) -%}
{{(OPERATION_NAME(pointer,operation)+"_request")|to_pascal_case}}
{%- endmacro -%}

{# レスポンス型名をパスカルケースで返すマクロ #}
{%- macro OPERATION_RESPONSE_NAME(pointer,operation) -%}
{{(OPERATION_NAME(pointer,operation)+"_response")|to_pascal_case}}
{%- endmacro -%}

{# リクエスト型定義を生成するマクロ #}
{# parameters と requestBody は deref フィルタで $ref を解決する #}
{%- macro OPERATION_REQUEST(pointer,operation) -%}
// Request type for {{OPERATION_NAME(pointer,operation)}}
type {{OPERATION_REQUEST_NAME(pointer,operation)}} = {
{%- for parameter_ref in operation.parameters %}
{%- set parameter = parameter_ref|deref %}
	{{parameter.name}}:{%-with inner=SCHEMA(pointer+"/parameters/"+loop.index0|string,parameter.schema) -%}{%- if parameter.required -%}{{inner}}{%- else -%}{{inner}}|undefined{%- endif -%}{%- endwith %},
{%- endfor %}
{%- if operation.requestBody %}
{%- set requestBody = operation.requestBody|deref %}
{%- for content_key, content in requestBody.content|items %}
	body:{{SCHEMA(pointer+"/requestBody/content/"+content_key|encode+"/schema",content.schema)}},
{%- break %}
{%- endfor %}
{%- endif %}
}
{%- endmacro -%}

{# レスポンスのユニオン型を生成するマクロ #}
{# responses は deref フィルタで $ref を解決する #}
{%- macro OPERATION_RESPONSE(pointer,operation) -%}
// Response type for {{OPERATION_NAME(pointer,operation)}}
type {{OPERATION_RESPONSE_NAME(pointer,operation)}} =
{%- for response_key, response_ref in operation.responses|items %}
{%- set response = response_ref|deref %}
{%- with code=response_key|replace("default","500")%}
	| { code: {{code}};{%-if response.content%}{%- for content_key, content in response.content|items %} body:{{SCHEMA(pointer+"/responses/"+response_key+"/content/"+content_key|encode+"/schema",content.schema)}}{%-break%}{%-endfor%}{%-endif%}}
{%- endwith %}
{%- endfor %}
{%- endmacro -%}

{# ===== コード生成本体 ===== #}
import { Hono } from 'hono'
import { serve } from '@hono/node-server'

// This file was automatically generated from OpenAPI specification by mandolin https://github.com/lzpel/mandolin

{# ===== Phase 1: 名前付きスキーマ定義 ===== #}
{# components/schemasの全エントリを先行登録してから直接出力する #}
{# これにより$ref参照箇所でref_nameフィルタが正しい型名を返せる #}
{%- if spec.components and spec.components.schemas %}
{%- for schema_name, schema in spec.components.schemas|items %}
{%- do schema_push("#/components/schemas/"+schema_name, none) %}
{%- endfor %}
{%- for schema_name, schema in spec.components.schemas|items %}
{{IDENTIFIED_SCHEMA("#/components/schemas/"+schema_name, schema)}}
{%- endfor %}
{%- endif %}

{# リクエスト/レスポンス型の生成 #}
{%- for path_key, path_item in spec.paths|items %}
{%- for method in methods %}
{%- if path_item[method] is defined %}
{%- set pointer = "#/paths/" + path_key|encode + "/" + method %}
{%- set operation = path_item[method] %}
{{OPERATION_REQUEST(pointer, operation)}}
{{OPERATION_RESPONSE(pointer, operation)}}
{%- endif %}
{%- endfor %}
{%- endfor %}

{# インライン匿名スキーマの出力 #}
{{ IDENTIFIED_SCHEMA_DRAIN() }}

/// API Interface: Define handlers for each operation
interface ApiInterface{
{%- for path_key, path_item in spec.paths|items %}
{%- for method in methods %}
{%- if path_item[method] is defined %}
{%- set pointer = "#/paths/" + path_key|encode + "/" + method %}
{%- set operation = path_item[method] %}
	// {{method|upper}} {{path_key}}
	{{OPERATION_NAME(pointer,operation)}}?(request: {{OPERATION_REQUEST_NAME(pointer,operation)}}): Promise<{{OPERATION_RESPONSE_NAME(pointer,operation)}}>
{%- endif %}
{%- endfor %}
{%- endfor %}
}

/// Register operation routes to Hono app
export function addHonoOperations(app: Hono, implement: ApiInterface){
{%- for path_key, path_item in spec.paths|items %}
{%- for method in methods %}
{%- if path_item[method] is defined %}
{%- set pointer = "#/paths/" + path_key|encode + "/" + method %}
{%- set operation = path_item[method] %}
	app.{{method}}('{{ path_key | re_replace('\\{(\\w+)\\}', ':$1') }}', async (c) => {
		if (implement.{{OPERATION_NAME(pointer,operation)}}===undefined)return c.text("not yet implemented", 500)
		const request: Partial<{{OPERATION_REQUEST_NAME(pointer,operation)}}> = {}
		{
{%- for parameter_ref in operation.parameters %}
{%- set parameter = parameter_ref|deref %}
			let {{parameter.name|to_snake_case}} = c.req.{{ {"path":"param", "query":"query", "header":"header", "cookie":"cookie" }[parameter.in] }}("{{parameter.name}}")
{%- if parameter.required %}
			if({{parameter.name|to_snake_case}}===undefined)return c.text("required parameter '{{parameter.name}}' is not in '{{parameter.in}}'", 400)
{%- endif %}
			request.{{parameter.name}} = {{parameter.name|to_snake_case}};
{%- endfor %}
		}
{%- if operation.requestBody %}
{%- set requestBody = operation.requestBody|deref %}
{%- for content_key, content in requestBody.content|items %}
		request.body = {%- if "json" in content_key %}(await c.req.json()) as {{SCHEMA(pointer+"/requestBody/content/"+content_key|encode+"/schema", content.schema)}}
{%- elif "text" in content_key %}await c.req.text()
{%- else %}c.req.arrayBuffer()
{%- endif %}
{%- break %}
{%- endfor %}
{%- endif %}
		const response = await implement.{{OPERATION_NAME(pointer,operation)}}(request as {{OPERATION_REQUEST_NAME(pointer,operation)}})
		switch (response.code){
{%- for response_key, response_ref in operation.responses|items %}
{%- set response = response_ref|deref %}
{%- with code=response_key|replace("default","500")%}
			case {{code}}:
{%- if response.content %}
{%- for contentType, media in response.content|items %}
{%- if "json" in contentType %}
				return c.json(response.body, {{code}})
{%- elif media.schema and media.schema.format and media.schema.format == "binary" %}
				return c.body(response.body, {{code}}, {'Content-Type': '{{contentType}}'})
{%- else %}
				return c.text(response.body, {{code}}, {'Content-Type': '{{contentType}}'})
{%- endif %}
{%- break %}
{%- endfor %}
{%- else %}
				return c.text("{{response.description}}")
{%- endif %}
{%- endwith %}
{%- endfor %}
		}
	})
{%- endif %}
{%- endfor %}
{%- endfor %}
}

/// Test server implementation (all methods not yet implemented)
class TestServer implements ApiInterface{}

/// Entry point to start the server
export function main(){
	const app = new Hono()
	addHonoOperations(app,new TestServer());
	serve({
		fetch: app.fetch,
		port: 3000
	}, (info) => {
		console.log(`Server is running on http://localhost:${info.port}`)
	})
}
