#!/usr/bin/env python3

import cgi
import cgitb
import html
import json
import os
import sys
import time

# Add the apps directory to Python path for imports
sys.path.insert(0, '/opt/ngon/apps')
sys.path.insert(0, '/var/www/html/ngon')
from managers.auth_manager import AuthManager, generate_login_page_html, generate_access_denied_html, generate_login_modal_html
from links import generate_dropdown_html, generate_dropdown_css, generate_dropdown_js

# File paths
MASTER_CONFIG_FILE = "/opt/ngon/config/master_config.json"

# Pre-defined generator statuses
def _load_gen_statuses():
    """Generator maintenance statuses from master_config misc.display.

    Same source constants_cgi.py serves to the browser, so this page, the status
    page and the gen modal cannot drift -- previously the set was written out in
    four places and kept in step by hand. The literal below is the fallback if
    config is unreadable, and it is what misc.display was seeded from.

    Only label and color are taken. This page derives its badge text colour from
    the background further down rather than reading misc.display's textColor, so
    that logic is left alone.
    """
    fallback = {
        'active': {'label': 'Active', 'color': '#00ff00'},
        'active-swap': {'label': 'Active-Swap', 'color': '#ff6600'},
        'swap': {'label': 'Swap', 'color': '#ff6600'},
        'inspect': {'label': 'Inspect', 'color': '#ffaa00'},
        'parts': {'label': 'Parts', 'color': '#ff4444'},
        'gas': {'label': 'Gas', 'color': '#4a9eff'},
        'oos': {'label': 'OOS', 'color': '#888888'}
    }
    try:
        from managers.config_manager import config
        display = ((config._master_config or {}).get('misc', {}) or {}).get('display', {}) or {}
        statuses = display.get('genStatuses')
        if statuses:
            return {key: {'label': meta.get('label', key),
                          'color': meta.get('color', '#888888')}
                    for key, meta in statuses.items()}
    except Exception as exc:
        sys.stderr.write("gen_manager: gen status config read failed (%s), using fallback\n" % exc)
    return fallback


GEN_STATUSES = _load_gen_statuses()

# Handle form requests
form = cgi.FieldStorage()

# Initialize authentication
auth = AuthManager('gen_manager')
auth_required, should_exit, headers = auth.require_auth(form)

# Handle authentication responses
if should_exit:
    print("Content-Type: application/json")
    if headers:
        print(headers)
    print("")  # End headers

    if auth_required:
        print('{"success": false, "error": "Authentication required"}')
    else:
        print('{"success": true}')

    sys.exit(0)

# Handle logout request
if "logout" in form and form.getvalue("logout"):
    print("Content-Type: text/html")
    # Clear the authentication cookie
    import http.cookies
    cookie = http.cookies.SimpleCookie()
    cookie['ngon_auth'] = ''
    cookie['ngon_auth']['path'] = '/'
    cookie['ngon_auth']['max-age'] = 0
    print(cookie.output())
    print("")  # End headers
    print("""
    <html><body style="background: #1a1a1a; color: #fff; font-family: Arial, sans-serif; text-align: center; padding: 50px;">
        <h1 style="color: #00ff00;">Logged Out Successfully</h1>
        <p>Your authentication session has been cleared.</p>
        <a href="gen_manager.py" style="color: #4a9eff;">Return to Generator Manager</a>
    </body></html>
    """)
    sys.exit(0)

# Handle direct config save (simplified approach)
if "direct_config_save" in form and form.getvalue("direct_config_save"):
    config_data = form.getvalue("direct_config_save")

    print("Content-Type: application/json\n")

    try:
        # Parse the JSON config data
        new_config = json.loads(config_data)

        # Write directly to file
        with open(MASTER_CONFIG_FILE, "w") as f:
            json.dump(new_config, f, indent=2)

        print('{"success": true}')

    except Exception as e:
        print(f'{{"success": false, "error": "Save failed: {str(e)}"}}')

    sys.exit(0)

# Handle session check requests
if "session_check" in form and form.getvalue("session_check"):
    print("Content-Type: application/json\n")
    print('{"success": true}')
    sys.exit(0)

# Disable cgitb for remaining requests
cgitb.enable()

