/* Editor: tabs + four view modes (Original / Updated / Diff / Split) + line selection */

function climbToLine(node) {
  let el = node && node.nodeType === 3 ? node.parentElement : node;
  while (el && !(el.dataset && el.dataset.line)) el = el.parentElement;
  return el || null;
}

function EditorTabs({ tabs, active, onActivate, onClose }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current && ref.current.querySelector(".tab.active");
    if (el) el.scrollIntoView({ block: "nearest", inline: "nearest" });
  }, [active]);
  return (
    <div className="tabs" ref={ref}>
      {tabs.map((t) => {
        const name = t.path.split("/").pop();
        return (
          <div key={t.path}
            className={"tab" + (active === t.path ? " active" : "")}
            onClick={() => onActivate(t.path)}
            onAuxClick={(e) => { if (e.button === 1) { e.preventDefault(); onClose(t.path); } }}
            title={t.path}>
            <FileIcon path={t.path} />
            <span className="tname">{name}</span>
            {t.changed && <span className="tab-mode">{t.modeLabel}</span>}
            <span className="tclose" onClick={(e) => { e.stopPropagation(); onClose(t.path); }}>
              {Icon.close({})}
            </span>
          </div>
        );
      })}
    </div>
  );
}

/* Generic pane: renders an array of line descriptors with selection + caret + context. */
function PaneView({ cacheKey, path, lines, lang, showSign, refLine, cursor, selection, setCursor, setSelection, onContext }) {
  const anchorRef = useRef(null);
  const html = useMemo(() => lines.map((l) => HL.hlLine(l.text, lang)), [cacheKey]);

  function gutterClick(e, no) {
    if (no == null) return;
    e.stopPropagation();
    if (e.shiftKey && anchorRef.current != null) {
      const a = anchorRef.current;
      setSelection({ path, start: Math.min(a, no), end: Math.max(a, no), anchor: a });
    } else {
      anchorRef.current = no;
      setSelection({ path, start: no, end: no, anchor: no });
    }
    setCursor({ path, line: no, col: 1 });
  }
  function caretCol(sel) {
    try {
      const el = climbToLine(sel.focusNode);
      const code = el.querySelector(".ln-code");
      const r = document.createRange();
      r.setStart(code, 0); r.setEnd(sel.focusNode, sel.focusOffset);
      return r.toString().length + 1;
    } catch (e) { return 1; }
  }
  function onMouseUp() {
    const sel = window.getSelection();
    if (sel && !sel.isCollapsed) {
      const a = climbToLine(sel.anchorNode), f = climbToLine(sel.focusNode);
      if (a && f) {
        const an = +a.dataset.line, fn = +f.dataset.line;
        const s = Math.min(an, fn), e = Math.max(an, fn);
        if (s !== e) { setSelection({ path, start: s, end: e, anchor: an }); setCursor({ path, line: fn, col: caretCol(sel) }); return; }
      }
    }
    if (sel && sel.focusNode) {
      const el = climbToLine(sel.focusNode);
      if (el) { setCursor({ path, line: +el.dataset.line, col: caretCol(sel) }); setSelection(null); }
    }
  }
  function handleContext(e) {
    e.preventDefault();
    const sel = window.getSelection();
    let info = { path, kind: "editor" };
    const a = sel && sel.anchorNode && climbToLine(sel.anchorNode);
    const f = sel && sel.focusNode && climbToLine(sel.focusNode);
    if (sel && !sel.isCollapsed && a && f && +a.dataset.line !== +f.dataset.line) {
      const s = Math.min(+a.dataset.line, +f.dataset.line), en = Math.max(+a.dataset.line, +f.dataset.line);
      info.sel = { start: s, end: en }; info.line = s;
    } else if (selection && selection.path === path && selection.start !== selection.end) {
      info.sel = { start: selection.start, end: selection.end }; info.line = selection.start;
    } else {
      let no = null;
      const r = document.caretRangeFromPoint ? document.caretRangeFromPoint(e.clientX, e.clientY) : null;
      const el = r && climbToLine(r.startContainer);
      if (el) no = +el.dataset.line;
      info.line = no || (cursor && cursor.path === path ? cursor.line : 1);
    }
    setCursor({ path, line: info.line, col: 1 });
    onContext(e, info);
  }

  const curLine = cursor && cursor.path === path ? cursor.line : -1;
  const sel = selection && selection.path === path ? selection : null;

  return (
    <div className={"editor" + (showSign ? " diff" : "")} onMouseUp={onMouseUp} onContextMenu={handleContext}>
      {lines.map((l, i) => {
        const no = l.no;
        const inSel = sel && no != null && no >= sel.start && no <= sel.end;
        const cls = "ln-row"
          + (l.row === "add" ? " add" : l.row === "del" ? " del" : "")
          + (l.row === "bar-add" ? " bar-add" : l.row === "bar-del" ? " bar-del" : "")
          + (no === curLine && !inSel && !l.row ? " cursor" : "")
          + (inSel ? " selrange" : "");
        return (
          <div key={i} data-line={no == null ? undefined : no} className={cls}>
            <span className="ln-gutter" onClick={(e) => gutterClick(e, no)}>{no == null ? "" : no}</span>
            {showSign && <span className="ln-sign">{l.sign === " " || !l.sign ? "" : l.sign}</span>}
            <span className="ln-code" dangerouslySetInnerHTML={{ __html: html[i] }} />
          </div>
        );
      })}
    </div>
  );
}

