<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Browser Git + Markdown Hierarchy (vanilla)</title>
  <link rel="manifest" href="manifest.json" />
  <style>
    :root{--bg:#0f1720;--panel:#0b1220;--muted:#9aa4b2;--accent:#4f46e5;--card:#0b1226}
    html,body{height:100%;margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;background:linear-gradient(180deg,#071025 0%,#071827 100%);color:#e6eef6}
    .app{display:grid;grid-template-columns:260px 1fr 420px;gap:12px;padding:12px;height:100vh;box-sizing:border-box}
    .panel{background:var(--panel);border-radius:8px;padding:10px;box-shadow:0 6px 18px rgba(2,6,23,.6);overflow:auto}
    header{display:flex;gap:12px;align-items:center;margin-bottom:8px}
    header h1{font-size:14px;margin:0}
    .tree{font-size:13px}
    .node{padding:6px;border-radius:6px;cursor:pointer;color:var(--muted)}
    .node:hover{background:#0f1a2b;color:#fff}
    .node.active{background:linear-gradient(90deg,var(--accent),#2dd4bf);color:#021124}
    .toolbar{display:flex;gap:6px;margin-bottom:8px}
    button{background:#0b1226;border:1px solid rgba(255,255,255,.04);color:#dbeafe;padding:6px 8px;border-radius:6px;cursor:pointer}
    textarea{width:100%;height:60vh;background:#071226;color:#e6eef6;border:1px solid rgba(255,255,255,.04);padding:8px;border-radius:6px;font-family:monospace;font-size:13px}
    .preview{background:#071226;padding:10px;border-radius:6px;height:60vh;overflow:auto}
    .meta{display:flex;gap:8px;align-items:center;margin-bottom:8px}
    input[type=text]{padding:6px;border-radius:6px;border:1px solid rgba(255,255,255,.04);background:#071226;color:#e6eef6}
    .small{font-size:12px;color:var(--muted)}
    footer{margin-top:8px;font-size:12px;color:var(--muted)}
  </style>
</head>
<body>
  <div class="app">
    <div class="panel" id="left">
      <header>
        <h1>Repo</h1>
        <div class="small" id="status">init...</div>
      </header>
      <div class="toolbar">
        <button id="newFile">New file</button>
        <button id="newFolder">New folder</button>
        <button id="refreshTree">Refresh</button>
      </div>
      <div class="tree" id="tree"></div>
      <footer class="small">Files stored in browser IndexedDB via LightningFS</footer>
    </div>

    <div class="panel" id="center">
      <div class="meta">
        <input id="filepath" type="text" placeholder="objects/example.md" style="flex:1" />
        <input id="commitMsg" type="text" placeholder="Commit message" style="width:260px" />
        <button id="saveCommit">Save & Commit</button>
      </div>
      <textarea id="editor" placeholder="File content (Markdown or YAML/JSON)"></textarea>
      <div style="display:flex;gap:8px;margin-top:8px">
        <button id="initRepo">Init Repo</button>
        <button id="logBtn">Log</button>
        <button id="setRemote">Set Remote</button>
        <button id="pushBtn">Push</button>
        <button id="pullBtn">Pull</button>
      </div>
      <pre id="log" class="small" style="margin-top:8px;white-space:pre-wrap"></pre>
    </div>

    <div class="panel" id="right">
      <header><h1>Markdown Preview</h1></header>
      <div class="preview" id="preview">Nothing selected</div>
    </div>
  </div>

  <script type="module">
    import LightningFS from 'https://unpkg.com/@isomorphic-git/lightning-fs@4.0.1?module';
    import * as git from 'https://unpkg.com/isomorphic-git@1.20.0?module';
    import { marked } from 'https://cdn.jsdelivr.net/npm/marked@5.1.1/+esm';

    // Basic globals
    const repoDir = '/repo';
    const fs = new LightningFS('browser-fs', { wipe: false });
    git.plugins.set('fs', fs);

    const pfs = fs.promises;
    const treeEl = document.getElementById('tree');
    const editor = document.getElementById('editor');
    const preview = document.getElementById('preview');
    const filepathInput = document.getElementById('filepath');
    const commitMsgInput = document.getElementById('commitMsg');
    const statusEl = document.getElementById('status');
    const logEl = document.getElementById('log');

    // Utility: ensure repo dir exists
    async function ensureRepoDir() {
      try { await pfs.stat(repoDir); } catch (e) { await pfs.mkdir(repoDir); }
    }

    // Initialize git repo if not present
    async function initRepo() {
      status('initializing repo...');
      await ensureRepoDir();
      try {
        const isRepo = await git.isRepo({ fs, dir: repoDir });
        if (!isRepo) {
          await git.init({ fs, dir: repoDir, defaultBranch: 'main' });
          status('repo initialized');
        } else {
          status('repo already initialized');
        }
      } catch (e) {
        console.error(e);
        status('init error: ' + e.message);
      }
      await refreshTree();
    }

    // Read directory recursively and build tree
    async function readTree(dir = repoDir) {
      const items = [];
      try {
        const names = await pfs.readdir(dir);
        for (const name of names) {
          const path = dir + '/' + name;
          const st = await pfs.stat(path);
          if (st && st.type === 'directory') {
            items.push({ name, path, type: 'dir', children: await readTree(path) });
          } else {
            items.push({ name, path, type: 'file' });
          }
        }
      } catch (e) {
        // ignore
      }
      return items;
    }

    function renderTree(nodes, container) {
      container.innerHTML = '';
      for (const node of nodes) {
        const div = document.createElement('div');
        div.className = 'node';
        div.textContent = node.name;
        div.title = node.path;
        div.onclick = async (ev) => {
          ev.stopPropagation();
          document.querySelectorAll('.node').forEach(n=>n.classList.remove('active'));
          div.classList.add('active');
          if (node.type === 'file') {
            const rel = node.path.replace(repoDir + '/', '');
            filepathInput.value = rel;
            const data = await pfs.readFile(node.path, { encoding: 'utf8' }).catch(()=>'');
            editor.value = data;
            preview.innerHTML = marked.parse(data || '');
          } else {
            // toggle expand: show children inline
            if (div.nextSibling && div.nextSibling.classList && div.nextSibling.classList.contains('children')) {
              div.nextSibling.remove();
            } else {
              const childWrap = document.createElement('div');
              childWrap.className = 'children';
              childWrap.style.paddingLeft = '12px';
              renderTree(node.children, childWrap);
              div.after(childWrap);
            }
          }
        };
        container.appendChild(div);
      }
    }

    async function refreshTree() {
      const nodes = await readTree();
      renderTree(nodes, treeEl);
      status('tree refreshed');
    }

    // Save file to fs and stage/commit
    async function saveAndCommit() {
      const rel = filepathInput.value.trim();
      if (!rel) { alert('Enter a filepath'); return; }
      const full = repoDir + '/' + rel;
      // ensure parent dirs
      const parts = full.split('/');
      for (let i = 2; i <= parts.length - 1; i++) {
        const p = parts.slice(0, i).join('/');
        try { await pfs.stat(p); } catch (e) { await pfs.mkdir(p); }
      }
      await pfs.writeFile(full, editor.value, { encoding: 'utf8' });
      await git.add({ fs, dir: repoDir, filepath: rel });
      const message = commitMsgInput.value.trim() || 'Edit ' + rel;
      try {
        const sha = await git.commit({
          fs, dir: repoDir,
          author: { name: 'Browser User', email: 'user@local' },
          message
        });
        status('committed ' + sha.slice(0,7));
        await refreshTree();
        log('Committed: ' + sha);
      } catch (e) {
        console.error(e);
        status('commit error: ' + e.message);
      }
    }

    // Show log
    async function showLog() {
      try {
        const commits = await git.log({ fs, dir: repoDir, depth: 20 });
        logEl.textContent = commits.map(c => `${c.oid.slice(0,7)} ${c.commit.message} — ${c.commit.author.name}`).join('\n\n');
      } catch (e) {
        logEl.textContent = 'Log error: ' + e.message;
      }
    }

    // Set remote (store in localStorage)
    function setRemote() {
      const url = prompt('Remote URL (HTTPS, e.g. https://github.com/you/repo.git)');
      if (url) {
        localStorage.setItem('remoteUrl', url);
        status('remote set');
      }
    }

    // Push (prompts for token)
    async function push() {
      const remote = localStorage.getItem('remoteUrl');
      if (!remote) { alert('Set remote first'); return; }
      const token = prompt('Personal access token (will not be stored). For GitHub use token as password and username "x-access-token" or "token".');
      if (!token) return;
      status('pushing...');
      try {
        await git.addRemote({ fs, dir: repoDir, remote: 'origin', url: remote }).catch(()=>{});
        await git.push({
          fs, http: git.http, dir: repoDir,
          remote: 'origin', ref: 'main',
          onAuth: () => ({ username: 'x-access-token', password: token })
        });
        status('push complete');
      } catch (e) {
        console.error(e);
        status('push failed: ' + e.message);
      }
    }

    // Pull (prompts for token)
    async function pull() {
      const remote = localStorage.getItem('remoteUrl');
      if (!remote) { alert('Set remote first'); return; }
      const token = prompt('Personal access token (will not be stored).');
      if (!token) return;
      status('pulling...');
      try {
        await git.addRemote({ fs, dir: repoDir, remote: 'origin', url: remote }).catch(()=>{});
        await git.pull({
          fs, http: git.http, dir: repoDir,
          remote: 'origin', ref: 'main',
          singleBranch: true,
          author: { name: 'Browser User', email: 'user@local' },
          onAuth: () => ({ username: 'x-access-token', password: token })
        });
        status('pull complete');
        await refreshTree();
      } catch (e) {
        console.error(e);
        status('pull failed: ' + e.message);
      }
    }

    // Helpers
    function status(text) { statusEl.textContent = text; }
    function log(text) { logEl.textContent = (new Date()).toLocaleTimeString() + ' — ' + text + '\n' + logEl.textContent; }

    // Wire UI
    document.getElementById('initRepo').onclick = initRepo;
    document.getElementById('refreshTree').onclick = refreshTree;
    document.getElementById('saveCommit').onclick = saveAndCommit;
    document.getElementById('logBtn').onclick = showLog;
    document.getElementById('newFile').onclick = async () => {
      const name = prompt('New file path (e.g. objects/note.md)');
      if (!name) return;
      filepathInput.value = name;
      editor.value = '# ' + name.split('/').pop();
    };
    document.getElementById('newFolder').onclick = async () => {
      const name = prompt('New folder path (e.g. objects/notes)');
      if (!name) return;
      const full = repoDir + '/' + name;
      try { await pfs.mkdir(full, { recursive: true }); status('folder created'); await refreshTree(); } catch(e){status('mkdir error')}
    };
    document.getElementById('setRemote').onclick = setRemote;
    document.getElementById('pushBtn').onclick = push;
    document.getElementById('pullBtn').onclick = pull;

    // Live preview
    editor.addEventListener('input', () => {
      preview.innerHTML = marked.parse(editor.value || '');
    });

    // Register service worker
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('sw.js').then(()=>console.log('sw registered')).catch(()=>console.warn('sw failed'));
    }

    // Auto init on load
    await initRepo();
    await refreshTree();
    status('ready');
  </script>
</body>
</html>