#!/usr/bin/env seiscomp-python

from getopt import getopt, GetoptError
from time import time, gmtime
from datetime import datetime
import os
import sys
import signal
import glob
import re
import json

from seiscomp.myconfig import MyConfig
import seiscomp.slclient
import seiscomp.kernel, seiscomp.config
from urllib.request import urlopen

# A dictionary to store station coordinates
station_coordinates = {}

def load_station_coordinates(config):
    """Load station coordinates from FDSN web service"""
    global station_coordinates

    # Get base URL from config or use default
    base_url = config['setup'].get('fdsnws_url', 'http://localhost:8080/fdsnws/')

    # Create a dictionary in the format needed by data_fetcher
    stations_config = {}
    for key in config.station:
        network = config.station[key]['net']
        station = config.station[key]['sta']
        station_id = f"{network}.{station}"
        stations_config[station_id] = {
            'network': network,
            'station': station,
            'location': '',  # Default location
            'stream': 'HHZ'  # Default stream
        }

    # Fetch coordinates for each station
    for station_id, station_info in stations_config.items():
        network = station_info['network']
        station = station_info['station']

        try:
            with urlopen(base_url + f"station/1/query?net={network}&sta={station}&format=text") as fp:
                fp.readline()
                location_info = dict(zip(('lat', 'lon', 'elevation'), map(float, fp.readline().split(b'|')[2:5])))

            if location_info:
                station_coordinates[f"{network}_{station}"] = location_info
                print(f"Loaded coordinates for {network}_{station}: {location_info}")
            else:
                print(f"Could not fetch coordinates for {network}_{station}")
        except Exception as e:
            print(f"Error fetching coordinates for {network}_{station}: {str(e)}")

    # Print summary
    print(f"Loaded coordinates for {len(station_coordinates)} stations")



usage_info = """
Usage:
  slmon [options]

Enhanced SeedLink monitor creating modern, interactive web dashboards

Options:
  -h, --help       display this help message
  -c               ini_setup = arg
  -s               ini_stations = arg
  -t               refresh = float(arg) # XXX not yet used
  -v               verbose = 1
  -g, --generate   generate only template files and exit

Examples:
Start slmon from the command line
  slmon -c $SEISCOMP_ROOT/var/lib/slmon/config.ini

Restart slmon in order to update the web pages. Use crontab entries for
automatic restart, e.g.:
  */3 * * * * /home/sysop/seiscomp/bin/seiscomp check slmon >/dev/null 2>&1
"""

def usage(exitcode=0):
    sys.stderr.write(usage_info)
    exit(exitcode)

try:
    seiscompRoot = os.environ["SEISCOMP_ROOT"]
except:
    print("\nSEISCOMP_ROOT must be defined - EXIT\n", file=sys.stderr)
    usage(exitcode=2)

ini_stations = os.path.join(seiscompRoot, 'var/lib/slmon2/stations.ini')
ini_setup = os.path.join(seiscompRoot, 'var/lib/slmon2/config.ini')

regexStreams = re.compile("[SLBVEH][HNLGD][ZNE123ADHF]")
verbose = 0
generate_only = False


class Module(seiscomp.kernel.Module):
    def __init__(self, env):
        seiscomp.kernel.Module.__init__(self, env, env.moduleName(__file__))

    def printCrontab(self):
        print("3 * * * * %s/bin/seiscomp check slmon >/dev/null 2>&1" % (self.env.SEISCOMP_ROOT))


class Status:
    def __repr__(self):
        return "%2s %-5s %2s %3s %1s %s %s" % \
               (self.net, self.sta, self.loc, self.cha, self.typ,
                str(self.last_data), str(self.last_feed))


class StatusDict(dict):
    def __init__(self, source=None):
        if source:
            self.read(source)

    def fromSlinkTool(self, server="", stations=["AU_ARMA", "AU_BLDU", "AU_YAPP"]):
        # later this shall use XML
        cmd = "slinktool -nd 10 -nt 10 -Q %s" % server
        print(cmd)
        f = os.popen(cmd)
        # regex = re.compile("[SLBVEH][HNLG][ZNE123]")
        regex = regexStreams
        for line in f:
            net_sta = line[:2].strip() + "_" + line[3:8].strip()
            if not net_sta in stations:
                continue
            typ = line[16]
            if typ != "D":
                continue
            cha = line[12:15].strip()
            if not regex.match(cha):
                continue

            d = Status()
            d.net = line[0: 2].strip()
            d.sta = line[3: 8].strip()
            d.loc = line[9:11].strip()
            d.cha = line[12:15]
            d.typ = line[16]
            d.last_data = seiscomp.slclient.timeparse(line[47:70])
            d.last_feed = d.last_data
            sec = "%s_%s" % (d.net, d.sta)
            sec = "%s.%s.%s.%s.%c" % (d.net, d.sta, d.loc, d.cha, d.typ)
            self[sec] = d

    def read(self, source):
        """
        Read status data from various source types (file path, file object, or list of lines)
        Python 3 compatible version
        """
        lines = []

        # Handle different source types
        if isinstance(source, str):
            # String - treat as file path
            with open(source, 'r', encoding='utf-8') as f:
                lines = f.readlines()
        elif hasattr(source, 'readlines'):
            # File-like object
            lines = source.readlines()
        elif isinstance(source, list):
            # Already a list of lines
            lines = source
        else:
            raise TypeError(f'Cannot read from {type(source).__name__}')

        # Process each line
        for line in lines:
            line = str(line).rstrip('\n\r')

            # Skip lines that are too short
            if len(line) < 65:
                continue

            # Create status object and parse fields
            d = Status()
            d.net = line[0:2].strip()
            d.sta = line[3:8].strip()
            d.loc = line[9:11].strip()
            d.cha = line[12:15].strip()
            d.typ = line[16]

            # Parse timestamps with error handling
            try:
                d.last_data = seiscomp.slclient.timeparse(line[18:41])
            except:
                d.last_data = None

            try:
                d.last_feed = seiscomp.slclient.timeparse(line[42:65])
            except:
                d.last_feed = None

            # Ensure last_feed is not earlier than last_data
            if d.last_feed and d.last_data and d.last_feed < d.last_data:
                d.last_feed = d.last_data

            # Create dictionary key and store
            sec = f"{d.net}_{d.sta}:{d.loc}.{d.cha}.{d.typ}"
            self[sec] = d

    def write(self, f):
        """
        Write status data to file or file-like object
        Python 3 compatible version
        """
        should_close = False

        if isinstance(f, str):
            # String - treat as file path
            f = open(f, "w", encoding='utf-8')
            should_close = True

        try:
            # Prepare and write sorted lines
            lines = [str(self[key]) for key in sorted(self.keys())]
            f.write('\n'.join(lines) + '\n')
        finally:
            if should_close:
                f.close()

    def to_json(self):
        """Convert status dictionary to JSON for JavaScript use"""
        global station_coordinates
        stations_data = {}

        # Group by network and station
        for key, value in self.items():
            net_sta = f"{value.net}_{value.sta}"
            if net_sta not in stations_data:
                stations_data[net_sta] = {
                    "network": value.net,
                    "station": value.sta,
                    "channels": [],
                    "channelGroups": {
                        "HH": [],  # High-frequency, High-gain channels
                        "BH": [],  # Broadband, High-gain channels
                        "LH": [],  # Long-period, High-gain channels
                        "SH": [],  # Short-period, High-gain channels
                        "EH": [],  # Extremely Short-period, High-gain channels
                        "other": []  # All other channel types
                    }
                }

                # Add coordinates if available
                if net_sta in station_coordinates:
                    stations_data[net_sta]["coordinates"] = station_coordinates[net_sta]

            # Get latency information
            now = datetime.utcnow()
            latency_data = now - value.last_data
            latency_seconds = total_seconds(latency_data)

            # Extract channel type (first two characters, e.g., 'LH', 'BH', 'HH', 'EH')
            channel_type = value.cha[:2] if len(value.cha) >= 2 else "other"

            # Get status with channel-aware thresholds
            status = get_status_from_seconds(latency_seconds, channel_type)

            # Create channel data
            channel_data = {
                "location": value.loc,
                "channel": value.cha,
                "type": value.typ,
                "channelType": channel_type,
                "last_data": value.last_data.isoformat() if value.last_data else None,
                "last_feed": value.last_feed.isoformat() if value.last_feed else None,
                "latency": latency_seconds,
                "status": status
            }

            # Add to main channels list
            stations_data[net_sta]["channels"].append(channel_data)

            # Add to channel group for separated status calculation
            if channel_type in stations_data[net_sta]["channelGroups"]:
                stations_data[net_sta]["channelGroups"][channel_type].append(channel_data)
            else:
                stations_data[net_sta]["channelGroups"]["other"].append(channel_data)

        # Convert to list for easier JavaScript processing
        stations_list = []
        for net_sta, data in stations_data.items():
            # Calculate overall station status based on priority channels (non-LH channels)
            # First try HH channels
            if data["channelGroups"]["HH"]:
                worst_latency = max([ch["latency"] for ch in data["channelGroups"]["HH"]])
                data["status"] = get_status_from_seconds(worst_latency)
                data["primaryChannelType"] = "HH"
            # Then try BH channels
            elif data["channelGroups"]["BH"]:
                worst_latency = max([ch["latency"] for ch in data["channelGroups"]["BH"]])
                data["status"] = get_status_from_seconds(worst_latency)
                data["primaryChannelType"] = "BH"
            # Then try SH channels
            elif data["channelGroups"]["SH"]:
                worst_latency = max([ch["latency"] for ch in data["channelGroups"]["SH"]])
                data["status"] = get_status_from_seconds(worst_latency)
                data["primaryChannelType"] = "SH"
            # Then try EH channels
            elif data["channelGroups"]["EH"]:
                worst_latency = max([ch["latency"] for ch in data["channelGroups"]["EH"]])
                data["status"] = get_status_from_seconds(worst_latency)
                data["primaryChannelType"] = "EH"
            # Only use LH if nothing else is available
            elif data["channelGroups"]["LH"]:
                worst_latency = max([ch["latency"] for ch in data["channelGroups"]["LH"]])
                data["status"] = get_status_from_seconds(worst_latency, "LH")
                data["primaryChannelType"] = "LH"
            # Fall back to other channels
            elif data["channelGroups"]["other"]:
                worst_latency = max([ch["latency"] for ch in data["channelGroups"]["other"]])
                data["status"] = get_status_from_seconds(worst_latency)
                data["primaryChannelType"] = "other"
            else:
                # Failsafe if no channels
                data["status"] = "unavailable"
                data["primaryChannelType"] = "none"
                worst_latency = 0

            data["latency"] = worst_latency
            data["id"] = net_sta
            stations_list.append(data)

        return json.dumps(stations_list)

def get_map_settings(config):
    """Extract map settings from config for JavaScript use"""
    map_settings = {
        'center': {
            'lat': -25.6,  # Default latitude
            'lon': 134.3,  # Default longitude
            'zoom': 6   # Default zoom
        },
        'defaultLayer': 'street',
        'enableClustering': True,
        'showFullscreenControl': True,
        'showLayerControl': True,
        'showLocateControl': True,
        'darkModeLayer': 'dark',
        'lightModeLayer': 'street'
    }

    # Extract center coordinates from config
    if 'center_map' in config['setup']:
        if 'lat' in config['setup']['center_map']:
            map_settings['center']['lat'] = float(config['setup']['center_map']['lat'])
        if 'lon' in config['setup']['center_map']:
            map_settings['center']['lon'] = float(config['setup']['center_map']['lon'])
        if 'zoom' in config['setup']['center_map']:
            map_settings['center']['zoom'] = int(config['setup']['center_map']['zoom'])

    # Extract other map settings
    if 'map_settings' in config['setup']:
        map_config = config['setup']['map_settings']

        if 'default_layer' in map_config:
            map_settings['defaultLayer'] = map_config['default_layer']

        if 'enable_clustering' in map_config:
            map_settings['enableClustering'] = map_config['enable_clustering'] == 'true' or map_config['enable_clustering'] is True

        if 'show_fullscreen_control' in map_config:
            map_settings['showFullscreenControl'] = map_config['show_fullscreen_control'] == 'true' or map_config['show_fullscreen_control'] is True

        if 'show_layer_control' in map_config:
            map_settings['showLayerControl'] = map_config['show_layer_control'] == 'true' or map_config['show_layer_control'] is True

        if 'show_locate_control' in map_config:
            map_settings['showLocateControl'] = map_config['show_locate_control'] == 'true' or map_config['show_locate_control'] is True

        if 'dark_mode_layer' in map_config:
            map_settings['darkModeLayer'] = map_config['dark_mode_layer']

        if 'light_mode_layer' in map_config:
            map_settings['lightModeLayer'] = map_config['light_mode_layer']

    return map_settings

