<h1>New project</h1>

<% if (error) { %><div class="flash err"><%= error %></div><% } %>

<div class="card">
  <form method="post" action="/projects/new" id="new-project-form">
    <label for="f-gitacc">Import from</label>
    <select id="f-gitacc" name="git_account_id" onchange="gitAccountChanged()">
      <option value="">Manual — enter repository URL below</option>
      <% (gitAccounts || []).forEach(function (a) { %>
        <option value="<%= a.id %>"><%= a.provider %> · <%= a.label %></option>
      <% }) %>
    </select>
    <div class="hint">Connect accounts under <a href="/settings/git">Git accounts</a> to browse repositories. With an account selected, the deploy key and push webhook are set up automatically and the first build starts right away.</div>

    <div id="git-repo-picker" style="display:none">
      <label>Repository</label>
      <div class="geo-dd" id="repo-dd">
        <div class="geo-dd-toggle" onclick="repoDdToggle()">
          <span id="repo-picked" class="muted">Select repository…</span>
          <span class="geo-caret">▾</span>
        </div>
        <div class="geo-dd-panel" id="repo-dd-panel" hidden>
          <input type="text" id="repo-search" placeholder="Search repositories…" autocomplete="off">
          <div id="repo-list" class="geo-list" style="flex-direction:column;flex-wrap:nowrap"></div>
        </div>
      </div>
    </div>
    <input type="hidden" name="repo_full" id="f-repo-full" value="<%= values.repo_full || '' %>">

    <label for="f-name">Name</label>
    <input id="f-name" type="text" name="name" value="<%= values.name || '' %>" required pattern="[a-z][a-z0-9-]{1,30}" autofocus>
    <div class="hint">Lowercase slug (<code>^[a-z][a-z0-9-]{1,30}$</code>). Also used as the PM2 app name.</div>

    <% var caps = (typeof capabilities !== 'undefined' && capabilities) ? capabilities : { laravel: false, db: false, phpVersions: [], phpDefault: '' }; %>
    <% var rt = values.runtime === 'laravel' ? 'laravel' : 'node'; %>
    <label>Runtime</label>
    <div class="runtime-pick" id="runtime-pick">
      <label class="runtime-opt"><input type="radio" name="runtime" value="node" <%= rt === 'node' ? 'checked' : '' %> onchange="runtimeChanged()"> <strong>Node.js</strong> <span class="muted">— Nuxt/Nitro style: build → PM2 process behind Caddy (+ optional Varnish)</span></label>
      <label class="runtime-opt <%= caps.laravel ? '' : 'disabled' %>"><input type="radio" name="runtime" value="laravel" <%= rt === 'laravel' ? 'checked' : '' %> <%= caps.laravel ? '' : 'disabled' %> onchange="runtimeChanged()"> <strong>Laravel</strong> <span class="muted">— composer build → PHP-FPM pool behind Caddy, MariaDB via the Database card</span></label>
    </div>
    <% if (!caps.laravel) { %><div class="hint">Laravel is not enabled on this server — bootstrap it with <code>WITH_LARAVEL=1</code> (installs PHP-FPM, composer and MariaDB) to offer it here.</div><% } %>

    <label for="f-repo">Repository URL</label>
    <input id="f-repo" type="text" name="repo_url" class="mono" value="<%= values.repo_url || '' %>" placeholder="git@github.com:org/repo.git">
    <div class="hint">HTTPS or SSH. For private repos, add the deploy key (shown after creation) to the repository. Optional — leave empty to deploy via ZIP upload (project page &rarr; "Deploy from ZIP&hellip;"); a repository can be linked later.</div>

    <label for="f-branch">Branch</label>
    <select id="f-branch-select" style="display:none" onchange="document.getElementById('f-branch').value=this.value"></select>
    <input id="f-branch" type="text" name="branch" value="<%= values.branch || 'main' %>" list="branch-list">
    <datalist id="branch-list"></datalist>
    <div class="hint" id="branch-hint" style="display:none">Branches of the selected repository — the repository default is preselected.</div>

    <label for="f-build">Build command <span class="muted">— or pick a preset:
      <select onchange="if(this.value){document.getElementById('f-build').value=this.value;this.selectedIndex=0}">
        <option value="">preset…</option>
        <option value="npm ci && npm run build">Standard (npm ci)</option>
        <option value="npm ci --legacy-peer-deps && npm run build">Compatible (legacy peer deps)</option>
        <option value="rm -rf node_modules package-lock.json && npm install --legacy-peer-deps && npm run build">Clean install (regenerate lockfile)</option>
        <option value="composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader && if [ -f package.json ]; then npm ci && npm run build; fi">Laravel (composer + Vite if package.json)</option>
        <option value="composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader">Laravel (composer only, no frontend build)</option>
      </select></span></label>
    <input id="f-build" type="text" name="build_cmd" class="mono" value="<%= values.build_cmd || 'npm ci && npm run build' %>">
    <div class="hint">Runs with <code>bash -lc</code> in the checkout, with the app env vars and <code>CI=true</code>.</div>

    <label for="f-out">Output directory</label>
    <input id="f-out" type="text" name="output_dir" class="mono" value="<%= values.output_dir || (rt === 'laravel' ? '.' : '.output') %>">
    <div class="hint">Relative to the checkout — handed to <code>paas deploy</code> as the release content. <span class="rt-laravel">Laravel: <code>.</code> (the whole built checkout; <code>storage/</code> and <code>.env</code> are shared across releases).</span></div>

    <div class="rt-node">
      <label for="f-start">Start command</label>
      <input id="f-start" type="text" name="start_cmd" class="mono" value="<%= values.start_cmd || 'node server/index.mjs' %>">
      <div class="hint">How PM2 starts the app inside the release directory.</div>
    </div>

    <div class="rt-laravel">
      <label for="f-php">PHP version</label>
      <select id="f-php" name="php_version">
        <% (caps.phpVersions || []).forEach(function (pv) { %>
          <option value="<%= pv %>" <%= (values.php_version || caps.phpDefault) === pv ? 'selected' : '' %>>PHP <%= pv %></option>
        <% }) %>
      </select>
      <div class="hint">Own PHP-FPM pool per app; switchable later on the project page.</div>
    </div>

    <label for="f-node">Node.js version</label>
    <select id="f-node" name="node_version">
      <% (typeof nodeVersions !== 'undefined' ? nodeVersions : ['system']).forEach(function (nv) { %>
        <option value="<%= nv %>" <%= values.node_version === nv ? 'selected' : '' %>><%= nv === 'system' ? 'system (Node 22)' : 'Node ' + nv %></option>
      <% }) %>
    </select>
    <div class="hint"><span class="rt-node">Used for both build and runtime of this project.</span><span class="rt-laravel">Used for the asset build only (Vite/Mix) — the app itself runs on PHP-FPM.</span></div>

    <label for="f-health">Health check path</label>
    <input id="f-health" type="text" name="health_path" class="mono" value="<%= values.health_path || '' %>" placeholder="<%= rt === 'laravel' ? '/up' : '/' %>">
    <div class="hint">Probed on the app port before a release goes live and by monitoring every 5 s — keep it cheap. Empty = runtime default (<code>/</code> for Node, <code>/up</code> for Laravel). <span class="rt-laravel">Laravel ≤ 10 has no <code>/up</code> — use <code>/</code> or add a route. Must not depend on a migration the same deploy introduces (the gate runs before <code>migrate</code>).</span></div>

    <label for="f-group">Group <span class="muted">(optional — manage under <a href="/groups">Groups</a>)</span></label>
    <select id="f-group" name="group_name">
      <option value="">— none —</option>
      <% (groups || []).forEach(function (g) { %>
        <option value="<%= g %>" <%= values.group_name === g ? 'selected' : '' %>><%= g %></option>
      <% }) %>
    </select>

    <label for="f-domains">Domains <span class="muted">(optional — a system domain like name-a1b2c3d4.younex.de is always generated)</span></label>
    <input id="f-domains" type="text" name="domains" value="<%= values.domains || '' %>" placeholder="example.com, www.example.com">
    <div class="hint">Comma-separated. Caddy config and TLS are handled by paas.</div>

    <label id="cache-label"><input type="checkbox" name="cache" id="f-cache" value="1" <%= values.cache && rt !== 'laravel' ? 'checked' : '' %> <%= rt === 'laravel' ? 'disabled' : '' %>> Enable Varnish cache <span class="muted rt-laravel">— not available for Laravel (the edge strips cookies; sessions/CSRF would break)</span></label>
    <label><input type="checkbox" name="noindex" value="1" <%= values.noindex ? 'checked' : '' %>> Send noindex header (staging)</label>
    <% if (caps.db) { %>
    <label><input type="checkbox" name="create_db" id="f-create-db" value="1" <%= (values.create_db || (rt === 'laravel' && !values.name)) ? 'checked' : '' %>> Create a MariaDB database now <span class="muted">— one database + user per project; <code>DB_*</code> credentials are written into the environment (Node projects also get <code>DATABASE_URL</code>)</span></label>
    <% } %>

    <label for="f-env">Environment variables</label>
    <textarea id="f-env" name="env" rows="6" spellcheck="false" placeholder="KEY=value"><%= values.env || '' %></textarea>
    <div class="hint">One <code>KEY=VALUE</code> per line. Written to the app&#39;s <code>shared/.env</code>. <span class="rt-laravel">Laravel: <code>APP_KEY</code>, <code>APP_ENV=production</code>, <code>APP_DEBUG=false</code>, <code>APP_URL</code> and <code>LOG_CHANNEL=daily</code> are generated for you unless you set them here; the file is read through <code>config:cache</code> at deploy time.</span></div>

    <label for="f-zip">First deployment from ZIP (optional)</label>
    <input id="f-zip" type="file" accept=".zip,application/zip">
    <div class="hint">Upload a ZIP of the repository as the first deployment — useful while the git host is down, or for repo-less projects. The project is created first, then the ZIP is uploaded and built; you land on the live deployment log.</div>

    <div style="margin-top:18px">
      <button class="btn primary" type="submit" id="create-btn">Create project</button>
      <a class="btn" href="/">Cancel</a>
      <progress id="zip-progress" max="100" value="0" style="display:none;width:220px;vertical-align:middle;margin-left:10px"></progress>
    </div>
  </form>
  <script>
    // fetch() cannot report upload progress — XHR can.
    function uploadZipXhr(url, file, onProgress) {
      return new Promise((resolve) => {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', url);
        xhr.setRequestHeader('Content-Type', 'application/zip');
        xhr.upload.onprogress = (e) => {
          if (e.lengthComputable) onProgress(Math.round((e.loaded / e.total) * 100));
        };
        xhr.onload = () => {
          let data = {};
          try { data = JSON.parse(xhr.responseText); } catch { /* non-JSON */ }
          resolve({ status: xhr.status, data });
        };
        xhr.onerror = () => resolve({ status: 0, data: {} });
        xhr.send(file);
      });
    }
    // With a ZIP selected the form goes through fetch (want_json=1) so the
    // upload can be chained right after creation; without one it submits
    // normally and nothing changes.
    document.getElementById('new-project-form').addEventListener('submit', async (e) => {
      const zip = document.getElementById('f-zip').files[0];
      if (!zip) return; // classic navigation submit
      e.preventDefault();
      const form = e.target;
      const btn = document.getElementById('create-btn');
      btn.disabled = true;
      btn.textContent = 'Creating project…';
      const params = new URLSearchParams();
      for (const [k, v] of new FormData(form)) {
        if (typeof v === 'string') params.append(k, v); // skip File entries
      }
      params.append('want_json', '1');
      try {
        let res = await fetch('/projects/new', {
          method: 'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          body: params.toString(),
        });
        let data = await res.json().catch(() => ({}));
        if (!res.ok || !data.ok) throw new Error(data.error || ('HTTP ' + res.status));
        const bar = document.getElementById('zip-progress');
        bar.style.display = 'inline-block';
        const { status, data: up } = await uploadZipXhr('/projects/' + data.name + '/upload-zip', zip, (pct) => {
          bar.value = pct;
          btn.textContent = 'Uploading ' + (zip.size / 1048576).toFixed(1) + ' MB — ' + pct + ' %';
        });
        bar.style.display = 'none';
        if (status < 200 || status >= 300 || !up.ok) {
          alert('Project created, but the ZIP upload failed: ' + (up.error || status) + '\nUse "Deploy from ZIP…" on the project page.');
          location.href = '/projects/' + data.name;
          return;
        }
        location.href = '/deployments/' + up.deployment;
      } catch (err) {
        alert('Create failed: ' + err.message);
        btn.disabled = false;
        btn.textContent = 'Create project';
      }
    });
  </script>
