// Projects (wired to the real project-manager API) + Operations.
// PageProjects implements the actual create/track-project flow — pick GitHub
// repos, label their folders, save/edit/remove — against /api/repos and
// /api/projects (proxied to the project-manager service). When the API is
// unreachable it falls back to the design's sample data so the standalone
// preview stays fully navigable. Operations is the design prototype verbatim.
const { useState, useEffect, useRef, useCallback } = React;

// ---- helpers ported from project-manager/public/app.js ----
function pmSlugify(value) {
  return String(value || "")
    .trim().toLowerCase()
    .replace(/['"]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 80);
}
function repoBase(fullName) { return String(fullName || "").split("/").pop() || ""; }
function stripProjectPrefix(repoSlug, projectSlug) {
  if (!repoSlug || !projectSlug) return "";
  if (repoSlug === projectSlug) return "monorepo";
  if (repoSlug.startsWith(`${projectSlug}-`)) return repoSlug.slice(projectSlug.length + 1);
  const repoTokens = repoSlug.split("-").filter(Boolean);
  const projectTokens = projectSlug.split("-").filter(Boolean);
  if (projectTokens.length && projectTokens.every((token, i) => repoTokens[i] === token)) {
    return repoTokens.slice(projectTokens.length).join("-") || "monorepo";
  }
  return "";
}
function inferFolderLabel(fullName, projectName) {
  const repoSlug = pmSlugify(repoBase(fullName));
  const stripped = stripProjectPrefix(repoSlug, pmSlugify(projectName));
  return stripped || repoSlug || "repo";
}
function uniqueLabel(label, used) {
  const base = pmSlugify(label) || "repo";
  let candidate = base, i = 2;
  while (used.has(candidate)) { candidate = `${base}-${i}`; i += 1; }
  used.add(candidate);
  return candidate;
}
// dedupe + auto-label the current selection for display and for the save payload
function computeLabels(selected, name) {
  const used = new Set();
  const out = [];
  for (const [full, item] of selected) {
    const base = item.auto ? inferFolderLabel(full, name) : item.label;
    out.push([full, uniqueLabel(base, used), item.auto]);
  }
  return out;
}

// ---- suggested folders (ported from the legacy project-manager UI) ----
// Groups untracked repos by GitHub owner + shared name prefix (e.g.
// factoryos-frontend + factoryos-backend -> "Factoryos") for one-click setup.
const KNOWN_LABELS = new Set([
  "frontend", "backend", "api", "mobile", "web", "admin", "app", "worker", "service",
  "server", "client", "infra", "docs", "documentation", "marketing", "e2e", "tests",
  "dashboard", "gateway", "bot",
]);

function titleFromSlug(value) {
  return String(value || "")
    .split(/[-_]/)
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(" ");
}

function pmInferRepo(repo) {
  const rawName = repo.name || repoBase(repo.fullName) || repo.fullName;
  const owner = repo.owner || String(repo.fullName || "").split("/")[0] || "";
  const normalized = pmSlugify(rawName);
  const parts = normalized.split("-").filter(Boolean);
  let label = "repo";
  let projectSlug = normalized;
  for (let i = parts.length - 1; i >= 1; i -= 1) {
    const candidate = parts.slice(i).join("-");
    if (KNOWN_LABELS.has(candidate) || KNOWN_LABELS.has(parts[i])) {
      label = candidate;
      projectSlug = parts.slice(0, i).join("-");
      break;
    }
  }
  if (label === "repo") label = "monorepo";
  if (!projectSlug) projectSlug = normalized || pmSlugify(owner) || "project";
  return {
    organization: owner,
    groupKey: `${pmSlugify(owner) || "unknown-owner"}/${projectSlug}`,
    projectName: titleFromSlug(projectSlug) || projectSlug,
    label,
  };
}

function buildSuggestions(repos, trackedRepoNames) {
  const groups = new Map();
  for (const repo of repos) {
    if (trackedRepoNames.has(repo.fullName)) continue;
    const inferred = pmInferRepo(repo);
    if (!groups.has(inferred.groupKey)) {
      groups.set(inferred.groupKey, { id: inferred.groupKey, organization: inferred.organization, name: inferred.projectName, repos: [] });
    }
    groups.get(inferred.groupKey).repos.push({ ...repo, suggestedLabel: inferred.label });
  }
  return [...groups.values()]
    .filter((group) => group.repos.length > 1)
    .sort((a, b) => b.repos.length - a.repos.length || a.name.localeCompare(b.name))
    .slice(0, 12);
}

async function pmApi(path, options = {}) {
  const res = await fetch(path, {
    headers: { "content-type": "application/json", ...(options.headers || {}) },
    ...options,
  });
  const body = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(body.error || `Request failed: ${res.status}`);
  return body;
}

function wdRepoName(value) { return String(value || "").trim().replace(/\.git$/i, "").toLowerCase(); }
function matchWatchdogProject(project, snapshot) {
  const rows = snapshot?.projects || [];
  if (project?.watchdogProjectId) {
    const exact = rows.find((row) => row.projectId === project.watchdogProjectId);
    if (exact) return exact;
  }
  const repos = new Set(Object.values(project?.repos || {}).map(wdRepoName));
  const matches = rows.filter((row) => row.github && repos.has(wdRepoName(`${row.github.owner}/${row.github.name}`)));
  return matches.length === 1 ? matches[0] : null;
}
function WatchdogProjectStatus({ row, dashboardUrl }) {
  if (!row) return <span className="badge"><span className="d" />not linked</span>;
  const tone = row.state === "healthy" ? "ok" : ["critical", "degraded"].includes(row.state) ? "bad" : ["maintenance", "readiness-unverified"].includes(row.state) ? "warn" : "";
  const href = dashboardUrl ? `${dashboardUrl}/projects/${encodeURIComponent(row.projectId)}` : "";
  const badge = <span className={cls("badge", tone)}><span className="d" />{row.state}{row.openIncidentCount ? ` · ${row.openIncidentCount} incident${row.openIncidentCount === 1 ? "" : "s"}` : ""}</span>;
  return href ? <a href={href} target="_blank" rel="noopener noreferrer" title="Open this project in Watchdog">{badge}</a> : badge;
}

function MockProjectCard({ p, toast }) {
  return (
    <div className="card" style={{ display: "flex", flexDirection: "column", gap: 14, cursor: "pointer" }} onClick={() => toast(p.name)}>
      <div className="flex between">
        <div className="flex" style={{ gap: 12 }}>
          <div className="glyph" style={{ width: 42, height: 42, fontSize: 17, color: `oklch(0.72 0.15 ${p.accent})`, background: `oklch(0.72 0.15 ${p.accent} / 0.14)`, borderColor: "transparent" }}>{p.name[0]}</div>
          <div><div style={{ fontWeight: 650, fontSize: 15 }}>{p.name}</div><div className="faint mono" style={{ fontSize: 11.5 }}>{p.repos} repos · {p.openTasks} open tasks</div></div>
        </div>
        <HealthBadge health={p.health} />
      </div>
      <div className="divider" />
      <div className="flex between">
        <div className="flex" style={{ gap: 0 }}>{p.agents.map((id, i) => <div key={id} style={{ marginLeft: i ? -8 : 0, border: "2px solid var(--surface)", borderRadius: 9 }}><AgentBot agent={id} size={28} /></div>)}</div>
        <span className="faint mono" style={{ fontSize: 11.5 }}><Icon name="clock" size={13} style={{ display: "inline", verticalAlign: "-2px", marginRight: 5 }} />{p.lastActivity}</span>
      </div>
    </div>
  );
}

function PageProjects({ go, toast }) {
  const [repos, setRepos] = useState([]);
  const [projects, setProjects] = useState([]);
  const [selected, setSelected] = useState(() => new Map());
  const [name, setName] = useState("");
  const [query, setQuery] = useState("");
  const [visibility, setVisibility] = useState("all");
  const [live, setLive] = useState(null); // null = loading, true = real API, false = preview/mock
  const [watchdog, setWatchdog] = useState(null);
  const [watchdogProjectId, setWatchdogProjectId] = useState(null);
  const [saving, setSaving] = useState(false);
  const [editingId, setEditingId] = useState(null); // id of the folder being edited (its repos stay pickable)
  const nameRef = useRef(null);

  const reload = useCallback(async () => {
    try {
      const [reposRes, projRes] = await Promise.all([pmApi("/api/repos"), pmApi("/api/projects")]);
      setRepos(Array.isArray(reposRes.repos) ? reposRes.repos : []);
      setProjects(Array.isArray(projRes.projects) ? projRes.projects : []);
      setLive(true);
    } catch {
      setLive(false);
    }
  }, []);
  const reloadWatchdog = useCallback(async () => {
    try { setWatchdog(await HERMES.watchdogOverview()); }
    catch (e) { setWatchdog({ configured: true, error: e?.message || "Watchdog unavailable", counts: {}, projects: [] }); }
  }, []);
  useEffect(() => { reload(); reloadWatchdog(); }, [reload, reloadWatchdog]);

  const toggleRepo = (full) => setSelected((prev) => {
    const next = new Map(prev);
    if (next.has(full)) next.delete(full);
    else next.set(full, { label: inferFolderLabel(full, name), auto: true });
    return next;
  });
  const relabel = (full, value) => setSelected((prev) => {
    const next = new Map(prev);
    next.set(full, { label: value, auto: false });
    return next;
  });

  const labels = computeLabels(selected, name);
  const canSave = live && name.trim() && selected.size > 0 && !saving;
  const selectedProject = { repos: Object.fromEntries(labels.map(([full, label]) => [label, full])) };
  const suggestedWatchdog = matchWatchdogProject(selectedProject, watchdog);

  const save = async () => {
    const repoMap = {};
    for (const [full, label] of computeLabels(selected, name)) repoMap[label] = full;
    setSaving(true);
    try {
      const linkedWatchdogProjectId = watchdogProjectId === null ? (suggestedWatchdog?.projectId || "") : watchdogProjectId;
      const { projects: next } = await pmApi("/api/projects", { method: "POST", body: JSON.stringify({ name: name.trim(), repos: repoMap, watchdogProjectId: linkedWatchdogProjectId }) });
      setProjects(next);
      setSelected(new Map());
      setName("");
      setWatchdogProjectId(null);
      setEditingId(null);
      toast("Project saved · Discord provisioning runs within ~30s");
    } catch (e) { toast(e.message); }
    setSaving(false);
  };

  const editProject = (p) => {
    setEditingId(p.id || pmSlugify(p.name));
    setName(p.name);
    setWatchdogProjectId(p.watchdogProjectId || matchWatchdogProject(p, watchdog)?.projectId || null);
    const m = new Map();
    for (const [label, full] of Object.entries(p.repos || {})) m.set(full, { label, auto: false });
    setSelected(m);
    window.scrollTo({ top: 0, behavior: "smooth" });
    nameRef.current?.focus();
  };
  const removeProject = async (p) => {
    if (!window.confirm("Remove this project from Hermes tracking and delete its local cloned repo folder?")) return;
    try {
      const { projects: next } = await pmApi(`/api/projects/${encodeURIComponent(p.id || pmSlugify(p.name))}`, { method: "DELETE" });
      setProjects(next);
      toast("Project removed · Discord channels were not deleted");
    } catch (e) { toast(e.message); }
  };

  // Repos already tracked in another project folder are hidden from the picker —
  // each repo belongs to exactly one folder. The folder being edited is excluded
  // so its own repos stay visible (and removable) while editing it.
  const trackedElsewhere = new Set();
  for (const p of projects) {
    if ((p.id || pmSlugify(p.name)) === editingId) continue;
    for (const full of Object.values(p.repos || {})) trackedElsewhere.add(full);
  }

  const suggestions = live ? buildSuggestions(repos, trackedElsewhere) : [];
  const useSuggestion = (group) => {
    setEditingId(null);
    setName(group.name);
    const used = new Set();
    const next = new Map();
    for (const repo of group.repos) next.set(repo.fullName, { label: uniqueLabel(repo.suggestedLabel, used), auto: false });
    setSelected(next);
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  const shownRepos = repos.filter((r) => {
    if (trackedElsewhere.has(r.fullName)) return false;
    const hay = `${r.fullName} ${r.description || ""} ${r.language || ""}`.toLowerCase();
    if (query && !hay.includes(query.toLowerCase())) return false;
    if (visibility === "private" && !r.private) return false;
    if (visibility === "public" && r.private) return false;
    return true;
  }).slice(0, 200);

  return (
    <div className="page">
      <PageHead eyebrow="Tracking" title="Projects" sub="Codebases your agents work across — pick repos, label their folders, and track them as project folders.">
        <button className="btn btn-ghost" onClick={() => { reload(); reloadWatchdog(); }}><Icon name="refresh" size={16} />Refresh</button>
        <button className="btn btn-primary" onClick={() => { window.scrollTo({ top: 0, behavior: "smooth" }); nameRef.current?.focus(); }}><Icon name="plus" size={16} />New project</button>
      </PageHead>

      {/* ---- create / update flow ---- */}
      <div className="card" style={{ marginBottom: "var(--gap)" }}>
        <div className="card-head">
          <div>
            <h2>Create or update project folder</h2>
            <p>Choose repos, review the auto-generated folder labels, then save the project folder.</p>
          </div>
          {live === true && <span className="badge ok"><span className="d" />live · {repos.length} repos</span>}
          {live === false && <span className="badge warn"><span className="d" />preview data</span>}
          {live === null && <span className="badge"><span className="d" />loading…</span>}
        </div>

        {live === false && (
          <div style={{ marginBottom: "var(--gap)", padding: "12px 14px", border: "1px solid var(--line)", borderRadius: 10, background: "var(--surface-2)", fontSize: 12.5, lineHeight: 1.6, color: "var(--muted)" }}>
            <b style={{ color: "var(--text)" }}>Preview mode.</b> The project-manager API isn’t reachable, so live GitHub repos and saving are disabled. Run the project-manager service (with <span className="mono">GITHUB_TOKEN</span>) and point <span className="mono">PROJECT_MANAGER_URL</span> at it — repos and tracked projects below come straight from the real API.
          </div>
        )}

        {suggestions.length > 0 && (
          <div style={{ marginBottom: "var(--gap)" }}>
            <span className="section-title">Suggested folders</span>
            <div className="chips" style={{ marginTop: 8 }}>
              {suggestions.map((group) => (
                <button
                  key={group.id}
                  className="btn btn-subtle btn-sm"
                  title={`${group.organization || "unknown owner"} · ${group.repos.map((r) => r.name).join(", ")}`}
                  onClick={() => useSuggestion(group)}
                >
                  {group.name} · {group.repos.length} repos
                </button>
              ))}
            </div>
          </div>
        )}

        <div className="field" style={{ marginBottom: "var(--gap)", maxWidth: 460 }}>
          <label>Project folder</label>
          <input ref={nameRef} className="input" value={name} onChange={(e) => setName(e.target.value)} placeholder="ArogyaLens, VakeelOS, Varun Portfolio" disabled={!live} />
        </div>

        <div className="field" style={{ marginBottom: "var(--gap)", maxWidth: 460 }}>
          <label>Watchdog health source <span className="faint">(read-only)</span></label>
          <select className="select" value={watchdogProjectId === null ? (suggestedWatchdog?.projectId || "") : watchdogProjectId} onChange={(e) => setWatchdogProjectId(e.target.value)} disabled={!watchdog?.configured || !!watchdog?.error}>
            <option value="">{watchdog === null ? "Loading Watchdog…" : !watchdog?.configured ? "Watchdog connector not configured" : watchdog?.error ? "Watchdog unavailable" : "Do not link to Watchdog"}</option>
            {(watchdog?.projects || []).map((row) => <option key={row.projectId} value={row.projectId}>{row.displayName} · {row.state}</option>)}
          </select>
          {suggestedWatchdog && watchdogProjectId === null && <small className="faint">Matched from GitHub repository: {suggestedWatchdog.displayName}</small>}
          {watchdog?.error && <small style={{ color: "var(--danger)" }}>{watchdog.error}</small>}
        </div>

        <div className="grid cols-2">
          <div>
            <div className="flex between" style={{ marginBottom: 12 }}>
              <span className="section-title">GitHub repositories</span>
              <div className="flex" style={{ gap: 8 }}>
                <input className="input" style={{ height: 36, width: 150 }} placeholder="Filter repos" value={query} onChange={(e) => setQuery(e.target.value)} disabled={!live} />
                <select className="select" style={{ height: 36, width: 110 }} value={visibility} onChange={(e) => setVisibility(e.target.value)} disabled={!live}>
                  <option value="all">All</option>
                  <option value="private">Private</option>
                  <option value="public">Public</option>
                </select>
              </div>
            </div>
            <div className="rows scroll">
              {shownRepos.map((r) => {
                const on = selected.has(r.fullName);
                return (
                  <button key={r.fullName} className={cls("row", on && "sel")} onClick={() => toggleRepo(r.fullName)}>
                    <span className={cls("ck", on && "on")}><Icon name="check" size={13} /></span>
                    <div className="row-main">
                      <b className="mono" style={{ fontSize: 13 }}>{r.fullName}</b>
                      <div className="row-sub mono"><span>{r.private ? "Private" : "Public"}</span>{r.language && <><span>·</span><span>{r.language}</span></>}</div>
                    </div>
                  </button>
                );
              })}
              {live && !shownRepos.length && <div className="empty">No repositories match.</div>}
              {!live && <div className="empty">Connect the project-manager API to list your GitHub repositories.</div>}
            </div>
          </div>

          <div>
            <div className="flex between" style={{ marginBottom: 12 }}>
              <span className="section-title">Selected repositories</span>
              <span className="faint mono" style={{ fontSize: 12 }}>{selected.size} selected</span>
            </div>
            <div className="rows scroll">
              {labels.map(([full, label]) => (
                <div className="row" key={full} style={{ cursor: "default", alignItems: "flex-start" }}>
                  <div className="row-main">
                    <b className="mono" style={{ fontSize: 12.5 }}>{full}</b>
                    <input className="input" style={{ height: 38, marginTop: 6 }} value={label} onChange={(e) => relabel(full, e.target.value)} aria-label={`Folder label for ${full}`} />
                  </div>
                  <button className="btn btn-subtle btn-sm" onClick={() => toggleRepo(full)} title="Remove"><Icon name="x" size={14} /></button>
                </div>
              ))}
              {!selected.size && <div className="empty">Select one or more repos from the left.</div>}
            </div>
          </div>
        </div>

        <div style={{ display: "flex", justifyContent: "flex-end", marginTop: "var(--gap)" }}>
          <button className="btn btn-primary" disabled={!canSave} onClick={save}><Icon name="check" size={16} />{saving ? "Saving…" : "Save folder"}</button>
        </div>
      </div>

      {/* ---- tracked projects ---- */}
      <div className="card-head">
        <h2 className="mono" style={{ fontSize: 13, letterSpacing: ".06em", textTransform: "uppercase" }}>Tracked project folders</h2>
        {watchdog?.configured && !watchdog?.error && <a href={watchdog.dashboardUrl || "#"} target="_blank" rel="noopener noreferrer" className={cls("badge", watchdog.counts?.openIncidents ? "bad" : "ok")}><span className="d" />Watchdog · {watchdog.counts?.openIncidents || 0} open incidents</a>}
        {watchdog && !watchdog.configured && <span className="badge"><span className="d" />Watchdog not configured</span>}
      </div>
      {live === false ? (
        <div className="grid cols-2">
          {window.DATA.projects.map((p) => <MockProjectCard key={p.id} p={p} toast={toast} />)}
        </div>
      ) : !projects.length ? (
        <div className="card"><div className="empty">No tracked projects yet. Create one above and the Discord controller will pick it up.</div></div>
      ) : (
        <div className="grid cols-2">
          {projects.map((p) => {
            const entries = Object.entries(p.repos || {});
            const watchdogRow = matchWatchdogProject(p, watchdog);
            return (
              <div className="card" key={p.id || p.name} style={{ display: "flex", flexDirection: "column", gap: 14 }}>
                <div className="flex between">
                  <div className="flex" style={{ gap: 12 }}>
                    <div className="glyph" style={{ width: 42, height: 42, fontSize: 17 }}>{(p.name[0] || "?").toUpperCase()}</div>
                    <div><div style={{ fontWeight: 650, fontSize: 15 }}>{p.name}</div><div className="faint mono" style={{ fontSize: 11.5 }}>{entries.length} repo{entries.length === 1 ? "" : "s"} tracked</div></div>
                  </div>
                  <div className="flex" style={{ gap: 6 }}>
                    <WatchdogProjectStatus row={watchdogRow} dashboardUrl={watchdog?.dashboardUrl} />
                    <button className="btn btn-ghost btn-sm" onClick={() => editProject(p)}><Icon name="edit" size={13} />Edit</button>
                    <button className="btn btn-danger btn-sm" aria-label={`Delete ${p.name || p.id} project`} onClick={() => removeProject(p)}><Icon name="trash" size={13} /></button>
                  </div>
                </div>
                <div className="divider" />
                <div className="chips">
                  {entries.map(([label, full]) => <span className="tag" key={label} title={full}>{label}</span>)}
                </div>
                {watchdogRow && (
                  <div className="faint mono" style={{ fontSize: 11.5 }}>
                    {watchdogRow.endpoints.length} monitored endpoint{watchdogRow.endpoints.length === 1 ? "" : "s"}
                    {watchdogRow.endpoints.length ? ` · ${watchdogRow.endpoints.map((item) => `${item.service}:${item.readiness}`).join(" · ")}` : ""}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}
window.PageProjects = PageProjects;

/* The real "Operations" page (the persistent agent team) lives in
   page-operations.jsx. The old container-health mock that used to live here was
   removed — runtime/gateway health is shown on the Gateway page instead. */
