<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<title>Oduflow</title>
<style>
  :root {
    --bg: #0d1117;
    --surface: #161b22;
    --border: #30363d;
    --text: #e6edf3;
    --text-dim: #8b949e;
    --accent: #58a6ff;
    --green: #3fb950;
    --red: #f85149;
    --yellow: #d29922;
    --radius: 8px;
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    background: var(--bg);
    color: var(--text);
    padding: 24px;
    padding-bottom: 56px;
    min-height: 100vh;
  }
  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 24px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--border);
  }
  header h1 { font-size: 20px; font-weight: 600; }
  header .actions { display: flex; gap: 8px; align-items: center; }
  .auto-label { font-size: 12px; color: var(--text-dim); }
  #refresh-btn {
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    padding: 6px 14px;
    border-radius: var(--radius);
    cursor: pointer;
    font-size: 13px;
  }
  #refresh-btn:hover { border-color: var(--accent); }
  .empty {
    text-align: center;
    padding: 64px 0;
    color: var(--text-dim);
    font-size: 15px;
  }
  .env-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 20px;
    margin-bottom: 12px;
  }
  .env-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
  }
  .env-header .left { display: flex; align-items: center; gap: 10px; }
  .branch-name { font-weight: 600; font-size: 15px; }
  .badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 12px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
  }
  .badge-running { background: rgba(63,185,80,0.15); color: var(--green); }
  .badge-partial { background: rgba(210,153,34,0.15); color: var(--yellow); }
  .badge-exited  { background: rgba(248,81,73,0.15); color: var(--red); }
  .env-url { margin-bottom: 10px; }
  .env-url a {
    color: var(--accent);
    text-decoration: none;
    font-size: 13px;
  }
  .env-url a:hover { text-decoration: underline; }
  .env-meta {
    display: flex;
    gap: 24px;
    font-size: 12px;
    color: var(--text-dim);
    margin-bottom: 10px;
  }
  .env-meta strong { color: var(--text); font-weight: 500; }
  .containers {
    font-size: 12px;
    color: var(--text-dim);
    margin-bottom: 12px;
  }
  .containers span { margin-right: 16px; }
  .env-actions { display: flex; gap: 8px; }
  .btn {
    padding: 5px 14px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
  }
  .btn:hover { border-color: var(--accent); color: var(--accent); }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }
  .btn-start { border-color: var(--green); color: var(--green); }
  .btn-start:hover { background: rgba(63,185,80,0.1); }
  .btn-stop { border-color: var(--red); color: var(--red); }
  .btn-stop:hover { background: rgba(248,81,73,0.1); }
  .btn-restart { border-color: var(--yellow); color: var(--yellow); }
  .btn-restart:hover { background: rgba(210,153,34,0.1); }
  .btn-delete { border-color: var(--red); color: var(--red); }
  .btn-delete:hover { background: rgba(248,81,73,0.1); }
  .btn-update { border-color: var(--yellow); color: var(--yellow); }
  .btn-update:hover { background: rgba(210,153,34,0.1); }
  .btn-logs { border-color: var(--accent); color: var(--accent); }
  .btn-logs:hover { background: rgba(88,166,255,0.1); }
  .btn-protect { border-color: var(--yellow); color: var(--yellow); }
  .btn-protect:hover { background: rgba(210,153,34,0.1); }
  .btn-protect.active { background: var(--yellow); color: #000; border-color: var(--yellow); }
  .btn-create {
    background: var(--accent);
    color: #fff;
    border: 1px solid var(--accent);
    padding: 6px 14px;
    border-radius: var(--radius);
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
  }
  .btn-create:hover { opacity: 0.85; }
  .btn-create:disabled { opacity: 0.4; cursor: not-allowed; }
  .form-group { margin-bottom: 14px; }
  .form-group label {
    display: block;
    font-size: 13px;
    font-weight: 500;
    margin-bottom: 4px;
    color: var(--text);
  }
  .form-group input, .form-group textarea {
    width: 100%;
    padding: 8px 10px;
    font-size: 13px;
    font-family: inherit;
    background: var(--bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    outline: none;
    box-sizing: border-box;
  }
  .form-group textarea { resize: vertical; }
  .form-group input:focus, .form-group textarea:focus { border-color: var(--accent); }
  .form-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 18px; }
  .form-error { color: var(--red); font-size: 12px; margin-bottom: 10px; display: none; }
  .modal-overlay {
    position: fixed; top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.6); z-index: 200;
    display: none; align-items: center; justify-content: center;
  }
  .modal-overlay.show { display: flex; }
  .modal {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); width: 80%; max-width: 900px;
    max-height: 80vh; display: flex; flex-direction: column;
  }
  .modal-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 20px; border-bottom: 1px solid var(--border);
  }
  .modal-header h2 { font-size: 15px; font-weight: 600; }
  .modal-close {
    background: none; border: none; color: var(--text-dim);
    font-size: 20px; cursor: pointer; padding: 0 4px;
  }
  .modal-close:hover { color: var(--text); }
  .modal-body {
    padding: 16px 20px; overflow-y: auto; flex: 1;
  }
  .modal-body pre {
    margin: 0; font-size: 12px; line-height: 1.5;
    white-space: pre-wrap; word-break: break-all;
    color: var(--text-dim); font-family: "SFMono-Regular", Consolas, monospace;
  }
  .logs-toolbar {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 20px; border-bottom: 1px solid var(--border);
    flex-shrink: 0;
  }
  .logs-toolbar input[type="text"] {
    flex: 1; max-width: 260px; padding: 5px 8px; font-size: 12px;
    background: var(--bg); color: var(--text); border: 1px solid var(--border);
    border-radius: var(--radius); outline: none; font-family: inherit;
  }
  .logs-toolbar input[type="text"]:focus { border-color: var(--accent); }
  .logs-toolbar select {
    padding: 5px 6px; font-size: 12px; background: var(--bg); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius); outline: none;
    font-family: inherit; cursor: pointer;
  }
  .logs-toolbar .logs-btn {
    padding: 4px 10px; font-size: 12px; border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--bg); color: var(--text-dim);
    cursor: pointer; white-space: nowrap; font-family: inherit;
  }
  .logs-toolbar .logs-btn:hover { border-color: var(--accent); color: var(--text); }
  .logs-toolbar .logs-btn.active { border-color: var(--green); color: var(--green); }
  .logs-match-count { font-size: 11px; color: var(--text-dim); white-space: nowrap; }
  .toast {
    position: fixed;
    bottom: 24px;
    right: 24px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 20px;
    font-size: 13px;
    max-width: 400px;
    opacity: 0;
    transform: translateY(10px);
    transition: all 0.25s;
    pointer-events: none;
    z-index: 100;
  }
  .toast.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
  .toast.error { border-color: var(--red); color: var(--red); }
  .spinner {
    display: inline-block;
    width: 12px; height: 12px;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
    margin-right: 4px;
    vertical-align: middle;
  }
  @keyframes spin { to { transform: rotate(360deg); } }
  .container-stats {
    display: inline-flex;
    gap: 12px;
    margin-left: 8px;
    font-size: 11px;
    color: var(--text-dim);
  }
  .container-stats .stat-val { color: var(--text); font-weight: 500; }
  .system-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding: 8px 24px;
    display: flex;
    align-items: center;
    gap: 24px;
    font-size: 12px;
    color: var(--text-dim);
    z-index: 50;
  }
  .system-bar .sys-label { font-weight: 600; color: var(--text); margin-right: 4px; }
  .system-bar .sys-val { color: var(--text); font-weight: 500; }
  .usage-bar {
    display: inline-block;
    width: 64px;
    height: 10px;
    background: rgba(255,255,255,0.06);
    border-radius: 5px;
    overflow: hidden;
    vertical-align: middle;
    margin-left: 6px;
    border: 1px solid rgba(255,255,255,0.08);
  }
  .usage-bar-fill {
    height: 100%;
    border-radius: 4px;
    transition: width 0.4s ease, background 0.4s ease;
    min-width: 2px;
  }
  .tabs {
    display: flex;
    gap: 0;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--border);
  }
  .tab-btn {
    background: none;
    border: none;
    color: var(--text-dim);
    font-size: 14px;
    font-weight: 500;
    padding: 8px 18px;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    transition: all 0.15s;
  }
  .tab-btn:hover { color: var(--text); }
  .tab-btn.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
  }
  .tab-content { display: none; }
  .tab-content.active { display: block; }
  .tpl-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 20px;
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .tpl-name { font-weight: 600; font-size: 15px; }
  .tpl-meta {
    display: flex;
    gap: 16px;
    font-size: 12px;
    color: var(--text-dim);
  }
  .tpl-meta .tag {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 12px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
  }
  .tag-ok { background: rgba(63,185,80,0.15); color: var(--green); }
  .tag-missing { background: rgba(248,81,73,0.15); color: var(--red); }
  .form-group select {
    width: 100%;
    padding: 8px 10px;
    font-size: 13px;
    background: var(--bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    outline: none;
  }
  .form-group select:focus { border-color: var(--accent); }
  .svc-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 20px;
    margin-bottom: 12px;
  }
  .svc-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
  }
  .svc-header .left { display: flex; align-items: center; gap: 10px; }
  .svc-name { font-weight: 600; font-size: 15px; }
  .svc-meta {
    display: flex;
    gap: 24px;
    font-size: 12px;
    color: var(--text-dim);
    margin-bottom: 6px;
  }
  .svc-meta strong { color: var(--text); font-weight: 500; }
  .svc-actions { display: flex; gap: 8px; }
  .license-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 4px 12px;
    border-radius: 14px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.3px;
    white-space: nowrap;
  }
  .license-unlicensed { background: rgba(248,81,73,0.12); color: var(--red); border: 1px solid rgba(248,81,73,0.3); }
  .license-individual { background: rgba(63,185,80,0.10); color: var(--green); border: 1px solid rgba(63,185,80,0.25); }
  .license-business { background: rgba(63,185,80,0.10); color: var(--green); border: 1px solid rgba(63,185,80,0.25); }
  .license-integrator { background: rgba(88,166,255,0.10); color: var(--accent); border: 1px solid rgba(88,166,255,0.25); }