</div>

<style>
  .runtime-pick { display:flex; flex-direction:column; gap:6px; margin-bottom:8px; }
  .runtime-opt { display:flex; align-items:flex-start; gap:8px; padding:8px 10px; border:1px solid var(--border); border-radius:8px; cursor:pointer; font-weight:normal; }
  .runtime-opt input { margin-top:3px; }
  .runtime-opt.disabled { opacity:.55; cursor:not-allowed; }
</style>
<script>
var RUNTIME_DEFAULTS = <%- JSON.stringify(typeof runtimeDefaults !== 'undefined' ? runtimeDefaults : {}) %>;
var lastRuntime = null;
function currentRuntime() {
  var r = document.querySelector('input[name="runtime"]:checked');
  return r ? r.value : 'node';
}
// Switch presets only where the field still holds the OTHER runtime's default —
// anything the user typed survives a flip.
function runtimeChanged() {
  var rt = currentRuntime();
  var prev = lastRuntime || (rt === 'laravel' ? 'node' : 'laravel');
  var from = RUNTIME_DEFAULTS[prev] || {}, to = RUNTIME_DEFAULTS[rt] || {};
  var map = { 'f-build': 'build_cmd', 'f-out': 'output_dir', 'f-start': 'start_cmd' };
  Object.keys(map).forEach(function (id) {
    var el = document.getElementById(id);
    if (!el) return;
    if (!el.value || el.value === from[map[id]]) el.value = to[map[id]] || '';
  });
  var health = document.getElementById('f-health');
  if (health) { health.placeholder = to.health || '/'; if (health.value === from.health) health.value = ''; }
  var cache = document.getElementById('f-cache');
  if (cache) { if (rt === 'laravel') { cache.checked = false; cache.disabled = true; } else { cache.disabled = false; } }
  var cdb = document.getElementById('f-create-db');
  if (cdb && lastRuntime !== null) cdb.checked = (rt === 'laravel');
  document.querySelectorAll('.rt-node').forEach(function (el) { el.style.display = rt === 'node' ? '' : 'none'; });
  document.querySelectorAll('.rt-laravel').forEach(function (el) { el.style.display = rt === 'laravel' ? '' : 'none'; });
  lastRuntime = rt;
}
lastRuntime = currentRuntime();
document.querySelectorAll('.rt-node').forEach(function (el) { el.style.display = lastRuntime === 'node' ? '' : 'none'; });
document.querySelectorAll('.rt-laravel').forEach(function (el) { el.style.display = lastRuntime === 'laravel' ? '' : 'none'; });