# If not an AJAX request, output HTML header
# no-store, matching index.py. Without a caching directive a browser applies
# HEURISTIC caching, and WebKit -- which is every browser on iOS, Chrome
# included -- is aggressive about it. That matters more here than on an
# ordinary page: this document is what carries the ?v= numbers on every
# script and stylesheet, so a cached copy of it pins the whole bundle to
# whatever versions it named and defeats cache-busting at the root. The
# symptom is a phone that keeps loading old code however many times its
# owner pulls to refresh.
print("Content-Type: text/html")
print("Cache-Control: no-cache, no-store, must-revalidate")
print("Expires: 0")
print("")

def load_config():
    """Load master config with all generator data"""
    if os.path.exists(MASTER_CONFIG_FILE):
        with open(MASTER_CONFIG_FILE, "r") as f:
            return json.load(f)
    return {"sites": {}}

# Load config after form processing
config = load_config()

# The digest of what this page draws, handed to the browser so it can tell a
# real edit from the automation traffic that dominates config writes. Same
# function status_api uses when it emits gen_config_changed — one definition, or
# the two sides would drift and the page would either cry wolf or go deaf.
try:
    from managers.config_manager import gen_display_digest
    _GEN_DIGEST = gen_display_digest(config)
except Exception:
    # No digest -> the page treats every event as real, which is how it behaved
    # before this existed. Noisy, never wrong.
    _GEN_DIGEST = ""

# Retrofit campaigns (misc.retrofits): fleet-wide kit installs tracked per gen,
# e.g. the jacket water heaters going onto the ex-TX gens now at WW. Defined in
# config so adding the next campaign doesn't need a code change.
RETROFITS = (config.get("misc", {}) or {}).get("retrofits", {}) or {}

print("""
<!DOCTYPE html>
<html>
<head>
    <title>NGON Generator Manager</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #1a1a1a;
            color: #e0e0e0;
            margin: 0;
            padding: 0;
            line-height: 1.4;
        }

        /* Header - matching status page style */
        .header {
            background-color: rgba(255,255,255,0.05) !important;
            border-radius: 8px !important;
            padding: 30px !important;
            margin: 20px !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
            border-bottom: none !important;
            display: flex !important;
            justify-content: space-between !important;
            align-items: flex-start !important;
        }
        .header-buttons {
            display: flex;
            gap: 8px;
        }
        .header-controls {
            display: flex;
            flex-direction: column;
            align-items: flex-end;
            gap: 10px;
        }
        .filter-group {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .filter-group label {
            color: #888;
            font-size: 12px;
        }
        .multi-select {
            position: relative;
            min-width: 150px;
        }
        .multi-select-toggle {
            background: #2a2a2a;
            border: 1px solid #3a3a3a;
            border-radius: 4px;
            padding: 6px 12px;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 10px;
            font-size: 12px;
            color: #e0e0e0;
        }
        .multi-select-toggle:hover {
            border-color: #00ff00;
        }
        .multi-select-dropdown {
            display: none;
            position: absolute;
            top: 100%;
            right: 0;
            background: #2a2a2a;
            border: 1px solid #3a3a3a;
            border-radius: 4px;
            margin-top: 4px;
            min-width: 160px;
            z-index: 100;
            max-height: 300px;
            overflow-y: auto;
        }
        .multi-select-dropdown.show {
            display: block;
        }
        .filter-option {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 12px;
            cursor: pointer;
            font-size: 12px;
            color: #e0e0e0;
        }
        .filter-option:hover {
            background: #333;
        }
        .filter-option input {
            accent-color: #00ff00;
        }
        .status-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            display: inline-block;
        }

        /* Main content area */
        .main-content {
            padding: 0 20px 20px 20px;
        }
""")

# Output dropdown CSS from links.py
print(generate_dropdown_css())

