package at.procon.eventhub.tachographfilesession.service;

import at.procon.eventhub.dto.CardSlot;
import at.procon.eventhub.dto.CardStatus;
import at.procon.eventhub.dto.DriverCardRefDto;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.DrivingStatus;
import at.procon.eventhub.dto.EventDetailsDto;
import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.EventHubPackageRequest;
import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventSourceDto;
import at.procon.eventhub.dto.EventType;
import at.procon.eventhub.dto.GeoPointDto;
import at.procon.eventhub.dto.ImportScopeDto;
import at.procon.eventhub.dto.SourcePackageRefDto;
import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
import at.procon.eventhub.service.EventDetailsFactory;
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographTimelineEventBundle;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.springframework.stereotype.Component;

@Component
public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineEventBuilder {

    private final DriverTimelineBuilder driverTimelineBuilder;
    private final DriverKeyFactory driverKeyFactory;
    private final VehicleKeyFactory vehicleKeyFactory;
    private final EventDetailsFactory detailsFactory;

    public IntervalBackedDriverTimelineEventBuilder(
            DriverTimelineBuilder driverTimelineBuilder,
            DriverKeyFactory driverKeyFactory,
            VehicleKeyFactory vehicleKeyFactory,
            EventDetailsFactory detailsFactory
    ) {
        this.driverTimelineBuilder = driverTimelineBuilder;
        this.driverKeyFactory = driverKeyFactory;
        this.vehicleKeyFactory = vehicleKeyFactory;
        this.detailsFactory = detailsFactory;
    }

    @Override
    public TachographTimelineEventBundle buildEventBundle(
            TachographFileSession session,
            DriverExtractionSession driverSession
    ) {
        ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
        return buildEventBundle(session, driverSession, timeline);
    }

    @Override
    public TachographTimelineEventBundle buildEventBundle(
            TachographFileSession session,
            DriverExtractionSession driverSession,
            ResolvedDriverTimeline timeline
    ) {
        if (session == null || driverSession == null || timeline == null) {
            return new TachographTimelineEventBundle(List.of(), List.of(), List.of());
        }

        Map<String, ExtractedVehicleRegistration> registrationsByKey = new LinkedHashMap<>();
        for (ExtractedVehicleRegistration registration : driverSession.vehicleRegistrations()) {
            registrationsByKey.put(registration.registrationKey(), registration);
        }
        Map<String, ExtractedVehicle> vehiclesByKey = new LinkedHashMap<>();
        for (ExtractedVehicle vehicle : driverSession.vehicles()) {
            vehiclesByKey.put(vehicle.vehicleKey(), vehicle);
        }

        DriverRefDto driverRef = driverRef(driverSession);
        EventSourceDto eventSource = eventSource(session, timeline);
        SourcePackageRefDto sourcePackageRef = sourcePackageRef(session, timeline);

        List<EventHubEventDto> activityEvents = buildActivityEvents(
                session,
                timeline.activityIntervals(),
                driverRef,
                registrationsByKey,
                vehiclesByKey,
                eventSource,
                sourcePackageRef
        );
        List<EventHubEventDto> vehicleUsageEvents = buildVehicleUsageEvents(
                session,
                timeline.vehicleUsageIntervals(),
                driverRef,
                registrationsByKey,
                vehiclesByKey,
                eventSource,
                sourcePackageRef
        );
        List<EventHubEventDto> supportEvents = buildSupportEvents(
                session,
                timeline.supportEvents(),
                driverRef,
                registrationsByKey,
                vehiclesByKey,
                eventSource,
                sourcePackageRef
        );
        return new TachographTimelineEventBundle(activityEvents, vehicleUsageEvents, supportEvents);
    }

    private List<EventHubEventDto> buildActivityEvents(
            TachographFileSession session,
            List<ResolvedActivityInterval> intervals,
            DriverRefDto driverRef,
            Map<String, ExtractedVehicleRegistration> registrationsByKey,
            Map<String, ExtractedVehicle> vehiclesByKey,
            EventSourceDto eventSource,
            SourcePackageRefDto sourcePackageRef
    ) {
        if (intervals == null || intervals.isEmpty()) {
            return List.of();
        }
        List<EventHubEventDto> events = new ArrayList<>(intervals.size() * 2);
        for (ResolvedActivityInterval interval : intervals) {
            VehicleRefDto vehicleRef = vehicleRef(interval.registrationKey(), interval.vehicleKey(), registrationsByKey, vehiclesByKey);
            EventType eventType = activityEventType(interval.activityType());
            EventDetailsDto details = detailsFactory.driverActivity(
                    cardSlot(interval.slot()),
                    cardStatus(interval.cardStatus()),
                    drivingStatus(interval.drivingStatus())
            );
            Map<String, Object> raw = new LinkedHashMap<>();
            raw.put("intervalId", interval.intervalId());
            raw.put("sourceRowId", interval.intervalId());
            raw.put("sourceRowIds", interval.sourceIntervalIds());
            raw.put("startedAt", timeText(interval.from()));
            raw.put("endedAt", timeText(interval.to()));
            raw.put("durationSeconds", interval.durationSeconds());
            raw.put("sourceKind", interval.sourceKind());
            raw.put("registrationKey", interval.registrationKey());
            raw.put("vehicleKey", interval.vehicleKey());
            raw.put("synthetic", interval.synthetic());
            raw.put("clippedToRequestedPeriod", interval.clippedToRequestedPeriod());
            raw.put("level", interval.level());

            events.add(event(
                    session,
                    interval.from(),
                    EventDomain.DRIVER_ACTIVITY,
                    eventType,
                    EventLifecycle.START,
                    eventSource,
                    driverRef,
                    vehicleRef,
                    null,
                    null,
                    details,
                    sourcePackageRef,
                    raw,
                    isManualEntry(interval.cardStatus(), interval.drivingStatus()),
                    "ACTIVITY",
                    interval.intervalId()
            ));
            events.add(event(
                    session,
                    interval.to(),
                    EventDomain.DRIVER_ACTIVITY,
                    eventType,
                    EventLifecycle.END,
                    eventSource,
                    driverRef,
                    vehicleRef,
                    null,
                    null,
                    details,
                    sourcePackageRef,
                    raw,
                    isManualEntry(interval.cardStatus(), interval.drivingStatus()),
                    "ACTIVITY",
                    interval.intervalId()
            ));
        }
        return List.copyOf(events);
    }

    private List<EventHubEventDto> buildVehicleUsageEvents(
            TachographFileSession session,
            List<ResolvedVehicleUsageInterval> intervals,
            DriverRefDto driverRef,
            Map<String, ExtractedVehicleRegistration> registrationsByKey,
            Map<String, ExtractedVehicle> vehiclesByKey,
            EventSourceDto eventSource,
            SourcePackageRefDto sourcePackageRef
    ) {
        if (intervals == null || intervals.isEmpty()) {
            return List.of();
        }
        List<EventHubEventDto> events = new ArrayList<>(intervals.size() * 2);
        for (ResolvedVehicleUsageInterval interval : intervals) {
            VehicleRefDto vehicleRef = vehicleRef(interval.registrationKey(), interval.vehicleKey(), registrationsByKey, vehiclesByKey);
            EventDetailsDto insertDetails = detailsFactory.driverCard(
                    null,
                    CardStatus.INSERTED,
                    driverRef == null ? null : driverRef.driverCard()
            );
            EventDetailsDto withdrawDetails = detailsFactory.driverCard(
                    null,
                    CardStatus.NOT_INSERTED,
                    driverRef == null ? null : driverRef.driverCard()
            );
            Map<String, Object> raw = new LinkedHashMap<>();
            raw.put("intervalId", interval.intervalId());
            raw.put("sourceRowId", interval.intervalId());
            raw.put("sourceRowIds", interval.sourceIntervalIds());
            raw.put("startedAt", timeText(interval.from()));
            raw.put("endedAt", timeText(interval.to()));
            raw.put("durationSeconds", interval.durationSeconds());
            raw.put("registrationKey", interval.registrationKey());
            raw.put("vehicleKey", interval.vehicleKey());
            raw.put("sourceKind", interval.sourceKind());
            raw.put("odometerBeginKm", interval.odometerBeginKm());
            raw.put("odometerEndKm", interval.odometerEndKm());

            events.add(event(
                    session,
                    interval.from(),
                    EventDomain.DRIVER_CARD,
                    EventType.CARD_INSERTED,
                    EventLifecycle.INSERT,
                    eventSource,
                    driverRef,
                    vehicleRef,
                    odometerMeters(interval.odometerBeginKm()),
                    null,
                    insertDetails,
                    sourcePackageRef,
                    raw,
                    false,
                    "VEHICLE_USAGE",
                    interval.intervalId()
            ));
            if (interval.to() != null) {
                events.add(event(
                        session,
                        interval.to(),
                        EventDomain.DRIVER_CARD,
                        EventType.CARD_WITHDRAWN,
                        EventLifecycle.WITHDRAW,
                        eventSource,
                        driverRef,
                        vehicleRef,
                        odometerMeters(interval.odometerEndKm()),
                        null,
                        withdrawDetails,
                        sourcePackageRef,
                        raw,
                        false,
                        "VEHICLE_USAGE",
                        interval.intervalId()
                ));
            }
        }
        return List.copyOf(events);
    }

    private List<EventHubEventDto> buildSupportEvents(
            TachographFileSession session,
            List<ExtractedSupportEvent> supportEvents,
            DriverRefDto driverRef,
            Map<String, ExtractedVehicleRegistration> registrationsByKey,
            Map<String, ExtractedVehicle> vehiclesByKey,
            EventSourceDto eventSource,
            SourcePackageRefDto sourcePackageRef
    ) {
        if (supportEvents == null || supportEvents.isEmpty()) {
            return List.of();
        }
        List<EventHubEventDto> events = new ArrayList<>(supportEvents.size());
        for (ExtractedSupportEvent supportEvent : supportEvents) {
            EventDomain eventDomain = supportEventDomain(supportEvent.eventDomain());
            EventType eventType = supportEventType(eventDomain, supportEvent.eventType(), supportEvent.code());
            EventLifecycle lifecycle = supportEventLifecycle(eventDomain, supportEvent.eventType(), supportEvent.eventLifecycle());
            boolean manualEntry = isManualPlaceEvent(supportEvent.eventType());
            VehicleRefDto vehicleRef = vehicleRef(
                    supportEvent.registrationKey(),
                    supportEvent.vehicleKey(),
                    registrationsByKey,
                    vehiclesByKey
            );
            EventDetailsDto details = supportDetails(eventDomain, supportEvent);
            Map<String, Object> raw = new LinkedHashMap<>();
            raw.put("sourceRowId", supportEvent.eventId());
            raw.put("supportEventId", supportEvent.eventId());
            raw.put("driverKey", supportEvent.driverKey());
            raw.put("supportEventType", supportEvent.eventType());
            raw.put("slot", supportEvent.slot());
            raw.put("registrationKey", supportEvent.registrationKey());
            raw.put("vehicleKey", supportEvent.vehicleKey());
            raw.put("country", supportEvent.country());
            raw.put("region", supportEvent.region());
            raw.put("countryFrom", supportEvent.countryFrom());
            raw.put("countryTo", supportEvent.countryTo());
            raw.put("operation", supportEvent.operation());
            raw.put("authenticationStatus", supportEvent.authenticationStatus());
            raw.put("odometerKm", supportEvent.odometerKm());
            raw.put("code", supportEvent.code());
            raw.put("avgSpeedKmh", supportEvent.avgSpeedKmh());
            raw.put("maxSpeedKmh", supportEvent.maxSpeedKmh());
            raw.put("rawRecordPath", supportEvent.rawRecordPath());

            events.add(event(
                    session,
                    supportEvent.occurredAt(),
                    eventDomain,
                    eventType,
                    lifecycle,
                    eventSource,
                    driverRef,
                    vehicleRef,
                    odometerMeters(supportEvent.odometerKm()),
                    position(supportEvent.latitude(), supportEvent.longitude()),
                    details,
                    sourcePackageRef,
                    raw,
                    manualEntry,
                    "SUPPORT",
                    supportEvent.eventId()
            ));
        }
        return List.copyOf(events);
    }

    private EventHubEventDto event(
            TachographFileSession session,
            OffsetDateTime occurredAt,
            EventDomain eventDomain,
            EventType eventType,
            EventLifecycle lifecycle,
            EventSourceDto eventSource,
            DriverRefDto driverRef,
            VehicleRefDto vehicleRef,
            Long odometerM,
            GeoPointDto position,
            EventDetailsDto details,
            SourcePackageRefDto sourcePackageRef,
            Map<String, Object> rawPayload,
            boolean manualEntry,
            String group,
            String sourceId
    ) {
        return new EventHubEventDto(
                UUID.randomUUID(),
                externalSourceEventId(session.sessionId(), group, sourceId, lifecycle, occurredAt),
                driverRef,
                vehicleRef,
                occurredAt,
                null,
                OffsetDateTime.now(),
                eventDomain,
                eventType,
                lifecycle,
                odometerM,
                position,
                details,
                sourcePackageRef,
                detailsFactory.payloadFromMap(Map.of("raw", rawPayload)),
                manualEntry,
                packageInfo(session, eventSource, eventDomain, occurredAt)
        );
    }

    private DriverRefDto driverRef(DriverExtractionSession driverSession) {
        ExtractedDriver driver = driverSession.driver();
        ExtractedDriverCard card = driverSession.driverCard();
        DriverCardRefDto driverCardRef = card == null || card.cardNumber() == null
                ? driverCardFromKey(driverSession.driverKey())
                : new DriverCardRefDto(card.cardNation(), card.cardNumber());
        String sourceDriverId = driver == null || driver.sourceDriverId() == null
                ? driverKeyFactory.createSourceDriverId(driverSession.driverKey())
                : driver.sourceDriverId();
        return new DriverRefDto(sourceDriverId, driverCardRef);
    }

    private DriverCardRefDto driverCardFromKey(String driverKey) {
        if (driverKey == null || driverKey.isBlank()) {
            return null;
        }
        int separator = driverKey.indexOf(':');
        if (separator < 0) {
            return new DriverCardRefDto(null, driverKey);
        }
        return new DriverCardRefDto(driverKey.substring(0, separator), driverKey.substring(separator + 1));
    }

    private VehicleRefDto vehicleRef(
            String registrationKey,
            String vehicleKey,
            Map<String, ExtractedVehicleRegistration> registrationsByKey,
            Map<String, ExtractedVehicle> vehiclesByKey
    ) {
        ExtractedVehicleRegistration registration = registrationKey == null ? null : registrationsByKey.get(registrationKey);
        ExtractedVehicle vehicle = vehicleKey == null ? null : vehiclesByKey.get(vehicleKey);
        VehicleRegistrationRefDto registrationRef = registration != null
                ? new VehicleRegistrationRefDto(registration.registrationNation(), registration.registrationNumber())
                : registrationFromKey(registrationKey);
        VehicleRefDto vehicleRef = new VehicleRefDto(
                vehicle != null ? vehicle.sourceVehicleId() : vehicleKeyFactory.createSourceVehicleId(vehicleKey),
                vehicle != null ? vehicle.vin() : vehicleKey,
                registration != null ? registration.sourceVehicleRegistrationId() : vehicleKeyFactory.createSourceVehicleRegistrationId(registrationKey),
                registrationRef
        );
        return vehicleRef.hasAnyReference() ? vehicleRef : null;
    }

    private VehicleRegistrationRefDto registrationFromKey(String registrationKey) {
        if (registrationKey == null || registrationKey.isBlank()) {
            return null;
        }
        int separator = registrationKey.indexOf(':');
        if (separator < 0) {
            return new VehicleRegistrationRefDto(null, registrationKey);
        }
        return new VehicleRegistrationRefDto(registrationKey.substring(0, separator), registrationKey.substring(separator + 1));
    }

    private EventSourceDto eventSource(TachographFileSession session, ResolvedDriverTimeline timeline) {
        String sourceKind = timeline.sourceKind() == null || timeline.sourceKind().isBlank()
                ? (session.metadata().driverCardFile() ? "DRIVER_CARD" : "VEHICLE_UNIT")
                : timeline.sourceKind();
        return new EventSourceDto(
                "TACHOGRAPH",
                sourceKind,
                "TACHOGRAPH_" + sourceKind,
                session.metadata().sourceInstanceKey(),
                null,
                null
        );
    }

    private SourcePackageRefDto sourcePackageRef(TachographFileSession session, ResolvedDriverTimeline timeline) {
        return new SourcePackageRefDto(
                "TACHOGRAPH_FILE_SESSION",
                session.sessionId().toString(),
                session.metadata().uploadedFileSha256(),
                timeline.loadedFrom(),
                timeline.loadedTo(),
                session.createdAt().atOffset(ZoneOffset.UTC)
        );
    }

    private EventHubPackageRequest packageInfo(
            TachographFileSession session,
            EventSourceDto eventSource,
            EventDomain eventDomain,
            OffsetDateTime occurredAt
    ) {
        LocalDate businessDate = occurredAt == null ? null : occurredAt.toLocalDate();
        return new EventHubPackageRequest(
                tenantOrDefault(session.metadata().tenantKey()),
                eventSource,
                null,
                ImportScopeDto.tenantAll(null, null),
                eventDomain.name(),
                businessDate,
                "TACHOGRAPH_FILE_SESSION:" + session.sessionId() + ":" + eventDomain.name() + ":" + businessDate
        );
    }

    private EventType activityEventType(String activityType) {
        if (activityType == null) {
            return EventType.UNKNOWN_ACTIVITY;
        }
        return switch (activityType) {
            case "DRIVE" -> EventType.DRIVE;
            case "WORK" -> EventType.WORK;
            case "AVAILABILITY" -> EventType.AVAILABILITY;
            case "BREAK_REST" -> EventType.BREAK_REST;
            default -> EventType.UNKNOWN_ACTIVITY;
        };
    }

    private CardSlot cardSlot(String value) {
        return parseEnum(CardSlot.class, value, null);
    }

    private CardStatus cardStatus(String value) {
        return parseEnum(CardStatus.class, value, null);
    }

    private DrivingStatus drivingStatus(String value) {
        return parseEnum(DrivingStatus.class, value, DrivingStatus.UNKNOWN);
    }

    private boolean isManualEntry(String cardStatus, String drivingStatus) {
        return Objects.equals("NOT_INSERTED", normalizeToken(cardStatus))
                && Objects.equals("KNOWN", normalizeToken(drivingStatus));
    }

    private EventDomain supportEventDomain(String value) {
        return switch (normalizeToken(value)) {
            case "PLACE" -> EventDomain.PLACE;
            case "POSITION" -> EventDomain.POSITION;
            case "BORDER_CROSSING" -> EventDomain.BORDER_CROSSING;
            case "LOAD_UNLOAD" -> EventDomain.LOAD_UNLOAD;
            case "SPECIFIC_CONDITION" -> EventDomain.SPECIFIC_CONDITION;
            case "SPEEDING" -> EventDomain.SPEEDING;
            default -> EventDomain.TELEMATICS_DATA;
        };
    }

    private EventType supportEventType(EventDomain eventDomain, String eventType, String code) {
        EventType explicit = parseEnum(EventType.class, eventType, null);
        if (explicit != null) {
            return explicit;
        }
        return switch (eventDomain) {
            case PLACE -> EventType.WORKING_DAY_PLACE_RECORDED;
            case POSITION -> EventType.POSITION_RECORDED;
            case SPECIFIC_CONDITION -> {
                String normalizedCode = normalizeToken(code);
                if (Objects.equals("FERRY_TRAIN", normalizedCode) || Objects.equals("FERRY_OR_TRAIN", normalizedCode)) {
                    yield EventType.FERRY_TRAIN;
                }
                if (Objects.equals("OUT_OF_SCOPE", normalizedCode) || Objects.equals("OUT", normalizedCode)) {
                    yield EventType.OUT;
                }
                yield EventType.UNKNOWN_EVENT;
            }
            case BORDER_CROSSING -> EventType.BORDER_OUTBOUND;
            case LOAD_UNLOAD -> EventType.LOAD_UNLOAD;
            case SPEEDING -> EventType.SPEEDING;
            default -> parseEnum(EventType.class, eventType, EventType.UNKNOWN_EVENT);
        };
    }

    private EventLifecycle supportEventLifecycle(EventDomain eventDomain, String eventType, String lifecycle) {
        EventLifecycle explicit = parseEnum(EventLifecycle.class, lifecycle, null);
        if (explicit != null) {
            return explicit;
        }
        if (eventDomain == EventDomain.PLACE) {
            String normalized = normalizeToken(eventType);
            if (normalized != null && normalized.startsWith("BEGIN")) {
                return EventLifecycle.BEGIN;
            }
            if (normalized != null && normalized.startsWith("END")) {
                return EventLifecycle.END;
            }
        }
        return EventLifecycle.SNAPSHOT;
    }

    private boolean isManualPlaceEvent(String eventType) {
        String normalized = normalizeToken(eventType);
        return normalized != null && normalized.contains("MANUAL");
    }

    private EventDetailsDto supportDetails(EventDomain eventDomain, ExtractedSupportEvent supportEvent) {
        return switch (eventDomain) {
            case PLACE -> detailsFactory.place(supportEvent.country(), supportEvent.region());
            case POSITION -> detailsFactory.position("GNSS_ACCUMULATED_DRIVING");
            case BORDER_CROSSING -> detailsFactory.borderCrossing(supportEvent.countryFrom(), supportEvent.countryTo());
            case LOAD_UNLOAD -> detailsFactory.loadUnload(supportEvent.operation());
            case SPECIFIC_CONDITION -> detailsFactory.specificCondition();
            case SPEEDING -> detailsFactory.speeding(supportEvent.avgSpeedKmh(), supportEvent.maxSpeedKmh(), null);
            default -> new EventDetailsDto("TACHOGRAPH_SUPPORT", detailsFactory.payloadFromMap(Map.of()));
        };
    }

    private GeoPointDto position(BigDecimal latitude, BigDecimal longitude) {
        return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
    }

    private Long odometerMeters(Long kilometers) {
        return kilometers == null ? null : kilometers * 1_000L;
    }

    private String timeText(OffsetDateTime value) {
        return value == null ? null : value.toString();
    }

    private String externalSourceEventId(
            UUID sessionId,
            String group,
            String sourceId,
            EventLifecycle lifecycle,
            OffsetDateTime occurredAt
    ) {
        return "TACHOGRAPH_FILE_SESSION:"
                + sessionId
                + ":"
                + group
                + ":"
                + sourceId
                + ":"
                + lifecycle.name()
                + ":"
                + occurredAt;
    }

    private String tenantOrDefault(String value) {
        return value == null || value.isBlank() ? "default" : value.trim();
    }

    private String normalizeToken(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        return value.trim().toUpperCase().replace('-', '_').replace(' ', '_');
    }

    private <T extends Enum<T>> T parseEnum(Class<T> type, String value, T fallback) {
        String normalized = normalizeToken(value);
        if (normalized == null) {
            return fallback;
        }
        try {
            return Enum.valueOf(type, normalized);
        } catch (IllegalArgumentException ignored) {
            return fallback;
        }
    }
}