/* Build the line descriptors for a given mode. */
function buildLines(mode, diff, fileText) {
  if (mode === "original") return { lines: diff.left.map((l) => ({ no: l.no, text: l.text, row: l.mark === "del" ? "bar-del" : null })), showSign: false };
  if (mode === "updated") return { lines: diff.right.map((l) => ({ no: l.no, text: l.text, row: l.mark === "add" ? "bar-add" : null })), showSign: false };
  if (mode === "diff") return { lines: diff.rows.map((r) => ({ no: r.newNo || r.oldNo, text: r.text, sign: r.sign, row: r.sign === "+" ? "add" : r.sign === "-" ? "del" : null })), showSign: true };
  // plain file
  const arr = (fileText || "").replace(/\n$/, "").split("\n");
  return { lines: arr.map((t, i) => ({ no: i + 1, text: t })), showSign: false };
}

const SEGMENTS = [
  { id: "original", label: "Original" },
  { id: "updated", label: "Updated" },
  { id: "diff", label: "Diff" },
];

function Editor({ tabs, active, mode, setMode, onActivate, onClose, onContext, onSplit, splitOpen, cursor, selection, setCursor, setSelection }) {
  const tab = tabs.find((t) => t.path === active);
  const change = tab ? PROJECT.changes.find((c) => c.path === tab.path) : null;
  const diff = tab ? PROJECT.diffs[tab.path] : null;
  const lang = tab ? HL.langFor(tab.path) : null;
  const effMode = change ? mode : "code";

  let built = null;
  if (tab) {
    if (change && diff) built = buildLines(effMode, diff, PROJECT.files[tab.path]);
    else built = buildLines("code", null, PROJECT.files[tab.path]);
  }

  const statusWord = change ? (change.status === "A" ? "Added" : change.status === "D" ? "Deleted" : "Modified") : "";
  const activeSeg = splitOpen ? "split" : effMode;
  const emptyUpdated = effMode === "updated" && built && built.lines.length === 0;
  const emptyOriginal = effMode === "original" && built && built.lines.length === 0;

  return (
    <React.Fragment>
      <EditorTabs tabs={tabs} active={active} onActivate={onActivate} onClose={onClose} />
      {!tab ? (
        <div className="empty-ed">
          <div style={{ opacity: .5 }}>{Icon.file({ width: 30, height: 30 })}</div>
          <div className="big">No file open</div>
          <div className="klist">
            <div><span>Search files & content</span><kbd>⌘ F</kbd></div>
            <div><span>Copy reference</span><kbd>right-click</kbd></div>
            <div><span>Pass on to Agent</span><kbd>right-click</kbd></div>
          </div>
        </div>
      ) : (
        <div className="editor-wrap">
          {change && (
            <div className="diff-bar">
              <span className={"git-stat " + change.status} style={{ width: "auto" }}>{statusWord}</span>
              {change.add > 0 && <span className="a">+{change.add}</span>}
              {change.del > 0 && <span className="d">−{change.del}</span>}
              <div className="seg">
                {SEGMENTS.map((s) => (
                  <button key={s.id} className={activeSeg === s.id ? "on" : ""} onClick={() => setMode(s.id)}>{s.label}</button>
                ))}
                <button className={"split-btn" + (activeSeg === "split" ? " on" : "")} onClick={() => onSplit(tab.path)} title="Split — full screen side-by-side">
                  <svg width="11" height="11" viewBox="0 0 12 12" fill="none"><rect x="1" y="1.5" width="10" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.2"/><line x1="6" y1="1.5" x2="6" y2="10.5" stroke="currentColor" strokeWidth="1.2"/></svg>
                  Split
                </button>
              </div>
            </div>
          )}
          {emptyUpdated ? (
            <div className="empty-ed"><div className="big" style={{ color: "var(--del)" }}>No updated version</div><div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--fg-3)" }}>This file was deleted in the change.</div></div>
          ) : emptyOriginal ? (
            <div className="empty-ed"><div className="big" style={{ color: "var(--add)" }}>No original version</div><div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--fg-3)" }}>This file is new in the change.</div></div>
          ) : (
            <PaneView cacheKey={tab.path + ":" + effMode} path={tab.path} lines={built.lines}
              lang={lang} showSign={built.showSign} cursor={cursor} selection={selection}
              setCursor={setCursor} setSelection={setSelection} onContext={onContext} />
          )}
        </div>
      )}
    </React.Fragment>
  );
}

