using System;
using System.Collections.Generic;
using Godot;
namespace IslaApocalypse.Tools
{
///
/// Composites an elevation LEGEND down the right-hand side of a rendered map — a vertical colour
/// bar with height ticks, in the manner of the scale on a physical relief plate.
///
/// ═══ ⚠⚠ THE TICKS ARE IN RAW / RELATIVE UNITS, NOT METRES ═══
///
/// The height field is raw noise units (sea 0.15, peaks ~1.45). It is NOT metres, and there is
/// no conversion available yet — the metres-per-unit mapping belongs to Phase 2's elevation
/// profile. Labelling this bar "m" would be inventing a fact about the world in the one artefact
/// a reader is most likely to trust. So it says RELATIVE HEIGHT, NOT METRES, and it says it on
/// the image where it cannot be separated from the picture.
///
/// The bar is drawn from the palette's own ramp, so it cannot drift from the map beside it: if a
/// stop moves, the legend moves with it.
///
/// ⚠ THE LAYOUT IS DERIVED FROM TEXT METRICS, NOT FROM GUESSED FRACTIONS. The first cut sized
/// the font off the map height and the strip off the map width independently, so at 2048 the
/// title was 450 px of text in a 235 px strip — clipped, and overlapping the bar. Everything
/// below is now computed from the longest string that must fit, and the bar is placed between
/// the measured header and footer blocks rather than at a fixed offset.
///
public static class LegendRenderer
{
public static Image WithLegend(Image map, ReliefPalette.Kind palette, float seaLevel,
float landTop, string title)
{
int mapW = map.GetWidth(), mapH = map.GetHeight();
// ---- content first, then a layout that fits it ----
var header = new List { title, "RELATIVE HEIGHT", "NOT METRES" };
var footer = new List { "BELOW SEA IS", "A DEPTH TINT" };
var ticks = new List { landTop, 1.20f, 1.00f, 0.80f, 0.60f, 0.46f, 0.31f, seaLevel };
ticks.RemoveAll(h => h > landTop || h < seaLevel);
int stripW = Mathf.Max(220, Mathf.RoundToInt(mapW * 0.16f));
int pad = Mathf.Max(10, Mathf.RoundToInt(stripW * 0.075f));
// Longest header line decides the font scale — everything else is narrower by design.
int longest = 0;
foreach (string h in header) longest = Math.Max(longest, h.Length);
int usable = stripW - pad * 2;
int scale = Mathf.Clamp(usable / (longest * (TinyFont.GlyphW + 1)), 1, 6);
int textH = TinyFont.Height(scale);
int lineGap = Mathf.Max(2, scale);
int lineH = textH + lineGap;
int barX = pad;
int barW = Mathf.Max(12, Mathf.RoundToInt(stripW * 0.18f));
int tickLen = Mathf.Max(4, barW / 3);
int labelX = barX + barW + tickLen + Mathf.Max(4, scale * 2);
int barTop = pad + header.Count * lineH + pad;
int barBottom = mapH - pad - footer.Count * lineH - pad;
int barH = barBottom - barTop;
if (barH < 32) return map; // absurdly small map — a broken legend is worse than none
var outImg = Image.CreateEmpty(mapW + stripW, mapH, false, Image.Format.Rgb8);
var paper = new Color(0.098f, 0.106f, 0.125f);
var ink = new Color(0.941f, 0.949f, 0.961f);
var faint = new Color(0.565f, 0.596f, 0.643f);
outImg.Fill(paper);
outImg.BlitRect(map, new Rect2I(0, 0, mapW, mapH), new Vector2I(0, 0));
int sx = mapW; // strip origin
// ---- header ----
for (int i = 0; i < header.Count; i++)
TinyFont.Draw(outImg, header[i], sx + pad, pad + i * lineH, scale,
i == 0 ? ink : faint);
// ---- the colour bar: TOP = highest, so it reads the way the terrain does ----
for (int row = 0; row < barH; row++)
{
float t = 1f - row / (float)(barH - 1);
float h = Mathf.Lerp(seaLevel, landTop, t);
Color c = ReliefPalette.Tint(palette, h, seaLevel);
for (int px = 0; px < barW; px++)
outImg.SetPixel(sx + barX + px, barTop + row, c);
}
// A thin frame, so the bar reads as an object rather than a smear.
for (int row = -1; row <= barH; row++)
{
SetSafe(outImg, sx + barX - 1, barTop + row, faint);
SetSafe(outImg, sx + barX + barW, barTop + row, faint);
}
for (int px = -1; px <= barW; px++)
{
SetSafe(outImg, sx + barX + px, barTop - 1, faint);
SetSafe(outImg, sx + barX + px, barTop + barH, faint);
}
// ---- ticks. The waterline gets the one word that means something. ----
foreach (float h in ticks)
{
float t = (h - seaLevel) / (landTop - seaLevel);
int row = barTop + Mathf.RoundToInt((1f - t) * (barH - 1));
for (int px = 0; px < tickLen; px++)
SetSafe(outImg, sx + barX + barW + px, row, ink);
string label = h.ToString("0.00") + (Mathf.Abs(h - seaLevel) < 0.0001f ? " SEA" : "");
int ty = Mathf.Clamp(row - textH / 2, barTop - textH, mapH - textH - 1);
TinyFont.Draw(outImg, label, sx + labelX, ty, scale, ink);
}
// ---- footer ----
int fy = barBottom + pad;
for (int i = 0; i < footer.Count; i++)
TinyFont.Draw(outImg, footer[i], sx + pad, fy + i * lineH, scale, faint);
return outImg;
}
private static void SetSafe(Image img, int x, int y, Color c)
{
if (x >= 0 && y >= 0 && x < img.GetWidth() && y < img.GetHeight()) img.SetPixel(x, y, c);
}
}
}