/**
 * Git Board — Surface Entry Point
 *
 * Kanban board for Gitea issues and PRs.
 * Calls Starlark backend via /s/git-board/api/*.
 * Authentication via gitea-client library connections.
 *
 * Features: DnD between columns, issue detail modal with comments.
 */
(async function () {
  'use strict';

  var mount = document.getElementById('extension-mount');
  if (!mount) return;

  var base = window.__BASE__ || '';
  var ver  = window.__VERSION__ || '0';
  var slug = 'git-board';

  // ── Boot SDK ───────────────────────────────
  try {
    if (!window.preact) {
      var { h, render } = await import(base + '/js/sw/vendor/preact.module.js');
      var hooksModule   = await import(base + '/js/sw/vendor/hooks.module.js');
      var htmModule     = await import(base + '/js/sw/vendor/htm.module.js');
      window.preact = { h, render };
      window.hooks  = hooksModule;
      window.html   = htmModule.default.bind(h);
    }
    var sdk = await import(base + '/js/sw/sdk/index.js?v=' + ver);
    await sdk.boot();
  } catch (e) {
    mount.innerHTML = '<p style="color:var(--danger);padding:24px;">SDK boot failed: ' + e.message + '</p>';
    return;
  }

  var { html } = window;
  var { useState, useEffect, useCallback, useRef, useMemo } = hooks;
  var { render } = preact;

  var API = base + '/s/' + slug + '/api';

  async function api(path, opts) {
    var token = sw.auth._getToken();
    var fetchOpts = { headers: { 'Authorization': 'Bearer ' + token } };
    if (opts && opts.method) fetchOpts.method = opts.method;
    if (opts && opts.body) {
      fetchOpts.body = JSON.stringify(opts.body);
      fetchOpts.headers['Content-Type'] = 'application/json';
    }
    var resp = await fetch(API + path, fetchOpts);
    var text = await resp.text();
    try { return JSON.parse(text); } catch (_) { return { error: text }; }
  }

  // ── App ────────────────────────────────────

  function App() {
    var [owner, setOwner]     = useState('');
    var [repo, setRepo]       = useState('');
    var [repos, setRepos]     = useState([]);
    var [board, setBoard]     = useState(null);
    var [loading, setLoading] = useState(false);
    var [needsConn, setNeedsConn] = useState(false);
    var [modal, setModal]     = useState(null); // {owner, repo, number}
    var menuRef = useRef(null);

    useEffect(function () {
      if (menuRef.current && sw.userMenu) {
        sw.userMenu(menuRef.current, { placement: 'down-left' });
      }
    }, []);

    useEffect(function () {
      api('/repos').then(function (d) {
        if (d.error) {
          if (d.error.indexOf('no gitea connection configured') !== -1) {
            setNeedsConn(true);
          } else { sw.toast(d.error, 'error'); }
          return;
        }
        setRepos(d.data || []);
        if (d.data && d.data.length > 0 && !owner) {
          setOwner(d.data[0].owner);
          setRepo(d.data[0].name);
        }
      });
    }, []);

    var loadBoard = useCallback(function () {
      if (!owner || !repo) return;
      setLoading(true);
      api('/board?owner=' + encodeURIComponent(owner) + '&repo=' + encodeURIComponent(repo))
        .then(function (d) {
          if (d.error) {
            sw.toast(d.error, 'error');
            if (d.error.indexOf('no gitea connection configured') !== -1) setNeedsConn(true);
          } else {
            setBoard(d);
            setNeedsConn(false);
          }
          setLoading(false);
        });
    }, [owner, repo]);

    useEffect(function () { loadBoard(); }, [loadBoard]);

    var onDrop = useCallback(function (issueNumber, targetCol) {
      if (!board) return;
      var issue = (board.issues || []).find(function (i) { return i.number === issueNumber; });
      if (!issue) return;

      var patch = {};
      if (targetCol === 'open') {
        // Move to Open: unassign + reopen
        if (issue.state === 'closed') patch.state = 'open';
        // Note: Gitea PATCH /issues doesn't support clearing assignee via empty string,
        // but we do our best — the board will re-split on refresh.
      } else if (targetCol === 'in_progress') {
        // Move to In Progress: assign to current user + ensure open
        if (issue.state === 'closed') patch.state = 'open';
        // Gitea needs assignees array — not supported by our simple update_issue.
        // For now, just reopen. User assigns via modal.
        if (issue.state === 'closed') patch.state = 'open';
      } else if (targetCol === 'done') {
        patch.state = 'closed';
      }

      if (!patch.state) return; // no meaningful change

      // Optimistic update
      var newIssues = (board.issues || []).map(function (i) {
        if (i.number !== issueNumber) return i;
        var copy = {};
        for (var k in i) copy[k] = i[k];
        if (patch.state) copy.state = patch.state;
        return copy;
      });
      setBoard({ issues: newIssues, pull_requests: board.pull_requests || [] });

      api('/issue/' + owner + '/' + repo + '/' + issueNumber, {
        method: 'POST', body: patch
      }).then(function (d) {
        if (d.error) {
          sw.toast('Update failed: ' + d.error, 'error');
          loadBoard(); // revert
        }
      });
    }, [board, owner, repo, loadBoard]);

    var openModal = useCallback(function (number) {
      setModal({ owner: owner, repo: repo, number: number });
    }, [owner, repo]);

    var closeModal = useCallback(function (changed) {
      setModal(null);
      if (changed) loadBoard();
    }, [loadBoard]);

    return html`
      <div class="user-menu-container" ref=${menuRef}></div>
      ${needsConn ? html`<${ConnectionSetup} />` : html`
        <div class="gb-shell">
          <header class="gb-header">
            <div class="gb-header__left">
              <h1 class="gb-title">Git Board</h1>
              <${RepoPicker} repos=${repos} owner=${owner} repo=${repo}
                             onSelect=${function (o, r) { setOwner(o); setRepo(r); }} />
            </div>
            <div class="gb-header__right">
              <button class="btn-small" onClick=${loadBoard} disabled=${loading}>
                ${loading ? '↻' : 'Refresh'}
              </button>
            </div>
          </header>
          ${board ? html`<${Board} data=${board} onDrop=${onDrop} onCardClick=${openModal} />` : html`
            <div class="gb-empty">${loading ? 'Loading…' : 'Select a repository'}</div>
          `}
        </div>
      `}
      ${modal && html`<${IssueModal} ...${modal} onClose=${closeModal} />`}
    `;
  }

  // ── Connection Setup ─────────────────────────

  function ConnectionSetup() {
    return html`
      <div class="gb-shell">
        <header class="gb-header">
          <div class="gb-header__left"><h1 class="gb-title">Git Board</h1></div>
        </header>
        <div class="gb-setup">
          <h2>Connect to Gitea</h2>
          <p>Git Board requires a Gitea connection to fetch repositories, issues, and pull requests.</p>
          <p>Ask your admin to add a <strong>Gitea</strong> connection in
             <strong>Admin → Connections</strong>, or add a personal one in
             <strong>Settings → Connections</strong>.</p>
          <button class="btn-small btn-primary"
                  onClick=${function () { window.location.href = base + '/settings'; }}>
            Open Settings
          </button>
          <p class="gb-setup__hint">Connections are managed centrally — no API tokens in extension settings.</p>
        </div>
      </div>
    `;
  }

  // ── Repo Picker ────────────────────────────

  function RepoPicker({ repos, owner, repo, onSelect }) {
    var current = owner + '/' + repo;
    return html`
      <select class="gb-repo-picker" value=${current}
              onChange=${function (e) {
                var parts = e.target.value.split('/');
                onSelect(parts[0], parts.slice(1).join('/'));
              }}>
        ${repos.map(function (r) {
          return html`<option key=${r.full_name} value=${r.full_name}>${r.full_name}</option>`;
        })}
      </select>
    `;
  }

  // ── Kanban Board with DnD ─────────────────

  function Board({ data, onDrop, onCardClick }) {
    var issues = data.issues || [];
    var prs    = data.pull_requests || [];

    var openUnassigned = issues.filter(function (i) { return i.state === 'open' && !i.assignee; });
    var inProgress     = issues.filter(function (i) { return i.state === 'open' && !!i.assignee; });

    return html`
      <div class="gb-board">
        <${Column} id="open" title="Open" count=${openUnassigned.length} onDrop=${onDrop}>
          ${openUnassigned.map(function (i) {
            return html`<${IssueCard} key=${i.number} issue=${i} onClick=${onCardClick} />`;
          })}
        <//>
        <${Column} id="in_progress" title="In Progress" count=${inProgress.length} onDrop=${onDrop}>
          ${inProgress.map(function (i) {
            return html`<${IssueCard} key=${i.number} issue=${i} onClick=${onCardClick} />`;
          })}
        <//>
        <${Column} id="done" title="Done" count=${0} onDrop=${onDrop}>
        <//>
        <${Column} id="prs" title="Pull Requests" count=${prs.length}>
          ${prs.map(function (p) { return html`<${PRCard} key=${p.number} pr=${p} />`; })}
        <//>
      </div>
    `;
  }

  function Column({ id, title, count, children, onDrop }) {
    var [over, setOver] = useState(false);

    var handlers = onDrop ? {
      onDragOver: function (e) { e.preventDefault(); setOver(true); },
      onDragLeave: function () { setOver(false); },
      onDrop: function (e) {
        e.preventDefault();
        setOver(false);
        var num = parseInt(e.dataTransfer.getData('text/plain'), 10);
        if (num && onDrop) onDrop(num, id);
      }
    } : {};

    return html`
      <div class="gb-column ${over ? 'gb-column--dragover' : ''}" ...${handlers}>
        <div class="gb-column__header">
          <span class="gb-column__title">${title}</span>
          <span class="gb-column__count">${count || 0}</span>
        </div>
        <div class="gb-column__cards">${children}</div>
      </div>
    `;
  }

  function IssueCard({ issue, onClick }) {
    return html`
      <div class="gb-card" draggable="true"
           onDragStart=${function (e) {
             e.dataTransfer.setData('text/plain', String(issue.number));
             e.dataTransfer.effectAllowed = 'move';
           }}
           onClick=${function (e) {
             e.preventDefault();
             if (onClick) onClick(issue.number);
           }}>
        <div class="gb-card__header">
          <span class="gb-card__number">#${issue.number}</span>
          ${issue.assignee && html`<span class="gb-card__assignee">@${esc(issue.assignee)}</span>`}
        </div>
        <div class="gb-card__title">${esc(issue.title)}</div>
        <div class="gb-card__labels">
          ${(issue.labels || []).map(function (l) {
            return html`<span key=${l} class="badge">${esc(l)}</span>`;
          })}
        </div>
      </div>
    `;
  }

  function PRCard({ pr }) {
    return html`
      <a class="gb-card gb-card--pr" href=${pr.html_url} target="_blank" rel="noopener">
        <div class="gb-card__header">
          <span class="gb-card__number">#${pr.number}</span>
          <span class="gb-card__branch">${esc(pr.head)} → ${esc(pr.base)}</span>
        </div>
        <div class="gb-card__title">${esc(pr.title)}</div>
        <div class="gb-card__meta">
          <span>@${esc(pr.user)}</span>
          <span>${timeAgo(pr.created_at)}</span>
        </div>
      </a>
    `;
  }

  // ── Issue Detail Modal ────────────────────

  function IssueModal({ owner, repo, number, onClose }) {
    var [issue, setIssue]     = useState(null);
    var [loading, setLoading] = useState(true);
    var [comment, setComment] = useState('');
    var [posting, setPosting] = useState(false);
    var [changed, setChanged] = useState(false);
    var bodyRef = useRef(null);

    useEffect(function () {
      setLoading(true);
      api('/issue/' + owner + '/' + repo + '/' + number).then(function (d) {
        if (d.error) { sw.toast(d.error, 'error'); onClose(false); return; }
        setIssue(d);
        setLoading(false);
      });
    }, [owner, repo, number]);

    // Close on Escape
    useEffect(function () {
      var handler = function (e) { if (e.key === 'Escape') onClose(changed); };
      document.addEventListener('keydown', handler);
      return function () { document.removeEventListener('keydown', handler); };
    }, [changed]);

    var postComment = useCallback(function () {
      if (!comment.trim() || posting) return;
      setPosting(true);
      api('/issue/' + owner + '/' + repo + '/' + number + '/comment', {
        method: 'POST', body: { body: comment.trim() }
      }).then(function (d) {
        if (d.error) { sw.toast(d.error, 'error'); }
        else {
          // Append to local comments
          setIssue(function (prev) {
            if (!prev) return prev;
            var copy = {};
            for (var k in prev) copy[k] = prev[k];
            copy.comments = (prev.comments || []).concat([d]);
            return copy;
          });
          setComment('');
          setChanged(true);
        }
        setPosting(false);
      });
    }, [comment, posting, owner, repo, number]);

    var toggleState = useCallback(function () {
      if (!issue) return;
      var newState = issue.state === 'open' ? 'closed' : 'open';
      api('/issue/' + owner + '/' + repo + '/' + number, {
        method: 'POST', body: { state: newState }
      }).then(function (d) {
        if (d.error) { sw.toast(d.error, 'error'); return; }
        setIssue(function (prev) {
          if (!prev) return prev;
          var copy = {};
          for (var k in prev) copy[k] = prev[k];
          copy.state = newState;
          return copy;
        });
        setChanged(true);
      });
    }, [issue, owner, repo, number]);

    return html`
      <div class="gb-modal-overlay" onClick=${function (e) {
        if (e.target.classList.contains('gb-modal-overlay')) onClose(changed);
      }}>
        <div class="gb-modal">
          <div class="gb-modal__header">
            <div class="gb-modal__title-row">
              ${loading ? 'Loading…' : html`
                <span class="gb-card__number">#${number}</span>
                <h2 class="gb-modal__title">${esc(issue && issue.title)}</h2>
              `}
            </div>
            <button class="gb-modal__close" onClick=${function () { onClose(changed); }}>✕</button>
          </div>

          ${!loading && issue && html`
            <div class="gb-modal__body" ref=${bodyRef}>
              <div class="gb-modal__meta">
                <span class="badge ${issue.state === 'open' ? 'badge--green' : 'badge--muted'}">${issue.state}</span>
                ${issue.assignee && html`<span class="gb-card__assignee">@${esc(issue.assignee)}</span>`}
                ${issue.created_at && html`<span class="gb-modal__date">${new Date(issue.created_at).toLocaleDateString()}</span>`}
                <a href=${issue.html_url || '#'} target="_blank" rel="noopener"
                   class="gb-modal__extlink">Open in Gitea ↗</a>
              </div>

              ${issue.labels && issue.labels.length > 0 && html`
                <div class="gb-card__labels" style="margin-bottom:12px;">
                  ${issue.labels.map(function (l) { return html`<span key=${l} class="badge">${esc(l)}</span>`; })}
                </div>
              `}

              <div class="gb-modal__description">
                ${issue.body ? html`<pre class="gb-modal__body-text">${esc(issue.body)}</pre>`
                  : html`<p class="gb-modal__empty">No description.</p>`}
              </div>

              <div class="gb-modal__comments">
                <h3 class="gb-modal__section-title">Comments (${(issue.comments || []).length})</h3>
                ${(issue.comments || []).length === 0 && html`
                  <p class="gb-modal__empty">No comments yet.</p>
                `}
                ${(issue.comments || []).map(function (c, i) {
                  return html`
                    <div class="gb-comment" key=${i}>
                      <div class="gb-comment__header">
                        <strong>@${esc(c.user)}</strong>
                        <span class="gb-comment__date">${timeAgo(c.created_at)}</span>
                      </div>
                      <div class="gb-comment__body">${esc(c.body)}</div>
                    </div>
                  `;
                })}
              </div>

              <div class="gb-modal__add-comment">
                <textarea class="gb-modal__textarea" rows="3"
                          placeholder="Add a comment…"
                          value=${comment}
                          onInput=${function (e) { setComment(e.target.value); }}
                          onKeyDown=${function (e) {
                            if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) postComment();
                          }} />
                <div class="gb-modal__actions">
                  <button class="btn-small btn-primary" disabled=${posting || !comment.trim()}
                          onClick=${postComment}>
                    ${posting ? 'Posting…' : 'Comment'}
                  </button>
                  <button class="btn-small ${issue.state === 'open' ? 'btn-danger' : 'btn-secondary'}"
                          onClick=${toggleState}>
                    ${issue.state === 'open' ? 'Close Issue' : 'Reopen Issue'}
                  </button>
                </div>
              </div>
            </div>
          `}
        </div>
      </div>
    `;
  }

  // ── Utilities ──────────────────────────────

  function esc(s) {
    var el = document.createElement('span');
    el.textContent = String(s || '');
    return el.innerHTML;
  }

  function timeAgo(iso) {
    if (!iso) return '';
    var sec = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
    if (sec < 60) return 'now';
    var min = Math.floor(sec / 60);
    if (min < 60) return min + 'm';
    var hr = Math.floor(min / 60);
    if (hr < 24) return hr + 'h';
    return Math.floor(hr / 24) + 'd';
  }

  // ── Mount ──────────────────────────────────
  render(html`<${App} />`, mount);
  console.log('[git-board] Surface mounted');
})();