print("""
        .btn {
            padding: 6px 12px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            font-size: 12px;
        }
        .btn-primary { background: #00ff00; color: black; }
        .btn-primary:hover { background: #00cc00; }
        .btn-danger { background: #dc3545; color: white; }
        .btn-danger:hover { background: #b91c1c; }
        .btn-secondary { background: #666; color: white; }
        .btn-secondary:hover { background: #555; }

        /* Search section - compact */
        .search-section {
            background-color: rgba(255,255,255,0.05);
            border: 1px solid rgba(255,255,255,0.1);
            padding: 12px 15px;
            border-radius: 8px;
            margin-top: 15px;
            display: flex;
            gap: 10px;
            align-items: center;
            flex-wrap: wrap;
        }
        .search-section input {
            padding: 6px 10px;
            background: #2a2a2a;
            border: 1px solid #3a3a3a;
            color: white;
            border-radius: 4px;
            width: 200px;
            font-size: 12px;
        }
        .search-section select {
            padding: 6px 10px;
            background: #2a2a2a;
            border: 1px solid #3a3a3a;
            color: white;
            border-radius: 4px;
            font-size: 12px;
        }

        /* Sites grid - multi-column layout */
        #sitesContainer {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            align-items: flex-start;
        }

        /* Site containers - compact */
        .site-container {
            background-color: rgba(255,255,255,0.05);
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 10px;
            padding: 12px;
            flex: 1 1 300px;
            max-width: 500px;
            min-width: 280px;
        }
        .site-container:hover {
            border-color: rgba(255,255,255,0.2);
        }
        .site-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            padding-bottom: 8px;
            border-bottom: 1px solid #2a2a2a;
        }
        .site-title {
            color: #00ff00;
            font-size: 16px;
            font-weight: bold;
        }
        .site-id {
            color: #888;
            font-size: 10px;
        }

        /* Group containers - compact */
        .groups-wrapper {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        .group-container {
            background: #222;
            border: 1px solid #333;
            border-radius: 6px;
            padding: 8px 10px;
        }
        .group-container:hover {
            border-color: #00ff00;
        }
        .group-title {
            color: #4a9eff;
            font-weight: bold;
            font-size: 12px;
            margin-bottom: 6px;
        }

        /* Generator grid - compact */
        .generators-grid {
            display: flex;
            flex-wrap: wrap;
            gap: 4px;
            min-height: 20px;
        }
        .generators-grid.empty {
            min-height: 36px;
            border: 1px dashed #444;
            border-radius: 4px;
            align-items: center;
            justify-content: center;
        }
        .empty-hint {
            color: #666;
            font-size: 11px;
            font-style: italic;
            pointer-events: none;
        }
        .gen-item {
            padding: 4px 8px;
            background: #1a1a1a;
            cursor: pointer;
            border-radius: 4px;
            border: 1px solid #333;
            font-size: 11px;
            transition: all 0.15s ease;
            display: flex;
            flex-direction: column;
        }
        .gen-item:hover {
            background: #2a2a2a;
            border-color: #00ff00;
        }
        .gen-item.dragging {
            opacity: 0.5;
            border-color: #00ff00;
        }
        .gen-id {
            font-weight: bold;
            color: #fff;
        }
        .gen-note {
            color: #888;
            font-size: 10px;
            margin-top: 2px;
            max-width: 120px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .gen-status-badge {
            display: inline-block;
            padding: 1px 4px;
            border-radius: 2px;
            font-size: 9px;
            font-weight: bold;
            margin-left: 4px;
        }

        .gen-origin-chip {
            background: #2b3946;
            color: #9fb6cc;
            border: 1px solid #4a6076;
            font-weight: normal;
        }
        .gen-note-preview {
            font-size: 9px;
            color: #888;
            margin-top: 2px;
            max-width: 120px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* Out of Service / Spares section - spans full width */
        .oos-section {
            background-color: rgba(255,255,255,0.05);
            border: 2px dashed #ff4444;
            border-radius: 10px;
            padding: 12px;
            flex: 1 1 100%;
            max-width: 100%;
        }
        .oos-title {
            color: #ff4444;
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 10px;
        }

        /* Search results section - compact */
        .results-section {
            background-color: rgba(255,255,255,0.05);
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 8px;
            padding: 12px;
            margin-top: 15px;
            display: none;
        }
        .results-title {
            color: #4a9eff;
            font-size: 14px;
            font-weight: bold;
            margin-bottom: 10px;
        }
        .result-item {
            padding: 6px 10px;
            background: #2a2a2a;
            border-radius: 4px;
            margin-bottom: 5px;
            font-size: 12px;
        }
        .result-item:hover {
            background: #333;
        }

        /* Stats section - compact */
        .stats-section {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            margin-top: 10px;
            padding-top: 10px;
            border-top: 1px solid #2a2a2a;
        }
        .stat-box {
            background: #2a2a2a;
            padding: 6px 12px;
            border-radius: 4px;
            text-align: center;
        }
        .stat-value {
            font-size: 18px;
            font-weight: bold;
        }
        .stat-label {
            font-size: 10px;
            color: #888;
        }

        /* Modal styles */
        .modal {
            display: none;
            position: fixed;
            z-index: 1000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.8);
        }
        .modal-content {
            background-color: #1a1a1a;
            margin: 5% auto;
            padding: 20px;
            border: 2px solid #00ff00;
            border-radius: 8px;
            width: 450px;
            max-width: 90%;
            max-height: 85vh;
            overflow-y: auto;
            box-shadow: 0 4px 20px rgba(0, 255, 0, 0.3);
            scrollbar-width: thin;
            scrollbar-color: #666 transparent;
        }
        /* Generator detail modal (shared from status page) */
        #genModal .modal-content {
            position: relative;
            width: 98%;
            max-width: none;
            max-height: none;
            height: 95vh;
            margin: 1% auto;
            border: 1px solid #3a3a3a;
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
        }
        .close {
            color: #aaa;
            position: absolute;
            top: 15px;
            right: 20px;
            font-size: 28px;
            font-weight: bold;
            cursor: pointer;
        }
        .close:hover { color: #fff; }
        /* Scoped to #genEditModal so they don't bleed into the shared
           gen details modal (#genModal) — that one needs its inline-styled
           timeframe dropdown to size to its content, not stretch the column. */
        #genEditModal h3 {
            color: #00ff00;
            margin-top: 0;
            margin-bottom: 15px;
            font-size: 16px;
        }
        #genEditModal label {
            color: #00ff00;
            display: block;
            margin-bottom: 4px;
            margin-top: 12px;
            font-size: 12px;
        }
        #genEditModal select, #genEditModal input, #genEditModal textarea {
            width: 100%;
            padding: 8px;
            background: #2a2a2a;
            border: 1px solid #3a3a3a;
            color: white;
            border-radius: 4px;
            font-size: 13px;
            box-sizing: border-box;
        }
        #genEditModal textarea {
            min-height: 60px;
            resize: vertical;
            font-family: inherit;
        }
        .note-input-wrapper {
            position: relative;
            display: flex;
            gap: 8px;
        }
        .note-input-wrapper textarea {
            flex: 1;
        }
        .note-clear-btn {
            background: #333;
            border: 1px solid #444;
            color: #888;
            cursor: pointer;
            font-size: 18px;
            padding: 0 12px;
            border-radius: 4px;
            align-self: stretch;
        }
        .note-clear-btn:hover {
            background: #dc3545;
            border-color: #dc3545;
            color: white;
        }
        .modal-buttons {
            margin-top: 18px;
            display: flex;
            gap: 8px;
            justify-content: center;
        }
        .modal-btn {
            padding: 8px 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
        }
        .modal-btn-save { background: #00ff00; color: black; }
        .modal-btn-save:hover { background: #00cc00; }
        .modal-btn-cancel { background: #666; color: white; }
        .modal-btn-cancel:hover { background: #555; }
        .modal-btn-delete { background: #dc3545; color: white; }
        .modal-btn-delete:hover { background: #b91c1c; }

        /* History panel in modal - compact */
        .history-panel {
            margin-top: 15px;
            padding: 10px;
            background: #0a0a0a;
            border-radius: 6px;
            max-height: 150px;
            overflow-y: auto;
            scrollbar-width: thin;
            scrollbar-color: #666 transparent;
        }
        .history-title {
            color: #4a9eff;
            font-weight: bold;
            margin-bottom: 8px;
            font-size: 12px;
        }
        .history-item {
            padding: 6px;
            background: #1a1a1a;
            border-radius: 4px;
            margin-bottom: 4px;
            font-size: 11px;
        }
        .history-date {
            color: #888;
            font-size: 9px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .history-delete-btn {
            background: transparent;
            border: none;
            color: #666;
            cursor: pointer;
            font-size: 14px;
            padding: 0 4px;
            line-height: 1;
        }
        .history-delete-btn:hover {
            color: #ff4444;
        }
        .history-note {
            color: #e0e0e0;
            margin-top: 2px;
        }
        .history-status {
            display: inline-block;
            padding: 1px 4px;
            border-radius: 2px;
            font-size: 9px;
            margin-left: 4px;
        }

        /* Error text */
        .error-text {
            color: #ff6b6b;
            margin-bottom: 15px;
            display: none;
        }

        /* Login form specific */
        .login-form {
            margin: 20px 0;
        }
        .login-form label {
            text-align: left;
            margin-bottom: 5px;
        }
        .login-form input {
            margin-bottom: 15px;
        }

        /* Drag-drop styling */
        .drag-over {
            border-color: #00ff00 !important;
            background: rgba(0, 255, 0, 0.1) !important;
        }
        .gen-item.drag-over {
            border-left: 3px solid #00ff00 !important;
            margin-left: -2px;
        }
        .gen-item[draggable="true"] {
            cursor: grab;
        }
        .gen-item[draggable="true"]:active {
            cursor: grabbing;
        }

        /* Status colors - used dynamically */
        .status-active { background: #00ff00; color: black; }
        .status-active-swap { background: #ff6600; color: white; }
        .status-swap { background: #ff6600; color: white; }
        .status-inspect { background: #ffaa00; color: black; }
        .status-parts { background: #ff4444; color: white; }
        .status-gas { background: #4a9eff; color: white; }
        .status-transport { background: #aa44ff; color: white; }
        .status-oos { background: #888888; color: white; }

        /* Config changed modal overlay */
        .config-changed-banner {
            display: none;
            position: fixed;
            top: 0;
            left: 50%;
            transform: translateX(-50%);
            z-index: 20000;
            background: #2a2a2a;
            border: 1px solid #ffaa00;
            border-top: none;
            border-radius: 0 0 8px 8px;
            padding: 10px 16px;
            color: #ffaa00;
            font-size: 13px;
            align-items: center;
            gap: 10px;
            box-shadow: 0 4px 14px rgba(0,0,0,0.5);
        }
        .config-changed-banner button {
            background: #ffaa00;
            color: #000;
            border: none;
            padding: 5px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            font-size: 12px;
        }
        .config-changed-banner button.ccb-dismiss {
            background: transparent;
            color: #888;
            font-weight: normal;
            border: 1px solid #444;
        }
        .config-changed-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.85);
            z-index: 20000;
            justify-content: center;
            align-items: center;
        }
        .config-changed-modal {
            background: #1a1a1a;
            border: 2px solid #ff6600;
            border-radius: 12px;
            padding: 40px;
            text-align: center;
            max-width: 400px;
        }
        .config-changed-modal h2 {
            color: #ff6600;
            margin: 0 0 20px 0;
        }
        .config-changed-modal p {
            color: #ccc;
            margin: 0 0 30px 0;
            font-size: 16px;
        }
        .config-changed-modal button {
            background: #ff6600;
            color: white;
            border: none;
            padding: 15px 40px;
            font-size: 16px;
            border-radius: 6px;
            cursor: pointer;
        }
        .config-changed-modal button:hover {
            background: #ff8833;
        }
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
</head>
<body>

<!-- Someone else edited a gen. A BANNER, not a blocking overlay: the old modal
     covered the page and demanded a reload, which meant losing whatever you were
     in the middle of. Now it sits at the top and waits. -->
<div id="configChangedBanner" class="config-changed-banner">
    <span>Another user changed generator data.</span>
    <button onclick="location.reload()">Reload</button>
    <button class="ccb-dismiss" onclick="dismissConfigChanged()" title="Keep working; reload whenever you like">Dismiss</button>
</div>

""")

