{ config, containerCfg, pkgs, lib, builder, name, ... }:
let
  serverCfg = config.syscfg.server;
  mediaCfg = config.syscfg.media;
  palette = serverCfg.colorScheme.palette or { };
  port = 8080;
  assetSize = 64;
  cacheControl = containerCfg.extra.cacheControl or "public, max-age=86400";
  priority = toString (containerCfg.extra.priority or 2147482647);
  logoSvgFileName = builtins.baseNameOf (toString mediaCfg.logo.svg);
  logoSvgMount = "/assets/${logoSvgFileName}";
  borderRadius = toString (containerCfg.extra.borderRadius or 32);
  resolveColor = value:
    if value == null then null
    else if !builtins.isString value then
      throw "favicon color values must be strings"
    else if lib.hasPrefix "#" value then
      value
    else
      lib.attrByPath [ value ] (throw "Unknown favicon color reference `${value}`") palette;
  normalizeProfile = profile:
    let
      bg =
        if profile ? bg then resolveColor profile.bg
        else if profile ? background then resolveColor profile.background
        else null;
      fg =
        if profile ? fg then resolveColor profile.fg
        else if profile ? foreground then resolveColor profile.foreground
        else null;
    in
    (lib.filterAttrs (name: _: !(builtins.elem name [ "bg" "background" "fg" "foreground" ])) profile)
    // lib.optionalAttrs (bg != null) { bg = bg; }
    // lib.optionalAttrs (fg != null) { fg = fg; };
  hostMappings = lib.mapAttrs' (mapping: profile:
    lib.nameValuePair mapping (normalizeProfile profile)
  ) (containerCfg.extra.mappings or {});
  traefikAssetPathRegexp =
    "^/(.*/)?"
    + "(fav(icon)?(-[0-9]+x[0-9]+)?\\.(ico|png|svg)"
    + "|favicon"
    + "|logo\\.(ico)"
    + "|fav([0-9]+)?\\.(ico|png)"
    + "|apple-touch-icon(-precomposed)?\\.png"
    + "|android-chrome-[0-9]+x[0-9]+\\.png"
    + "|mstile-[0-9]+x[0-9]+\\.png)$";
  configFile = pkgs.writeText "favicon-config.json" (builtins.toJSON {
    inherit cacheControl;
    borderRadius = borderRadius;
    domain = serverCfg.domain;
    mappings = hostMappings;
    default =
      if containerCfg.extra ? default then normalizeProfile containerCfg.extra.default
      else null;
  });
  pythonEnv = pkgs.python3.withPackages (ps: with ps; [
    cairosvg
    pillow
  ]);
  serverScript = pkgs.writeText "favicon-server.py" ''
    import base64
    import hashlib
    from io import BytesIO
    import json
    import os
    import re
    from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
    from pathlib import Path

    import cairosvg
    from PIL import Image

    CONFIG_PATH = os.environ.get("FAVICON_CONFIG", "/config/config.json")
    LOGO_PATH = os.environ.get("FAVICON_LOGO", "/assets/logo.svg")
    CACHE_DIR = Path(os.environ.get("FAVICON_CACHE_DIR", "/cache"))
    LISTEN_HOST = os.environ.get("FAVICON_LISTEN_HOST", "0.0.0.0")
    LISTEN_PORT = int(os.environ.get("FAVICON_PORT", "8080"))
    ASSET_SIZE = int(os.environ.get("FAVICON_ASSET_SIZE", "${toString assetSize}"))
    DEFAULT_CACHE_CONTROL = "public, max-age=86400"

    with open(CONFIG_PATH, "r", encoding="utf-8") as fh:
        APP_CONFIG = json.load(fh)
    with open(LOGO_PATH, "rb") as fh:
        LOGO_BYTES = fh.read()
    MAPPINGS = APP_CONFIG.get("mappings", {})
    DEFAULT_PROFILE = APP_CONFIG.get("default")
    APP_DOMAIN = (APP_CONFIG.get("domain", "") or "").strip().lower()
    CACHE_CONTROL = APP_CONFIG.get("cacheControl", DEFAULT_CACHE_CONTROL)
    LOGO_HASH = hashlib.sha256(LOGO_BYTES).hexdigest()

    def _normalize_host(host):
        host = (host or "").split(",", 1)[0].split(":", 1)[0].strip().lower()
        if APP_DOMAIN and host.endswith(f".{APP_DOMAIN}"):
            return host[:-(len(APP_DOMAIN) + 1)]
        return host

    def _request_host(headers):
        forwarded = headers.get("X-Forwarded-Host", "")
        original = headers.get("X-Original-Host", "")
        host = forwarded or original or headers.get("Host", "")
        return _normalize_host(host)

    def _pick_profile(host):
        return MAPPINGS.get(host) or DEFAULT_PROFILE

    def _color(value, fallback):
        return value if isinstance(value, str) and value else fallback

    def _resolved_profile(profile):
        return {
            "bg": _color(profile.get("bg") or profile.get("background"), "#111827"),
            "fg": _color(profile.get("fg") or profile.get("foreground"), "#f8fafc"),
        }

    def _replace_svg_color(svg, attribute, color):
        if attribute in {"fill", "stroke"}:
            svg = re.sub(
                rf'{attribute}="(?!none\\b)[^"]*"',
                f'{attribute}="{color}"',
                svg,
                flags=re.IGNORECASE,
            )
            svg = re.sub(
                rf"{attribute}='(?!none\\b)[^']*'",
                f"{attribute}='{color}'",
                svg,
                flags=re.IGNORECASE,
            )
        return re.sub(
            rf"{attribute}\\s*:\\s*(?!none\\b)[^;\"\\']+",
            f"{attribute}:{color}",
            svg,
            flags=re.IGNORECASE,
        )

    def _tinted_logo_data_uri(color):
        svg = LOGO_BYTES.decode("utf-8")
        svg = _replace_svg_color(svg, "fill", color)
        svg = _replace_svg_color(svg, "stroke", color)
        return "data:image/svg+xml;base64," + base64.b64encode(svg.encode("utf-8")).decode("ascii")

    border_radius = str(APP_CONFIG.get("borderRadius", "8")).strip()
    if not border_radius.endswith("px"):
        border_radius = f"{border_radius}px"

    def _render_svg(colors):
        logo_data_uri = _tinted_logo_data_uri(colors["fg"])

        canvas = 256
        return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{canvas}" height="{canvas}" viewBox="0 0 {canvas} {canvas}">
          <rect x="0" y="0" width="{canvas}" height="{canvas}" rx="{border_radius}" ry="{border_radius}" fill="{colors["bg"]}" />
          <image href="{logo_data_uri}" x="0" y="0" width="{canvas}" height="{canvas}" preserveAspectRatio="xMidYMid meet" />
        </svg>"""

    def _cache_key(host, colors):
        cache_inputs = {
            "asset_size": ASSET_SIZE,
            "bg": colors["bg"],
            "border_radius": border_radius,
            "fg": colors["fg"],
            "host": host,
            "logo_hash": LOGO_HASH,
        }
        payload = json.dumps(cache_inputs, sort_keys=True, separators=(",", ":"))
        return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]

    def _cache_name(host, colors):
        safe_host = re.sub(r"[^a-z0-9.-]+", "_", host or "default")
        return f"{safe_host}-{_cache_key(host, colors)}.ico"

    def _generate_asset(host, profile):
        colors = _resolved_profile(profile)
        cache_name = _cache_name(host, colors)
        target = CACHE_DIR / cache_name
        if target.exists():
            return target

        CACHE_DIR.mkdir(parents=True, exist_ok=True)
        svg = _render_svg(colors).encode("utf-8")
        png_bytes = cairosvg.svg2png(bytestring=svg, output_width=ASSET_SIZE, output_height=ASSET_SIZE)
        image = Image.open(BytesIO(png_bytes))
        image.save(target, format="ICO", sizes=[(ASSET_SIZE, ASSET_SIZE)])
        image.close()
        return target

    class Handler(BaseHTTPRequestHandler):
        server_version = "favicon-router/1.0"

        def _serve(self, include_body):
            host = _request_host(self.headers)
            profile = _pick_profile(host)
            if not profile:
                self.send_error(404, "No favicon mapping for host")
                return

            asset_path = _generate_asset(host, profile)
            etag = f'"{asset_path.stem.rsplit("-", 1)[-1]}"'
            if self.headers.get("If-None-Match") == etag:
                self.send_response(304)
                self.send_header("ETag", etag)
                self.send_header("Cache-Control", CACHE_CONTROL)
                self.end_headers()
                return

            payload = asset_path.read_bytes()
            self.send_response(200)
            self.send_header("Content-Type", "image/x-icon")
            self.send_header("Content-Length", str(len(payload)))
            self.send_header("Cache-Control", CACHE_CONTROL)
            self.send_header("ETag", etag)
            self.end_headers()
            if include_body:
                self.wfile.write(payload)

        def do_GET(self):
            self._serve(include_body=True)

        def do_HEAD(self):
            self._serve(include_body=False)

        def log_message(self, fmt, *args):
            print("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), fmt % args))

    if __name__ == "__main__":
        httpd = ThreadingHTTPServer((LISTEN_HOST, LISTEN_PORT), Handler)
        httpd.serve_forever()
  '';
  image = pkgs.dockerTools.streamLayeredImage {
    name = "favicon";
    tag = "1";
    contents = [
      pythonEnv
      pkgs.cacert
      pkgs.tzdata
    ];
    config = {
      Entrypoint = [ "${pythonEnv}/bin/python3" serverScript ];
      ExposedPorts = { "${toString port}/tcp" = { }; };
      WorkingDir = "/";
    };
  };
in {
  runtime = {
    paths = [
      {
        path = "${serverCfg.path.config.path}/favicon";
        mode = "0755";
        dirs = [ "cache" ];
      }
    ];

    containers = {
      server = builder.mkContainer {
        imageStream = image;
        port = port;
        extraEnv = {
          FAVICON_CONFIG = "/config/config.json";
          FAVICON_LOGO = logoSvgMount;
          FAVICON_CACHE_DIR = "/cache";
          FAVICON_PORT = toString port;
          FAVICON_ASSET_SIZE = toString assetSize;
        };
        extraLabels = {
          "traefik.enable" = "true";
          "traefik.http.routers.${name}.entrypoints" = "web-secure";
          "traefik.http.routers.${name}.rule" = "PathRegexp(`${traefikAssetPathRegexp}`)";
          "traefik.http.routers.${name}.priority" = priority;
          "traefik.http.routers.${name}.tls" = "true";
          "traefik.http.services.${name}.loadbalancer.server.port" = toString port;
        };
        overrides = {
          volumes = [
            "${configFile}:/config/config.json:ro"
            "${serverCfg.path.config.path}/favicon/cache:/cache"
            "${mediaCfg.logo.svg}:${logoSvgMount}:ro"
          ];
        };
      };
    };
  };
}