</style>
</head>
<body>

<header>
  <h1>Oduflow</h1>
  <span id="license-badge" class="license-badge license-unlicensed">UNLICENSED — NON-COMMERCIAL USE ONLY</span>
  <div class="actions">
    <label class="auto-label" for="refresh-interval">Refresh
      <input type="number" id="refresh-interval" value="60" min="5" step="5"
             style="width:48px;background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:2px 4px;font-size:12px;text-align:center;">s
    </label>
    <button id="refresh-btn" onclick="loadEnvironments()">Refresh</button>
    <button class="btn-create" onclick="openCreateModal()">+ Create</button>
  </div>
</header>

<div class="tabs">
  <button class="tab-btn active" data-tab="environments" onclick="switchTab('environments')">Environments</button>
  <button class="tab-btn" data-tab="services" onclick="switchTab('services')">Services</button>
  <button class="tab-btn" data-tab="extra-addons" onclick="switchTab('extra-addons')">Extra Addons</button>
  <button class="tab-btn" data-tab="templates" onclick="switchTab('templates')">Templates</button>
  <button class="tab-btn" data-tab="agents-guide" onclick="switchTab('agents-guide')">Agents Guide</button>
</div>

<div id="tab-environments" class="tab-content active">
  <div id="env-list"></div>
</div>

<div id="tab-templates" class="tab-content">
  <div id="tpl-list"></div>
</div>

<div id="tab-services" class="tab-content">
  <div style="display:flex;justify-content:flex-end;gap:8px;margin-bottom:12px;">
    <button class="btn" style="border-color:var(--green);color:var(--green);" onclick="openRestoreServiceModal()">↻ Restore Service</button>
    <button class="btn-create" onclick="openCreateServiceModal()">+ Create Service</button>
  </div>
  <div id="svc-list"></div>
  <div id="svc-presets-section" style="margin-top:24px;display:none;">
    <h3 style="font-size:14px;font-weight:600;margin-bottom:10px;color:var(--text-dim);">Saved Presets</h3>
    <div id="svc-presets-list"></div>
  </div>
</div>

<div id="tab-extra-addons" class="tab-content">
  <div style="display:flex;justify-content:flex-end;margin-bottom:12px;">
    <button class="btn-create" onclick="openAddExtraRepoModal()">+ Add Repo</button>
  </div>
  <div id="extra-repos-list"></div>
</div>

<div id="tab-agents-guide" class="tab-content">
  <div style="margin-bottom:12px;">
    <span style="font-size:13px;color:var(--text-dim);">Version: <strong id="guide-version">—</strong></span>
  </div>
  <textarea id="guide-editor" 
    style="width:100%;min-height:600px;padding:16px;font-size:13px;font-family:'SFMono-Regular',Consolas,monospace;line-height:1.6;background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);resize:vertical;outline:none;tab-size:2;opacity:0.85;"
    spellcheck="false"
    readonly
    placeholder="Loading agents guide..."></textarea>
</div>

<div class="toast" id="toast"></div>
<div class="modal-overlay" id="create-modal" onclick="closeCreateModal(event)">
  <div class="modal" style="max-width:500px;">
    <div class="modal-header">
      <h2>Create Environment</h2>
      <button class="modal-close" onclick="closeCreateModal()">&times;</button>
    </div>
    <div class="modal-body">
      <div class="form-error" id="create-error"></div>
      <div class="form-group">
        <label for="cr-branch">Branch name</label>
        <input type="text" id="cr-branch" placeholder="e.g. feature/my-branch">
      </div>
      <div class="form-group">
        <label for="cr-repo">Repository URL</label>
        <input type="text" id="cr-repo" placeholder="https://github.com/owner/repo.git">
      </div>
      <div class="form-group">
        <label for="cr-image">Odoo image</label>
        <input type="text" id="cr-image" placeholder="e.g. odoo:17.0">
      </div>
      <div class="form-group">
        <label for="cr-template">Template</label>
        <select id="cr-template">
        </select>
      </div>
      <div class="form-group">
        <label>Extra Addons Repos</label>
        <div id="cr-extra-addons-checkboxes" style="max-height:120px;overflow-y:auto;padding:4px 0;"></div>
        <div style="font-size:11px;color:var(--text-dim);margin-top:4px;">Select repos to mount in this environment</div>
      </div>
      
      <div class="form-actions">
        <button class="btn" onclick="closeCreateModal()">Cancel</button>
        <button class="btn-create" id="cr-submit" onclick="submitCreate()">Create</button>
      </div>
    </div>
  </div>