# Show login modal if not authenticated
if auth_required:
    username = auth.is_authenticated()
    if username:
        print(generate_access_denied_html("Generator Manager"))
    else:
        print(generate_login_modal_html("Generator Manager"))
    print("</body></html>")
    sys.exit(0)

# Get user access for filtered nav
_user_access = AuthManager.get_user_access()

# Main content
print("""
<div id="mainContent">
    <div class="header">
        <div class="dropdown">
            <h1 class="dropdown-title">NGON Mining - Generator Manager</h1>
            <div class="dropdown-content">""")

# Output the dropdown menu
print(generate_dropdown_html(_user_access))

print("""
            </div>
        </div>
        <div class="header-controls">
            <div class="filter-group">
                <label>Filter by Status:</label>
                <div class="multi-select" id="statusFilter">
                    <div class="multi-select-toggle" onclick="toggleFilterDropdown()">
                        <span id="filterLabel">All</span>
                        <i class="fas fa-chevron-down"></i>
                    </div>
                    <div class="multi-select-dropdown" id="filterDropdown">
                        <label class="filter-option">
                            <input type="checkbox" value="all" checked onchange="handleFilterChange(this)"> All
                        </label>
""")

# Output status filter options
for status_key, status_data in GEN_STATUSES.items():
    print(f'''                        <label class="filter-option">
                            <input type="checkbox" value="{status_key}" onchange="handleFilterChange(this)">
                            <span class="status-dot" style="background:{status_data['color']};"></span> {status_data['label']}
                        </label>''')