def get_status_from_seconds(seconds, channel_type=None):
    """
    Get status code based on latency in seconds with channel-specific thresholds

    Args:
        seconds (float): Latency in seconds
        channel_type (str): Channel type (e.g., 'LH', 'BH', 'HH', 'EH')

    Returns:
        str: Status code (good, delayed, etc.)
    """
    # Special handling for LH channels - they're naturally delayed
    if channel_type == 'LH':
        # More lenient thresholds for LH channels
        if seconds > 604800:  # > 7 days
            return "unavailable"
        elif seconds > 518400:  # > 6 days
            return "four-day"
        elif seconds > 432000:  # > 5 days
            return "three-day"
        elif seconds > 345600:  # > 4 days
            return "multi-day"
        elif seconds > 259200:  # > 3 days
            return "day-delayed"
        elif seconds > 86400:  # > 1 day
            return "critical"
        elif seconds > 43200:  # > 12 hours
            return "warning"
        elif seconds > 21600:  # > 6 hours
            return "hour-delayed"
        elif seconds > 10800:  # > 3 hours
            return "very-delayed"
        elif seconds > 3600:  # > 1 hour
            return "long-delayed"
        elif seconds > 1800:  # > 30 minutes
            return "delayed"
        else:  # <= 30 minutes (LH channels are considered good even with moderate delay)
            return "good"

    # Standard thresholds for other channels
    if seconds > 432000:  # > 5 days
        return "unavailable"
    elif seconds > 345600:  # > 4 days
        return "four-day"
    elif seconds > 259200:  # > 3 days
        return "three-day"
    elif seconds > 172800:  # > 2 days
        return "multi-day"
    elif seconds > 86400:  # > 1 day
        return "day-delayed"
    elif seconds > 21600:  # > 6 hours
        return "critical"
    elif seconds > 7200:  # > 2 hours
        return "warning"
    elif seconds > 3600:  # > 1 hour
        return "hour-delayed"
    elif seconds > 1800:  # > 30 minutes
        return "very-delayed"
    elif seconds > 600:  # > 10 minutes
        return "long-delayed"
    elif seconds > 60:  # > 1 minute
        return "delayed"
    else:  # <= 1 minute
        return "good"


def getColor(delta):
    delay = total_seconds(delta)
    if delay > 432000: return '#666666'  # > 5 days
    elif delay > 345600: return '#999999'  # > 4 days
    elif delay > 259200: return '#CCCCCC'  # > 3 days
    elif delay > 172800: return '#FFB3B3'  # > 2 days
    elif delay > 86400: return '#FF3333'  # > 1 day
    elif delay > 21600: return '#FF9966'  # > 6 hours
    elif delay > 7200: return '#FFFF00'  # > 2 hours
    elif delay > 3600: return '#00FF00'  # > 1 hour
    elif delay > 1800: return '#3399FF'  # > 30 minutes
    elif delay > 600: return '#9470BB'  # > 10 minutes
    elif delay > 60: return '#EBD6FF'  # > 1 minute
    else: return '#FFFFFF'  # <= 1 minute


def total_seconds(td):
    return td.seconds + (td.days*86400)


def myrename(name1, name2):
    # fault-tolerant rename that doesn't cause an exception if it fails, which
    # may happen e.g. if the target is on a non-reachable NFS directory
    try:
        os.rename(name1, name2)
    except OSError:
        print("failed to rename(%s,%s)" % (name1, name2), file=sys.stderr)


def formatLatency(delta):
    """Format latency for display"""
    if delta is None: return 'n/a'

    t = total_seconds(delta)

    if t > 86400: return f"{t/86400:.1f} d"
    elif t > 7200: return f"{t/3600:.1f} h"
    elif t > 120: return f"{t/60:.1f} m"
    else: return f"{t:.1f} s"


