#!/usr/bin/env python3
# Embed every <img src="..."> in an HTML file as an inline asset so a
# single-file whitepaper travels without a server. SVGs splice in as
# inline <svg>. Raster images (png/jpg/etc.) base64-encode into a
# data: URI. Absolute URLs (http://, https://, data:) pass through.
#
# Usage: embed-images.py <html-file>

import base64
import mimetypes
import os
import re
import sys


def _is_absolute(src):
    return bool(re.match(r"^(https?:)?//|^data:", src))


def _svg_inline(path, attrs):
    """Read a .svg file and return an <svg> element ready to splice in,
    preserving any alt text from the <img> tag via <title>."""
    with open(path, "r") as f:
        svg = f.read()
    # Strip the XML prolog and the <!DOCTYPE svg ...> declaration;
    # keeping them inside an HTML5 document is harmless but messy.
    svg = re.sub(r"^\s*<\?xml[^>]*\?>\s*", "", svg)
    svg = re.sub(r"^\s*<!DOCTYPE[^>]*>\s*", "", svg, flags=re.IGNORECASE)
    # If the original <img> carried alt="..." surface it as <title> so
    # hover-text and a11y tools still get the description.
    alt = attrs.get("alt", "").strip()
    if alt and "<title" not in svg[:512]:
        svg = re.sub(r"(<svg\b[^>]*>)", r"\1<title>" + alt + "</title>", svg, count=1)
    return svg


def _data_uri(path):
    mime, _ = mimetypes.guess_type(path)
    if not mime:
        mime = "application/octet-stream"
    with open(path, "rb") as f:
        payload = base64.b64encode(f.read()).decode()
    return f"data:{mime};base64,{payload}"


_IMG_RE = re.compile(r"<img\b([^>]*)>", re.IGNORECASE)
_ATTR_RE = re.compile(r'([a-zA-Z_:][a-zA-Z0-9_:.-]*)\s*=\s*"([^"]*)"')


def _parse_attrs(blob):
    return dict(_ATTR_RE.findall(blob))


def _render_attrs(attrs):
    return " " + " ".join(f'{k}="{v}"' for k, v in attrs.items()) if attrs else ""


def embed(html, base_dir):
    def replace(match):
        attr_blob = match.group(1)
        attrs = _parse_attrs(attr_blob)
        src = attrs.get("src", "")
        if not src or _is_absolute(src):
            return match.group(0)
        fs_path = os.path.join(base_dir, src)
        if not os.path.isfile(fs_path):
            sys.stderr.write(f"embed-images: missing {src}\n")
            return match.group(0)
        ext = os.path.splitext(src)[1].lower()
        if ext == ".svg":
            # Inline the SVG in place of the <img>. Drops the img entirely.
            return _svg_inline(fs_path, attrs)
        attrs["src"] = _data_uri(fs_path)
        return f"<img{_render_attrs(attrs)}>"

    return _IMG_RE.sub(replace, html)


def main(argv):
    if len(argv) != 2:
        sys.stderr.write("usage: embed-images.py <html-file>\n")
        return 2
    path = argv[1]
    base_dir = os.path.dirname(os.path.abspath(path))
    with open(path, "r") as f:
        html = f.read()
    out = embed(html, base_dir)
    with open(path, "w") as f:
        f.write(out)
    print(f"embed-images: rewrote {path} ({len(out)} bytes)")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
