# Home Assistant energy: current power price + grid power.
#
# Keys pushed by Home Assistant (POST /api/device/<id>/data):
#   power_price  current price in EUR/kWh, e.g. 0.231 (negative prices are fine)
#   grid_w       grid power in watts: positive = using from the grid,
#                negative = sending back (solar surplus)
#
# Example payload:
#   {"power_price": 0.231, "grid_w": -850}
#
# The app reads them as config.get("ha_power_price") and config.get("ha_grid_w").
# Values that are missing, empty or "unavailable" show as "--".

load("render.star", "render")

# Price bands in EUR/kWh: below CHEAP_BELOW is green, from EXPENSIVE_FROM up is red.
CHEAP_BELOW = 0.15
EXPENSIVE_FROM = 0.30

AMBER = "#f59e0b"
WHITE = "#f8f8f0"
DIM = "#70707a"
DARK = "#2a2a30"
GREEN = "#5ee08a"
RED = "#ff5040"

# Bold 7x12 digits: drawn thin (6x11) and thickened by bold().
D_SIDES = ["#....#"]
DIGITS = {
    "0": [".####."] + D_SIDES * 9 + [".####."],
    "1": ["...#..", "..##..", ".#.#.."] + ["...#.."] * 8,
    "2": [".####.", "#....#"] + [".....#"] * 3 + [".####."] + ["#....."] * 4 + ["######"],
    "3": [".####.", "#....#"] + [".....#"] * 3 + ["..###."] + [".....#"] * 3 + ["#....#", ".####."],
    "4": D_SIDES * 5 + ["######"] + [".....#"] * 5,
    "5": ["######"] + ["#....."] * 3 + ["#####."] + [".....#"] * 4 + ["#....#", ".####."],
    "6": [".####.", "#....#", "#.....", "#.....", "#####."] + D_SIDES * 5 + [".####."],
    "7": ["######", ".....#", ".....#", "....#.", "....#.", "...#..", "...#.."] + ["..#..."] * 4,
    "8": [".####."] + D_SIDES * 4 + [".####."] + D_SIDES * 4 + [".####."],
    "9": [".####."] + D_SIDES * 4 + [".#####"] + [".....#"] * 3 + ["#....#", ".####."],
    ".": ["."] * 10 + ["#"],
    "-": ["...."] * 5 + ["####"] + ["...."] * 5,
}
DIGIT_GAP = 2

EURO = [
    "..####",
    ".#....",
    "#####.",
    ".#....",
    "#####.",
    ".#....",
    "..####",
]
ARROW_DOWN = ["#####", ".###.", "..#.."]
ARROW_UP = ["..#..", ".###.", "#####"]

def num(s, default):
    # Starlark has no try/except: only accept digits, one dot and a leading minus.
    s = s.strip()
    neg = s.startswith("-")
    body = s[1:] if neg else s
    if body == "" or body == "." or body.count(".") > 1:
        return default
    for ch in body.elems():
        if ch not in "0123456789.":
            return default
    if body.startswith("."):
        body = "0" + body
    if body.endswith("."):
        body = body + "0"
    return float(("-" if neg else "") + body)