def generate_css_file(config):
    """Generate the CSS file with theme support"""
    css_content = """
:root {
    /* Light theme variables */
    --primary-color: #4f46e5;
    --primary-hover: #4338ca;
    --text-primary: #1f2937;
    --text-secondary: #6b7280;
    --bg-primary: #ffffff;
    --bg-secondary: #f9fafb;
    --bg-tertiary: #f3f4f6;
    --border-color: #e5e7eb;
    --border-radius: 8px;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
    --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
    --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);

    /* Status colors */
    --status-good: #ffffff;
    --status-delayed: #c084fc;
    --status-long-delayed: #8b5cf6;
    --status-very-delayed: #3b82f6;
    --status-hour-delayed: #10b981;
    --status-warning: #fbbf24;
    --status-critical: #f97316;
    --status-day-delayed: #ef4444;
    --status-multi-day: #f87171;
    --status-three-day: #d1d5db;
    --status-four-day: #9ca3af;
    --status-unavailable: #6b7280;
}

.dark-mode {
    /* Dark theme variables */
    --primary-color: #818cf8;
    --primary-hover: #a5b4fc;
    --text-primary: #f9fafb;
    --text-secondary: #9ca3af;
    --bg-primary: #1f2937;
    --bg-secondary: #111827;
    --bg-tertiary: #374151;
    --border-color: #374151;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
    --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.3);
    --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.3);

    /* Dark theme status colors - background stays dark */
    --status-good: #1f2937;
}

/* General Styles */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: var(--text-primary);
    background-color: var(--bg-secondary);
    padding: 0;
    margin: 0;
}

.container {
    max-width: 1400px;
    margin: 20px auto;
    padding: 30px;
    background-color: var(--bg-primary);
    border-radius: var(--border-radius);
    box-shadow: var(--shadow-md);
}

.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--border-color);
    padding-bottom: 15px;
}

h1 {
    font-size: 28px;
    font-weight: 600;
    color: var(--text-primary);
    letter-spacing: -0.5px;
}

.subtitle {
    color: var(--text-secondary);
    font-size: 16px;
    margin-bottom: 20px;
}

/* Navigation Tabs */
.view-toggle {
    display: flex;
    gap: 10px;
}

.view-toggle a {
    padding: 8px 15px;
    border-radius: 6px;
    color: var(--text-secondary);
    text-decoration: none;
    transition: all 0.2s ease;
    font-weight: 500;
    font-size: 14px;
}

.view-toggle a:hover {
    background-color: var(--bg-tertiary);
    color: var(--primary-color);
}

.view-toggle a.active {
    background-color: var(--primary-color);
    color: white;
}

/* Controls */
.controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: 20px 0;
    flex-wrap: wrap;
    gap: 15px;
}

.actions {
    display: flex;
    gap: 10px;
}

.action-button {
    padding: 8px 15px;
    display: flex;
    align-items: center;
    gap: 6px;
    background-color: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    transition: all 0.2s ease;
}

.action-button:hover {
    background-color: var(--bg-primary);
    color: var(--primary-color);
}

.action-button svg {
    width: 16px;
    height: 16px;
}

.refresh-control {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 15px;
    background-color: var(--bg-tertiary);
    border-radius: 6px;
}

.input-group {
    display: flex;
    align-items: center;
    gap: 8px;
}

.refresh-control input {
    width: 60px;
    padding: 6px 10px;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    font-size: 14px;
    background-color: var(--bg-primary);
    color: var(--text-primary);
    text-align: center;
}

.refresh-control button {
    padding: 6px 12px;
    background-color: var(--primary-color);
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-weight: 500;
    transition: background-color 0.2s ease;
}

.refresh-control button:hover {
    background-color: var(--primary-hover);
}

.status-counter {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
}

#refresh-status {
    font-size: 13px;
    color: var(--text-secondary);
}

.countdown {
    font-size: 13px;
    color: var(--text-secondary);
}

#next-refresh {
    color: var(--primary-color);
    font-weight: 500;
}

/* Filter and Search */
.filters {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 20px;
    padding: 15px;
    background-color: var(--bg-tertiary);
    border-radius: var(--border-radius);
}

.filter-group {
    display: flex;
    align-items: center;
    gap: 8px;
}

.filter-group label {
    font-size: 14px;
    color: var(--text-secondary);
    font-weight: 500;
}

.filter-group select {
    padding: 6px 10px;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background-color: var(--bg-primary);
    color: var(--text-primary);
    font-size: 14px;
}

.search-box {
    padding: 6px 12px;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background-color: var(--bg-primary);
    color: var(--text-primary);
    font-size: 14px;
    min-width: 200px;
}

/* Table View */
.table-container {
    overflow-x: auto;
    margin-bottom: 20px;
}

table {
    width: 100%;
    border-collapse: collapse;
}

table th {
    padding: 12px 15px;
    background-color: var(--bg-tertiary);
    color: var(--text-secondary);
    font-weight: 600;
    text-align: left;
    border-bottom: 1px solid var(--border-color);
    position: sticky;
    top: 0;
    z-index: 10;
}

table td {
    padding: 10px 15px;
    border-bottom: 1px solid var(--border-color);
}

table tr:hover {
    background-color: var(--bg-tertiary);
}

/* Grid View */
.grid-container {
    display: table;
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
}

.grid-row {
    display: table-row;
}

.network-label {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
    font-weight: 600;
    width: 60px;
    min-width: 60px;
    height: 34px;
    background-color: var(--bg-tertiary);
    border-radius: 6px;
    color: var(--text-secondary);
    box-shadow: var(--shadow-sm);
    padding: 4px;
    margin: 2px;
    border: 1px solid var(--border-color);
}

.stations-container {
    display: table-cell;
    padding-left: 6px;
}

.stations-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 2px 0;
}

.grid-cell {
    width: 60px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 500;
    box-shadow: var(--shadow-sm);
    text-decoration: none;
    color: var(--text-primary);
    transition: all 0.15s ease;
    position: relative;
    border: 1px solid var(--border-color);
    background-color: var(--status-good);
}

.grid-cell:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md);
    z-index: 10;
}

/* Map View */
.map-container {
    width: 100%;
    height: 600px;
    background-color: var(--bg-tertiary);
    border-radius: var(--border-radius);
    margin-bottom: 20px;
    position: relative;
}

/* Status Colors */
.station-unavailable {
    background-color: var(--status-unavailable);
    color: white;
    border-color: var(--status-unavailable);
}

.station-warning {
    background-color: var(--status-warning);
    color: #7c2d12;
    border-color: var(--status-warning);
}

.station-critical {
    background-color: var(--status-critical);
    color: white;
    border-color: var(--status-critical);
}

.station-delayed {
    background-color: var(--status-delayed);
    color: #4a044e;
    border-color: var(--status-delayed);
}

.station-long-delayed {
    background-color: var(--status-long-delayed);
    color: white;
    border-color: var(--status-long-delayed);
}

.station-very-delayed {
    background-color: var(--status-very-delayed);
    color: white;
    border-color: var(--status-very-delayed);
}

.station-hour-delayed {
    background-color: var(--status-hour-delayed);
    color: white;
    border-color: var(--status-hour-delayed);
}

.station-day-delayed {
    background-color: var(--status-day-delayed);
    color: white;
    border-color: var(--status-day-delayed);
}

.station-multi-day {
    background-color: var(--status-multi-day);
    color: #7f1d1d;
    border-color: var(--status-multi-day);
}

.station-three-day {
    background-color: var(--status-three-day);
    color: #1f2937;
    border-color: var(--status-three-day);
}

.station-four-day {
    background-color: var(--status-four-day);
    color: white;
    border-color: var(--status-four-day);
}

.station-good {
    background-color: var(--status-good);
    color: var(--text-primary);
    border-color: var(--border-color);
}

/* Tooltip */
.grid-cell::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 120%;
    left: 50%;
    transform: translateX(-50%);
    background-color: #1f2937;
    color: white;
    text-align: center;
    padding: 8px 12px;
    border-radius: 6px;
    font-size: 12px;
    white-space: nowrap;
    z-index: 20;
    opacity: 0;
    visibility: hidden;
    transition: all 0.2s ease;
    pointer-events: none;
    box-shadow: var(--shadow-md);
}

.grid-cell::before {
    content: '';
    position: absolute;
    top: -6px;
    left: 50%;
    transform: translateX(-50%);
    border-width: 6px 6px 0;
    border-style: solid;
    border-color: #1f2937 transparent transparent;
    z-index: 20;
    opacity: 0;
    visibility: hidden;
    transition: all 0.2s ease;
    pointer-events: none;
}

.grid-cell:hover::after,
.grid-cell:hover::before {
    opacity: 1;
    visibility: visible;
}

/* Stats */
.stats-container {
    margin: 20px 0;
    padding: 20px;
    background-color: var(--bg-tertiary);
    border-radius: var(--border-radius);
    display: none;
}

.stats-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

.stats-title {
    font-weight: 600;
    font-size: 16px;
    color: var(--text-primary);
}

.network-stats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 15px;
}

.network-stat {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.network-name {
    font-weight: 600;
    font-size: 14px;
    color: var(--text-primary);
    display: flex;
    justify-content: space-between;
}

.progress-bar {
    height: 8px;
    background-color: var(--border-color);
    border-radius: 4px;
    overflow: hidden;
}

.progress {
    height: 100%;
    background-color: var(--primary-color);
    border-radius: 4px;
}

/* Legend */
.legend {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 25px 0;
    padding: 15px;
    background-color: var(--bg-tertiary);
    border-radius: var(--border-radius);
    justify-content: center;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 13px;
    color: var(--text-secondary);
}

.legend-color {
    width: 16px;
    height: 16px;
    border-radius: 4px;
    border: 1px solid rgba(0, 0, 0, 0.1);
}

/* Footer */
.footer {
    margin-top: 30px;
    padding-top: 15px;
    border-top: 1px solid var(--border-color);
    display: flex;
    justify-content: space-between;
    color: var(--text-secondary);
    font-size: 14px;
}

.footer a {
    color: var(--primary-color);
    text-decoration: none;
}

.footer a:hover {
    text-decoration: underline;
}

/* Loading */
#loading {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 30px 0;
    color: var(--text-secondary);
}

.loading-spinner {
    width: 24px;
    height: 24px;
    border: 3px solid var(--bg-tertiary);
    border-top: 3px solid var(--primary-color);
    border-radius: 50%;
    margin-right: 12px;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* Error Message */
#error-message {
    padding: 15px;
    margin: 20px 0;
    border-radius: var(--border-radius);
    background-color: #fee2e2;
    color: #b91c1c;
    border-left: 4px solid #ef4444;
    display: none;
}

/* Responsive Design */
@media (max-width: 768px) {
    .container {
        margin: 10px;
        padding: 15px;
        border-radius: 6px;
    }

    .header {
        flex-direction: column;
        align-items: flex-start;
        gap: 10px;
    }

    .view-toggle {
        align-self: flex-end;
    }

    .controls {
        flex-direction: column;
        align-items: stretch;
    }

    .actions {
        justify-content: space-between;
    }

    .refresh-control {
        flex-direction: column;
        align-items: flex-start;
        gap: 10px;
    }

    .filters {
        flex-direction: column;
        gap: 10px;
    }

    .filter-group {
        width: 100%;
    }

    .search-box {
        width: 100%;
    }

    .network-stats {
        grid-template-columns: 1fr;
    }

    .map-container {
            height: 400px;
        }
    }
    /* Marker Cluster Styles */
    .marker-cluster {
        background-clip: padding-box;
        border-radius: 20px;
    }

    .marker-cluster div {
        width: 36px;
        height: 36px;
        margin-left: 2px;
        margin-top: 2px;
        text-align: center;
        border-radius: 18px;
        font-size: 12px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    /* Map Controls */
    .leaflet-control-locate {
        border: 2px solid rgba(0,0,0,0.2);
        background-clip: padding-box;
    }

    .leaflet-control-locate a {
        background-color: var(--bg-primary);
        background-position: 50% 50%;
        background-repeat: no-repeat;
        display: block;
        width: 30px;
        height: 30px;
        line-height: 30px;
        color: var(--text-primary);
        text-align: center;
    }

    .leaflet-control-locate a:hover {
        background-color: var(--bg-tertiary);
        color: var(--primary-color);
    }

    .leaflet-control-locate.active a {
        color: var(--primary-color);
    }

    .leaflet-control-fullscreen {
        border: 2px solid rgba(0,0,0,0.2);
        background-clip: padding-box;
    }

    .leaflet-control-fullscreen a {
        background-color: var(--bg-primary);
        background-position: 50% 50%;
        background-repeat: no-repeat;
        display: block;
        width: 30px;
        height: 30px;
        line-height: 30px;
        color: var(--text-primary);
        text-align: center;
    }

    .leaflet-control-fullscreen a:hover {
        background-color: var(--bg-tertiary);
        color: var(--primary-color);
    }

    /* Map layers control */
    .leaflet-control-layers {
        border-radius: var(--border-radius);
        background-color: var(--bg-primary);
        color: var(--text-primary);
        border: 1px solid var(--border-color);
        box-shadow: var(--shadow-sm);
    }

    .dark-mode .leaflet-control-layers {
        background-color: var(--bg-tertiary);
    }

    .leaflet-control-layers-toggle {
        width: 36px;
        height: 36px;
        background-size: 20px 20px;
    }

    .leaflet-control-layers-expanded {
        padding: 10px;
        background-color: var(--bg-primary);
        color: var(--text-primary);
        border-radius: var(--border-radius);
    }

    .dark-mode .leaflet-control-layers-expanded {
        background-color: var(--bg-tertiary);
    }

    .leaflet-control-layers-list {
        margin-top: 8px;
    }

    .leaflet-control-layers label {
        margin-bottom: 5px;
        display: block;
    }

    /* Map layer selection buttons */
    .map-layers-control {
        position: absolute;
        top: 10px;
        right: 10px;
        z-index: 1000;
        background: white;
        padding: 5px;
        border-radius: 4px;
        box-shadow: 0 1px 5px rgba(0,0,0,0.65);
    }

    .map-layers-control button {
        display: block;
        margin: 5px 0;
        padding: 5px;
        width: 100%;
        border: none;
        background: #f8f8f8;
        cursor: pointer;
    }

    .map-layers-control button:hover {
        background: #f0f0f0;
    }

    .map-layers-control button.active {
        background: #ddd;
        font-weight: bold;
    }

    /* Map tools control */
    .map-tools-control {
        position: absolute;
        bottom: 30px;
        right: 10px;
        z-index: 1000;
        display: flex;
        flex-direction: column;
        gap: 5px;
    }

    .map-tools-control button {
        width: 34px;
        height: 34px;
        background: white;
        border: 2px solid rgba(0,0,0,0.2);
        border-radius: 4px;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        color: #333;
    }

    .map-tools-control button:hover {
        background: #f4f4f4;
    }

    .dark-mode .map-tools-control button {
        background: #333;
        color: #fff;
        border-color: rgba(255,255,255,0.2);
    }

    .dark-mode .map-tools-control button:hover {
        background: #444;
    }

    /* Map measurement widget */
    .leaflet-measure-path-measurement {
        position: absolute;
        font-size: 12px;
        color: black;
        text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;
        white-space: nowrap;
        transform-origin: 0;
        pointer-events: none;
    }

    .dark-mode .leaflet-measure-path-measurement {
        color: white;
        text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
    }

    /* Popup styling */
    .leaflet-popup-content-wrapper {
        border-radius: var(--border-radius);
        background-color: var(--bg-primary);
        color: var(--text-primary);
        box-shadow: var(--shadow-md);
    }

    .dark-mode .leaflet-popup-content-wrapper {
        background-color: var(--bg-tertiary);
    }

    .leaflet-popup-content {
        margin: 12px;
        line-height: 1.5;
    }

    .leaflet-popup-tip {
        background-color: var(--bg-primary);
    }

    .dark-mode .leaflet-popup-tip {
        background-color: var(--bg-tertiary);
    }

    .leaflet-popup-content a {
        color: var(--primary-color);
        text-decoration: none;
    }

    .leaflet-popup-content a:hover {
        text-decoration: underline;
    }

    /* Make the map more responsive on mobile */
    @media (max-width: 768px) {
        .map-container {
            height: 450px;
        }

        .leaflet-control-layers,
        .leaflet-control-zoom,
        .leaflet-control-fullscreen,
        .leaflet-control-locate {
            margin-right: 10px !important;
        }

        .leaflet-control-scale {
            margin-bottom: 40px !important;
        }
    }
    """

    try:
        css_path = os.path.join(config['setup']['wwwdir'], 'styles.css')
        with open(css_path, 'w') as f:
            f.write(css_content)
        print(f"CSS file generated at {css_path}")
        return css_path
    except Exception as e:
        print(f"Error generating CSS file: {str(e)}")
        return None