</div>
<div class="modal-overlay" id="logs-modal" onclick="closeLogsModal(event)">
  <div class="modal">
    <div class="modal-header">
      <h2 id="logs-title">Logs</h2>
      <button class="modal-close" onclick="closeLogsModal()">&times;</button>
    </div>
    <div class="logs-toolbar">
      <input type="text" id="logs-search" placeholder="Filter logs..." oninput="filterLogs()">
      <span class="logs-match-count" id="logs-match-count"></span>
      <select id="logs-lines" onchange="changeLogLines()">
        <option value="100">100 lines</option>
        <option value="300" selected>300 lines</option>
        <option value="500">500 lines</option>
        <option value="1000">1000 lines</option>
      </select>
      <button class="logs-btn" onclick="fetchLogs()" title="Refresh logs">↻ Refresh</button>
    </div>
    <div class="modal-body"><pre id="logs-content">Loading...</pre></div>
  </div>
</div>
<div class="modal-overlay" id="create-svc-modal" onclick="closeCreateServiceModal(event)">
  <div class="modal" style="max-width:500px;">
    <div class="modal-header">
      <h2>Create Service</h2>
      <button class="modal-close" onclick="closeCreateServiceModal()">&times;</button>
    </div>
    <div class="modal-body">
      <div class="form-error" id="create-svc-error"></div>
      <div class="form-group">
        <label for="svc-name">Service name</label>
        <input type="text" id="svc-name" placeholder="e.g. redis, meilisearch">
      </div>
      <div class="form-group">
        <label for="svc-image">Docker image</label>
        <input type="text" id="svc-image" placeholder="e.g. redis:7, getmeili/meilisearch:v1.6">
      </div>
      <div class="form-group">
        <label for="svc-port">Container port</label>
        <input type="number" id="svc-port" placeholder="e.g. 6379">
      </div>
      <div class="form-group">
        <label for="svc-hostname">Hostname (optional, traefik mode)</label>
        <input type="text" id="svc-hostname" placeholder="e.g. redis.example.com">
      </div>
      <div class="form-group">
        <label for="svc-envvars">Environment variables (optional)</label>
        <textarea id="svc-envvars" rows="5" placeholder="KEY1=val1&#10;KEY2=val2"></textarea>
      </div>
      <div class="form-actions">
        <button class="btn" onclick="closeCreateServiceModal()">Cancel</button>
        <button class="btn-create" id="svc-submit" onclick="submitCreateService()">Create</button>
      </div>
    </div>
  </div>
</div>
<div class="modal-overlay" id="restore-svc-modal" onclick="closeRestoreServiceModal(event)">
  <div class="modal" style="max-width:420px;">
    <div class="modal-header">
      <h2>Restore Service</h2>
      <button class="modal-close" onclick="closeRestoreServiceModal()">&times;</button>
    </div>
    <div class="modal-body">
      <div class="form-error" id="restore-svc-error"></div>
      <div class="form-group">
        <label for="restore-svc-select">Select preset</label>
        <select id="restore-svc-select" style="width:100%;padding:8px 10px;font-size:13px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);">
          <option value="">Loading...</option>
        </select>
      </div>
      <div id="restore-svc-details" style="font-size:12px;color:var(--text-dim);margin-top:8px;"></div>
      <div class="form-actions">
        <button class="btn" onclick="closeRestoreServiceModal()">Cancel</button>
        <button class="btn-create" id="restore-svc-submit" onclick="submitRestoreService()" style="background:var(--green);border-color:var(--green);">Restore</button>
      </div>
    </div>
  </div>
</div>
<div class="system-bar" id="system-bar">
  <span><span class="sys-label">System</span></span>
  <span>CPU: <span class="sys-val" id="sys-cpu">—</span></span>
  <span>RAM: <span class="sys-val" id="sys-ram">—</span></span>
  <span>Load: <span class="sys-val" id="sys-load">—</span></span>
</div>

<div class="modal-overlay" id="license-modal" onclick="closeLicenseModal(event)">
  <div class="modal" style="max-width:460px;">
    <div class="modal-header">
      <h2>Activate License</h2>
      <button class="modal-close" onclick="closeLicenseModal()">&times;</button>
    </div>
    <div class="modal-body">
      <p style="font-size:13px;color:var(--text-dim);margin-bottom:16px;">
        Paste your license key below. Purchase a key at <a href="https://oduflow.dev" target="_blank" style="color:var(--accent);">oduflow.dev</a>
      </p>
      <div class="form-error" id="license-error"></div>
      <div class="form-group">
        <label for="lic-key">License key</label>
        <textarea id="lic-key" rows="4" placeholder="Paste your license key here..." style="font-family:SFMono-Regular,Consolas,monospace;font-size:12px;"></textarea>
      </div>
      <div class="form-actions">
        <button class="btn" onclick="closeLicenseModal()">Cancel</button>
        <button class="btn-create" id="lic-submit" onclick="activateLicense()">Activate</button>
      </div>
    </div>
  </div>
</div>
<div class="modal-overlay" id="add-extra-repo-modal" onclick="closeAddExtraRepoModal(event)">
  <div class="modal" style="max-width:500px;">
    <div class="modal-header">
      <h2>Add Extra Addons Repository</h2>
      <button class="modal-close" onclick="closeAddExtraRepoModal()">&times;</button>
    </div>
    <div class="modal-body">
      <div class="form-error" id="add-extra-repo-error"></div>
      <div class="form-group">
        <label for="extra-repo-name">Name</label>
        <input type="text" id="extra-repo-name" placeholder="e.g. enterprise, custom-themes">
      </div>
      <div class="form-group">
        <label for="extra-repo-url">Repository URL</label>
        <input type="text" id="extra-repo-url" placeholder="https://github.com/odoo/enterprise.git">
      </div>
      <div class="form-actions">
        <button class="btn" onclick="closeAddExtraRepoModal()">Cancel</button>
        <button class="btn-create" id="extra-repo-submit" onclick="submitAddExtraRepo()">Clone</button>
      </div>
    </div>
  </div>
</div>
<script>
const API = '/api/environments';
const API_TEMPLATES = '/api/templates';
const MIN_INTERVAL = 5;
let refreshInterval = 60;
let refreshTimer = null;
let statsTimer = null;
let containerStatsMap = {};
let cachedTemplates = [];

function showToast(msg, isError) {
  const t = document.getElementById('toast');
  t.textContent = msg;
  t.className = 'toast show' + (isError ? ' error' : '');
  clearTimeout(t._tid);
  t._tid = setTimeout(() => t.className = 'toast', 3000);
}

function badgeClass(status) {
  if (status === 'running') return 'badge-running';
  if (status === 'partial') return 'badge-partial';
  return 'badge-exited';
}

function usageColor(pct) {
  // Smooth HSL gradient: 120 (green) → 60 (yellow) → 30 (orange) → 0 (red)
  var hue = Math.max(0, 120 - pct * 1.2);
  var sat = 70 + Math.min(pct, 100) * 0.15;
  var lit = 45 + (pct < 50 ? 0 : (pct - 50) * 0.1);
  return 'hsl(' + hue + ',' + sat + '%,' + lit + '%)';
}

function usageBar(pct) {
  var w = Math.min(pct, 100);
  var color = usageColor(pct);
  return '<span class="usage-bar"><span class="usage-bar-fill" style="' +
    'width:' + Math.max(w, 2) + '%;background:' + color + '"></span></span>';
}

function containerStatsHtml(name) {
  const s = containerStatsMap[name];
  if (!s) return '';
  return '<span class="container-stats">' +
    '<span>CPU <span class="stat-val">' + s.cpu_percent + '%</span></span>' +
    '<span>RAM <span class="stat-val">' + s.mem_usage_mb + ' MB</span> ' +
      usageBar(s.mem_percent) + '</span>' +
  '</span>';
}

