// Saola prototype comment layer
const COMMENT_PROTOTYPE_ID = "saola-ldp";
const COMMENT_ACTION_LABEL = "Send to Saola";
const COMMENT_LOCAL_KEY = `${COMMENT_PROTOTYPE_ID}:open-comments`;
const {
  useCallback: useCallbackCL,
  useEffect: useEffectCL,
  useMemo: useMemoCL,
  useState: useStateCL
} = React;

function commentScreenFromPath(pathname) {
  if (pathname.endsWith("/projects.html")) {
    return { screenId: "portfolio-all-projects", label: "All Projects", route: "/projects.html" };
  }
  return { screenId: "landing-home", label: "Landing Page", route: "/" };
}

function commentLabelForElement(target) {
  let el = target;
  while (el && el !== document.body) {
    if (el.closest && el.closest(".comment-layer")) return "Comment tool";
    const aria = el.getAttribute && (el.getAttribute("aria-label") || el.getAttribute("title"));
    const text = (el.innerText || el.textContent || "").replace(/\s+/g, " ").trim();
    if (aria) return aria.slice(0, 80);
    if (text && text.length < 90) return text;
    if (el.id) return `#${el.id}`;
    if (el.className && typeof el.className === "string") {
      const firstClass = el.className.split(/\s+/).filter(Boolean)[0];
      if (firstClass) return `.${firstClass}`;
    }
    el = el.parentElement;
  }
  return "Page";
}

function statusLabel(status) {
  return {
    reopened: "Reopened",
    open: "Open",
    sent: "Sent",
    resolved: "Resolved",
    deleted: "Deleted",
    ai_task_draft: "Sent"
  }[status] || status;
}

function isLocalComment(comment) {
  return String(comment.id || "").startsWith("local-");
}

function loadOpenComments() {
  try {
    const localComments = JSON.parse(window.localStorage.getItem(COMMENT_LOCAL_KEY) || "[]");
    return Array.isArray(localComments) ? localComments.filter(c => c.status === "open") : [];
  } catch {
    return [];
  }
}

function saveOpenComments(comments) {
  const localComments = comments.filter(c => isLocalComment(c) && c.status === "open");
  window.localStorage.setItem(COMMENT_LOCAL_KEY, JSON.stringify(localComments));
}

function documentSize() {
  const doc = document.documentElement;
  return {
    width: Math.max(doc.scrollWidth, window.innerWidth),
    height: Math.max(doc.scrollHeight, window.innerHeight)
  };
}

function pointStyle(comment, pageSize) {
  const left = Number.isFinite(comment.pageX) ? comment.pageX : comment.x * pageSize.width;
  const top = Number.isFinite(comment.pageY) ? comment.pageY : comment.y * pageSize.height;
  return {
    left: `${left}px`,
    top: `${top}px`
  };
}

async function commentApi(path, options = {}) {
  const response = await fetch(path, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      ...(options.headers || {})
    }
  });
  if (!response.ok) throw new Error(await response.text());
  return response.status === 204 ? null : response.json();
}

