#!/usr/bin/env python
#
# Pre-build script: gzip webui/index.html into a PROGMEM C header so the
# webconfig portal can serve the page straight from flash with
# Content-Encoding: gzip. The generated header is .gitignored; this script
# regenerates it whenever the source page (or this script) content changes.
#
# Freshness is decided by a content hash of the build inputs (not timestamps):
# a generated header written with a skewed/future mtime would otherwise mask a
# later source edit and embed stale UI. gzip output is deterministic (mtime=0),
# so an unchanged source produces an unchanged header.
#
# The generator is idempotent and cheap: when the inputs are unchanged it only
# hashes two small files and returns, so running it from esp32_base on every
# ESP32 build is negligible even for targets that don't compile the portal.
#
# Output: src/helpers/esp32/WebConfigHtml.h
#   WEBCONFIG_HTML_GZ[]   - gzipped page (PROGMEM)
#   WEBCONFIG_HTML_GZ_LEN - byte length
#   WEBCONFIG_HTML_ETAG   - quoted strong ETag (sha256 prefix of the gz body)

import gzip
import hashlib
import os
import sys

Import("env")  # noqa: F821

SOURCE = os.path.join("webui", "index.html")
OUTPUT = os.path.join("src", "helpers", "esp32", "WebConfigHtml.h")
# __file__ is not defined inside PIO/SCons-executed extra_scripts
SCRIPT = os.path.join("scripts", "generate_webconfig_html.py")
HASH_MARKER = "// build-inputs-sha256: "


def status(msg):
    sys.stderr.write("WebConfig HTML: %s\n" % msg)


def content_hash():
    # Hash the source page and this generator so any change to either forces a
    # regenerate, independent of file timestamps.
    h = hashlib.sha256()
    with open(SOURCE, "rb") as f:
        h.update(f.read())
    if os.path.isfile(SCRIPT):
        h.update(b"\0")
        with open(SCRIPT, "rb") as f:
            h.update(f.read())
    return h.hexdigest()


def stored_hash():
    try:
        with open(OUTPUT, "r") as f:
            for line in f:
                if line.startswith(HASH_MARKER):
                    return line[len(HASH_MARKER):].strip()
                if line.startswith("#"):  # reached the C preprocessor lines
                    break
    except OSError:
        return None
    return None


def main():
    if not os.path.isfile(SOURCE):
        status("ERROR: %s not found" % SOURCE)
        sys.exit(2)

    src_hash = content_hash()
    if os.path.isfile(OUTPUT) and stored_hash() == src_hash:
        return

    with open(SOURCE, "rb") as f:
        raw = f.read()

    # mtime=0 keeps the gzip output (and therefore the ETag) deterministic
    gz = gzip.compress(raw, compresslevel=9, mtime=0)
    etag = hashlib.sha256(gz).hexdigest()[:16]

    lines = []
    lines.append("// Auto-generated by scripts/generate_webconfig_html.py from %s" % SOURCE.replace(os.sep, "/"))
    lines.append("// DO NOT EDIT - edit webui/index.html instead.")
    lines.append("%s%s" % (HASH_MARKER, src_hash))
    lines.append("#pragma once")
    lines.append("#include <stdint.h>")
    lines.append("#include <pgmspace.h>")
    lines.append("")
    lines.append("const uint32_t WEBCONFIG_HTML_GZ_LEN = %d;" % len(gz))
    lines.append('const char WEBCONFIG_HTML_ETAG[] = "\\"%s\\"";' % etag)
    lines.append("const uint8_t WEBCONFIG_HTML_GZ[] PROGMEM = {")
    for i in range(0, len(gz), 16):
        chunk = gz[i:i + 16]
        lines.append("  " + "".join("0x%02x," % b for b in chunk))
    lines.append("};")
    lines.append("")

    os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
    with open(OUTPUT, "w") as f:
        f.write("\n".join(lines))

    status("%s -> %s (%d bytes raw, %d bytes gzipped)" % (SOURCE, OUTPUT, len(raw), len(gz)))


main()