function renderEnvironments(envs) {
  const el = document.getElementById('env-list');
  if (!envs || envs.length === 0) {
    el.innerHTML = '<div class="empty">No environments found.<br>Create one via MCP tools.</div>';
    return;
  }

  el.innerHTML = envs.map(env => {
    const allRunning = env.status === 'running';
    const allStopped = env.containers.every(c => c.status !== 'running');

    const containerHtml = env.containers.map(c =>
      '<span>' + esc(c.name) + ' [' + esc(c.status) + ']' + containerStatsHtml(c.name) + '</span>'
    ).join('');

    const urlHtml = env.url
      ? '<div class="env-url"><a href="' + esc(env.url) + '" target="_blank">' + esc(env.url) + '</a></div>'
      : '';

    const metaHtml = (env.odoo_image || env.repo_url || env.db_name)
      ? '<div class="env-meta">' +
          (env.db_name ? '<span>DB: <strong>' + esc(env.db_name) + '</strong></span>' : '') +
          (env.odoo_image ? '<span>Image: <strong>' + esc(env.odoo_image) + '</strong></span>' : '') +
          (env.repo_url ? '<span>Repo: <strong>' + esc(env.repo_url) + '</strong></span>' : '') +
        '</div>'
      : '';

    var extraHtml = '';
    if (env.extra_addons && typeof env.extra_addons === 'object' && Object.keys(env.extra_addons).length > 0) {
      var parts = Object.entries(env.extra_addons).map(function(e) {
        return esc(e[0]) + ' <span style="color:var(--text-dim);">(' + esc(e[1]) + ')</span>';
      });
      extraHtml = '<div class="env-meta"><span>Extra: <strong>' + parts.join(', ') + '</strong></span></div>';
    }

    return '<div class="env-card" data-branch="' + esc(env.branch) + '">' +
      '<div class="env-header">' +
        '<div class="left">' +
          '<span class="branch-name">' + esc(env.branch) + '</span>' +
          '<span class="badge ' + badgeClass(env.status) + '">' + esc(env.status) + '</span>' +
        '</div>' +
        '<div class="env-actions">' +
          '<button class="btn btn-start" onclick="doAction(\'' + escAttr(env.branch) + '\',\'start\')" ' +
            (allRunning ? 'disabled' : '') + '>Start</button>' +
          '<button class="btn btn-stop" onclick="doAction(\'' + escAttr(env.branch) + '\',\'stop\')" ' +
            (allStopped || env.protected ? 'disabled title="Environment is protected"' : '') + '>Stop</button>' +
          '<button class="btn btn-restart" onclick="doAction(\'' + escAttr(env.branch) + '\',\'restart\')" ' +
            (allStopped ? 'disabled' : '') + '>Restart</button>' +
          '<button class="btn btn-logs" onclick="showLogs(\'' + escAttr(env.branch) + '\')">Logs</button>' +
          '<button class="btn btn-protect' + (env.protected ? ' active' : '') + '" ' +
            'onclick="doProtect(\'' + escAttr(env.branch) + '\',' + (env.protected ? 'true' : 'false') + ')">' +
            (env.protected ? '🔒 Protected' : '🔓 Protect') + '</button>' +
          '<button class="btn btn-delete" onclick="doDelete(\'' + escAttr(env.branch) + '\')" ' +
            (env.protected ? 'disabled title="Environment is protected"' : '') + '>Delete</button>' +
        '</div>' +
      '</div>' +
      urlHtml +
      metaHtml +
      extraHtml +
      '<div class="containers">' + containerHtml + '</div>' +
    '</div>';
  }).join('');
}

function esc(s) {
  const d = document.createElement('div');
  d.textContent = s || '';
  return d.innerHTML;
}