# Add Loaner filter option (separate from status)
print('''                        <label class="filter-option">
                            <input type="checkbox" value="loaner" onchange="handleFilterChange(this)">
                            <span class="status-dot" style="background:#aa44ff;"></span> Loaner
                        </label>''')

# Retrofit filters - "who still needs this kit" is the working list, so each
# campaign gets a pending filter rather than a chip on every affected gen.
for _key, _camp in RETROFITS.items():
    _label = html.escape(_camp.get("short") or _camp.get("label") or _key)
    _color = _camp.get("color") or "#4a9eff"
    print(f'''                        <label class="filter-option">
                            <input type="checkbox" value="retrofit:{html.escape(_key)}" onchange="handleFilterChange(this)">
                            <span class="status-dot" style="background:{_color};"></span> {_label} pending
                        </label>''')

print("""
                    </div>
                </div>
            </div>
            <div class="header-buttons">
                <button class="btn btn-primary" onclick="exportCSV()"><i class="fas fa-download"></i> Export CSV</button>
            </div>
        </div>
    </div>

    <div class="main-content">
    <!-- Generator Groups by Site -->
    <div id="sitesContainer">
""")

# Output sites with generators
for site_name, site_data in config.get("sites", {}).items():
    generator_groups = site_data.get("generator_groups", {})

    # Count total generators
    total_gens = sum(len(g.get("generators", {})) for g in generator_groups.values())

    # Always show Spares/Out of Service sections (for drag-drop targets).
    # Show normal sites if they have any generators OR any groups defined
    # (empty groups still render as drop targets so gens can be moved in).
    is_special_section = site_name in ["Out of Service", "Out of Service - TX", "Spares"]
    if total_gens == 0 and not is_special_section and not generator_groups:
        continue

    site_id = site_data.get("site_id", "")

    # Special section for Out of Service / Spares
    if site_name in ["Out of Service", "Out of Service - TX", "Spares"]:
        print(f'<div class="oos-section" data-site="{site_name}">')
        print(f'<div class="oos-title"><i class="fas fa-exclamation-triangle"></i> {site_name}</div>')
    else:
        print(f'<div class="site-container" data-site="{site_name}">')
        print(f'<div class="site-header">')
        print(f'<div>')
        print(f'<div class="site-title">{site_name}</div>')
        if site_id:
            print(f'<div class="site-id">{site_id}</div>')
        print(f'</div>')
        print(f'</div>')

    print(f'<div class="groups-wrapper">')

    for group_name, group_data in generator_groups.items():
        generators = group_data.get("generators", {})

        grid_class = "generators-grid empty" if not generators else "generators-grid"
        print(f'<div class="group-container" data-site="{site_name}" data-group="{group_name}" ondrop="dropInGroup(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)">')
        print(f'<div class="group-title">{group_name} ({len(generators)})</div>')
        print(f'<div class="{grid_class}">')
        if not generators:
            print(f'<div class="empty-hint">Drop generator here</div>')

        # Sort generators by ID for special sections (Spares/Out of Service)
        gen_items = sorted(generators.items(), key=lambda x: x[0]) if is_special_section else generators.items()

        for gen_id, gen_data in gen_items:
            if isinstance(gen_data, dict):
                status = gen_data.get("maintenance_status", "active")
                notes = gen_data.get("notes", "")
                is_loaner = gen_data.get("is_loaner", False)
                repaired_at = gen_data.get("repaired_at")
                origin = gen_data.get("origin", "")
                retrofits = gen_data.get("retrofits") or {}
            else:
                status = "active"
                notes = ""
                is_loaner = False
                repaired_at = None
                origin = ""
                retrofits = {}

            # Get status display info
            status_info = GEN_STATUSES.get(status, GEN_STATUSES["active"])
            status_color = status_info["color"]
            status_label = status_info["label"]

            # Escape for HTML attributes
            escaped_notes = notes.replace('"', '&quot;').replace("'", "&#39;")

            # Compact inline display: ID + status badge, note on second line
            badge_html = ""
            if status != "active":
                text_color = "black" if status_color in ["#00ff00", "#ffaa00"] else "white"
                badge_html = f'<span class="gen-status-badge" style="background:{status_color};color:{text_color};">{status_label}</span>'

            loaner_html = ''
            if is_loaner:
                loaner_html = '<span class="gen-status-badge" style="background:#aa44ff;color:white;">Loaner</span>'

            # Origin chip - provenance, not status, so it gets a muted slate
            # colour that can't be mistaken for one of the status badges.
            origin_html = ''
            if origin:
                origin_html = (f'<span class="gen-status-badge gen-origin-chip" '
                               f'title="Came from {html.escape(origin)}">'
                               f'{html.escape(origin)}</span>')

            # Pending retrofits aren't chipped (the list would get noisy) - they
            # surface through the "<kit> pending" filter instead.
            retro_states = {k: (v or {}).get("status", "") for k, v in retrofits.items()
                            if isinstance(v, dict)}
            retro_attr = html.escape(json.dumps(retro_states), quote=True)

            # Self-fading "Repaired" chip (12h, only while back to active)
            repaired_html = ''
            repaired_at_attr = ''
            if status == "active" and repaired_at:
                try:
                    age = time.time() - float(repaired_at)
                except (TypeError, ValueError):
                    age = None
                if age is not None and 0 <= age < 12 * 3600:
                    rlabel = f"{max(1, round(age / 60))}m" if age < 3600 else f"{round(age / 3600)}h"
                    repaired_html = (
                        f'<span class="gen-status-badge gen-repaired-chip" '
                        f'style="background:#00aa44;color:white;" '
                        f'title="Repaired {rlabel} ago">✓ Repaired {rlabel} ago</span>'
                    )
                    repaired_at_attr = f' data-repaired-at="{int(float(repaired_at))}"'

            # Truncate notes for display
            note_html = ""
            if notes:
                display_note = notes[:30] + "..." if len(notes) > 30 else notes
                note_html = f'<div class="gen-note" title="{escaped_notes}">{html.escape(display_note)}</div>'

            is_loaner_str = "true" if is_loaner else "false"
            print(f'<div class="gen-item" draggable="true" data-gen="{gen_id}" data-status="{status}" data-is-loaner="{is_loaner_str}" data-origin="{html.escape(origin, quote=True)}" data-retrofits="{retro_attr}" data-notes="{escaped_notes}" data-site="{site_name}" data-group="{group_name}"{repaired_at_attr} onclick="editGenerator(this)" ondragstart="dragGen(event)" ondragend="endDrag(event)" ondrop="dropOnGenerator(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)">')
            print(f'<div><span class="gen-id">{gen_id}</span>{badge_html}{loaner_html}{origin_html}{repaired_html}</div>{note_html}')
            print(f'</div>')

        print(f'</div>')  # generators-grid
        print(f'</div>')  # group-container

    print(f'</div>')  # groups-wrapper
    print(f'</div>')  # site-container