def fixed(x, dec):
    # Like "%.2f" (Starlark has no precision in %): round half away from zero.
    scale = 1
    for _ in range(dec):
        scale *= 10
    n = int(abs(x) * scale + 0.5)
    s = str(n // scale)
    if dec > 0:
        s += "." + ("0" * dec + str(n % scale))[-dec:]
    return "-" + s if x < 0 and n > 0 else s

def pixels(rows, x, y, color):
    # One box per horizontal run of lit pixels.
    out = []
    for r, line in enumerate(rows):
        start = -1
        for c in range(len(line) + 1):
            on = c < len(line) and line[c] == "#"
            if on and start < 0:
                start = c
            elif not on and start >= 0:
                out.append(render.Padding(pad = (x + start, y + r, 0, 0), child = render.Box(width = c - start, height = 1, color = color)))
                start = -1
    return out

def bold(rows):
    # Thicken every stroke to 2 px: a pixel is lit when it or its left/upper neighbour is.
    h = len(rows)
    w = len(rows[0])
    out = []
    for r in range(h + 1):
        line = ""
        for c in range(w + 1):
            on = False
            for dr, dc in [(0, 0), (1, 0), (0, 1), (1, 1)]:
                rr = r - dr
                cc = c - dc
                if rr >= 0 and rr < h and cc >= 0 and cc < w and rows[rr][cc] == "#":
                    on = True
            line += "#" if on else "."
        out.append(line)
    return out

def sprite(rows, color):
    return render.Stack(children = pixels(rows, 0, 0, color))

def big_width(text):
    w = 0
    for ch in text.elems():
        if ch in DIGITS:
            w += len(DIGITS[ch][0]) + 1 + DIGIT_GAP
    return w - DIGIT_GAP if w > 0 else 0

def big(text, color):
    out = [render.Box(width = 1, height = 12)]  # full height, so "-" sits mid-line
    x = 0
    for ch in text.elems():
        if ch in DIGITS:
            glyph = bold(DIGITS[ch])
            out.extend(pixels(glyph, x, 0, color))
            x += len(glyph[0]) + DIGIT_GAP
    return render.Stack(children = out)

def price_part(price):
    if price == None or abs(price) >= 10000:
        return "NO DATA", DIM, "--"
    a = abs(price)
    text = fixed(price, 2 if a < 10 else (1 if a < 100 else 0))
    if price < CHEAP_BELOW:
        return "CHEAP", GREEN, text
    if price < EXPENSIVE_FROM:
        return "NORMAL", AMBER, text
    return "HIGH", RED, text

def grid_part(grid):
    if grid == None or abs(grid) >= 999999.5:
        return [render.Text("GRID ", font = "tom-thumb", color = DIM), render.Text("--", font = "5x8", color = DIM)]
    a = abs(grid)
    value = str(int(a + 0.5)) + "W" if a < 9999.5 else str(int(a / 1000 + 0.5)) + "kW"
    if a < 0.5:
        return [render.Text("GRID ", font = "tom-thumb", color = DIM), render.Text(value, font = "5x8", color = WHITE)]
    if grid > 0:
        arrow, color, label = sprite(ARROW_DOWN, AMBER), WHITE, "USING"
    else:
        arrow, color, label = sprite(ARROW_UP, GREEN), GREEN, "EXPORT"
    return [
        arrow,
        render.Box(width = 3, height = 1),
        render.Text(value, font = "5x8", color = color),
        render.Box(width = 3, height = 1),
        render.Text(label, font = "tom-thumb", color = DIM),
    ]

def main(config):
    price = num(config.get("ha_power_price", "0.231"), None)
    grid = num(config.get("ha_grid_w", "-850"), None)

    tier, color, text = price_part(price)

    price_row = [
        render.Padding(pad = (0, 0, 0, 2), child = sprite(EURO, color)),
        render.Box(width = 3, height = 1),
        big(text, color),
    ]

    # Unit only when it still fits next to a long price.
    width = 7 + 3 + big_width(text)
    if width + 18 <= 64:
        price_row.append(render.Box(width = 2, height = 1))
        price_row.append(render.Text("/KWH", font = "tom-thumb", color = DIM))

    return render.Root(
        child = render.Box(
            width = 64,
            height = 32,
            color = "#000000",
            child = render.Column(
                children = [
                    render.Box(
                        width = 64,
                        height = 8,
                        child = render.Padding(
                            pad = (1, 1, 1, 0),
                            child = render.Row(
                                expanded = True,
                                main_align = "space_between",
                                children = [
                                    render.Text("PRICE", font = "tom-thumb", color = DIM),
                                    render.Text(tier, font = "tom-thumb", color = color),
                                ],
                            ),
                        ),
                    ),
                    render.Box(width = 64, height = 13, child = render.Row(cross_align = "end", children = price_row)),
                    render.Box(width = 64, height = 2, child = render.Box(width = 60, height = 1, color = DARK)),
                    render.Box(width = 64, height = 9, child = render.Row(cross_align = "center", children = grid_part(grid))),
                ],
            ),
        ),
    )
