import * as d3 from "d3";
import dayjs from "dayjs";
import { debounce, each, filter, get, includes, indexOf, isEmpty, isNil, isObject, keys, last, sortBy, values, } from "lodash-es";
import { getAssetPath, h, Host, } from "@stencil/core";
import { getStore } from "../../store";
import { sensorInfo } from "../../utils/sensorInfo";
import { watchStoreLocale } from "../../utils/locale-state";
import { getStoreUnavailableError } from "../../utils/store-error";
export class LsSwimlane {
    constructor() {
        /**
         * Specifies the IDs of the sensors that are to be displayed.
         */
        this.sensorIds = "500,501,502,503,514,515";
        /**
         * The sensor IDs input property, converted to an array of numbers.
         */
        this.sensorIdsArray = this.sensorIds
            .split(",")
            .filter((id) => id.trim() !== "")
            .map((id) => parseInt(id.trim(), 10));
        /**
         * Centralized error state for tracking component errors
         */
        this.errorState = {
            hasError: false,
            messageKey: undefined,
            type: "",
        };
        /**
         * Component loading state
         */
        this.isLoading = true;
        this.store = null;
        this.DEBOUNCEDELAY = 350;
        this.MARGIN = { BOTTOM: 0, LEFT: 10, RIGHT: 10, TOP: 0 };
        // Using a debounce here to save resources
        this.rebuildSwimlaneOnResize = debounce(() => {
            // Redraw the chart
            this.drawChart();
            // Rebuild chart data elements with new dimensions
            this.rebuildChartData();
            this.updateTimeline();
        }, this.DEBOUNCEDELAY, 
        // Enables the first trigger i.e. for when the screen gets maximized or media queries are triggered
        { leading: true });
        this.canvas = null;
        this.swimLineWidth = 0;
        this.sensorLabelsWidth = 0;
        this.dateRange = {
            from: null,
            to: null,
        };
        this.chartDataCache = [];
        this.storedAndOrderedSensorData = {};
        /**
         * Settings object with some values
         */
        this.swimLaneSettings = {
            minHeight: 200,
            laneHeight: 25,
            timer: null,
            subscriptions: [],
            swimlanes: {},
            labels: [],
            scale: {
                units: "minute",
                value: 1,
            },
        };
        this.storeUnsubscribes = [];
        /**
         * Get display symbol for sensor value
         */
        this.getSensorDisplaySymbol = (value, sensorId) => {
            const stringValue = String(value);
            // Normal check sensors (✓ for 1, ✗ for 0)
            if ([104, 10, 524].includes(sensorId)) {
                if (stringValue === "1")
                    return "✓";
                if (stringValue === "0")
                    return "✗";
            }
            // Inverted check sensors (✗ for 1, ✓ for 0)
            if ([105, 212, 550].includes(sensorId)) {
                if (stringValue === "1")
                    return "✗";
                if (stringValue === "0")
                    return "✓";
            }
            // Direction arrows
            if (value === "D")
                return "↓";
            if (value === "U")
                return "↑";
            // Default: return original value
            return value;
        };
        /**
         * Create fixed colors for sensor ID's
         */
        this.sensorValueColorCode = (value, sensorId) => {
            const stringValue = String(value);
            const { SENSOR_COLORS } = LsSwimlane;
            // Check normal sensors
            if (SENSOR_COLORS.normal.sensors.includes(sensorId)) {
                return SENSOR_COLORS.normal.colors[stringValue] || SENSOR_COLORS.fallback;
            }
            // Check inverted sensors
            if (SENSOR_COLORS.inverted.sensors.includes(sensorId)) {
                return (SENSOR_COLORS.inverted.colors[stringValue] || SENSOR_COLORS.fallback);
            }
            // Check string patterns
            if (stringValue in SENSOR_COLORS.strings) {
                return SENSOR_COLORS.strings[stringValue];
            }
            // Handle numeric values
            const number = parseInt(stringValue, 10);
            if (!isNaN(number)) {
                if (sensorId === "514") {
                    return number % 2 === 0
                        ? SENSOR_COLORS.sensor514.even
                        : SENSOR_COLORS.sensor514.odd;
                }
                return SENSOR_COLORS.numeric;
            }
            return SENSOR_COLORS.fallback;
        };
        /**
         * Truncate text to fit within the available width and add ellipsis if needed
         * We can't use CSS text-overflow: ellipsis here unfortunately
         */
        this.truncateText = (textElement, maxWidth) => {
            var _a, _b;
            const originalText = textElement.text();
            const ellipsis = "...";
            // If text fits, no need to truncate
            if ((((_a = textElement.node()) === null || _a === void 0 ? void 0 : _a.getBBox().width) || 0) <= maxWidth) {
                return;
            }
            // Binary search for the maximum length that fits
            let low = 0;
            let high = originalText.length;
            while (low < high) {
                const mid = Math.floor((low + high + 1) / 2);
                const truncated = originalText.substring(0, mid) + ellipsis;
                textElement.text(truncated);
                const width = ((_b = textElement.node()) === null || _b === void 0 ? void 0 : _b.getBBox().width) || 0;
                if (width <= maxWidth) {
                    low = mid;
                }
                else {
                    high = mid - 1;
                }
            }
            // Set final truncated text
            textElement.text(originalText.substring(0, low) + ellipsis);
        };
        this.clearChart = () => {
            // clear out the html of the chart so that we have a clear chart
            d3.select(this.canvas).html("");
        };
        /**
         * Create initial chart layout
         */
        this.drawChart = () => {
            if (this.canvas === null || this.canvas === undefined) {
                return;
            }
            // lets first remove the current chart before we draw a new one to prevent overlapping
            this.clearChart();
            // (Re)calculate width and height
            const componentWidth = this.element.clientWidth <= 0 ? 1 : this.element.clientWidth;
            const availableWidth = componentWidth - this.MARGIN.LEFT - this.MARGIN.RIGHT;
            const width = Math.max(100, availableWidth);
            this.sensorLabelsWidth =
                width < 200 ? width * 0.3 : width < 400 ? width * 0.35 : width * 0.25;
            this.swimLineWidth = width - this.sensorLabelsWidth;
            // Calculate the exact height needed upfront to prevent scrollbars
            const totalHeight = this.sensorIdsArray.length * this.swimLaneSettings.laneHeight + 23;
            // Chart
            const chart = d3
                .select(this.canvas)
                .append("svg")
                .attr("viewBox", `0 0 ${componentWidth} ${totalHeight}`)
                .attr("width", "100%")
                .attr("height", totalHeight)
                .attr("class", "chart");
            // Main group with margin transform
            const mainGroup = chart
                .append("g")
                .attr("transform", `translate(${this.MARGIN.LEFT}, ${this.MARGIN.TOP})`);
            // Swimlanes
            const swimlanes = mainGroup
                .append("g")
                .attr("width", this.swimLineWidth)
                .attr("transform", `translate(${Math.floor(this.sensorLabelsWidth)},0)`)
                .attr("class", "swimlanes");
            // Sensor
            const sensorLabels = mainGroup
                .append("g")
                .attr("width", this.sensorLabelsWidth)
                .attr("class", "sensors-labels");
            // Sensor background
            sensorLabels
                .append("rect")
                .attr("width", this.sensorLabelsWidth)
                .attr("height", "100%")
                .style("fill", "transparent");
            // Horizontal and vertical scales
            const endDate = dayjs().toDate();
            const startDate = dayjs()
                .subtract(this.swimLaneSettings.scale.value, this.swimLaneSettings.scale.units)
                .toDate();
            const horizontalScale = d3
                .scaleTime()
                .domain([startDate, endDate])
                .range([0, this.swimLineWidth]);
            this.xAxisScale = horizontalScale;
            this.dateRange.from = startDate;
            this.dateRange.to = endDate;
            // Add the x-axis
            const horizontalAxis = d3
                .axisBottom(horizontalScale)
                .ticks(5, "s")
                .tickFormat(d3.timeFormat("%H:%M:%S"))
                .tickSize(6);
            // Append the labels to the x axis
            mainGroup
                .append("g")
                .attr("transform", `translate(${Math.floor(this.sensorLabelsWidth)},${this.sensorIdsArray.length * this.swimLaneSettings.laneHeight})`)
                .attr("class", "main axis time")
                .call(horizontalAxis)
                .selectAll("text")
                .attr("dx", 5)
                .attr("dy", 12);
            this.xAxis = horizontalAxis;
            swimlanes.append("g").attr("class", "paths");
            // Add swimlane for each sensor
            each(this.sensorIdsArray, (sensorId) => {
                this.addSwimlane(sensorId);
            });
        };
        /**
         * Add swimlane to chart
         */
        this.addSwimlane = (sensorId) => {
            var _a;
            if (this.xAxis !== undefined) {
                // Only add swimlanes for sensors that we support
                if (indexOf(this.sensorIdsArray, sensorId) > -1) {
                    const indexHeight = this.swimLaneSettings.laneHeight *
                        this.sensorIdsArray.indexOf(sensorId);
                    // Put the ticks (date and time) at the bottom
                    const chart = d3.select(this.canvas);
                    // Add swimlane group
                    const swimlaneGroup = chart
                        .select(".swimlanes")
                        .append("g")
                        .attr("width", this.swimLineWidth)
                        .attr("height", this.swimLaneSettings.laneHeight)
                        .attr("class", "swimlane")
                        .attr("transform", `translate(0,${indexHeight})`);
                    // Add label group
                    const labelGroup = chart
                        .select(".sensors-labels")
                        .append("g")
                        .attr("width", this.sensorLabelsWidth)
                        .attr("height", this.swimLaneSettings.laneHeight)
                        .attr("class", "sensor")
                        .attr("transform", `translate(0,${indexHeight})`);
                    // Add text label
                    const labelTextElement = labelGroup
                        .append("text")
                        .text(sensorInfo.getLabel(sensorId, (_a = this.store) === null || _a === void 0 ? void 0 : _a.state.locale))
                        .attr("transform", `translate(0,${this.swimLaneSettings.laneHeight - 9})`)
                        .attr("font-size", "10")
                        .attr("class", "sensor-title");
                    // Truncate text if it's too long for the available space
                    this.truncateText(labelTextElement, this.sensorLabelsWidth - 10);
                    // Add lines
                    swimlaneGroup
                        .append("line")
                        .attr("x1", 0)
                        .attr("y1", this.swimLaneSettings.laneHeight)
                        .attr("x2", this.swimLineWidth)
                        .attr("y2", this.swimLaneSettings.laneHeight)
                        .attr("stroke", "lightgray");
                    labelGroup
                        .append("line")
                        .attr("x1", 0)
                        .attr("y1", this.swimLaneSettings.laneHeight)
                        .attr("x2", this.sensorLabelsWidth)
                        .attr("y2", this.swimLaneSettings.laneHeight)
                        .attr("stroke", "lightgray");
                    // Add the swimlane
                    this.swimLaneSettings.swimlanes[sensorId] = swimlaneGroup;
                    this.swimLaneSettings.labels.push({
                        element: labelGroup,
                        index: indexOf(this.sensorIdsArray, sensorId),
                    });
                }
            }
        };
        /**
         * Add data to state
         */
        this.addData = (sensorId, value, timestamp) => {
            if (isNil(sensorId) || !this.sensorIdsArray.includes(sensorId))
                return;
            // Get or initialize sensor data collection
            const sensorDataCollection = this.storedAndOrderedSensorData[`${sensorId}`] || [];
            const lastValue = last(sensorDataCollection);
            // If value changed, close previous entry and add new one
            if ((lastValue === null || lastValue === void 0 ? void 0 : lastValue.value) !== value) {
                // Close previous entry if it exists
                if (!isNil(lastValue)) {
                    sensorDataCollection[sensorDataCollection.length - 1] = Object.assign(Object.assign({}, lastValue), { end: timestamp });
                }
                // Add new entry
                sensorDataCollection.push({
                    value,
                    start: timestamp,
                    color: "#eee", // Color will be overwritten
                });
                // Update stored data and trigger rebuild
                this.storedAndOrderedSensorData[`${sensorId}`] = sortBy(sensorDataCollection, "start");
                this.rebuildChartData();
            }
        };
        this.updateTimeline = () => {
            var _a;
            if (this.canvas === null) {
                return;
            }
            const currentTime = dayjs();
            const theLimit = currentTime
                .clone()
                .subtract(this.swimLaneSettings.scale.value, this.swimLaneSettings.scale.units);
            // Rebuild chart data with current time to extend current values
            this.updateChartDataWithCurrentTime(currentTime);
            // Only update scale domain - this is the smooth scrolling part
            (_a = this.xAxisScale) === null || _a === void 0 ? void 0 : _a.domain([theLimit.toDate(), currentTime]);
            // Update the axis
            const chart = d3.select(this.canvas);
            chart.select(".main.axis.time").call(this.xAxis);
            // Update transforms of existing items for smooth scrolling
            const swimlaneGroup = chart.select(".swimlanes");
            const itemGroups = swimlaneGroup.selectAll("g.mainItemGroup");
            // Remove items that are completely past the time limit from DOM
            itemGroups
                .filter((d) => {
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                return theLimit.isAfter(theEnd);
            })
                .remove();
            // Update transforms for remaining items
            itemGroups
                .filter((d) => {
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                return !theLimit.isAfter(theEnd);
            })
                .attr("transform", (d) => {
                let theBegin = new Date(d.start);
                if (theLimit.isAfter(theBegin)) {
                    theBegin = theLimit.toDate();
                }
                return `translate( ${this.xAxisScale(theBegin)},${this.swimLaneSettings.laneHeight * d.index + 2})`;
            });
            // Update widths of rectangles for remaining items
            swimlaneGroup
                .selectAll("rect")
                .filter((d) => {
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                return !theLimit.isAfter(theEnd);
            })
                .attr("width", (d) => {
                // For current values (end = 0), extend to current time
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                let theBegin = new Date(d.start);
                if (theLimit.isAfter(theBegin)) {
                    theBegin = theLimit.toDate();
                }
                let result = this.xAxisScale(theEnd) - this.xAxisScale(theBegin);
                if (result < 1) {
                    result = 5;
                }
                return result;
            });
            // Update text positions for remaining items
            swimlaneGroup
                .selectAll("text.sensor-value")
                .filter((d) => {
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                return !theLimit.isAfter(theEnd);
            })
                .attr("transform", (d) => {
                // For current values (end = 0), extend to current time
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                let theBegin = new Date(d.start);
                if (theLimit.isAfter(theBegin)) {
                    theBegin = theLimit.toDate();
                }
                const width = this.xAxisScale(theEnd) - this.xAxisScale(theBegin);
                const xPosition = Math.max(width / 2, 6);
                return `translate(${xPosition},0)`;
            });
            // Update image positions for remaining items at same frequency as other elements for smooth movement
            swimlaneGroup
                .selectAll("image")
                .filter((d) => {
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                return !theLimit.isAfter(theEnd);
            })
                .style("transform", (d) => {
                // For current values (end = 0), extend to current time
                const theEnd = d.end.getTime() === 0 ? currentTime : dayjs(d.end);
                let theBegin = new Date(d.start);
                if (theLimit.isAfter(theBegin)) {
                    theBegin = theLimit.toDate();
                }
                const width = this.xAxisScale(theEnd) - this.xAxisScale(theBegin);
                // Use precise positioning for smooth continuous movement
                const xPosition = Math.max(width / 2 - 6, 0);
                return `translateX(${xPosition}px)`;
            });
        };
        /**
         * Update chart data with current time to extend current values
         */
        this.updateChartDataWithCurrentTime = (currentTime) => {
            // Update the chart data cache with current time for items that should extend to now
            this.chartDataCache = this.chartDataCache.map((item) => {
                // If this item has end time = 0 (marker for current value), extend to current time
                if (item.end.getTime() === 0) {
                    return Object.assign(Object.assign({}, item), { end: currentTime.toDate() });
                }
                return item;
            });
        };
        /**
         * Rebuild chart data from stored sensor data - only called when data changes
         */
        this.rebuildChartData = () => {
            const now = dayjs();
            const results = [];
            const theLimit = now
                .clone()
                .subtract(this.swimLaneSettings.scale.value, this.swimLaneSettings.scale.units);
            // Remove 'old' items first
            each(this.storedAndOrderedSensorData, (data, sensorId) => {
                const intSensorId = parseInt(sensorId, 10);
                if (indexOf(this.sensorIdsArray, intSensorId) > -1) {
                    this.storedAndOrderedSensorData[`${sensorId}`] = filter(data, (sensorUpdate) => {
                        const end = get(sensorUpdate, "end", +new Date());
                        return theLimit.isBefore(dayjs(end));
                    });
                }
            });
            each(this.storedAndOrderedSensorData, (data, sensorId) => {
                const intSensorId = parseInt(sensorId, 10);
                if (indexOf(this.sensorIdsArray, intSensorId) > -1) {
                    const swimlaneIndex = indexOf(this.sensorIdsArray, intSensorId);
                    each(data, (sensorUpdate, index) => {
                        if (isObject(sensorUpdate)) {
                            const start = get(sensorUpdate, "start");
                            let value = get(sensorUpdate, "value", "?");
                            let end = get(sensorUpdate, "end");
                            // If this is the last item in the array and has no end time, extend to now
                            const isLastItem = index === data.length - 1;
                            if (isLastItem && (end === undefined || end === null)) {
                                end = 0; // Use 0 as marker for "current value - extend to now"
                            }
                            else if (end === undefined || end === null) {
                                end = +new Date(); // Fallback for other items
                            }
                            // The values of sensor 501, 514 and 515 are zero based, but we need to show them one based
                            if (includes([501, 514, 515], intSensorId) &&
                                (typeof value === "number" || !isNaN(parseInt(String(value), 10)))) {
                                value =
                                    (typeof value === "number"
                                        ? value
                                        : parseInt(String(value), 10)) + 1;
                            }
                            results.push({
                                start: new Date(start),
                                end: new Date(end),
                                index: swimlaneIndex,
                                color: this.sensorValueColorCode(value, sensorId),
                                value: value,
                            });
                        }
                    });
                }
            });
            this.chartDataCache = results;
            this.drawChartElements();
        };
        /**
         * Draw/redraw chart elements with current data
         */
        this.drawChartElements = () => {
            const chart = d3.select(this.canvas);
            const swimlaneGroup = chart.select(".swimlanes");
            // Remove all references
            swimlaneGroup.selectAll("*").remove();
            // Create the main groups for each data item
            const itemGroups = swimlaneGroup
                .selectAll("g.mainItemGroup")
                .data(this.chartDataCache)
                .enter()
                .append("g")
                .attr("height", this.swimLaneSettings.laneHeight)
                .attr("class", "mainItemGroup");
            // First, add all rectangles (background layer)
            itemGroups
                .append("rect")
                .attr("height", () => 20)
                .style("fill", (d) => d.color);
            // Then, add text labels for numeric values and direction arrows (foreground layer)
            itemGroups
                .filter((d) => {
                // Only add text for values that should display as text
                const value = d.value;
                return !["O", "C", "X", "-", "+", "I"].includes(String(value));
            })
                .append("text")
                .text((d) => {
                const sensorId = this.sensorIdsArray[d.index];
                return this.getSensorDisplaySymbol(d.value, sensorId);
            })
                .attr("text-anchor", "middle")
                .attr("font-size", "9")
                .attr("font-weight", "bold")
                .attr("class", "sensor-value")
                .attr("dominant-baseline", "central")
                .attr("y", 10); // Center vertically in 20px height rectangle
            // Finally, add images for special values (top layer)
            itemGroups
                .filter((d) => ["O", "C", "X", "-", "+"].includes(String(d.value)))
                .append("image")
                .attr("href", (d) => {
                let source = getAssetPath(`./assets/elevator-unknown.svg`);
                switch (d.value) {
                    case "O":
                        source = getAssetPath(`./assets/elevator-open.svg`);
                        break;
                    case "C":
                        source = getAssetPath(`./assets/elevator-closed.svg`);
                        break;
                    case "X":
                        source = getAssetPath(`./assets/elevator-locked.svg`);
                        break;
                    case "-":
                        source = getAssetPath(`./assets/elevator-closing.svg`);
                        break;
                    case "+":
                        source = getAssetPath(`./assets/elevator-opening.svg`);
                        break;
                }
                return source;
            })
                .attr("x", 0) // Start at 0, positioning will be handled by CSS transform
                .attr("y", 4) // Center vertically in 20px height rectangle (20-12)/2 = 4
                .attr("width", 12)
                .attr("height", 12)
                .style("image-rendering", "pixelated") // Ensure crisp icon rendering
                .style("shape-rendering", "crispEdges") // Prevent anti-aliasing blur
                .style("transform-origin", "center") // Set transform origin for smoother animation
                .style("will-change", "transform"); // Hint browser for optimization
        };
        /**
         * This method handles every incoming event from any sensor
         */
        this.addSensorUpdateToTimeline = (data, timestamp) => {
            if (!isNil(data)) {
                const sensorId = parseInt(keys(data)[0], 10);
                const value = values(data)[0];
                const ts = timestamp || +new Date();
                this.addData(sensorId, value, ts);
            }
        };
        this.processHistoricalData = (sensorValues) => {
            if (!isNil(values)) {
                for (const value of sensorValues) {
                    const { rows, sensor } = value;
                    if (!isEmpty(rows)) {
                        rows.forEach((row) => {
                            const update = {};
                            update[sensor.id] = row.val;
                            this.addSensorUpdateToTimeline(update, +new Date(row.ts));
                        });
                    }
                }
            }
        };
        this.processLiveData = (sensorData) => {
            const dataValues = sensorData.values;
            const timestamps = sensorData.updated;
            for (let i = 0; i < keys(dataValues).length; i++) {
                const formattedDataValues = {};
                formattedDataValues[keys(dataValues)[i]] = values(dataValues)[i];
                const timestamp = timestamps[keys(dataValues)[i]];
                this.addSensorUpdateToTimeline(formattedDataValues, timestamp);
            }
        };
        /**
         * Start (and stop) the animation interval
         */
        this.rafErrorCount = 0;
        this.rafErrorThreshold = 5;
        this.timelineTimer = () => {
            // Clear any existing interval or animation frame
            if (this.interval !== null && this.interval !== undefined) {
                if (typeof this.interval === "number") {
                    cancelAnimationFrame(this.interval);
                }
                else {
                    clearInterval(this.interval);
                }
            }
            // Use requestAnimationFrame for smoother animation with fallback
            const animate = () => {
                try {
                    // Check for error state before updating timeline
                    this.updateErrorState();
                    // If there's an error, stop the animation loop
                    if (this.errorState.hasError) {
                        this.interval = null;
                        return;
                    }
                    this.updateTimeline();
                    this.rafErrorCount = 0; // Reset error count on success
                    this.interval = requestAnimationFrame(animate);
                }
                catch (error) {
                    this.rafErrorCount++;
                    console.warn("Animation frame error:", error, `(${this.rafErrorCount}/${this.rafErrorThreshold})`);
                    if (this.rafErrorCount >= this.rafErrorThreshold) {
                        console.error("Too many animation errors, stopping animation loop.");
                        this.interval = null;
                    }
                    else {
                        // Fallback to setInterval if requestAnimationFrame fails
                        this.interval = setInterval(() => {
                            try {
                                // Check for error state before updating timeline
                                this.updateErrorState();
                                // If there's an error, stop the animation loop
                                if (this.errorState.hasError) {
                                    clearInterval(this.interval);
                                    this.interval = null;
                                    return;
                                }
                                this.updateTimeline();
                                this.rafErrorCount = 0; // Reset error count on success
                            }
                            catch (e) {
                                this.rafErrorCount++;
                                console.warn("setInterval animation error:", e, `(${this.rafErrorCount}/${this.rafErrorThreshold})`);
                                if (this.rafErrorCount >= this.rafErrorThreshold) {
                                    console.error("Too many setInterval animation errors, stopping interval loop.");
                                    clearInterval(this.interval);
                                    this.interval = null;
                                }
                            }
                        }, 32); // ~30fps fallback
                    }
                }
            };
            this.rafErrorCount = 0;
            this.interval = requestAnimationFrame(animate);
        };
    }
    // Listen for resize events and recalculate
    resizeWindow() {
        // If the component is in loading or error state, we don't want to trigger a render of the graph
        if (this.errorState.hasError || this.isLoading)
            return;
        this.rebuildSwimlaneOnResize();
    }
    /**
     * Updates the error state based on current component conditions
     */
    updateErrorState() {
        var _a;
        const previousErrorState = this.errorState.hasError;
        if (this.store === null) {
            this.errorState = getStoreUnavailableError((_a = this.store) === null || _a === void 0 ? void 0 : _a.state.locale);
            return;
        }
        if (this.store.state.error.hasError) {
            this.errorState = {
                hasError: true,
                messageKey: this.store.state.error.messageKey,
                type: this.store.state.error.type,
            };
            this.clearTimer();
            return;
        }
        // No error detected
        this.errorState = {
            hasError: false,
            messageKey: undefined,
            type: "",
        };
        // If we just recovered from an error state, restart the timer
        if (previousErrorState &&
            !this.errorState.hasError &&
            this.interval === null) {
            this.timelineTimer();
        }
    }
    clearTimer() {
        if (this.interval !== null && this.interval !== undefined) {
            if (typeof this.interval === "number") {
                cancelAnimationFrame(this.interval);
            }
            else {
                window.clearInterval(this.interval);
            }
            this.interval = null;
        }
    }
    async componentWillLoad() {
        this.store = await getStore(this.idKey);
        watchStoreLocale(this, this.store, () => {
            if (!this.errorState.hasError && !this.isLoading) {
                this.drawChart();
                this.rebuildChartData();
                this.updateTimeline();
            }
        });
        // Remove back door sensor if installation does not have one
        if (this.store && !this.store.state.hasBackDoor) {
            this.sensorIdsArray = this.sensorIdsArray.filter((value) => {
                return value !== 503;
            });
        }
        this.updateErrorState();
        if (this.store !== null) {
            this.storeUnsubscribes.push(this.store.onChange("error", () => {
                this.updateErrorState();
            }));
        }
    }
    componentDidLoad() {
        if (this.store === null) {
            this.isLoading = false;
            return;
        }
        if (this.errorState.hasError) {
            this.isLoading = false;
            return;
        }
        this.drawChart();
        // Fill chart with historical data
        this.processHistoricalData(this.store.state.historicalSensorData);
        // Initial chart data build
        this.rebuildChartData();
        // Start the timer to update the swim lane at regular intervals
        this.timelineTimer();
        // Subscribe to future sensor value updates
        this.storeUnsubscribes.push(this.store.onChange("currentSensorData", (sensorData) => {
            this.processLiveData(sensorData);
        }));
        // Remove the loading indicator
        this.isLoading = false;
    }
    render() {
        if (this.errorState.hasError) {
            return null;
        }
        return (h(Host, null, h("div", { ref: (el) => {
                this.canvas = el;
            } }, h("div", { class: "chart" })), this.isLoading && (h("div", { class: "loading" }, this.store.t("ui:general.loading"))), h("slot", null)));
    }
    disconnectedCallback() {
        this.clearTimer();
        // Unsubscribe from all store changes
        this.storeUnsubscribes.forEach((unsubscribe) => {
            unsubscribe();
        });
        this.storeUnsubscribes = [];
    }
    static get is() { return "ls-swimlane"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["ls-swimlane.css"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["ls-swimlane.css"]
        };
    }
    static get assetsDirs() { return ["assets"]; }
    static get properties() {
        return {
            "sensorIds": {
                "type": "string",
                "attribute": "sensor-ids",
                "mutable": false,
                "complexType": {
                    "original": "\"500,501,502,503,514,515\"",
                    "resolved": "\"500,501,502,503,514,515\"",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Specifies the IDs of the sensors that are to be displayed."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"500,501,502,503,514,515\""
            },
            "idKey": {
                "type": "string",
                "attribute": "id-key",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The unique key that is used to identify store data."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            }
        };
    }
    static get states() {
        return {
            "sensorIdsArray": {},
            "errorState": {},
            "isLoading": {}
        };
    }
    static get elementRef() { return "element"; }
    static get listeners() {
        return [{
                "name": "resize",
                "method": "resizeWindow",
                "target": "window",
                "capture": false,
                "passive": true
            }];
    }
}
/**
 * Sensor color configuration
 */
LsSwimlane.SENSOR_COLORS = {
    // Normal check sensors (green for 1, gray for 0)
    normal: {
        sensors: ["104", "10", "524"],
        colors: { "1": "#BBF7D0", "0": "#E4E4E7" },
    },
    // Inverted check sensors (red for 1, green for 0)
    inverted: {
        sensors: ["105", "212", "550"],
        colors: { "1": "#FECACA", "0": "#BBF7D0" },
    },
    // String value colors
    strings: {
        "+": "#FEF08A",
        U: "#FEF08A",
        "-": "#E9D5FF",
        D: "#E9D5FF",
        O: "#BBF7D0",
        C: "#FECACA",
        X: "#E4E4E7",
    },
    // Default colors
    numeric: "#C7D2FE",
    sensor514: { even: "#CFFAFE", odd: "#A5F3FC" },
    fallback: "transparent",
};
//# sourceMappingURL=ls-swimlane.js.map