print("""
    </div>

    <!-- Search Section -->
    <div class="search-section">
        <input type="text" id="searchInput" placeholder="Search generators or notes...">
        <select id="statusFilter">
            <option value="">All Statuses</option>
""")

# Output status options
for status_key, status_data in GEN_STATUSES.items():
    print(f'            <option value="{status_key}">{status_data["label"]}</option>')

print("""
        </select>
        <button class="btn btn-primary" onclick="performSearch()"><i class="fas fa-search"></i> Search</button>
        <button class="btn btn-secondary" onclick="clearSearch()"><i class="fas fa-times"></i> Clear</button>
    </div>

    <!-- Search Results Section -->
    <div id="resultsSection" class="results-section">
        <div class="results-title"><i class="fas fa-search"></i> Search Results</div>
        <div id="resultsContainer"></div>
        <div class="stats-section" id="statsSection"></div>
    </div>
    </div><!-- end main-content -->
</div><!-- end mainContent -->

<!-- Generator Edit Modal -->
<div id="genEditModal" class="modal">
    <div class="modal-content">
        <h3><i class="fas fa-bolt"></i> Edit Generator</h3>
        <p id="genModalId" style="color: #888; font-size: 14px;"></p>

        <label for="genModalStatus">Status:</label>
        <select id="genModalStatus">
""")