function CommentLayer() {
  const [comments, setComments] = useStateCL([]);
  const [mode, setMode] = useStateCL("browse");
  const [panelOpen, setPanelOpen] = useStateCL(false);
  const [draft, setDraft] = useStateCL(null);
  const [selectedId, setSelectedId] = useStateCL(null);
  const [selectedRows, setSelectedRows] = useStateCL([]);
  const [message, setMessage] = useStateCL("");
  const [commenterName, setCommenterName] = useStateCL("");
  const [busy, setBusy] = useStateCL(false);
  const [error, setError] = useStateCL("");
  const [screen, setScreen] = useStateCL(commentScreenFromPath(window.location.pathname));
  const [pageSize, setPageSize] = useStateCL(documentSize);

  const reload = useCallbackCL(async () => {
    try {
      setError("");
      const list = await commentApi(`/api/comments?prototypeId=${encodeURIComponent(COMMENT_PROTOTYPE_ID)}`);
      setComments([...loadOpenComments(), ...list]);
    } catch {
      setError("Comment API unavailable. Use node scripts/serve.mjs locally or Cloudflare Pages with the D1 binding.");
    }
  }, []);

  useEffectCL(() => {
    reload();
    const onPop = () => setScreen(commentScreenFromPath(window.location.pathname));
    window.addEventListener("popstate", onPop);
    return () => window.removeEventListener("popstate", onPop);
  }, [reload]);

  useEffectCL(() => {
    const tick = () => {
      setScreen(commentScreenFromPath(window.location.pathname));
      setPageSize(documentSize());
    };
    const id = window.setInterval(tick, 600);
    window.addEventListener("resize", tick);
    window.addEventListener("load", tick);
    return () => {
      window.clearInterval(id);
      window.removeEventListener("resize", tick);
      window.removeEventListener("load", tick);
    };
  }, []);

  const visiblePins = useMemoCL(() => (
    comments.filter(c => c.screenId === screen.screenId && c.status !== "resolved" && c.status !== "deleted")
  ), [comments, screen.screenId]);

  const selected = useMemoCL(() => (
    comments.find(c => c.id === selectedId) || null
  ), [comments, selectedId]);

  const selectedCommentRows = useMemoCL(() => (
    comments.filter(c => selectedRows.includes(c.id))
  ), [comments, selectedRows]);

  useEffectCL(() => {
    if (mode !== "pin") {
      document.body.classList.remove("comment-pinning");
      return undefined;
    }
    document.body.classList.add("comment-pinning");
    document.addEventListener("click", handleCanvasClick, true);
    return () => {
      document.body.classList.remove("comment-pinning");
      document.removeEventListener("click", handleCanvasClick, true);
    };
  }, [mode, screen.screenId, screen.route, pageSize.width, pageSize.height]);

  async function updateComment(id, status) {
    const previous = comments;
    const target = comments.find(c => c.id === id);
    if (!target) return;

    const next = comments.map(c => c.id === id ? { ...c, status, updatedAt: new Date().toISOString() } : c);
    setComments(next);
    if (status === "resolved") setSelectedId(null);

    if (isLocalComment(target)) {
      if (status === "sent") {
        try {
          const saved = await commentApi("/api/comments", {
        method: "POST",
            body: JSON.stringify({
              ...target,
              x: target.pageX || target.x * pageSize.width,
              y: target.pageY || target.y * pageSize.height,
              status: "sent"
            })
          });
          setComments(prev => {
            const replaced = prev.map(c => c.id === id ? saved : c);
            saveOpenComments(replaced);
            return replaced;
          });
          setSelectedId(saved.id);
        } catch {
          setComments(previous);
          setError("Could not send this comment to Saola. Please retry.");
        }
        return;
      }

      if (status === "deleted") {
        const withoutLocal = previous.filter(c => c.id !== id);
        setComments(withoutLocal);
        saveOpenComments(withoutLocal);
        if (selectedId === id) setSelectedId(null);
      } else {
        setComments(previous);
      }
      return;
    }

    commentApi(`/api/comments/${id}`, {
      method: "PATCH",
      body: JSON.stringify({ status })
    }).catch(() => {
      setComments(previous);
      setError("Could not update the comment. Please retry.");
    });
  }

  async function deleteComment(id) {
    const previous = comments;
    const target = comments.find(c => c.id === id);
    setComments(prev => prev.filter(c => c.id !== id));
    setSelectedRows(prev => prev.filter(rowId => rowId !== id));
    if (selectedId === id) setSelectedId(null);

    if (target && isLocalComment(target)) {
      const next = previous.filter(c => c.id !== id);
      saveOpenComments(next);
      return;
    }

    try {
      await commentApi(`/api/comments/${id}`, { method: "DELETE" });
    } catch {
      setComments(previous);
      setError("Could not delete the comment. Please retry.");
    }
  }

  async function bulkSendToImplementation() {
    const ids = selectedRows.filter(id => comments.some(c => c.id === id && c.status !== "sent"));
    if (ids.length === 0) return;
    const previous = comments;
    setComments(prev => prev.map(c => ids.includes(c.id) ? { ...c, status: "sent", updatedAt: new Date().toISOString() } : c));
    try {
      const updates = await Promise.all(ids.map(id => {
        const comment = previous.find(c => c.id === id);
        if (comment && isLocalComment(comment)) {
          return commentApi("/api/comments", {
            method: "POST",
            body: JSON.stringify({
              ...comment,
              x: comment.pageX || comment.x * pageSize.width,
              y: comment.pageY || comment.y * pageSize.height,
              status: "sent"
            })
          }).then(saved => ({ localId: id, saved }));
        }
        return commentApi(`/api/comments/${id}`, {
          method: "PATCH",
          body: JSON.stringify({ status: "sent" })
        }).then(saved => ({ localId: id, saved }));
      }));
      setComments(prev => {
        const next = prev.map(comment => {
          const update = updates.find(item => item.localId === comment.id);
          return update ? update.saved : comment;
        });
        saveOpenComments(next);
        return next;
      });
      setSelectedRows([]);
    } catch {
      setComments(previous);
      setError("Could not send selected comments. Please retry.");
    }
  }

  async function bulkDelete() {
    const ids = [...selectedRows];
    if (ids.length === 0) return;
    const previous = comments;
    setComments(prev => prev.filter(c => !ids.includes(c.id)));
    setSelectedRows([]);
    if (ids.includes(selectedId)) setSelectedId(null);
    saveOpenComments(comments.filter(c => !ids.includes(c.id)));
    try {
      await Promise.all(ids.map(id => {
        const comment = previous.find(c => c.id === id);
        return comment && isLocalComment(comment) ? Promise.resolve(null) : commentApi(`/api/comments/${id}`, { method: "DELETE" });
      }));
    } catch {
      setComments(previous);
      setError("Could not delete selected comments. Please retry.");
    }
  }

  async function bulkResolve() {
    const ids = selectedRows.filter(id => comments.some(c => c.id === id && !isLocalComment(c) && c.status !== "resolved"));
    if (ids.length === 0) return;
    const previous = comments;
    setComments(prev => prev.map(c => ids.includes(c.id) ? { ...c, status: "resolved", updatedAt: new Date().toISOString() } : c));
    if (ids.includes(selectedId)) setSelectedId(null);
    try {
      await Promise.all(ids.map(id => commentApi(`/api/comments/${id}`, {
        method: "PATCH",
        body: JSON.stringify({ status: "resolved" })
      })));
      setSelectedRows([]);
    } catch {
      setComments(previous);
      setError("Could not resolve selected comments. Please retry.");
    }
  }

  function openComment(comment) {
    if (comment.route !== screen.route) {
      window.location.href = `${comment.route}#comment-${comment.id}`;
      return;
    }
    setSelectedId(comment.id);
    setPanelOpen(false);
    const commentTop = Number.isFinite(comment.pageY) ? comment.pageY : comment.y * documentSize().height;
    window.scrollTo({
      top: Math.max(0, commentTop - window.innerHeight * 0.35),
      behavior: "smooth"
    });
  }

  function toggleRowSelection(id) {
    setSelectedRows(prev => prev.includes(id) ? prev.filter(rowId => rowId !== id) : [...prev, id]);
  }

  function handleCanvasClick(event) {
    if (mode !== "pin") return;
    if (event.target.closest(".comment-layer")) return;
    const elementLabel = commentLabelForElement(event.target);
    event.preventDefault();
    event.stopPropagation();
    const size = documentSize();
    const pageX = event.pageX;
    const pageY = event.pageY;
    setPageSize(size);
    setDraft({
      prototypeId: COMMENT_PROTOTYPE_ID,
      screenId: screen.screenId,
      route: screen.route,
      x: pageX / size.width,
      y: pageY / size.height,
      pageX,
      pageY,
      elementLabel
    });
    setSelectedId(null);
    setMessage("");
  }

  async function saveDraft(event) {
    event.preventDefault();
    if (!draft || !message.trim()) return;
    const temp = {
      ...draft,
      id: `local-${Date.now()}`,
      commenterName: commenterName.trim(),
      message: message.trim(),
      status: "open",
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };
    setBusy(true);
    setComments(prev => {
      const next = [temp, ...prev];
      saveOpenComments(next);
      return next;
    });
    setDraft(null);
    setMode("browse");
    setMessage("");
    setSelectedId(temp.id);
    setBusy(false);
  }

  return (
    <div className={`comment-layer ${mode === "pin" ? "is-pinning" : ""}`} style={{ height: `${pageSize.height}px` }}>
      {mode === "pin" && <div className="comment-capture-banner">Click anywhere on the prototype to leave feedback. Press Esc to cancel.</div>}

      {visiblePins.map((comment, index) => (
        <button
          key={comment.id}
          id={`comment-${comment.id}`}
          className={`comment-pin status-${comment.status}`}
          style={pointStyle(comment, pageSize)}
          onClick={(event) => {
            event.stopPropagation();
            setSelectedId(comment.id);
            setPanelOpen(false);
          }}
          aria-label={`Open comment ${index + 1}`}
        >
          {index + 1}
        </button>
      ))}

      <div className="comment-toolbar">
        <button className="comment-tool-btn" onClick={() => setPanelOpen(true)}>Comments <span>{comments.length}</span></button>
        <button className={`comment-tool-btn ${mode === "pin" ? "is-active" : ""}`} onClick={() => setMode(mode === "pin" ? "browse" : "pin")}>
          Add pin
        </button>
      </div>

      {error && (
        <div className="comment-toast">
          <span>{error}</span>
          <button onClick={() => setError("")}>Dismiss</button>
        </div>
      )}

      {draft && (
        <form className="comment-card comment-card--draft" style={pointStyle(draft, pageSize)} onSubmit={saveDraft}>
          <div className="comment-card__meta">New comment on {draft.elementLabel}</div>
          <input value={commenterName} onChange={(event) => setCommenterName(event.target.value)} placeholder="Your name" />
          <textarea autoFocus value={message} onChange={(event) => setMessage(event.target.value)} placeholder="Leave feedback for this exact spot..." />
          <div className="comment-card__actions">
            <button type="button" className="comment-secondary" onClick={() => setDraft(null)}>Cancel</button>
            <button type="submit" className="comment-primary" disabled={busy || !message.trim()}>Save comment</button>
          </div>
        </form>
      )}

      {selected && (
        <div className="comment-card" style={pointStyle(selected, pageSize)}>
          <div className="comment-card__top">
            <span className={`comment-status status-${selected.status}`}>{statusLabel(selected.status)}</span>
            <button aria-label="Close comment" onClick={() => setSelectedId(null)}>×</button>
          </div>
          <div className="comment-card__meta">{selected.elementLabel} · {commentScreenFromPath(selected.route).label}</div>
          {selected.commenterName && <div className="comment-card__author">{selected.commenterName}</div>}
          <p>{selected.message}</p>
          <div className="comment-card__actions">
            {selected.status === "open" ? null : selected.status !== "reopened" ? (
              <button className="comment-secondary" onClick={() => updateComment(selected.id, "reopened")}>Reopen</button>
            ) : (
              <button className="comment-secondary" onClick={() => updateComment(selected.id, "resolved")}>Resolve</button>
            )}
            <button className="comment-secondary" onClick={() => deleteComment(selected.id)}>Delete</button>
            <button className="comment-primary" disabled={selected.status === "sent"} onClick={() => updateComment(selected.id, "sent")}>
              {selected.status === "sent" ? "Sent" : COMMENT_ACTION_LABEL}
            </button>
          </div>
        </div>
      )}

      {panelOpen && (
        <aside className="comment-panel">
          <div className="comment-panel__head">
            <div>
              <strong>Prototype comments</strong>
              <span>{comments.length} total · {comments.filter(c => c.status === "open").length} open</span>
            </div>
            <button aria-label="Close comments" onClick={() => setPanelOpen(false)}>×</button>
          </div>
          {selectedRows.length > 0 && (
            <div className="comment-panel__bulk">
              <span>{selectedRows.length} selected</span>
              <button className="comment-bulk-primary" onClick={bulkSendToImplementation}>Send to Saola</button>
              <button onClick={bulkDelete}>Delete</button>
              <button onClick={bulkResolve}>Resolved</button>
            </div>
          )}
          <div className="comment-panel__list">
            {comments.length === 0 ? (
              <div className="comment-empty">No comments yet. Toggle Add pin, then click the prototype.</div>
            ) : comments.map(comment => {
              const canSelect = comment.status === "open" || comment.status === "reopened";
              return (
              <div key={comment.id} className={`comment-row status-${comment.status} ${selectedRows.includes(comment.id) ? "is-selected" : ""} ${!canSelect ? "is-disabled" : ""}`}>
                <label className="comment-row__select">
                  <input
                    type="checkbox"
                    checked={selectedRows.includes(comment.id)}
                    disabled={!canSelect}
                    onChange={() => canSelect && toggleRowSelection(comment.id)}
                  />
                  <span>Select comment</span>
                </label>
                <button className="comment-row__open" onClick={() => openComment(comment)}>
                  <span className="comment-row__screen">{commentScreenFromPath(comment.route).label}</span>
                  <span className={`comment-row__status-label status-${comment.status}`}>{statusLabel(comment.status)}</span>
                  <span className="comment-row__message">{comment.message}</span>
                  <span className="comment-row__foot">
                    {statusLabel(comment.status)}
                    {comment.commenterName ? ` · ${comment.commenterName}` : ""}
                    {" · "}
                    {comment.elementLabel}
                  </span>
                </button>
              </div>
              );
            })}
          </div>
        </aside>
      )}
    </div>
  );
}

document.addEventListener("keydown", (event) => {
  if (event.key === "Escape") {
    document.dispatchEvent(new CustomEvent("saola-comment-escape"));
  }
});

function CommentLayerShell() {
  const [escapeTick, setEscapeTick] = useStateCL(0);
  useEffectCL(() => {
    const handler = () => setEscapeTick(t => t + 1);
    document.addEventListener("saola-comment-escape", handler);
    return () => document.removeEventListener("saola-comment-escape", handler);
  }, []);
  return <CommentLayer key={escapeTick}/>;
}

const commentRoot = document.createElement("div");
commentRoot.id = "comment-root";
document.body.appendChild(commentRoot);
ReactDOM.createRoot(commentRoot).render(<CommentLayerShell/>);