/* Full-screen side-by-side split view */
function SplitView({ path, onClose, onContext }) {
  const diff = PROJECT.diffs[path];
  const lang = HL.langFor(path);
  const leftRef = useRef(null), rightRef = useRef(null);
  const lock = useRef(false);
  const change = PROJECT.changes.find((c) => c.path === path);

  const leftHtml = useMemo(() => diff.split.map((r) => r.l ? HL.hlLine(r.l.text, lang) : ""), [path]);
  const rightHtml = useMemo(() => diff.split.map((r) => r.r ? HL.hlLine(r.r.text, lang) : ""), [path]);

  function sync(from, to) {
    if (lock.current) return; lock.current = true;
    to.scrollTop = from.scrollTop; to.scrollLeft = from.scrollLeft;
    requestAnimationFrame(() => { lock.current = false; });
  }
  function ctx(e, side) {
    e.preventDefault();
    let no = null;
    const r = document.caretRangeFromPoint ? document.caretRangeFromPoint(e.clientX, e.clientY) : null;
    const el = r && climbToLine(r.startContainer);
    if (el) no = +el.dataset.line;
    onContext(e, { path, kind: "editor", line: no || 1 });
  }

  return (
    <div className="split-overlay">
      <div className="split-head">
        <FileIcon path={path} />
        <span className="sh-name">{path}</span>
        {change && <span className={"git-stat " + change.status} style={{ width: "auto" }}>{change.status === "A" ? "Added" : change.status === "D" ? "Deleted" : "Modified"}</span>}
        {change && change.add > 0 && <span className="a" style={{ fontFamily: "var(--mono)", color: "var(--add)" }}>+{change.add}</span>}
        {change && change.del > 0 && <span className="d" style={{ fontFamily: "var(--mono)", color: "var(--del)" }}>−{change.del}</span>}
        <button className="split-exit" onClick={onClose}>
          <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M7 1.5h3.5V5M5 10.5H1.5V7M10.5 1.5L7 5M1.5 10.5L5 7" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/></svg>
          Collapse <kbd>Esc</kbd>
        </button>
      </div>
      <div className="split-body">
        <div className="split-pane left">
          <div className="split-label">Original <span>before</span></div>
          <div className="editor" ref={leftRef} onScroll={() => sync(leftRef.current, rightRef.current)} onContextMenu={(e) => ctx(e, "l")}>
            {diff.split.map((row, i) => (
              <div key={i} data-line={row.l ? row.l.no : undefined} className={"ln-row" + (row.l && row.l.mark === "del" ? " bar-del" : "") + (!row.l ? " empty" : "")}>
                <span className="ln-gutter">{row.l ? row.l.no : ""}</span>
                <span className="ln-code" dangerouslySetInnerHTML={{ __html: row.l ? leftHtml[i] : "" }} />
              </div>
            ))}
          </div>
        </div>
        <div className="split-pane right">
          <div className="split-label">Updated <span>after</span></div>
          <div className="editor" ref={rightRef} onScroll={() => sync(rightRef.current, leftRef.current)} onContextMenu={(e) => ctx(e, "r")}>
            {diff.split.map((row, i) => (
              <div key={i} data-line={row.r ? row.r.no : undefined} className={"ln-row" + (row.r && row.r.mark === "add" ? " bar-add" : "") + (!row.r ? " empty" : "")}>
                <span className="ln-gutter">{row.r ? row.r.no : ""}</span>
                <span className="ln-code" dangerouslySetInnerHTML={{ __html: row.r ? rightHtml[i] : "" }} />
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { Editor, EditorTabs, PaneView, SplitView, buildLines, climbToLine });