for status_key, status_data in GEN_STATUSES.items():
    print(f'            <option value="{status_key}">{status_data["label"]}</option>')

print("""
        </select>

        <label for="genModalLoaner">Loaner:</label>
        <input type="checkbox" id="genModalLoaner" style="accent-color: #aa44ff; width: auto;">

        <label for="genModalOrigin">Origin:</label>
        <input type="text" id="genModalOrigin" placeholder="e.g. TX (where this gen came from)">

""")

# Retrofit selects, one per campaign
for _key, _camp in RETROFITS.items():
    _label = html.escape(_camp.get("label") or _key)
    print(f'''        <label for="genRetrofit_{html.escape(_key)}">{_label}:</label>
        <select id="genRetrofit_{html.escape(_key)}" class="retrofit-select" data-retrofit="{html.escape(_key)}">
            <option value="">Not applicable</option>
            <option value="pending">Pending</option>
            <option value="installed">Installed</option>
        </select>''')

print("""
        <label for="genModalMoveTo">Move To:</label>
        <select id="genModalMoveTo">
            <option value="">-- Keep in current location --</option>
        </select>

        <label for="genModalSwapTo">Swap With (OOS):</label>
        <select id="genModalSwapTo">
            <option value="">-- No swap --</option>
        </select>

        <label for="genModalNote">Active Note:</label>
        <div class="note-input-wrapper">
            <textarea id="genModalNote" placeholder="Enter maintenance note..."></textarea>
            <button type="button" class="note-clear-btn" onclick="clearCurrentNote()" title="Clear active note (keeps in history)">&times;</button>
        </div>

        <!-- Add to History Only -->
        <label for="genModalHistoryNote">Add to History:</label>
        <div style="display: flex; gap: 8px;">
            <textarea id="genModalHistoryNote" placeholder="Add note to history only..." style="flex: 1; min-height: 40px;"></textarea>
            <button type="button" class="modal-btn modal-btn-save" onclick="addHistoryNote()" style="padding: 8px 12px;">Add</button>
        </div>

        <!-- History Panel -->
        <div class="history-panel" id="historyPanel">
            <div class="history-title"><i class="fas fa-history"></i> Note History</div>
            <div id="historyContainer">Loading history...</div>
        </div>

        <div class="modal-buttons">
            <button class="modal-btn" style="background:#4a9eff;color:white;" onclick="var _gid=currentGenId; closeGenModal(); window.generatorModal.show(_gid);"><i class="fas fa-chart-line"></i> View Details</button>
            <button class="modal-btn" style="background:#00aa44;color:white;" onclick="markRepaired()" title="Clear tag + active note, log a Repaired note. Active note text (optional) is saved as the repair detail."><i class="fas fa-check"></i> Mark Repaired</button>
            <button class="modal-btn modal-btn-save" onclick="saveGenerator()">Save</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeGenModal()">Cancel</button>
        </div>
    </div>
</div>
""")