def generate_js_file(config):
    """Generate the JavaScript file with interactive features"""
    js_content = """
// Global variables
let refreshTimer = null;
let currentRefreshInterval = 60;
let lastRefreshTime = 0;
let isRefreshing = false;
let stationsData = [];
let viewMode = 'table'; // 'table', 'grid', or 'map'
let mapInitialized = false;
let map = null;
let markers = [];

// Function to initialize the application
document.addEventListener('DOMContentLoaded', function() {
    // Load saved preferences
    loadPreferences();

    // Set up event listeners
    setupEventListeners();

    //Add channel type filter
    setupFilters();

    // Set active view based on URL or default
    setActiveView();

    // Initial data load
    fetchData();
});

// Function to load user preferences from localStorage
function loadPreferences() {
    // Load refresh interval
    const savedInterval = parseInt(localStorage.getItem('seedlinkRefreshInterval'));
    if (savedInterval && savedInterval >= 10) {
        document.getElementById('refresh-interval').value = savedInterval;
        currentRefreshInterval = savedInterval;
    }

    // Load dark mode preference
    const darkModeEnabled = localStorage.getItem('seedlink-dark-mode') === 'true';
    if (darkModeEnabled) {
        document.body.classList.add('dark-mode');
        updateThemeToggleButton(true);
    }

    // Load view mode
    const savedViewMode = localStorage.getItem('seedlink-view-mode');
    if (savedViewMode) {
        viewMode = savedViewMode;
    }
}

// Function to set up all event listeners
function setupEventListeners() {
    // View toggle buttons
    document.querySelectorAll('.view-toggle a').forEach(link => {
        link.addEventListener('click', function(e) {
            e.preventDefault();
            const view = this.getAttribute('data-view');
            switchView(view);
        });
    });

    // Refresh controls
    document.getElementById('apply-refresh').addEventListener('click', function() {
        const interval = parseInt(document.getElementById('refresh-interval').value);
        if (interval && interval >= 10) {
            updateRefreshInterval(interval);
        }
    });

    document.getElementById('refresh-now').addEventListener('click', function() {
        if (refreshTimer) {
            clearTimeout(refreshTimer);
        }
        fetchData();
    });

    // Theme toggle
    document.getElementById('theme-toggle').addEventListener('click', toggleDarkMode);

    // Export CSV
    document.getElementById('export-csv').addEventListener('click', exportToCsv);

    // Stats toggle
    document.getElementById('stats-toggle').addEventListener('click', toggleStats);
    document.getElementById('close-stats').addEventListener('click', function() {
        document.getElementById('stats-container').style.display = 'none';
    });

    // Filter inputs
    document.getElementById('network-filter').addEventListener('change', applyFilters);
    document.getElementById('status-filter').addEventListener('change', applyFilters);
    document.getElementById('search-input').addEventListener('input', debounce(applyFilters, 300));

    // Sort headers in table view
    document.querySelectorAll('th[data-sort]').forEach(header => {
        header.addEventListener('click', function() {
            sortTable(this.getAttribute('data-sort'));
        });
    });

    // Handle visibility changes (tab switching)
    document.addEventListener('visibilitychange', function() {
        if (document.visibilityState === 'visible') {
            // If data is stale (not refreshed in over half the interval)
            const timeSinceLastRefresh = Date.now() - lastRefreshTime;
            if (timeSinceLastRefresh > (currentRefreshInterval * 500)) {
                if (refreshTimer) {
                    clearTimeout(refreshTimer);
                }
                fetchData();
            }
        }
    });
}

// Function to set active view based on URL or saved preference
function setActiveView() {
    // Extract view from URL if present
    const urlParams = new URLSearchParams(window.location.search);
    const urlView = urlParams.get('view');

    if (urlView && ['table', 'grid', 'map'].includes(urlView)) {
        viewMode = urlView;
    }

    // Set active class on the appropriate link
    document.querySelectorAll('.view-toggle a').forEach(link => {
        if (link.getAttribute('data-view') === viewMode) {
            link.classList.add('active');
        } else {
            link.classList.remove('active');
        }
    });

    // Show the appropriate view container
    document.querySelectorAll('.view-container').forEach(container => {
        if (container.id === `${viewMode}-view`) {
            container.style.display = 'block';
        } else {
            container.style.display = 'none';
        }
    });

    // Initialize map if needed
    if (viewMode === 'map' && !mapInitialized && typeof L !== 'undefined') {
        initializeMap();
    }

    // Save preference
    localStorage.setItem('seedlink-view-mode', viewMode);
}

// Function to switch between views
function switchView(view) {
    viewMode = view;

    // Update URL without reloading the page
    const url = new URL(window.location);
    url.searchParams.set('view', view);
    window.history.pushState({}, '', url);

    setActiveView();

    // Refresh data display for the new view
    renderData();
}

// Function to toggle dark mode
function toggleDarkMode() {
    document.body.classList.toggle('dark-mode');
    const isDarkMode = document.body.classList.contains('dark-mode');
    localStorage.setItem('seedlink-dark-mode', isDarkMode ? 'true' : 'false');

    updateThemeToggleButton(isDarkMode);

    // Update map tiles if map is initialized
    if (mapInitialized && map) {
        updateMapTiles(isDarkMode);
    }
}

// Function to update theme toggle button appearance
function updateThemeToggleButton(isDarkMode) {
    const themeToggle = document.getElementById('theme-toggle');
    if (isDarkMode) {
        themeToggle.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <circle cx="12" cy="12" r="5"></circle>
                <line x1="12" y1="1" x2="12" y2="3"></line>
                <line x1="12" y1="21" x2="12" y2="23"></line>
                <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                <line x1="1" y1="12" x2="3" y2="12"></line>
                <line x1="21" y1="12" x2="23" y2="12"></line>
                <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
            </svg>
            Light Mode
        `;
    } else {
        themeToggle.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
            </svg>
            Dark Mode
        `;
    }
}

// Function to initialize the map view
// 1. Enhanced map initialization function
// Updated initializeMap function with markerCluster safety checks
function initializeMap() {
    // Check if Leaflet is available
    if (typeof L === 'undefined') {
        console.error('Leaflet library not loaded');
        document.getElementById('map-container').innerHTML = '<div class="error-message">Map library not available. Please check your internet connection.</div>';
        return;
    }

    // Initialize markerCluster as null so it's defined even if the plugin isn't available
    markerCluster = null;

    // Read map settings from the page data if available
    const mapSettings = window.mapSettings || {
        center: { lat: 20, lon: 0, zoom: 2 },
        defaultLayer: 'street',
        enableClustering: true,
        showFullscreenControl: true,
        showLayerControl: true,
        showLocateControl: true
    };

    // Create map instance
    map = L.map('map-container', {
        center: [mapSettings.center.lat, mapSettings.center.lon],
        zoom: mapSettings.center.zoom,
        zoomControl: false // We'll add this separately for better positioning
    });

    // Define available base layers
    const baseLayers = {
        'Street': L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
            maxZoom: 19
        }),
        'Satellite': L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
            attribution: 'Imagery &copy; Esri &copy; ArcGIS',
            maxZoom: 19
        }),
        'Terrain': L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
            attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a>',
            maxZoom: 17
        }),
        'Dark': L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
            subdomains: 'abcd',
            maxZoom: 19
        })
    };

    // Add appropriate layer based on settings or dark mode
    const isDarkMode = document.body.classList.contains('dark-mode');
    let defaultLayer = isDarkMode ? 'Dark' : (mapSettings.defaultLayer || 'Street');
    defaultLayer = defaultLayer.charAt(0).toUpperCase() + defaultLayer.slice(1); // Capitalize

    // Add the default layer to the map
    if (baseLayers[defaultLayer]) {
        baseLayers[defaultLayer].addTo(map);
    } else {
        // Fallback to the first available layer
        baseLayers[Object.keys(baseLayers)[0]].addTo(map);
    }

    // Add layer control if enabled
    if (mapSettings.showLayerControl !== false) {
        L.control.layers(baseLayers, {}, {
            position: 'topright',
            collapsed: true
        }).addTo(map);
    }

    // Add zoom control in a better position
    L.control.zoom({
        position: 'bottomright'
    }).addTo(map);

    // Add scale control
    L.control.scale().addTo(map);

    // Add fullscreen control if enabled and the plugin is available
    if (mapSettings.showFullscreenControl !== false && typeof L.Control.Fullscreen !== 'undefined') {
        L.control.fullscreen({
            position: 'topright',
            title: {
                'false': 'View Fullscreen',
                'true': 'Exit Fullscreen'
            }
        }).addTo(map);
    }

    // Add locate control if enabled and the plugin is available
    if (mapSettings.showLocateControl !== false && typeof L.Control.Locate !== 'undefined') {
        L.control.locate({
            position: 'bottomright',
            icon: 'fa fa-location-arrow',
            strings: {
                title: 'Show my location'
            },
            locateOptions: {
                enableHighAccuracy: true,
                maxZoom: 10
            }
        }).addTo(map);
    }

    // Initialize marker cluster group if enabled and the plugin is available
    if (mapSettings.enableClustering !== false && typeof L.MarkerClusterGroup !== 'undefined') {
        try {
            markerCluster = L.markerClusterGroup({
                disableClusteringAtZoom: 10,
                spiderfyOnMaxZoom: true,
                showCoverageOnHover: false,
                iconCreateFunction: function(cluster) {
                    const count = cluster.getChildCount();

                    // Determine color based on worst status in the cluster
                    let worstStatus = 'good';
                    const markers = cluster.getAllChildMarkers();

                    for (const marker of markers) {
                        const status = marker.options.status || 'good';

                        // Simple ordering of statuses from least to most severe
                        const statusOrder = {
                            'good': 0,
                            'delayed': 1,
                            'long-delayed': 2,
                            'very-delayed': 3,
                            'hour-delayed': 4,
                            'warning': 5,
                            'critical': 6,
                            'day-delayed': 7,
                            'multi-day': 8,
                            'three-day': 9,
                            'four-day': 10,
                            'unavailable': 11
                        };

                        if ((statusOrder[status] || 0) > (statusOrder[worstStatus] || 0)) {
                            worstStatus = status;
                        }
                    }

                    // Get color for worst status
                    const color = getStatusColor(worstStatus);

                    const textColor = getBestTextColor(color);

                    return L.divIcon({
                        html: `<div style="background-color: ${color}; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; border-radius: 50%; border: 2px solid white; color: ${textColor}; font-weight: bold;">${count}</div>`,
                        className: 'marker-cluster',
                        iconSize: new L.Point(40, 40)
                    });
                }
            });

            map.addLayer(markerCluster);
            console.log("Marker clustering initialized successfully");
        } catch (e) {
            console.error("Error initializing marker clustering:", e);
            markerCluster = null; // Reset to null if initialization failed
        }
    } else {
        console.log("Marker clustering is disabled or not available");
    }

    // Mark as initialized
    mapInitialized = true;

    // Update markers if we already have data
    if (stationsData.length > 0) {
        updateMapMarkers();
    }
}

// Helper function to determine best text color (black or white) based on background color
function getBestTextColor(bgColor) {
    // Handle named colors
    if (bgColor.toLowerCase() === '#ffffff') return '#000000';
    if (bgColor.toLowerCase() === '#000000') return '#ffffff';

    // Convert hex to rgb
    let hex = bgColor.replace('#', '');
    let r, g, b;

    if (hex.length === 3) {
        r = parseInt(hex.charAt(0) + hex.charAt(0), 16);
        g = parseInt(hex.charAt(1) + hex.charAt(1), 16);
        b = parseInt(hex.charAt(2) + hex.charAt(2), 16);
    } else {
        r = parseInt(hex.substring(0, 2), 16);
        g = parseInt(hex.substring(2, 4), 16);
        b = parseInt(hex.substring(4, 6), 16);
    }

    // Calculate luminance
    const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;

    // Return white for dark backgrounds, black for light backgrounds
    return luminance > 0.5 ? '#000000' : '#ffffff';
}

function updateMapTiles(isDarkMode) {
    if (!map) return;

    // Get available layers from map's layer control
    const baseLayers = {};
    map.eachLayer(layer => {
        if (layer instanceof L.TileLayer) {
            map.removeLayer(layer);
        }
    });

    // Add the default layer based on theme
    if (isDarkMode) {
        if (window.mapSettings && window.mapSettings.darkModeLayer) {
            // Use configured dark mode layer
            const darkLayer = window.mapSettings.darkModeLayer.toLowerCase();
            if (darkLayer === 'satellite') {
                L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
                    attribution: 'Imagery &copy; Esri &copy; ArcGIS',
                    maxZoom: 19
                }).addTo(map);
            } else {
                L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
                    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
                    subdomains: 'abcd',
                    maxZoom: 19
                }).addTo(map);
            }
        } else {
            // Default dark theme
            L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
                attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
                subdomains: 'abcd',
                maxZoom: 19
            }).addTo(map);
        }
    } else {
        if (window.mapSettings && window.mapSettings.lightModeLayer) {
            // Use configured light mode layer
            const lightLayer = window.mapSettings.lightModeLayer.toLowerCase();
            if (lightLayer === 'satellite') {
                L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
                    attribution: 'Imagery &copy; Esri &copy; ArcGIS',
                    maxZoom: 19
                }).addTo(map);
            } else if (lightLayer === 'terrain') {
                L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
                    attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a>',
                    maxZoom: 17
                }).addTo(map);
            } else {
                L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
                    maxZoom: 19
                }).addTo(map);
            }
        } else {
            // Default light theme
            L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
                maxZoom: 19
            }).addTo(map);
        }
    }
}

function updateMapMarkers() {
    if (!mapInitialized || !map) return;

    // Clear existing markers
    if (markerCluster) {
        try {
            markerCluster.clearLayers();
        } catch (e) {
            console.error("Error clearing marker cluster:", e);
            // Fall back to standard markers if cluster fails
            markerCluster = null;
            markers.forEach(marker => {
                try { map.removeLayer(marker); } catch(e) {}
            });
        }
    } else {
        markers.forEach(marker => {
            try { map.removeLayer(marker); } catch(e) {}
        });
    }
    markers = [];

    // Variables to track bounds for auto-zooming
    let validCoordinates = false;
    const bounds = L.latLngBounds();

    // Add markers for each station
    stationsData.forEach(station => {
        // Skip stations without coordinates
        if (!station.coordinates || !station.coordinates.lat || !station.coordinates.lon) {
            console.log(`Station ${station.network}_${station.station} has no coordinates`);
            return;
        }

        validCoordinates = true;
        const lat = station.coordinates.lat;
        const lon = station.coordinates.lon;

        // Add to bounds for auto-zooming
        bounds.extend([lat, lon]);

        // Create marker with appropriate color based on status
        const markerColor = getStatusColor(station.status);

        // Create marker with a badge if it's using LH channels
        const isLH = station.primaryChannelType === 'LH';
        const markerIcon = L.divIcon({
            html: isLH
                ? `<div style="background-color: ${markerColor}; width: 14px; height: 14px; border-radius: 50%; border: 2px solid white; position: relative;">
                      <span style="position: absolute; top: -8px; right: -8px; background: #f3f4f6; border-radius: 50%; width: 10px; height: 10px; font-size: 7px; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #1f2937;">L</span>
                   </div>`
                : `<div style="background-color: ${markerColor}; width: 14px; height: 14px; border-radius: 50%; border: 2px solid white;"></div>`,
            className: 'station-marker',
            iconSize: [18, 18],
            iconAnchor: [9, 9]
        });

        const marker = L.marker([lat, lon], {
            icon: markerIcon,
            title: `${station.network}_${station.station}`,
            status: station.status // Store status for cluster coloring
        });

        // Create channel group summary for popup
        const channelGroupsText = station.channels.reduce((groups, channel) => {
            const type = channel.channelType || 'other';
            if (!groups[type]) groups[type] = 0;
            groups[type]++;
            return groups;
        }, {});

        const channelGroupsHTML = Object.entries(channelGroupsText)
            .map(([type, count]) => `${type}: ${count}`)
            .join(', ');

        // Add popup with station info
        marker.bindPopup(`
            <strong>${station.network}_${station.station}</strong><br>
            Primary channel type: <b>${station.primaryChannelType || 'N/A'}</b><br>
            Status: ${formatStatus(station.status, station.primaryChannelType)}<br>
            Latency: ${formatLatency(station.latency)}<br>
            Channels: ${channelGroupsHTML}<br>
            Coordinates: ${lat.toFixed(4)}, ${lon.toFixed(4)}
            ${station.coordinates.elevation ? '<br>Elevation: ' + station.coordinates.elevation.toFixed(1) + ' m' : ''}
            <br><a href="${station.network}_${station.station}.html" target="_blank">View Details</a>
        `);

        // Add to the cluster group or directly to the map
        try {
            if (markerCluster) {
                markerCluster.addLayer(marker);
            } else {
                marker.addTo(map);
            }
            markers.push(marker);
        } catch (e) {
            console.error("Error adding marker:", e);
            // If cluster fails, try adding directly to map
            try {
                marker.addTo(map);
                markers.push(marker);
            } catch (e2) {
                console.error("Also failed to add directly to map:", e2);
            }
        }
    });

    // Auto-zoom to fit all markers if we have valid coordinates
    if (validCoordinates && markers.length > 0) {
        // Don't zoom too close if there's only one station
        if (markers.length === 1) {
            map.setView(bounds.getCenter(), 8);
        } else {
            try {
                map.fitBounds(bounds, {
                    padding: [30, 30],
                    maxZoom: 12
                });
            } catch (e) {
                console.error("Error fitting bounds:", e);
                // Fallback to a default view
                map.setView([20, 0], 2);
            }
        }
    } else if (!validCoordinates && markers.length === 0) {
        // Show message if no stations have coordinates
        const noCoordinatesMsg = document.createElement('div');
        noCoordinatesMsg.className = 'error-message';
        noCoordinatesMsg.style.position = 'absolute';
        noCoordinatesMsg.style.top = '50%';
        noCoordinatesMsg.style.left = '50%';
        noCoordinatesMsg.style.transform = 'translate(-50%, -50%)';
        noCoordinatesMsg.style.background = 'rgba(255, 255, 255, 0.9)';
        noCoordinatesMsg.style.padding = '15px';
        noCoordinatesMsg.style.borderRadius = '8px';
        noCoordinatesMsg.style.zIndex = 1000;
        noCoordinatesMsg.innerHTML = `
            <p><strong>No station coordinates available</strong></p>
            <p>Make sure your FDSNWS service is properly configured and accessible.</p>
        `;
        document.getElementById('map-container').appendChild(noCoordinatesMsg);
    }
}

// Custom legend for the map
function addMapLegend() {
    if (!mapInitialized || !map) return;

    // Remove existing legend if any
    const existingLegend = document.querySelector('.map-legend');
    if (existingLegend) {
        existingLegend.remove();
    }

    // Create a custom legend
    const legend = L.control({position: 'bottomright'});

    legend.onAdd = function() {
        const div = L.DomUtil.create('div', 'map-legend');
        div.innerHTML = `
            <h4>Station Status</h4>
            <div><span style="background-color: #FFFFFF"></span> Good (&le; 1 min)</div>
            <div><span style="background-color: #c084fc"></span> &gt; 1 min</div>
            <div><span style="background-color: #8b5cf6"></span> &gt; 10 min</div>
            <div><span style="background-color: #3b82f6"></span> &gt; 30 min</div>
            <div><span style="background-color: #10b981"></span> &gt; 1 hour</div>
            <div><span style="background-color: #fbbf24"></span> &gt; 2 hours</div>
            <div><span style="background-color: #f97316"></span> &gt; 6 hours</div>
            <div><span style="background-color: #ef4444"></span> &gt; 1 day</div>
        `;

        // Add custom styles to the legend
        const style = document.createElement('style');
        style.textContent = `
            .map-legend {
                padding: 10px;
                background: white;
                background: rgba(255, 255, 255, 0.9);
                border-radius: 5px;
                line-height: 1.8;
                color: #333;
                box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
            }
            .dark-mode .map-legend {
                background: rgba(31, 41, 55, 0.9);
                color: #f9fafb;
            }
            .map-legend h4 {
                margin: 0 0 5px;
                font-size: 14px;
                font-weight: 600;
            }
            .map-legend div {
                display: flex;
                align-items: center;
                font-size: 12px;
                margin-bottom: 3px;
            }
            .map-legend span {
                display: inline-block;
                width: 16px;
                height: 16px;
                margin-right: 8px;
                border-radius: 50%;
                border: 1px solid rgba(0, 0, 0, 0.2);
            }
            .dark-mode .map-legend span {
                border-color: rgba(255, 255, 255, 0.2);
            }
        `;

        div.appendChild(style);
        return div;
    };

    legend.addTo(map);
}
function setupFilters() {
    // Add a channel type filter to the filters area
    const filtersArea = document.querySelector('.filters');

    if (!filtersArea) return;

    // Check if the filter already exists
    if (!document.getElementById('channel-filter')) {
        const channelFilterGroup = document.createElement('div');
        channelFilterGroup.className = 'filter-group';
        channelFilterGroup.innerHTML = `
            <label for="channel-filter">Channel Type:</label>
            <select id="channel-filter">
                <option value="">All Types</option>
                <option value="HH">HH (High Frequency)</option>
                <option value="BH">BH (Broadband)</option>
                <option value="LH">LH (Long Period)</option>
                <option value="SH">SH (Short Period)</option>
                <option value="EH">EH (Extremely Short Period)</option>
                <option value="other">Other</option>
            </select>
        `;

        filtersArea.appendChild(channelFilterGroup);

        // Add event listener
        document.getElementById('channel-filter').addEventListener('change', applyFilters);
    }
}

// Enhanced station filters for the map
function setupMapFilters() {
    if (!mapInitialized || !map) return;

    const mapFilters = L.control({position: 'topleft'});

    mapFilters.onAdd = function() {
        const div = L.DomUtil.create('div', 'map-filters');
        div.innerHTML = `
            <div class="filter-select">
                <label for="map-network-filter">Network:</label>
                <select id="map-network-filter">
                    <option value="">All Networks</option>
                </select>
            </div>
            <div class="filter-select">
                <label for="map-status-filter">Status:</label>
                <select id="map-status-filter">
                    <option value="">All Statuses</option>
                    <option value="good">Good</option>
                    <option value="warning">Warning</option>
                    <option value="critical">Critical</option>
                    <option value="unavailable">Unavailable</option>
                </select>
            </div>
        `;

        // Add styles
        const style = document.createElement('style');
        style.textContent = `
            .map-filters {
                padding: 10px;
                background: rgba(255, 255, 255, 0.9);
                border-radius: 5px;
                box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
                width: 200px;
            }
            .dark-mode .map-filters {
                background: rgba(31, 41, 55, 0.9);
                color: #f9fafb;
            }
            .map-filters .filter-select {
                margin-bottom: 8px;
            }
            .map-filters label {
                display: block;
                margin-bottom: 3px;
                font-weight: 500;
                font-size: 12px;
            }
            .map-filters select {
                width: 100%;
                padding: 4px 8px;
                border-radius: 4px;
                border: 1px solid #ddd;
                font-size: 12px;
            }
            .dark-mode .map-filters select {
                background: #374151;
                color: #f9fafb;
                border-color: #4b5563;
            }
        `;

        div.appendChild(style);

        // Prevent map interactions when using the filters
        L.DomEvent.disableClickPropagation(div);
        L.DomEvent.disableScrollPropagation(div);

        // Setup network filter options
        const networkFilter = div.querySelector('#map-network-filter');
        const networks = [...new Set(stationsData.map(station => station.network))].sort();

        networks.forEach(network => {
            const option = document.createElement('option');
            option.value = network;
            option.textContent = network;
            networkFilter.appendChild(option);
        });

        // Add event listeners
        networkFilter.addEventListener('change', function() {
            const selectedNetwork = this.value;
            updateMapMarkersFilter(selectedNetwork, div.querySelector('#map-status-filter').value);
        });

        div.querySelector('#map-status-filter').addEventListener('change', function() {
            const selectedStatus = this.value;
            updateMapMarkersFilter(networkFilter.value, selectedStatus);
        });

        return div;
    };

    mapFilters.addTo(map);
}

// Filter map markers based on selected criteria
function updateMapMarkersFilter(network, status) {
    if (!mapInitialized || !map) return;

    // Clear existing markers
    markers.forEach(marker => map.removeLayer(marker));
    markers = [];

    // Apply filters to data
    let filteredData = stationsData;

    if (network) {
        filteredData = filteredData.filter(station => station.network === network);
    }

    if (status) {
        filteredData = filteredData.filter(station => {
            if (status === 'good') {
                return station.status === 'good';
            } else if (status === 'warning') {
                return ['delayed', 'long-delayed', 'very-delayed', 'hour-delayed', 'warning'].includes(station.status);
            } else if (status === 'critical') {
                return ['critical', 'day-delayed', 'multi-day', 'three-day', 'four-day'].includes(station.status);
            } else if (status === 'unavailable') {
                return station.status === 'unavailable';
            }
            return true;
        });
    }

    // Add filtered markers
    const bounds = L.latLngBounds();
    let validCoordinates = false;

    filteredData.forEach(station => {
        // Skip stations without coordinates
        if (!station.coordinates || !station.coordinates.lat || !station.coordinates.lon) return;

        validCoordinates = true;
        const lat = station.coordinates.lat;
        const lon = station.coordinates.lon;

        // Add to bounds for auto-zooming
        bounds.extend([lat, lon]);

        // Create marker with appropriate color
        const markerColor = getStatusColor(station.status);
        const markerIcon = L.divIcon({
            html: `<div style="background-color: ${markerColor}; width: 14px; height: 14px; border-radius: 50%; border: 2px solid white;"></div>`,
            className: 'station-marker',
            iconSize: [18, 18],
            iconAnchor: [9, 9]
        });

        const marker = L.marker([lat, lon], {
            icon: markerIcon,
            title: `${station.network}_${station.station}`
        });

        // Add popup with station info
        marker.bindPopup(`
            <strong>${station.network}_${station.station}</strong><br>
            Status: ${formatStatus(station.status)}<br>
            Latency: ${formatLatency(station.latency)}<br>
            Coordinates: ${lat.toFixed(4)}, ${lon.toFixed(4)}
            ${station.coordinates.elevation ? '<br>Elevation: ' + station.coordinates.elevation.toFixed(1) + ' m' : ''}
            <br><a href="${station.network}_${station.station}.html" target="_blank">View Details</a>
        `);

        marker.addTo(map);
        markers.push(marker);
    });

    // Auto-zoom to fit all markers if we have valid coordinates
    if (validCoordinates && markers.length > 0) {
        // Don't zoom too close if there's only one station
        if (markers.length === 1) {
            map.setView(bounds.getCenter(), 8);
        } else {
            map.fitBounds(bounds, {
                padding: [30, 30],
                maxZoom: 12
            });
        }
    }
}

// Enhanced version of the setActiveView function to handle map initialization
function setActiveView() {
    // Extract view from URL if present
    const urlParams = new URLSearchParams(window.location.search);
    const urlView = urlParams.get('view');

    if (urlView && ['table', 'grid', 'map'].includes(urlView)) {
        viewMode = urlView;
    }

    // Set active class on the appropriate link
    document.querySelectorAll('.view-toggle a').forEach(link => {
        if (link.getAttribute('data-view') === viewMode) {
            link.classList.add('active');
        } else {
            link.classList.remove('active');
        }
    });

    // Show the appropriate view container
    document.querySelectorAll('.view-container').forEach(container => {
        if (container.id === `${viewMode}-view`) {
            container.style.display = 'block';
        } else {
            container.style.display = 'none';
        }
    });

    // Initialize map if needed
    if (viewMode === 'map') {
        if (!mapInitialized && typeof L !== 'undefined') {
            initializeMap();
            // Add map-specific UI elements after initialization
            setTimeout(() => {
                addMapLegend();
                setupMapFilters();
            }, 100);
        } else if (mapInitialized) {
            // If map is already initialized, ensure it's up to date
            map.invalidateSize();
            updateMapMarkers();
        }
    }

    // Save preference
    localStorage.setItem('seedlink-view-mode', viewMode);
}

// Function to fetch data from the server
function fetchData() {
    if (isRefreshing) return;

    isRefreshing = true;
    lastRefreshTime = Date.now();

    // Show loading state
    //document.getElementById('loading').style.display = 'flex';
    document.getElementById('error-message').style.display = 'none';
    document.getElementById('refresh-status').textContent = 'Refreshing...';

    // Use cache-busting to prevent stale data
    const timestamp = Date.now();

    // Fetch the JSON data
    fetch(`stations_data.json?_=${timestamp}`, {
        cache: 'no-cache',
        headers: {
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0'
        }
    })
    .then(response => {
        if (!response.ok) {
            throw new Error(`Server returned ${response.status}: ${response.statusText}`);
        }
        return response.json();
    })
    .then(data => {
        stationsData = data;

        // Update the filter dropdowns
        updateFilters();

        // Render the data based on current view
        renderData();

        // Update timestamp
        const updateTime = new Date().toUTCString();
        document.getElementById('update-time').textContent = updateTime;
        document.getElementById('refresh-status').textContent = 'Last refresh: ' + new Date().toLocaleTimeString();

        // Hide loading state
        document.getElementById('loading').style.display = 'none';
        document.getElementById('error-message').style.display = 'none';

        // Setup next refresh
        setupNextRefresh();
    })
    .catch(error => {
        console.error('Error fetching data:', error);
        document.getElementById('error-message').textContent = `Error loading data: ${error.message}`;
        document.getElementById('error-message').style.display = 'block';
        document.getElementById('loading').style.display = 'none';

        // Still setup next refresh to try again
        setupNextRefresh();
    })
    .finally(() => {
        isRefreshing = false;
    });
}

// Function to update the filter dropdowns based on available data
function updateFilters() {
    // Get unique networks
    const networks = [...new Set(stationsData.map(station => station.network))].sort();

    // Update network filter
    const networkFilter = document.getElementById('network-filter');
    const selectedNetwork = networkFilter.value;

    // Clear existing options except the first one
    while (networkFilter.options.length > 1) {
        networkFilter.remove(1);
    }

    // Add new options
    networks.forEach(network => {
        const option = document.createElement('option');
        option.value = network;
        option.textContent = network;
        networkFilter.appendChild(option);
    });

    // Restore selection if possible
    if (selectedNetwork && networks.includes(selectedNetwork)) {
        networkFilter.value = selectedNetwork;
    }
}

// Function to apply filters to the data
function applyFilters() {
    const networkFilter = document.getElementById('network-filter').value;
    const statusFilter = document.getElementById('status-filter').value;
    const channelFilter = document.getElementById('channel-filter')?.value || '';
    const searchText = document.getElementById('search-input').value.toLowerCase();

    // Apply filters to data
    let filteredData = stationsData;

    if (networkFilter) {
        filteredData = filteredData.filter(station => station.network === networkFilter);
    }

    if (statusFilter) {
        filteredData = filteredData.filter(station => {
            if (statusFilter === 'good') {
                return station.status === 'good';
            } else if (statusFilter === 'warning') {
                return ['delayed', 'long-delayed', 'very-delayed', 'hour-delayed', 'warning'].includes(station.status);
            } else if (statusFilter === 'critical') {
                return ['critical', 'day-delayed', 'multi-day', 'three-day', 'four-day'].includes(station.status);
            } else if (statusFilter === 'unavailable') {
                return station.status === 'unavailable';
            }
            return true;
        });
    }

    if (channelFilter) {
        filteredData = filteredData.filter(station =>
            station.primaryChannelType === channelFilter ||
            (channelFilter === 'other' && !['HH', 'BH', 'LH', 'SH', 'EH'].includes(station.primaryChannelType))
        );
    }

    if (searchText) {
        filteredData = filteredData.filter(station =>
            `${station.network}_${station.station}`.toLowerCase().includes(searchText)
        );
    }

    // Render filtered data
    renderData(filteredData);
}

// Function to render the data in the current view
function renderData(data = stationsData) {
    // Default to all data if not specified
    const displayData = data || stationsData;

    // Render based on current view mode
    if (viewMode === 'table') {
        renderTableView(displayData);
    } else if (viewMode === 'grid') {
        renderGridView(displayData);
    } else if (viewMode === 'map') {
        // Update map markers if map is initialized
        if (mapInitialized) {
            updateMapMarkers();
        }
    }
}

// Function to render table view
function renderTableView(data) {
    const tableBody = document.getElementById('table-body');
    tableBody.innerHTML = '';

    data.forEach(station => {
        const row = document.createElement('tr');

        // Network-Station cell
        const nameCell = document.createElement('td');
        nameCell.innerHTML = `<small>${station.network}</small> <a href="${station.network}_${station.station}.html">${station.station}</a>`;
        // Add a badge for primary channel type
        if (station.primaryChannelType) {
            nameCell.innerHTML += ` <span class="channel-badge">${station.primaryChannelType}</span>`;
        }
        row.appendChild(nameCell);

        // Status cell
        const statusCell = document.createElement('td');
        statusCell.classList.add(`station-${station.status}`);
        statusCell.textContent = formatStatus(station.status, station.primaryChannelType);
        row.appendChild(statusCell);

        // Latency cell
        const latencyCell = document.createElement('td');
        latencyCell.textContent = formatLatency(station.latency);
        latencyCell.style.backgroundColor = getStatusColor(station.status);
        row.appendChild(latencyCell);

        // Channels cell
        const channelsCell = document.createElement('td');

        // Create channel type summary
        const channelGroups = {};
        station.channels.forEach(channel => {
            const type = channel.channelType || 'other';
            if (!channelGroups[type]) {
                channelGroups[type] = 0;
            }
            channelGroups[type]++;
        });

        // Format channel groups
        const groupsHTML = Object.keys(channelGroups).map(type =>
            `<span class="channel-group">${type}: ${channelGroups[type]}</span>`
        ).join(' ');

        channelsCell.innerHTML = `<div>${station.channels.length} total</div><div class="channel-groups">${groupsHTML}</div>`;
        row.appendChild(channelsCell);

        // Last updated cell
        const lastDataCell = document.createElement('td');
        if (station.channels.length > 0 && station.channels[0].last_data) {
            const lastDataTime = new Date(station.channels[0].last_data);
            lastDataCell.textContent = lastDataTime.toLocaleString();
        } else {
            lastDataCell.textContent = 'Unknown';
        }
        row.appendChild(lastDataCell);

        tableBody.appendChild(row);
    });

    // Add CSS for channel badges if not already added
    if (!document.getElementById('channel-badges-css')) {
        const style = document.createElement('style');
        style.id = 'channel-badges-css';
        style.textContent = `
            .channel-badge {
                background-color: var(--bg-tertiary);
                color: var(--text-secondary);
                font-size: 10px;
                padding: 2px 4px;
                border-radius: 4px;
                margin-left: 4px;
                font-weight: 500;
                vertical-align: middle;
            }

            .channel-groups {
                display: flex;
                flex-wrap: wrap;
                gap: 4px;
                margin-top: 2px;
                font-size: 11px;
            }

            .channel-group {
                background-color: var(--bg-tertiary);
                border-radius: 3px;
                padding: 1px 4px;
                color: var(--text-secondary);
            }
        `;
        document.head.appendChild(style);
    }
}

// Function to render grid view
function renderGridView(data) {
    const gridContainer = document.getElementById('grid-container');
    gridContainer.innerHTML = '';

    // Group stations by network
    const networks = {};
    data.forEach(station => {
        if (!networks[station.network]) {
            networks[station.network] = [];
        }
        networks[station.network].push(station);
    });

    // Sort networks by name
    const sortedNetworks = Object.keys(networks).sort();

    for (const network of sortedNetworks) {
        const stations = networks[network];

        // Create a row for the network
        const networkRow = document.createElement('div');
        networkRow.className = 'grid-row';

        // Add network label as a separate cell
        const networkLabel = document.createElement('div');
        networkLabel.className = 'network-label';
        networkLabel.textContent = network;
        networkRow.appendChild(networkLabel);

        // Create a container for the stations
        const stationsContainer = document.createElement('div');
        stationsContainer.className = 'stations-container';

        // Create a row for the stations
        const stationsRow = document.createElement('div');
        stationsRow.className = 'stations-row';

        // Sort stations by name
        stations.sort((a, b) => a.station.localeCompare(b.station));

        // Add stations
        for (const station of stations) {
            const stationCell = document.createElement('a');
            stationCell.className = `grid-cell station-${station.status}`;
            stationCell.href = `${station.network}_${station.station}.html`;
            stationCell.textContent = station.station;
            stationCell.setAttribute('data-tooltip', `${station.network}_${station.station}: ${formatLatency(station.latency)}`);
            stationCell.setAttribute('data-network', station.network);
            stationCell.setAttribute('data-station', station.station);
            stationCell.setAttribute('data-status', station.status);
            stationCell.setAttribute('data-latency', formatLatency(station.latency));
            stationsRow.appendChild(stationCell);
        }

        stationsContainer.appendChild(stationsRow);
        networkRow.appendChild(stationsContainer);
        gridContainer.appendChild(networkRow);
    }
}

// Function to format latency for display
function formatLatency(seconds) {
    if (seconds === null || seconds === undefined) return 'n/a';

    if (seconds > 86400) return `${(seconds/86400).toFixed(1)} d`;
    if (seconds > 3600) return `${(seconds/3600).toFixed(1)} h`;
    if (seconds > 60) return `${(seconds/60).toFixed(1)} m`;
    return `${seconds.toFixed(1)} s`;
}

// Function to format status for display
function formatStatus(status, channelType) {
    const isLH = channelType === 'LH';

    // Add (LH) tag to status labels for LH channels
    const lhSuffix = isLH ? ' (LH)' : '';

    if (status === 'good') return 'Good' + lhSuffix;
    if (status === 'delayed') return 'Delayed (>1m)' + lhSuffix;
    if (status === 'long-delayed') return 'Delayed (>10m)' + lhSuffix;
    if (status === 'very-delayed') return 'Delayed (>30m)' + lhSuffix;
    if (status === 'hour-delayed') return 'Delayed (>1h)' + lhSuffix;
    if (status === 'warning') return 'Warning (>2h)' + lhSuffix;
    if (status === 'critical') return 'Critical (>6h)' + lhSuffix;
    if (status === 'day-delayed') return 'Delayed (>1d)' + lhSuffix;
    if (status === 'multi-day') return 'Delayed (>2d)' + lhSuffix;
    if (status === 'three-day') return 'Delayed (>3d)' + lhSuffix;
    if (status === 'four-day') return 'Delayed (>4d)' + lhSuffix;
    if (status === 'unavailable') return 'Unavailable (>5d)' + lhSuffix;
    return status.charAt(0).toUpperCase() + status.slice(1) + lhSuffix;
}

// Function to get color for a status
function getStatusColor(status) {
    const colors = {
        'good': '#FFFFFF',
        'delayed': '#EBD6FF',
        'long-delayed': '#9470BB',
        'very-delayed': '#3399FF',
        'hour-delayed': '#00FF00',
        'warning': '#FFFF00',
        'critical': '#FF9966',
        'day-delayed': '#FF3333',
        'multi-day': '#FFB3B3',
        'three-day': '#CCCCCC',
        'four-day': '#999999',
        'unavailable': '#666666'
    };

    return colors[status] || '#FFFFFF';
}

// Function to update the refresh interval
function updateRefreshInterval(seconds) {
    // Update current refresh interval
    currentRefreshInterval = seconds;

    // Clear existing timer
    if (refreshTimer) {
        clearTimeout(refreshTimer);
    }

    // Store the preference in localStorage
    localStorage.setItem('seedlinkRefreshInterval', seconds);

    // Set up next refresh
    setupNextRefresh();

    return seconds;
}

// Function to set up the next refresh
function setupNextRefresh() {
    // Calculate time until next refresh
    const timeUntilNextRefresh = Math.max(1000, (currentRefreshInterval * 1000));

    // Clear existing timer
    if (refreshTimer) {
        clearTimeout(refreshTimer);
    }

    // Set new timer
    refreshTimer = setTimeout(fetchData, timeUntilNextRefresh);

    // Update the display
    document.getElementById('refresh-interval').value = currentRefreshInterval;
    document.getElementById('next-refresh').textContent = currentRefreshInterval;

    // Start countdown
    startRefreshCountdown();
}

// Function to start countdown to next refresh
function startRefreshCountdown() {
    const countdownElement = document.getElementById('next-refresh');
    const currentValue = parseInt(countdownElement.textContent);

    // Clear any existing interval
    if (window.countdownInterval) {
        clearInterval(window.countdownInterval);
    }

    // Set new interval
    window.countdownInterval = setInterval(() => {
        const newValue = parseInt(countdownElement.textContent) - 1;
        if (newValue > 0) {
            countdownElement.textContent = newValue;
        } else {
            clearInterval(window.countdownInterval);
        }
    }, 1000);
}

// Function to export data to CSV
function exportToCsv() {
    // Create CSV content
    let csvContent = 'Network,Station,Status,Latency,Channels,Last Updated\\n';

    stationsData.forEach(station => {
        const lastUpdate = station.channels.length > 0 && station.channels[0].last_data
            ? new Date(station.channels[0].last_data).toISOString()
            : 'Unknown';

        csvContent += `${station.network},${station.station},${station.status},${station.latency},${station.channels.length},${lastUpdate}\\n`;
    });

    // Create download link
    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.setAttribute('href', url);
    link.setAttribute('download', `seedlink-status-${new Date().toISOString().split('T')[0]}.csv`);
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    // Clean up
    setTimeout(() => {
        URL.revokeObjectURL(url);
    }, 100);
}

// Function to toggle stats display
function toggleStats() {
    const statsContainer = document.getElementById('stats-container');

    if (statsContainer.style.display === 'block') {
        statsContainer.style.display = 'none';
        return;
    }

    // Show the stats container
    statsContainer.style.display = 'block';

    // Calculate statistics
    calculateStats();
}

// Function to calculate and display statistics
function calculateStats() {
    const networks = {};

    let totalStations = stationsData.length;
    let goodStations = 0;
    let warningStations = 0;
    let criticalStations = 0;
    let unavailableStations = 0;

    // Group by network and count statuses
    stationsData.forEach(station => {
        // Create network entry if it doesn't exist
        if (!networks[station.network]) {
            networks[station.network] = {
                total: 0,
                good: 0,
                warning: 0,
                critical: 0,
                unavailable: 0
            };
        }

        // Count by network
        networks[station.network].total++;

        // Count by status
        if (station.status === 'good') {
            networks[station.network].good++;
            goodStations++;
        } else if (['delayed', 'long-delayed', 'very-delayed', 'hour-delayed', 'warning'].includes(station.status)) {
            networks[station.network].warning++;
            warningStations++;
        } else if (['critical', 'day-delayed', 'multi-day', 'three-day', 'four-day'].includes(station.status)) {
            networks[station.network].critical++;
            criticalStations++;
        } else if (station.status === 'unavailable') {
            networks[station.network].unavailable++;
            unavailableStations++;
        }
    });

    // Update status counter
    const statusCounter = document.getElementById('status-counter');
    statusCounter.innerHTML = `
        <div style="font-weight: 600; margin-bottom: 10px; font-size: 16px;">
            ${totalStations - unavailableStations} active of ${totalStations} total stations
        </div>
        <div style="display: flex; gap: 20px; margin-bottom: 15px; justify-content: center; flex-wrap: wrap;">
            <div>
                <span style="color: var(--text-primary); font-weight: 500;">${goodStations}</span> good
            </div>
            <div>
                <span style="color: var(--status-warning); font-weight: 500;">${warningStations}</span> warning
            </div>
            <div>
                <span style="color: var(--status-critical); font-weight: 500;">${criticalStations}</span> critical
            </div>
            <div>
                <span style="color: var(--status-unavailable); font-weight: 500;">${unavailableStations}</span> unavailable
            </div>
        </div>
    `;

    // Update network stats
    const networkStats = document.getElementById('network-stats');
    networkStats.innerHTML = '';

    Object.keys(networks).sort().forEach(network => {
        const stats = networks[network];
        const activePercentage = Math.round(((stats.total - stats.unavailable) / stats.total) * 100);

        const networkStat = document.createElement('div');
        networkStat.className = 'network-stat';

        // Create the name and count display
        const nameContainer = document.createElement('div');
        nameContainer.className = 'network-name';
        nameContainer.innerHTML = `
            <span>${network}</span>
            <span>${stats.total - stats.unavailable}/${stats.total}</span>
        `;

        // Create the progress bar
        const progressContainer = document.createElement('div');
        progressContainer.className = 'progress-bar';

        const progressBar = document.createElement('div');
        progressBar.className = 'progress';
        progressBar.style.width = `${activePercentage}%`;

        progressContainer.appendChild(progressBar);

        networkStat.appendChild(nameContainer);
        networkStat.appendChild(progressContainer);

        networkStats.appendChild(networkStat);
    });
}

function sortTable(column) {
    // Get current sort direction from the header
    const header = document.querySelector(`th[data-sort="${column}"]`);
    const currentDirection = header.getAttribute('data-direction') || 'asc';
    const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';

    // Update all headers to remove sort indicators
    document.querySelectorAll('th[data-sort]').forEach(th => {
        th.setAttribute('data-direction', '');
        th.classList.remove('sort-asc', 'sort-desc');
    });

    // Set direction on current header
    header.setAttribute('data-direction', newDirection);
    header.classList.add(`sort-${newDirection}`);

    // Sort data based on column
    stationsData.sort((a, b) => {
        let valueA, valueB;

        if (column === 'name') {
            valueA = `${a.network}_${a.station}`;
            valueB = `${b.network}_${b.station}`;
        } else if (column === 'status') {
            valueA = a.status;
            valueB = b.status;
        } else if (column === 'latency') {
            valueA = a.latency;
            valueB = b.latency;
        } else if (column === 'channels') {
            valueA = a.channels.length;
            valueB = b.channels.length;
        } else if (column === 'updated') {
            valueA = a.channels.length > 0 ? new Date(a.channels[0].last_data || 0).getTime() : 0;
            valueB = b.channels.length > 0 ? new Date(b.channels[0].last_data || 0).getTime() : 0;
        }

        // Handle string comparison
        if (typeof valueA === 'string') {
            if (newDirection === 'asc') {
                return valueA.localeCompare(valueB);
            } else {
                return valueB.localeCompare(valueA);
            }
        }
        // Handle number comparison
        else {
            if (newDirection === 'asc') {
                return valueA - valueB;
            } else {
                return valueB - valueA;
            }
        }
    });

    // Re-render the table with sorted data
    renderTableView(stationsData);
}

// Utility function for debouncing
function debounce(func, wait) {
    let timeout;
    return function(...args) {
        const context = this;
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(context, args), wait);
    };
}
"""

    try:
        js_path = os.path.join(config['setup']['wwwdir'], 'script.js')
        with open(js_path, 'w') as f:
            f.write(js_content)
        print(f"JavaScript file generated at {js_path}")
        return js_path
    except Exception as e:
        print(f"Error generating JavaScript file: {str(e)}")
        return None


