{ config, ... }: { config.types.event = event@{ lib, events, ... }: let inherit (lib) mkOption types; cfg = event.config; sort-key = e: "${e.date.start} ${e.time.start}"; prior = let candidates = lib.filter ( e: e.venue != null && e.venue == cfg.venue && sort-key e < sort-key cfg && !e.address.once ) (lib.attrValues events); sorted = lib.sortOn sort-key candidates; in if sorted == [ ] then throw "Kein vorheriger Termin mit gleichem Veranstaltungsort gefunden." else lib.last sorted; in { imports = [ config.types.document ]; options = { title = mkOption { description = '' Übeschrift für den Termin ''; type = types.singleLineStr; }; summary = mkOption { description = '' Kurzbeschreibung des Termins ''; type = with types; nullOr singleLineStr; default = null; }; aside = mkOption { description = '' Anmerkung zur Beschreibung ''; type = with types; nullOr singleLineStr; default = null; }; description = mkOption { description = '' Beschreibung des Termins als Freitext ''; type = with types; nullOr str; default = null; }; data = mkOption { description = '' Semistrukturierte Daten über den Termin ''; type = with types; listOf (submodule { options = { name = mkOption { type = singleLineStr; }; value = mkOption { type = singleLineStr; }; }; }); example = [ { name = "Eintritt"; value = "frei – um Spenden wird gebeten"; } ]; default = [ ]; }; links = mkOption { description = '' Links zu weiteren Informationen ''; type = with types; listOf (submodule { options = { text = mkOption { type = singleLineStr; }; url = mkOption { type = strMatching "^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\\?([^#]*))?(#(.*))?"; }; external = mkOption { type = bool; default = true; }; }; }); default = [ ]; example = { text = "Details beim Veranstalter"; url = "https://examle.org/event/123"; }; }; hosts = mkOption { description = '' Veranstalter ''; type = with types; listOf (submodule { options = { name = mkOption { type = singleLineStr; }; url = mkOption { type = strMatching "^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\\?([^#]*))?(#(.*))?"; }; }; }); default = [ ]; example = { name = "Stadtteilbeirat Heimfeld"; url = "https://heimfeld.harburg"; }; }; date.start = mkOption { description = '' Datum des Termins ''; type = types.strMatching "[0-9]{4}-[0-9]{2}-[0-9]{2}"; example = "2025-12-20"; }; date.end = mkOption { description = '' Enddatum des Termins ''; type = with types; nullOr (strMatching "[0-9]{4}-[0-9]{2}-[0-9]{2}"); example = "2025-12-21"; default = null; }; time.start = mkOption { description = '' Startzeit des Termins ''; type = types.strMatching "[0-9]{2}:[0-9]{2}"; example = "17:00"; }; time.end = mkOption { description = '' Endzeit des Termins ''; type = with types; nullOr (strMatching "[0-9]{2}:[0-9]{2}"); example = "19:00"; default = null; }; datetime-range-text = mkOption { description = '' Zeitraum des Termins in Textform ''; type = types.str; example = "Samstag, 20.12.2025 von 15:00 bis 17:00 Uhr"; }; venue = mkOption { description = '' Kennzeichen des Veranstaltungsorts zum Wiederverwenden von Angaben in nachfolgenden Terminen. ''; type = with types; nullOr singleLineStr; default = null; }; address.name = mkOption { description = '' Eigenname des Veranstaltungsorts ''; type = with types; nullOr str; example = "Treffpunkthaus"; default = if cfg.venue == null then null else prior.address.name; defaultText = lib.literalMD "Adresse des vorherigen Termins mit gleichem Veranstaltungsort, falls vorhanden."; }; address.text = mkOption { description = '' Ort in Textform ''; type = types.lines; default = if cfg.venue == null then throw "Ohne Veranstaltungsort-Kennzeichen muss die Adresse vollständig angegeben werden." else prior.address.text; defaultText = lib.literalMD "Adresse des vorherigen Termins mit gleichem Veranstaltungsort."; }; address.openstreetmap = mkOption { description = '' Ort auf OpenStreetMap ''; type = with types; attrTag { way = mkOption { type = ints.positive; }; node = mkOption { type = ints.positive; }; }; example = { node = 6549438363; }; default = if cfg.venue == null then throw "Ohne Veranstaltungsort-Kennzeichen muss die Adresse vollständig angegeben werden." else prior.address.openstreetmap; defaultText = lib.literalMD "Adresse des vorherigen Termins mit gleichem Veranstaltungsort."; }; address.once = mkOption { description = '' Wenn gesetzt, wird die Adresse nicht in nachfolgende Termine vererbt. ''; type = types.bool; default = false; }; }; }; }