# Pass config data to JavaScript
print("""
<script>
// Generator statuses configuration
window.GEN_STATUSES = """)
print(json.dumps(GEN_STATUSES))
print(""";

// Retrofit campaign definitions (misc.retrofits)
window.GEN_RETROFITS = """)
print(json.dumps(RETROFITS))
print(""";

// Full config data
// What Gen Manager currently DRAWS, hashed. A config-changed event carrying
// this same digest means the write touched something this page doesn't show
// (a PM anchor, a hold, a gas reserve) and is ignored — that is 20-30 events an
// hour that used to interrupt someone.
window.GEN_CONFIG_DIGEST = """ + json.dumps(_GEN_DIGEST) + """;

window.genConfig = """)
# SITES ONLY. This used to ship the entire master_config to the browser, which
# put misc.users (every username with its plaintext password) and
# misc.api_tokens (inventory, research, textbridge, blink, mesa) in view-source
# for anyone holding the gen_manager page key — a far wider group than the two
# people with Site Manager. Nothing here ever read misc: retrofit campaigns and
# statuses are published as their own globals above, and the JS only touches
# genConfig.sites and .last_updated. If something later needs a misc value, add
# that ONE value; never the object.
print(json.dumps({
    "sites": config.get("sites", {}),
    "last_updated": config.get("last_updated"),
}))
print(""";
</script>
<link rel="stylesheet" href="shutdown_history.css?v=2">
<script src="shutdown_history.js?v=1"></script>
<script src="constants_cgi.py"></script>
<script src="status_gen_modal.js?v=17"></script>
<script src="gen_manager.js"></script>
<script>
""")

# Output dropdown JavaScript for mobile
print(generate_dropdown_js())

print("""
</script>
</body>
</html>
""")