def generate_html_base(config, title, active_view):
    """Generate base HTML structure for all pages"""

    # Determine if map plugins should be included
    include_map_plugins = 'enable_map' in config['setup'] and config['setup']['enable_map']

    # Start with the first part using proper variable interpolation
    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{config['setup']['title']} - {title}</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="styles.css">
    <link rel="shortcut icon" href="{config['setup']['icon']}">
    <!-- meta http-equiv="refresh" content="{int(config['setup']['refresh'])}" -->

    <!-- Include Leaflet for map view -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
"""

    # Include additional map plugins if map is enabled
    if include_map_plugins:
        html += """
    <!-- Leaflet plugins -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" />
    <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" />
    <link rel="stylesheet" href="https://unpkg.com/leaflet.fullscreen@1.6.0/Control.FullScreen.css" />
    <link rel="stylesheet" href="https://unpkg.com/leaflet.locatecontrol@0.76.0/dist/L.Control.Locate.min.css" />

    <script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
    <script src="https://unpkg.com/leaflet.fullscreen@1.6.0/Control.FullScreen.js"></script>
    <script src="https://unpkg.com/leaflet.locatecontrol@0.76.0/dist/L.Control.Locate.min.js"></script>
    <script src="https://unpkg.com/leaflet-measure-path@1.5.0/leaflet-measure-path.js"></script>
