{#- Reusable macros for changelog generation -#}

{#- Format author as GitHub @username link or fallback to name -#}
{#- URL-encode username to handle bot users like dependabot[bot] -#}
{%- macro format_author(name, username) -%}
{%- if username %}[@{{ username }}](https://github.com/{{ username | urlencode }}){% else %}{{ name }}{% endif -%}
{%- endmacro -%}

{#- Render all authors (main + co-authors from trailers) -#}
{%- macro render_authors(commit) -%}
{%- set main_username = commit.hexsha | github_username -%}
{%- set co_authors = [] -%}
{%- if commit.commit.trailers_list is defined -%}
{%- for key, value in commit.commit.trailers_list -%}
{%- if key | lower == "co-authored-by" -%}
{%- set _ = co_authors.append(value.split('<')[0] | trim) -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{{ format_author(commit.commit.author.name, main_username) }}{% if co_authors %}, {{ co_authors | join(", ") }}{% endif %}
{%- endmacro -%}

{#- Render commit metadata (hash, PR, issues) -#}
{%- macro commit_refs(commit) -%}
([{{ commit.short_hash }}]({{ commit.hexsha | commit_hash_url }}))
{%- if commit.linked_merge_request %} in [{{ commit.linked_merge_request }}]({{ commit.linked_merge_request | pull_request_url }}){%- endif %}
{%- if commit.linked_issues %}, closes {% for issue in commit.linked_issues | sort_numerically %}[{{ issue }}]({{ issue | issue_url }}){% if not loop.last %}, {% endif %}{% endfor %}{% endif %}
{%- endmacro -%}

{#- Extract body from raw commit message (preserves code blocks) -#}
{#- strip_breaking: stops at BREAKING CHANGE marker (for Features section) -#}
{#- remove_breaking_prefix: removes "BREAKING CHANGE:" and adds Migration header (for Breaking Changes section) -#}
{%- macro get_body(commit, strip_breaking=false, remove_breaking_prefix=false) -%}
{%- set msg = commit.message -%}
{%- set parts = msg.split('\n\n', 1) -%}
{%- if parts | length > 1 -%}
{%- set body = parts[1] -%}
{%- set body_lines = body.split('\n') -%}
{%- set filtered = [] -%}
{%- set ns = namespace(stop=false) -%}
{%- for line in body_lines -%}
{%- if strip_breaking and (line.startswith('BREAKING CHANGE:') or line.startswith('BREAKING-CHANGE:')) -%}
{%- set ns.stop = true -%}
{%- endif -%}
{%- if not ns.stop and not (line | lower).startswith(('co-authored-by:', 'signed-off-by:')) -%}
{%- if remove_breaking_prefix and (line.startswith('BREAKING CHANGE:') or line.startswith('BREAKING-CHANGE:')) -%}
{%- set _ = filtered.append('**Migration:** ' ~ (line | replace('BREAKING CHANGE: ', '') | replace('BREAKING-CHANGE: ', ''))) -%}
{%- else -%}
{%- set _ = filtered.append(line) -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{{ filtered | join('\n') | trim }}
{%- endif -%}
{%- endmacro -%}

{#- Render a commit entry with optional scope and body -#}
{#- for_breaking=true: shows full body with BREAKING CHANGE converted to Migration header -#}
{#- for_breaking=false: shows body but strips BREAKING CHANGE section for breaking commits -#}
{%- macro render_commit(commit, for_breaking=false) -%}
{%- set is_breaking = commit.breaking_descriptions | length > 0 -%}
{%- set body = get_body(commit, strip_breaking=(is_breaking and not for_breaking), remove_breaking_prefix=for_breaking) -%}
- {% if commit.scope %}**{{ commit.scope }}:** {% endif %}{{ commit.descriptions[0][:1] | upper }}{{ commit.descriptions[0][1:] }} by {{ render_authors(commit) }} {{ commit_refs(commit) }}
{%- if body %}

    {{ body | indent(4, first=false) }}
{%- endif %}
{%- endmacro -%}