function escAttr(s) {
  return (s || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}

async function loadEnvironments() {
  try {
    const r = await fetch(API);
    const data = await r.json();
    if (data.ok) {
      renderEnvironments(data.environments);
    } else {
      showToast(data.error || 'Failed to load', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
}

async function loadStats() {
  try {
    const r = await fetch('/api/stats');
    const data = await r.json();
    if (!data.ok) return;

    containerStatsMap = {};
    (data.containers || []).forEach(c => { containerStatsMap[c.name] = c; });

    const sys = data.system;
    document.getElementById('sys-cpu').innerHTML =
      sys.cpu_percent + '%' + usageBar(sys.cpu_percent);
    document.getElementById('sys-ram').innerHTML =
      sys.mem_used_mb + ' / ' + sys.mem_total_mb + ' MB' + usageBar(sys.mem_percent);
    document.getElementById('sys-load').textContent =
      sys.load_avg.join(' / ');

    loadEnvironments();
  } catch (e) {
    // stats are optional, don't show errors
  }
}

async function doDelete(branch) {
  if (!confirm('Delete environment "' + branch + '"? This will remove the container, database, and workspace.')) return;
  const card = document.querySelector('[data-branch="' + branch + '"]');
  if (card) card.querySelectorAll('.btn').forEach(b => b.disabled = true);
  try {
    const r = await fetch(API + '/' + encodeURIComponent(branch) + '/delete', { method: 'POST' });
    const data = await r.json();
    if (data.ok) {
      showToast(branch + ': deleted');
    } else {
      showToast(data.error || 'Delete failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
  await loadEnvironments();
}

async function doProtect(branch, isProtected) {
  var action = isProtected ? 'unprotect' : 'protect';
  if (isProtected && !confirm('Remove protection from "' + branch + '"?')) return;
  try {
    var r = await fetch(API + '/' + encodeURIComponent(branch) + '/' + action, { method: 'POST' });
    var data = await r.json();
    if (data.ok) {
      showToast(branch + ': ' + (isProtected ? 'unprotected' : 'protected'));
    } else {
      showToast(data.error || action + ' failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
  await loadEnvironments();
}

var _logsState = { branch: '', lines: [] };

async function showLogs(branch) {
  _logsState.branch = branch;
  _logsState.lines = [];
  document.getElementById('logs-search').value = '';
  document.getElementById('logs-match-count').textContent = '';
  var modal = document.getElementById('logs-modal');
  document.getElementById('logs-title').textContent = 'Logs — ' + branch;
  document.getElementById('logs-content').textContent = 'Loading...';
  modal.classList.add('show');
  document.getElementById('logs-search').focus();
  await fetchLogs();
}

async function fetchLogs() {
  var n = document.getElementById('logs-lines').value;
  try {
    var r = await fetch(API + '/' + encodeURIComponent(_logsState.branch) + '/logs?n=' + n);
    var data = await r.json();
    if (data.ok) {
      _logsState.lines = (data.logs || '').split('\n');
      renderLogs();
    } else {
      document.getElementById('logs-content').textContent = 'Error: ' + (data.error || 'Failed to load logs');
    }
  } catch (e) {
    document.getElementById('logs-content').textContent = 'Connection error: ' + e.message;
  }
}

function renderLogs() {
  var lines = _logsState.lines.slice();
  var search = document.getElementById('logs-search').value.toLowerCase();
  if (search) {
    lines = lines.filter(function(l) { return l.toLowerCase().indexOf(search) !== -1; });
    document.getElementById('logs-match-count').textContent = lines.length + ' match' + (lines.length !== 1 ? 'es' : '');
  } else {
    document.getElementById('logs-match-count').textContent = '';
  }
  document.getElementById('logs-content').textContent = lines.join('\n') || '(empty)';
  var body = document.getElementById('logs-content').parentElement;
  body.scrollTop = body.scrollHeight;
}

function filterLogs() { renderLogs(); }

function changeLogLines() { fetchLogs(); }

function closeLogsModal(event) {
  if (event && event.target !== event.currentTarget) return;
  document.getElementById('logs-modal').classList.remove('show');
}

function switchTab(name) {
  document.querySelectorAll('.tab-btn').forEach(function(b) {
    b.classList.toggle('active', b.getAttribute('data-tab') === name);
  });
  document.querySelectorAll('.tab-content').forEach(function(el) {
    el.classList.toggle('active', el.id === 'tab-' + name);
  });
  if (name === 'templates') loadTemplates();
  if (name === 'services') loadServices();
  if (name === 'agents-guide') loadAgentsGuide();
  if (name === 'extra-addons') loadExtraRepos();
}

async function loadTemplates() {
  try {
    var r = await fetch(API_TEMPLATES);
    var data = await r.json();
    if (data.ok) {
      cachedTemplates = data.templates || [];
      renderTemplates(cachedTemplates);
    } else {
      showToast(data.error || 'Failed to load templates', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
}

function renderTemplates(templates) {
  var el = document.getElementById('tpl-list');
  if (!templates || templates.length === 0) {
    el.innerHTML = '<div class="empty">No template profiles found.</div>';
    return;
  }
  el.innerHTML = templates.map(function(t) {
    var dbTag = t.db_loaded
      ? '<span class="tag tag-ok">DB Loaded</span>'
      : '<span class="tag tag-missing">DB Not Loaded</span>';
    var sqlTag = t.has_sql
      ? '<span class="tag tag-ok">SQL</span>'
      : '<span class="tag tag-missing">No SQL</span>';
    var fsTag = t.has_filestore
      ? '<span class="tag tag-ok">Filestore</span>'
      : '<span class="tag tag-missing">No Filestore</span>';
    var metaHtml = '';
    if (t.odoo_image || t.repo_url) {
      var parts = [];
      if (t.odoo_image) parts.push('<span>Image: <strong>' + esc(t.odoo_image) + '</strong></span>');
      if (t.repo_url) parts.push('<span>Repo: <strong>' + esc(t.repo_url) + '</strong></span>');
      metaHtml = '<div class="tpl-meta" style="margin-top:8px;">' + parts.join('') + '</div>';
    }
    return '<div class="tpl-card" style="flex-direction:column;align-items:stretch;">' +
      '<div style="display:flex;align-items:center;justify-content:space-between;">' +
        '<span class="tpl-name">' + esc(t.template_name) + '</span>' +
        '<div style="display:flex;align-items:center;gap:8px;">' +
          '<div class="tpl-meta">' + dbTag + sqlTag + fsTag + '</div>' +
          '<button class="btn-create" style="font-size:12px;padding:4px 12px;" onclick="openCreateFromTemplate(\'' + escAttr(t.template_name) + '\')">+ New Env</button>' +
        '</div>' +
      '</div>' +
      metaHtml +
    '</div>';
  }).join('');
}

function populateTemplateSelect() {
  var sel = document.getElementById('cr-template');
  sel.innerHTML = '';
  cachedTemplates.forEach(function(t) {
    sel.innerHTML += '<option value="' + esc(t.template_name) + '">' + esc(t.template_name) + '</option>';
  });
  sel.innerHTML += '<option value="none">None (init from scratch)</option>';
  sel.onchange = function() {
    var tpl = cachedTemplates.find(function(t) { return t.template_name === sel.value; });
    if (tpl) {
      if (tpl.repo_url && !document.getElementById('cr-repo').value.trim()) {
        document.getElementById('cr-repo').value = tpl.repo_url;
      }
      if (tpl.odoo_image && !document.getElementById('cr-image').value.trim()) {
        document.getElementById('cr-image').value = tpl.odoo_image;
      }
      var tplExtras = tpl.extra_addons || {};
      document.querySelectorAll('.cr-extra-cb').forEach(function(cb) {
        cb.checked = cb.value in tplExtras;
        var brInput = document.querySelector('.cr-extra-branch-input[data-repo="' + cb.value + '"]');
        if (brInput) brInput.value = tplExtras[cb.value] || '';
      });
    }
  };
}

function openCreateFromTemplate(templateName) {
  var tpl = cachedTemplates.find(function(t) { return t.template_name === templateName; });
  document.getElementById('cr-branch').value = '';
  document.getElementById('cr-repo').value = tpl && tpl.repo_url ? tpl.repo_url : '';
  document.getElementById('cr-image').value = tpl && tpl.odoo_image ? tpl.odoo_image : '';
  var errEl = document.getElementById('create-error');
  errEl.style.display = 'none';
  errEl.textContent = '';
  document.getElementById('cr-submit').disabled = false;
  loadTemplates().then(function() {
    populateTemplateSelect();
    var sel = document.getElementById('cr-template');
    sel.value = templateName;
  });
  loadExtraRepos().then(function() {
    var container = document.getElementById('cr-extra-addons-checkboxes');
    if (cachedExtraRepos.length === 0) {
      container.innerHTML = '<span style="font-size:12px;color:var(--text-dim);">No extra repos available</span>';
      return;
    }
    var tplExtras = (tpl && tpl.extra_addons) ? tpl.extra_addons : {};
    container.innerHTML = cachedExtraRepos.map(function(r) {
      var checked = (r.name in tplExtras) ? ' checked' : '';
      var defaultBranch = tplExtras[r.name] || '';
      return '<label style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px;cursor:pointer;">' +
        '<input type="checkbox" class="cr-extra-cb" value="' + escAttr(r.name) + '"' + checked + '>' +
        esc(r.name) + ' <span style="color:var(--text-dim);font-size:11px;">(' + esc(r.repo_url) + ')</span>' +
        '<input type="text" class="cr-extra-branch-input" data-repo="' + escAttr(r.name) + '"' +
        ' placeholder="branch" value="' + escAttr(defaultBranch) + '"' +
        ' style="width:100px;font-size:12px;padding:2px 6px;margin-left:auto;">' +
      '</label>';
    }).join('');
  });
  document.getElementById('create-modal').classList.add('show');
  document.getElementById('cr-branch').focus();
}

function openCreateModal() {
  document.getElementById('cr-branch').value = '';
  document.getElementById('cr-repo').value = '';
  document.getElementById('cr-image').value = '';
  
  var errEl = document.getElementById('create-error');
  errEl.style.display = 'none';
  errEl.textContent = '';
  document.getElementById('cr-submit').disabled = false;
  loadTemplates().then(populateTemplateSelect);
  loadExtraRepos().then(function() {
    var container = document.getElementById('cr-extra-addons-checkboxes');
    if (cachedExtraRepos.length === 0) {
      container.innerHTML = '<span style="font-size:12px;color:var(--text-dim);">No extra repos available</span>';
      return;
    }
    container.innerHTML = cachedExtraRepos.map(function(r) {
      return '<label style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px;cursor:pointer;">' +
        '<input type="checkbox" class="cr-extra-cb" value="' + escAttr(r.name) + '">' +
        esc(r.name) + ' <span style="color:var(--text-dim);font-size:11px;">(' + esc(r.repo_url) + ')</span>' +
        '<input type="text" class="cr-extra-branch-input" data-repo="' + escAttr(r.name) + '"' +
        ' placeholder="branch" value=""' +
        ' style="width:100px;font-size:12px;padding:2px 6px;margin-left:auto;">' +
      '</label>';
    }).join('');
  });
  document.getElementById('create-modal').classList.add('show');
  document.getElementById('cr-branch').focus();
}

function closeCreateModal(event) {
  if (event && event.target !== event.currentTarget) return;
  document.getElementById('create-modal').classList.remove('show');
}

async function submitCreate() {
  var branch = document.getElementById('cr-branch').value.trim();
  var repo = document.getElementById('cr-repo').value.trim();
  var image = document.getElementById('cr-image').value.trim();
  var template = document.getElementById('cr-template').value;
  var errEl = document.getElementById('create-error');

  if (!branch) {
    errEl.textContent = 'Branch name is required.';
    errEl.style.display = 'block';
    return;
  }

  errEl.style.display = 'none';
  var btn = document.getElementById('cr-submit');
  btn.disabled = true;
  btn.textContent = 'Creating...';

  try {
    var extraAddonsObj = {};
    document.querySelectorAll('.cr-extra-cb:checked').forEach(function(cb) {
      var repoName = cb.value;
      var branchInput = document.querySelector('.cr-extra-branch-input[data-repo="' + repoName + '"]');
      var br = branchInput ? branchInput.value.trim() : '';
      extraAddonsObj[repoName] = br;
    });
    var payload = {
      branch_name: branch,
      repo_url: repo,
      odoo_image: image,
      template_name: template,
      extra_addons: extraAddonsObj,
    };
    var r = await fetch(API + '/create', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(payload)
    });
    var data = await r.json();
    if (data.ok) {
      closeCreateModal();
      showToast('Environment "' + branch + '" created');
      await loadEnvironments();
    } else {
      errEl.textContent = data.error || 'Creation failed';
      errEl.style.display = 'block';
    }
  } catch (e) {
    errEl.textContent = 'Connection error: ' + e.message;
    errEl.style.display = 'block';
  } finally {
    btn.disabled = false;
    btn.textContent = 'Create';
  }
}

async function doAction(branch, action) {
  const card = document.querySelector('[data-branch="' + branch + '"]');
  if (card) card.querySelectorAll('.btn').forEach(b => b.disabled = true);

  try {
    const r = await fetch(API + '/' + encodeURIComponent(branch) + '/' + action, { method: 'POST' });
    const data = await r.json();
    if (data.ok) {
      showToast(branch + ': ' + action + ' OK');
    } else {
      showToast(data.error || 'Action failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }

  await loadEnvironments();
}

function resetTimers() {
  clearInterval(refreshTimer);
  clearInterval(statsTimer);
  var ms = refreshInterval * 1000;
  refreshTimer = setInterval(loadEnvironments, ms);
  statsTimer = setInterval(loadStats, ms);
}

document.getElementById('refresh-interval').addEventListener('change', function() {
  var v = parseInt(this.value, 10);
  if (isNaN(v) || v < MIN_INTERVAL) v = MIN_INTERVAL;
  this.value = v;
  refreshInterval = v;
  resetTimers();
});

const API_SERVICES = '/api/services';

async function loadServices() {
  try {
    var r = await fetch(API_SERVICES);
    var data = await r.json();
    if (data.ok) {
      renderServices(data.services || []);
    } else {
      showToast(data.error || 'Failed to load services', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
}

var SENSITIVE_PATTERNS = /key|password|token/i;

function maskValue(key, val) {
  if (!val) return val;
  if (!SENSITIVE_PATTERNS.test(key)) return val;
  return String(val).replace(/./g, '#');
}

function renderServices(services) {
  var el = document.getElementById('svc-list');
  if (!services || services.length === 0) {
    el.innerHTML = '<div class="empty">No services found.<br>Create one to get started.</div>';
    return;
  }
  el.innerHTML = services.map(function(svc) {
    var urlHtml = svc.url
      ? '<div class="svc-meta"><span>URL: <a href="' + esc(svc.url) + '" target="_blank" style="color:var(--accent);text-decoration:none;">' + esc(svc.url) + '</a></span></div>'
      : '';
    var envHtml = '';
    if (svc.env_vars && Object.keys(svc.env_vars).length > 0) {
      var envStr = Object.keys(svc.env_vars).map(function(k) { return esc(k) + '=' + esc(maskValue(k, svc.env_vars[k])); }).join(', ');
      envHtml = '<div class="svc-meta"><span>Env: <strong>' + envStr + '</strong></span></div>';
    }
    return '<div class="svc-card" data-svc="' + esc(svc.name) + '">' +
      '<div class="svc-header">' +
        '<div class="left">' +
          '<span class="svc-name">' + esc(svc.name) + '</span>' +
          '<span class="badge ' + badgeClass(svc.status) + '">' + esc(svc.status) + '</span>' +
        '</div>' +
        '<div class="svc-actions">' +
          '<button class="btn btn-update" onclick="doUpdateService(\'' + escAttr(svc.name) + '\')">Update</button>' +
          '<button class="btn btn-logs" onclick="showServiceLogs(\'' + escAttr(svc.name) + '\')">Logs</button>' +
          '<button class="btn btn-delete" onclick="doDeleteService(\'' + escAttr(svc.name) + '\')">Delete</button>' +
        '</div>' +
      '</div>' +
      '<div class="svc-meta">' +
        '<span>Image: <strong>' + esc(svc.image) + '</strong></span>' +
        '<span>Container: <strong>' + esc(svc.container_name) + '</strong></span>' +
        (svc.port ? '<span>Port: <strong>' + svc.port + '</strong></span>' : '') +
      '</div>' +
      urlHtml +
      envHtml +
    '</div>';
  }).join('');
}

async function doUpdateService(name) {
  if (!confirm('Update service "' + name + '"? This will pull the latest image and recreate the container.')) return;
  var card = document.querySelector('[data-svc="' + name + '"]');
  if (card) card.querySelectorAll('.btn').forEach(function(b) { b.disabled = true; });
  try {
    var r = await fetch(API_SERVICES + '/' + encodeURIComponent(name) + '/update', { method: 'POST' });
    var data = await r.json();
    if (data.ok) {
      var res = data.result || {};
      var digest = (res.new_digest || '').substring(0, 19);
      if (res.image_updated) {
        showToast('Service "' + name + '" updated — new image pulled (' + digest + ')');
      } else {
        showToast('Service "' + name + '" is already up-to-date (' + digest + ')');
      }
    } else {
      showToast(data.error || 'Update failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
  await loadServices();
}

async function doDeleteService(name) {
  if (!confirm('Delete service "' + name + '"? This will remove the container and its data.')) return;
  var card = document.querySelector('[data-svc="' + name + '"]');
  if (card) card.querySelectorAll('.btn').forEach(function(b) { b.disabled = true; });
  try {
    var r = await fetch(API_SERVICES + '/' + encodeURIComponent(name) + '/delete', { method: 'POST' });
    var data = await r.json();
    if (data.ok) {
      showToast('Service "' + name + '" deleted');
    } else {
      showToast(data.error || 'Delete failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
  await loadServices();
}

async function showServiceLogs(name) {
  var modal = document.getElementById('logs-modal');
  document.getElementById('logs-title').textContent = 'Service Logs — ' + name;
  document.getElementById('logs-content').textContent = 'Loading...';
  modal.classList.add('show');
  document.getElementById('logs-search').focus();
  try {
    var r = await fetch(API_SERVICES + '/' + encodeURIComponent(name) + '/logs?n=300');
    var data = await r.json();
    if (data.ok) {
      document.getElementById('logs-content').textContent = data.logs || '(empty)';
      var body = document.getElementById('logs-content').parentElement;
      body.scrollTop = body.scrollHeight;
    } else {
      document.getElementById('logs-content').textContent = 'Error: ' + (data.error || 'Failed to load logs');
    }
  } catch (e) {
    document.getElementById('logs-content').textContent = 'Connection error: ' + e.message;
  }
}

function openCreateServiceModal() {
  document.getElementById('svc-name').value = '';
  document.getElementById('svc-image').value = '';
  document.getElementById('svc-port').value = '';
  document.getElementById('svc-hostname').value = '';
  document.getElementById('svc-envvars').value = '';
  var errEl = document.getElementById('create-svc-error');
  errEl.style.display = 'none';
  errEl.textContent = '';
  document.getElementById('svc-submit').disabled = false;
  document.getElementById('create-svc-modal').classList.add('show');
  document.getElementById('svc-name').focus();
}

function closeCreateServiceModal(event) {
  if (event && event.target !== event.currentTarget) return;
  document.getElementById('create-svc-modal').classList.remove('show');
}

async function submitCreateService() {
  var name = document.getElementById('svc-name').value.trim();
  var image = document.getElementById('svc-image').value.trim();
  var port = document.getElementById('svc-port').value.trim();
  var hostname = document.getElementById('svc-hostname').value.trim();
  var envvars = document.getElementById('svc-envvars').value.trim();
  var errEl = document.getElementById('create-svc-error');

  if (!name || !image || !port) {
    errEl.textContent = 'Service name, Docker image and port are required.';
    errEl.style.display = 'block';
    return;
  }

  errEl.style.display = 'none';
  var btn = document.getElementById('svc-submit');
  btn.disabled = true;
  btn.textContent = 'Creating...';

  try {
    var payload = { name: name, image: image, port: parseInt(port, 10) };
    if (hostname) payload.hostname = hostname;
    if (envvars) payload.env_vars = envvars;
    var r = await fetch(API_SERVICES + '/create', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(payload)
    });
    var data = await r.json();
    if (data.ok) {
      closeCreateServiceModal();
      showToast('Service "' + name + '" created');
      await loadServices();
    } else {
      errEl.textContent = data.error || 'Creation failed';
      errEl.style.display = 'block';
    }
  } catch (e) {
    errEl.textContent = 'Connection error: ' + e.message;
    errEl.style.display = 'block';
  } finally {
    btn.disabled = false;
    btn.textContent = 'Create';
  }
}

// --- Service Presets ---
const API_PRESETS = '/api/service-presets';
var _cachedPresets = [];

async function loadServicePresets() {
  try {
    var r = await fetch(API_PRESETS);
    var data = await r.json();
    if (data.ok) {
      _cachedPresets = data.presets || [];
      renderPresetsList(_cachedPresets);
    }
  } catch (e) { /* silent */ }
}

function renderPresetsList(presets) {
  var section = document.getElementById('svc-presets-section');
  var el = document.getElementById('svc-presets-list');
  if (!presets || presets.length === 0) {
    section.style.display = 'none';
    return;
  }
  section.style.display = 'block';
  el.innerHTML = presets.map(function(p) {
    var envStr = '';
    if (p.env_vars && Object.keys(p.env_vars).length > 0) {
      envStr = '<span style="margin-left:12px;">Env: <strong>' + Object.keys(p.env_vars).map(function(k) { return esc(k) + '=' + esc(maskValue(k, p.env_vars[k])); }).join(', ') + '</strong></span>';
    }
    return '<div class="env-card" style="padding:10px 16px;">' +
      '<div style="display:flex;align-items:center;justify-content:space-between;">' +
        '<div style="display:flex;align-items:center;gap:10px;">' +
          '<span style="font-weight:600;font-size:13px;">' + esc(p.name) + '</span>' +
          '<span class="badge" style="background:rgba(88,166,255,0.12);color:var(--accent);">preset</span>' +
        '</div>' +
        '<div style="display:flex;gap:6px;">' +
          '<button class="btn btn-start" style="font-size:11px;padding:3px 10px;" onclick="doRestorePreset(\'' + escAttr(p.name) + '\')">Restore</button>' +
          '<button class="btn btn-delete" style="font-size:11px;padding:3px 10px;" onclick="doDeletePreset(\'' + escAttr(p.name) + '\')">Delete</button>' +
        '</div>' +
      '</div>' +
      '<div style="font-size:11px;color:var(--text-dim);margin-top:4px;">' +
        '<span>Image: <strong>' + esc(p.image) + '</strong></span>' +
        '<span style="margin-left:12px;">Port: <strong>' + p.port + '</strong></span>' +
        (p.hostname ? '<span style="margin-left:12px;">Host: <strong>' + esc(p.hostname) + '</strong></span>' : '') +
        envStr +
      '</div>' +
    '</div>';
  }).join('');
}

async function doRestorePreset(name) {
  if (!confirm('Restore service "' + name + '" from preset?')) return;
  try {
    var r = await fetch(API_PRESETS + '/' + encodeURIComponent(name) + '/restore', { method: 'POST' });
    var data = await r.json();
    if (data.ok) {
      showToast('Service "' + name + '" restored');
      await loadServices();
      await loadServicePresets();
    } else {
      showToast(data.error || 'Restore failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
}

async function doDeletePreset(name) {
  if (!confirm('Delete preset "' + name + '"? This only removes the saved configuration, not any running service.')) return;
  try {
    var r = await fetch(API_PRESETS + '/' + encodeURIComponent(name) + '/delete', { method: 'POST' });
    var data = await r.json();
    if (data.ok) {
      showToast('Preset "' + name + '" deleted');
      await loadServicePresets();
    } else {
      showToast(data.error || 'Delete failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
}

function openRestoreServiceModal() {
  var sel = document.getElementById('restore-svc-select');
  var errEl = document.getElementById('restore-svc-error');
  var details = document.getElementById('restore-svc-details');
  errEl.style.display = 'none';
  details.innerHTML = '';
  document.getElementById('restore-svc-submit').disabled = false;

  sel.innerHTML = '<option value="">Loading...</option>';
  document.getElementById('restore-svc-modal').classList.add('show');

  fetch(API_PRESETS).then(function(r) { return r.json(); }).then(function(data) {
    if (data.ok && data.presets && data.presets.length > 0) {
      sel.innerHTML = '<option value="">— select a preset —</option>' +
        data.presets.map(function(p) {
          return '<option value="' + escAttr(p.name) + '">' + esc(p.name) + ' (' + esc(p.image) + ')</option>';
        }).join('');
      sel.onchange = function() {
        var p = data.presets.find(function(x) { return x.name === sel.value; });
        if (p) {
          var lines = ['Image: ' + p.image, 'Port: ' + p.port];
          if (p.hostname) lines.push('Hostname: ' + p.hostname);
          if (p.env_vars && Object.keys(p.env_vars).length) {
            lines.push('Env: ' + Object.keys(p.env_vars).map(function(k) { return k + '=' + maskValue(k, p.env_vars[k]); }).join(', '));
          }
          details.innerHTML = lines.map(function(l) { return esc(l); }).join('<br>');
        } else {
          details.innerHTML = '';
        }
      };
    } else {
      sel.innerHTML = '<option value="">No presets available</option>';
    }
  }).catch(function() {
    sel.innerHTML = '<option value="">Failed to load presets</option>';
  });
}

function closeRestoreServiceModal(event) {
  if (event && event.target !== event.currentTarget) return;
  document.getElementById('restore-svc-modal').classList.remove('show');
}

async function submitRestoreService() {
  var sel = document.getElementById('restore-svc-select');
  var name = sel.value;
  var errEl = document.getElementById('restore-svc-error');
  if (!name) {
    errEl.textContent = 'Please select a preset.';
    errEl.style.display = 'block';
    return;
  }
  errEl.style.display = 'none';
  var btn = document.getElementById('restore-svc-submit');
  btn.disabled = true;
  btn.textContent = 'Restoring...';
  try {
    var r = await fetch(API_PRESETS + '/' + encodeURIComponent(name) + '/restore', { method: 'POST' });
    var data = await r.json();
    if (data.ok) {
      closeRestoreServiceModal();
      showToast('Service "' + name + '" restored');
      await loadServices();
      await loadServicePresets();
    } else {
      errEl.textContent = data.error || 'Restore failed';
      errEl.style.display = 'block';
    }
  } catch (e) {
    errEl.textContent = 'Connection error: ' + e.message;
    errEl.style.display = 'block';
  } finally {
    btn.disabled = false;
    btn.textContent = 'Restore';
  }
}

// Also reload presets when services change
var _origLoadServices = loadServices;
loadServices = async function() {
  await _origLoadServices();
  await loadServicePresets();
};

// --- Agents Guide ---
const API_GUIDE = '/api/agents-guide';

async function loadAgentsGuide() {
  try {
    var r = await fetch(API_GUIDE);
    var data = await r.json();
    if (data.ok) {
      document.getElementById('guide-editor').value = data.content || '';
      document.getElementById('guide-version').textContent = data.version || '0';

    }
  } catch (e) {
    showToast('Failed to load agents guide: ' + e.message, true);
  }
}

// --- License ---
const API_LICENSE = '/api/license';

async function loadLicense() {
  try {
    var r = await fetch(API_LICENSE);
    var data = await r.json();
    if (!data.ok) return;
    var lic = data.license;
    var badge = document.getElementById('license-badge');
    badge.textContent = lic.label;
    badge.className = 'license-badge license-' + lic.type;
    if (lic.type === 'unlicensed') {
      badge.style.cursor = 'pointer';
      badge.onclick = openLicenseModal;
      badge.title = 'Click to activate a license';
    } else {
      badge.style.cursor = 'default';
      badge.onclick = null;
      badge.title = '';
    }
  } catch (e) {}
}

function openLicenseModal() {
  document.getElementById('license-error').style.display = 'none';
  document.getElementById('lic-key').value = '';
  document.getElementById('lic-submit').disabled = false;
  document.getElementById('lic-submit').textContent = 'Activate';
  document.getElementById('license-modal').classList.add('show');
}

function closeLicenseModal(event) {
  if (event && event.target !== event.currentTarget) return;
  document.getElementById('license-modal').classList.remove('show');
}

async function activateLicense() {
  var key = document.getElementById('lic-key').value.trim();
  var errEl = document.getElementById('license-error');
  if (!key) {
    errEl.textContent = 'Please paste your license key.';
    errEl.style.display = 'block';
    return;
  }
  errEl.style.display = 'none';
  var btn = document.getElementById('lic-submit');
  btn.disabled = true;
  btn.textContent = 'Activating...';
  try {
    var r = await fetch(API_LICENSE + '/activate', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({ key: key })
    });
    var data = await r.json();
    if (data.ok) {
      closeLicenseModal();
      showToast('License activated: ' + data.license.label);
      loadLicense();
    } else {
      errEl.textContent = data.error || 'Invalid license key';
      errEl.style.display = 'block';
    }
  } catch (e) {
    errEl.textContent = 'Connection error: ' + e.message;
    errEl.style.display = 'block';
  } finally {
    btn.disabled = false;
    btn.textContent = 'Activate';
  }
}

const API_EXTRA_REPOS = '/api/extra-repos';
let cachedExtraRepos = [];

async function loadExtraRepos() {
  try {
    var r = await fetch(API_EXTRA_REPOS);
    var data = await r.json();
    if (data.ok) {
      cachedExtraRepos = data.repos || [];
      renderExtraRepos(cachedExtraRepos);
    } else {
      showToast(data.error || 'Failed to load extra repos', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
}

function renderExtraRepos(repos) {
  var el = document.getElementById('extra-repos-list');
  if (!repos || repos.length === 0) {
    el.innerHTML = '<div class="empty">No extra addons repositories.<br>Add one to use with environments.</div>';
    return;
  }
  el.innerHTML = repos.map(function(r) {
    var branches = (r.branches || []).slice(0, 10).map(function(b) { return esc(b); }).join(', ');
    return '<div class="svc-card">' +
      '<div class="svc-header">' +
        '<div class="left">' +
          '<span class="svc-name">' + esc(r.name) + '</span>' +
        '</div>' +
        '<div class="svc-actions">' +
          '<button class="btn btn-delete" onclick="doDeleteExtraRepo(\'' + escAttr(r.name) + '\')">Delete</button>' +
        '</div>' +
      '</div>' +
      '<div class="svc-meta">' +
        '<span>URL: <strong>' + esc(r.repo_url) + '</strong></span>' +
      '</div>' +
      (branches ? '<div class="svc-meta"><span>Branches: <strong>' + branches + '</strong></span></div>' : '') +
    '</div>';
  }).join('');
}

function openAddExtraRepoModal() {
  document.getElementById('extra-repo-name').value = '';
  document.getElementById('extra-repo-url').value = '';
  document.getElementById('add-extra-repo-error').style.display = 'none';
  document.getElementById('extra-repo-submit').disabled = false;
  document.getElementById('extra-repo-submit').textContent = 'Clone';
  document.getElementById('add-extra-repo-modal').classList.add('show');
  document.getElementById('extra-repo-name').focus();
}

function closeAddExtraRepoModal(event) {
  if (event && event.target !== event.currentTarget) return;
  document.getElementById('add-extra-repo-modal').classList.remove('show');
}

async function submitAddExtraRepo() {
  var name = document.getElementById('extra-repo-name').value.trim();
  var url = document.getElementById('extra-repo-url').value.trim();
  var errEl = document.getElementById('add-extra-repo-error');
  if (!name || !url) {
    errEl.textContent = 'Name and repository URL are required.';
    errEl.style.display = 'block';
    return;
  }
  errEl.style.display = 'none';
  var btn = document.getElementById('extra-repo-submit');
  btn.disabled = true;
  btn.textContent = 'Cloning...';
  try {
    var r = await fetch(API_EXTRA_REPOS + '/add', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({name: name, repo_url: url})
    });
    var data = await r.json();
    if (data.ok) {
      closeAddExtraRepoModal();
      showToast('Extra repo "' + name + '" cloned');
      await loadExtraRepos();
    } else {
      errEl.textContent = data.error || 'Clone failed';
      errEl.style.display = 'block';
    }
  } catch (e) {
    errEl.textContent = 'Connection error: ' + e.message;
    errEl.style.display = 'block';
  } finally {
    btn.disabled = false;
    btn.textContent = 'Clone';
  }
}

async function doDeleteExtraRepo(name) {
  if (!confirm('Delete extra repo "' + name + '"? The cloned repository will be removed.')) return;
  try {
    var r = await fetch(API_EXTRA_REPOS + '/' + encodeURIComponent(name) + '/delete', {method:'POST'});
    var data = await r.json();
    if (data.ok) {
      showToast('Extra repo "' + name + '" deleted');
      await loadExtraRepos();
    } else {
      showToast(data.error || 'Delete failed', true);
    }
  } catch (e) {
    showToast('Connection error: ' + e.message, true);
  }
}

loadLicense();
loadEnvironments();
loadStats();
loadServicePresets();
resetTimers();
</script>
</body>
</html>