"""

    # Continue with the second part, but using f-string instead of .format()
    html += f"""
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>{config['setup']['title']}</h1>
            <div class="view-toggle">
                <a href="index.html" data-view="table" class="{'active' if active_view == 'table' else ''}">Table View</a>
                <a href="index.html?view=grid" data-view="grid" class="{'active' if active_view == 'grid' else ''}">Grid View</a>
                <a href="index.html?view=map" data-view="map" class="{'active' if active_view == 'map' else ''}">Map View</a>
            </div>
        </div>

        <p class="subtitle">Real-time seismic station monitoring dashboard</p>

        <div class="controls">
            <div class="actions">
                <button id="stats-toggle" class="action-button">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
                    </svg>
                    Station Stats
                </button>
                <button id="export-csv" class="action-button">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                        <polyline points="7 10 12 15 17 10"></polyline>
                        <line x1="12" y1="15" x2="12" y2="3"></line>
                    </svg>
                    Export CSV
                </button>
                <button id="theme-toggle" class="action-button">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                    </svg>
                    Dark Mode
                </button>
            </div>

            <div class="refresh-control">
                <div class="input-group">
                    <label for="refresh-interval">Auto refresh:</label>
                    <input type="number" id="refresh-interval" min="10" value="{int(config['setup']['refresh'])}">
                    <span>seconds</span>
                </div>
                <button id="apply-refresh">Apply</button>
                <button id="refresh-now">Refresh Now</button>
                <div class="status-counter">
                    <div id="refresh-status">Last refresh: -</div>
                    <div class="countdown">Next in <span id="next-refresh">{int(config['setup']['refresh'])}</span> seconds</div>
                </div>
            </div>
        </div>