var repoCache = [];
var repoError = null;
function escHtml(s) { return String(s).replace(/[&<>"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); }
function branchTextMode() {
  document.getElementById('f-branch-select').style.display = 'none';
  document.getElementById('f-branch').style.display = '';
  document.getElementById('branch-hint').style.display = 'none';
}
function gitAccountChanged() {
  var id = document.getElementById('f-gitacc').value;
  var picker = document.getElementById('git-repo-picker');
  branchTextMode();
  repoCache = [];
  document.getElementById('repo-picked').textContent = 'Select repository…';
  document.getElementById('f-repo-full').value = '';
  if (!id) { picker.style.display = 'none'; return; }
  picker.style.display = '';
  document.getElementById('repo-list').innerHTML = '<div class="muted">loading repositories…</div>';
  fetch('/api/git/' + id + '/repos').then(function (r) { return r.json(); }).then(function (d) {
    repoCache = d.repos || [];
    repoError = d.error || null;
    renderRepoList('');
  }).catch(function () {
    repoError = 'failed to reach the panel — check your connection and retry';
    repoCache = [];
    renderRepoList('');
  });
}
function renderRepoList(q) {
  if (repoError) {
    document.getElementById('repo-list').innerHTML = '<div class="muted" style="color:#c0392b">' + escHtml(repoError) + '</div>';
    return;
  }
  q = (q || '').toLowerCase();
  var items = repoCache.filter(function (r) { return !q || r.full.toLowerCase().indexOf(q) >= 0; }).slice(0, 60);
  document.getElementById('repo-list').innerHTML = items.map(function (r, i) {
    return '<div class="geo-item" style="width:100%" data-repo="' + escHtml(r.full) + '" onclick="pickRepo(this.getAttribute(\'data-repo\'))">' + (r.private ? '🔒 ' : '') + escHtml(r.full) + '</div>';
  }).join('') || '<div class="muted">no matches</div>';
}
function pickRepo(full) {
  var r = null;
  for (var i = 0; i < repoCache.length; i++) if (repoCache[i].full === full) r = repoCache[i];
  if (!r) return;
  document.getElementById('f-repo-full').value = r.full;
  document.getElementById('f-repo').value = r.ssh_url;
  document.getElementById('repo-picked').textContent = (r.private ? '🔒 ' : '') + r.full;
  document.getElementById('repo-dd-panel').hidden = true;
  var name = document.getElementById('f-name');
  if (!name.value) {
    name.value = r.full.split('/').pop().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^[^a-z]+/, '').replace(/-+$/, '').slice(0, 30);
  }
  document.getElementById('f-branch').value = r.default_branch || 'main';
  branchTextMode();
  var accId = document.getElementById('f-gitacc').value;
  fetch('/api/git/' + accId + '/branches?repo=' + encodeURIComponent(r.full)).then(function (x) { return x.json(); }).then(function (d) {
    var branches = d.branches || [];
    document.getElementById('branch-list').innerHTML = branches.map(function (b) { return '<option value="' + escHtml(b) + '">'; }).join('');
    if (!branches.length) return;
    var def = branches.indexOf(r.default_branch) >= 0 ? r.default_branch : (branches.indexOf('main') >= 0 ? 'main' : branches[0]);
    var sel = document.getElementById('f-branch-select');
    sel.innerHTML = branches.map(function (b) { return '<option value="' + escHtml(b) + '"' + (b === def ? ' selected' : '') + '>' + escHtml(b) + '</option>'; }).join('');
    document.getElementById('f-branch').value = def;
    sel.style.display = '';
    document.getElementById('f-branch').style.display = 'none';
    document.getElementById('branch-hint').style.display = '';
  }).catch(function () {});
}
function repoDdToggle() {
  var p = document.getElementById('repo-dd-panel');
  p.hidden = !p.hidden;
  if (!p.hidden) document.getElementById('repo-search').focus();
}
document.getElementById('repo-search').addEventListener('input', function () { renderRepoList(this.value); });
document.addEventListener('click', function (ev) {
  if (!ev.target.closest('#repo-dd')) { var p = document.getElementById('repo-dd-panel'); if (p) p.hidden = true; }
});
</script>