"""

    return html

def generate_main_html(config, status):
    """Generate the main index.html with all three views"""

    html = generate_html_base(config, "Dashboard", "table")

    map_settings = get_map_settings(config)

    # Add filters
    html += f"""
        <script>
            //Map configuration settings
            windows.mapSettings = {json.dumps(map_settings)};
        </script>
        <div class="filters">
            <div class="filter-group">
                <label for="network-filter">Network:</label>
                <select id="network-filter">
                    <option value="">All Networks</option>
                </select>
            </div>
            <div class="filter-group">
                <label for="status-filter">Status:</label>
                <select id="status-filter">
                    <option value="">All Statuses</option>
                    <option value="good">Good</option>
                    <option value="warning">Warning</option>
                    <option value="critical">Critical</option>
                    <option value="unavailable">Unavailable</option>
                </select>
            </div>
            <div class="filter-group">
                <input type="text" id="search-input" placeholder="Search stations..." class="search-box">
            </div>
        </div>

        <div id="stats-container" class="stats-container">
            <div class="stats-header">
                <div class="stats-title">Station Statistics</div>
                <button id="close-stats" class="action-button">Close</button>
            </div>
            <div id="status-counter"></div>
            <div id="network-stats" class="network-stats">
                <!-- Network stats will be inserted here -->
            </div>
        </div>

        <div id="loading">
            <div class="loading-spinner"></div>
            Loading station data...
        </div>

        <div id="error-message"></div>

        <!-- Table View -->
        <div id="table-view" class="view-container">
            <div class="table-container">
                <table>
                    <thead>
                        <tr>
                            <th data-sort="name">Station</th>
                            <th data-sort="status">Status</th>
                            <th data-sort="latency">Latency</th>
                            <th data-sort="channels">Channels</th>
                            <th data-sort="updated">Last Updated</th>
                        </tr>
                    </thead>
                    <tbody id="table-body">
                        <!-- Table rows will be inserted here -->
                    </tbody>
                </table>
            </div>
        </div>

        <!-- Grid View -->
        <div id="grid-view" class="view-container" style="display: none;">
            <div id="grid-container" class="grid-container">
                <!-- Grid will be inserted here -->
            </div>
        </div>

        <!-- Map View -->
        <div id="map-view" class="view-container" style="display: none;">
            <div id="map-container" class="map-container">
                <!-- Map will be rendered here -->
            </div>
        </div>
    """

    # Add legend
    html += """
        <div class="legend">
            <div class="legend-item">
                <div class="legend-color" style="background-color: #ffffff; border: 1px solid #e5e7eb;"></div>
                <span>Good (&le; 1 min)</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #c084fc;"></div>
                <span>&gt; 1 min</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #8b5cf6;"></div>
                <span>&gt; 10 min</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #3b82f6;"></div>
                <span>&gt; 30 min</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #10b981;"></div>
                <span>&gt; 1 hour</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #fbbf24;"></div>
                <span>&gt; 2 hours</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #f97316;"></div>
                <span>&gt; 6 hours</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #ef4444;"></div>
                <span>&gt; 1 day</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #f87171;"></div>
                <span>&gt; 2 days</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #d1d5db;"></div>
                <span>&gt; 3 days</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #9ca3af;"></div>
                <span>&gt; 4 days</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #6b7280;"></div>
                <span>&gt; 5 days</span>
            </div>
        </div>
    """

    # Add footer and close tags
    html += f"""
        <div class="footer">
            <div>Last updated <span id="update-time">{gmtime()[:6][0]:04d}/{gmtime()[:6][1]:02d}/{gmtime()[:6][2]:02d} {gmtime()[:6][3]:02d}:{gmtime()[:6][4]:02d}:{gmtime()[:6][5]:02d} UTC</span></div>
            <div><a href="{config['setup']['linkurl']}" target="_top">{config['setup']['linkname']}</a></div>
        </div>
    </div>

    <!-- Export JSON data for JavaScript -->
    <script>
        // Initialize stationsData with server-side rendered data
        const initialStationsData = {status.to_json()};
    </script>

    <!-- Include the main JavaScript file -->
    <script src="script.js"></script>
</body>
</html>
"""

    try:
        html_path = os.path.join(config['setup']['wwwdir'], 'index.html')
        with open(html_path, 'w') as f:
            f.write(html)
        print(f"Main HTML file generated at {html_path}")
        return html_path
    except Exception as e:
        print(f"Error generating main HTML file: {str(e)}")
        return None


def generate_station_html(net_sta, config, status):
    """Generate individual station HTML page"""

    try:
        network, station = net_sta.split("_")
    except:
        print(f"Invalid station identifier: {net_sta}")
        return None

    html = generate_html_base(config, f"Station {station}", "table")

    # Add station info
    html += f"""
        <div class="station-header">
            <h2>{network}_{station}</h2>
    """

    # Add station information if available
    try:
        if 'info' in config.station[net_sta]:
            html += f'<div class="station-info">{config.station[net_sta]["info"]}</div>'
    except:
        pass

    html += """
        </div>
    """

    # Add custom text if available
    try:
        if 'text' in config.station[net_sta]:
            html += f'<p>{config.station[net_sta]["text"]}</p>'
    except:
        pass

    # Station details table
    html += """
        <div class="table-container">
            <table>
                <thead>
                    <tr>
                        <th>Channel</th>
                        <th>Last Sample</th>
                        <th>Data Latency</th>
                        <th>Last Received</th>
                        <th>Feed Latency</th>
                        <th>Diff</th>
                    </tr>
                </thead>
                <tbody>
    """

    now = datetime.utcnow()
    netsta2 = net_sta.replace("_", ".")
    streams = [x for x in list(status.keys()) if x.find(netsta2) == 0]
    streams.sort()

    for label in streams:
        tim1 = status[label].last_data
        tim2 = status[label].last_feed

        lat1, lat2, lat3 = now-tim1, now-tim2, tim2-tim1
        col1, col2, col3 = getColor(lat1), getColor(lat2), getColor(lat3)

        if lat1 == lat2:
            lat2 = lat3 = None

        if label[-2] == '.' and label[-1] in "DE":
            label = label[:-2]

        n, s, loc, c = label.split(".")
        c = ("%s.%s" % (loc, c)).strip(".")

        time1_str = tim1.strftime("%Y/%m/%d %H:%M:%S") if tim1 else "n/a"
        time2_str = tim2.strftime("%Y/%m/%d %H:%M:%S") if tim2 else "n/a"

        html += f"""
                <tr>
                    <td>{s} {c}</td>
                    <td>{time1_str}</td>
                    <td style="background-color:{col1}">{formatLatency(lat1)}</td>
                    <td>{time2_str}</td>
                    <td style="background-color:{col2}">{formatLatency(lat2)}</td>
                    <td style="background-color:{col3}">{formatLatency(lat3)}</td>
                </tr>
        """

    html += """
                </tbody>
            </table>
        </div>
    """

    # Legend
    html += """
        <div class="legend">
            <div class="legend-item">
                <div class="legend-color" style="background-color: #ffffff; border: 1px solid #e5e7eb;"></div>
                <span>Good (&le; 1 min)</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #c084fc;"></div>
                <span>&gt; 1 min</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #8b5cf6;"></div>
                <span>&gt; 10 min</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #3b82f6;"></div>
                <span>&gt; 30 min</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #10b981;"></div>
                <span>&gt; 1 hour</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background-color: #fbbf24;"></div>
                <span>&gt; 2 hours</span>
            </div>
        </div>
    """

    # Links
    html += '<div class="links">\n'
    html += '<p>Click here to <a href="index.html?view=grid" target="_blank">view in Grid View</a><br>\n'

    if 'liveurl' in config['setup']:
        # Substitute '%s' in live_url by station name
        s = net_sta.split("_")[-1]
        url = config['setup']['liveurl'] % s
        html += f'View a <a href="{url}" target="_blank">live seismogram</a> of station {s}</p>\n'

    html += '</div>\n'

    # Add footer and close tags
    html += f"""
        <div class="footer">
            <div>Last updated {gmtime()[:6][0]:04d}/{gmtime()[:6][1]:02d}/{gmtime()[:6][2]:02d} {gmtime()[:6][3]:02d}:{gmtime()[:6][4]:02d}:{gmtime()[:6][5]:02d} UTC</div>
            <div><a href="{config['setup']['linkurl']}" target="_top">{config['setup']['linkname']}</a></div>
        </div>
    </div>

    <script src="script.js"></script>
</body>
</html>
"""

    try:
        html_path = os.path.join(config['setup']['wwwdir'], f'{net_sta}.html')
        with open(html_path, 'w') as f:
            f.write(html)
        print(f"Station HTML file generated at {html_path}")
        return html_path
    except Exception as e:
        print(f"Error generating station HTML file: {str(e)}")
        return None


def generate_json_data(status):
    """Generate a JSON file with station data for JavaScript use"""
    try:
        json_data = status.to_json()
        json_path = os.path.join(config['setup']['wwwdir'], 'stations_data.json')
        with open(json_path, 'w') as f:
            f.write(json_data)
        print(f"JSON data file generated at {json_path}")
        return json_path
    except Exception as e:
        print(f"Error generating JSON data file: {str(e)}")
        return None


def generate_all_files(config, status):
    """Generate all the static files needed for the web interface"""

    # Create the directory if it doesn't exist
    try:
        os.makedirs(config['setup']['wwwdir'], exist_ok=True)
    except Exception as e:
        print(f"Error creating directory: {str(e)}")
        return False

    # Generate files
    css_path = generate_css_file(config)
    js_path = generate_js_file(config)
    json_path = generate_json_data(status)
    main_html = generate_main_html(config, status)

    # Generate station pages - Get UNIQUE station identifiers
    unique_stations = set()
    for k in status:
        net_sta = f"{status[k].net}_{status[k].sta}"
        unique_stations.add(net_sta)

    # Now generate each station page exactly once
    station_htmls = []
    for net_sta in unique_stations:
        html_path = generate_station_html(net_sta, config, status)
        station_htmls.append(html_path is not None)

    # Return success only if all files were generated
    all_stations_success = len(station_htmls) > 0 and all(station_htmls)

    # Log success or failure
    if all_stations_success:
        print(f"Successfully generated {len(station_htmls)} station HTML files")
    else:
        print(f"ERROR: Failed to generate some station HTML files")

    # Return success if all files were generated
    return all([css_path, js_path, json_path, main_html, all_stations_success])


def read_ini():
    """Read configuration files"""
    global config, ini_setup, ini_stations
    print("reading setup config from '%s'" % ini_setup)
    if not os.path.isfile(ini_setup):
        print("[error] setup config '%s' does not exist" % ini_setup, file=sys.stderr)
        usage(exitcode=2)

    config = MyConfig(ini_setup)
    print("reading station config from '%s'" % ini_stations)
    if not os.path.isfile(ini_stations):
        print("[error] station config '%s' does not exist" % ini_stations, file=sys.stderr)
        usage(exitcode=2)
    config.station = MyConfig(ini_stations)


def SIGINT_handler(signum, frame):
    """Handle interruption signals"""
    global status
    print("received signal #%d => will write status file and exit" % signum)
    sys.exit(0)


def main():
    """Main function to run the program"""
    global config, status, verbose, generate_only, ini_setup, ini_stations

    # Parse command line arguments
    try:
        opts, args = getopt(sys.argv[1:], "c:s:t:hvg", ["help", "generate"])
    except GetoptError:
        print("\nUnknown option in "+str(sys.argv[1:])+" - EXIT.", file=sys.stderr)
        usage(exitcode=2)

    for flag, arg in opts:
        if flag == "-c":        ini_setup = arg
        elif flag == "-s":      ini_stations = arg
        elif flag == "-t":      refresh = float(arg)  # XXX not yet used
        elif flag in ("-h", "--help"):     usage(exitcode=0)
        elif flag == "-v":      verbose = 1
        elif flag in ("-g", "--generate"): generate_only = True

    # Set up signal handlers
    signal.signal(signal.SIGHUP, SIGINT_handler)
    signal.signal(signal.SIGINT, SIGINT_handler)
    signal.signal(signal.SIGQUIT, SIGINT_handler)
    signal.signal(signal.SIGTERM, SIGINT_handler)

    # Read configuration
    read_ini()

    # Load station coordinates from the FDSN web service
    try:
        load_station_coordinates(config)
    except Exception as e:
        print(f"Warning: Failed to load station coordinates: {str(e)}")

    # Prepare station information
    s = config.station
    net_sta = ["%s_%s" % (s[k]['net'], s[k]['sta']) for k in s]
    s_arg = ','.join(net_sta)

    # Set server from config or use default
    if 'server' in config['setup']:
        server = config['setup']['server']
    else:
        server = "localhost"

    # Initialize status dictionary
    status = StatusDict()

    print("generating output to '%s'" % config['setup']['wwwdir'])

    if generate_only:
        # Generate template files without fetching data
        print("Generating template files only...")

        # Create dummy data for template rendering
        for net_sta_item in net_sta:
            net, sta = net_sta_item.split('_')

            d = Status()
            d.net = net
            d.sta = sta
            d.loc = ""
            d.cha = "HHZ"
            d.typ = "D"
            d.last_data = datetime.utcnow()
            d.last_feed = datetime.utcnow()

            sec = "%s.%s.%s.%s.%c" % (d.net, d.sta, d.loc, d.cha, d.typ)
            status[sec] = d

        # Generate all files
        if generate_all_files(config, status):
            print("Template files generated successfully.")
        else:
            print("Error generating template files.")

        sys.exit(0)

    # Get initial data
    print("getting initial time windows from SeedLink server '%s'" % server)
    status.fromSlinkTool(server, stations=net_sta)
    if verbose:
        status.write(sys.stderr)

    # Generate initial files
    generate_all_files(config, status)

    # Set up the next time to generate files
    nextTimeGenerateHTML = time()

    print("setting up connection to SeedLink server '%s'" % server)

    # Connect to the SeedLink server and start receiving data
    input = seiscomp.slclient.Input(server, [(s[k]['net'], s[k]['sta'], "", "???") for k in s])
    for rec in input:
        id = '.'.join([rec.net, rec.sta, rec.loc, rec.cha, rec.rectype])

        try:
            status[id].last_data = rec.end_time
            status[id].last_feed = datetime.utcnow()
        except:
            continue

        if time() > nextTimeGenerateHTML:
            generate_all_files(config, status)
            nextTimeGenerateHTML = time() + int(config['setup']['refresh'])


if __name__ == "__main__":
    main()
