// Journal view — reverse-chronological archive of free-form notes,
// optionally linked to a task. Also exports JournalEditor, a single
// editor component used inline on Today cards and Backlog rows so we
// never fork the editing surface.

const PROMPT_RE = /^Buffer\s*·\s*Journal:\s*(.+)$/;

function derivePromptFromTitle(taskTitle) {
  if (!taskTitle) return null;
  const m = taskTitle.match(PROMPT_RE);
  return m ? m[1].trim() : null;
}

function entriesForTask(journals, taskId) {
  if (!taskId) return [];
  return journals.filter(j => j.taskId === taskId);
}

function noteCountForTask(journals, taskId) {
  return entriesForTask(journals, taskId).length;
}

function isEmptyEntry(entry) {
  return (!entry.title || !entry.title.trim()) &&
         (!entry.body || !entry.body.trim());
}

// A blank entry is one with no body and either no title or a title that is
// just the seeded prompt — i.e. the user hasn't typed anything new.
function entryIsBlank(entry, task) {
  const bodyEmpty = !entry.body || !entry.body.trim();
  if (!bodyEmpty) return false;
  const titleEmpty = !entry.title || !entry.title.trim();
  if (titleEmpty) return true;
  if (task) {
    const seed = derivePromptFromTitle(task.title);
    if (seed && entry.title.trim() === seed.trim()) return true;
  }
  return false;
}

function shortDate(iso) {
  if (!iso) return "";
  const U = window.CAIRN_UTIL;
  const d = iso.includes("T") ? new Date(iso) : U.parseISO(iso);
  if (!d || isNaN(d.getTime())) return "";
  const month = U.MONTHS[d.getMonth()];
  const day = d.getDate();
  const pad = (n) => String(n).padStart(2, "0");
  const time = iso.includes("T")
    ? ` · ${pad(d.getHours())}:${pad(d.getMinutes())}`
    : "";
  return `${month} ${day}${time}`;
}

// Initial state for a new entry seeded from a task (or null).
function seedFromTask(task) {
  if (!task) {
    return { title: "", body: "", autoFocus: "title" };
  }
  const prompt = derivePromptFromTitle(task.title);
  if (prompt) {
    return { title: prompt, body: "", autoFocus: "body" };
  }
  return { title: "", body: "", autoFocus: "title" };
}

// ─────────────────────────────────────────────────────────────────────────────

function JournalEditor({
  host = "jrow",
  task = null,
  entry = null,
  autoFocus = "title",
  onSave,
  onDelete,
  onClose,
  saveHint = "Cmd-Enter to save · Esc to close",
}) {
  const { useState, useRef, useEffect } = React;
  const [title, setTitle] = useState(entry ? (entry.title || "") : "");
  const [body, setBody] = useState(entry ? (entry.body || "") : "");
  const titleRef = useRef(null);
  const bodyRef = useRef(null);

  useEffect(() => {
    const target = autoFocus === "body" ? bodyRef.current : titleRef.current;
    if (target) {
      target.focus();
      // Put caret at end so prefilled text is not selected.
      const len = target.value.length;
      try { target.setSelectionRange(len, len); } catch (e) { /* ignore */ }
    }
  }, []);

  const commit = () => {
    onSave && onSave({ title, body });
  };

  const close = () => {
    commit();
    onClose && onClose({ title, body });
  };

  const revert = () => {
    const t = entry ? (entry.title || "") : "";
    const b = entry ? (entry.body || "") : "";
    setTitle(t);
    setBody(b);
    onSave && onSave({ title: t, body: b });
    onClose && onClose({ title: t, body: b });
  };

  const handleKeyDown = (e) => {
    if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
      e.preventDefault();
      close();
    } else if (e.key === "Escape") {
      e.preventDefault();
      revert();
    }
  };

  const U = window.CAIRN_UTIL;
  const related = task ? U.relatedRefs(task) : [];
  const linkedLabel = task
    ? (() => {
        const tr = U.track(task.track);
        const ord = U.ordinalLabel(task);
        return `${tr.label} · ${ord ? ord + " · " : ""}${task.title}`;
      })()
    : (entry && entry.linkedTitleSnapshot
        ? `${entry.linkedTitleSnapshot}`
        : "Standalone");

  return (
    <div className={`jed jed--${host}`} onClick={(e) => e.stopPropagation()}>
      <input
        ref={titleRef}
        className="jed__title-input"
        placeholder="Title (optional)"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        onBlur={commit}
        onKeyDown={handleKeyDown}
      />
      <textarea
        ref={bodyRef}
        className="jed__body-input"
        placeholder="Write…"
        value={body}
        onChange={(e) => setBody(e.target.value)}
        onBlur={commit}
        onKeyDown={handleKeyDown}
      />
      {related.length > 0 && (
        <div className="jed__relates">
          {window.Refs && React.createElement(window.Refs, { refs: related, label: "↳ Refers to" })}
        </div>
      )}
      <div className="jed__foot">
        <span className="meta-sm jed__linked" title={linkedLabel}>
          {task ? "↗ " : ""}{linkedLabel.toUpperCase()}
        </span>
        <span className="jed__foot-spacer" />
        <span className="meta-sm jed__hint">{saveHint}</span>
        {onDelete && entry && (
          <button
            className="btn btn--ghost jed__del"
            onClick={() => onDelete(entry.id)}
          >
            Delete
          </button>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────

function JRow({
  entry,
  linkedTask,
  expanded,
  onToggle,
  onSave,
  onDelete,
  onClose,
}) {
  const U = window.CAIRN_UTIL;
  const trackColor = linkedTask
    ? U.track(linkedTask.track).color
    : "var(--ink-3)";
  const trackLabel = linkedTask ? U.track(linkedTask.track).label : null;
  const ordLabel = linkedTask ? U.ordinalLabel(linkedTask) : null;

  const titleText = (entry.title && entry.title.trim())
    ? entry.title
    : (linkedTask
        ? (derivePromptFromTitle(linkedTask.title) || linkedTask.title)
        : "Untitled");
  const preview = entry.body || "";

  return (
    <li className={`jrow ${expanded ? "jrow--open" : ""}`}>
      {!expanded && (
        <div className="jrow__main" onClick={onToggle}>
          <span className="jrow__mark" style={{ background: trackColor }} />
          <div className="jrow__body">
            <h3 className="jrow__title text-pretty">{titleText}</h3>
            {preview && (
              <p className="jrow__preview text-pretty">{preview}</p>
            )}
            {(linkedTask || entry.linkedTitleSnapshot) && (
              <p className="meta-sm jrow__linked">
                ↗ {trackLabel ? `${trackLabel.toUpperCase()} · ` : ""}
                {ordLabel ? `${ordLabel} · ` : ""}
                {(linkedTask ? linkedTask.title : entry.linkedTitleSnapshot)}
              </p>
            )}
          </div>
          <div className="jrow__meta">
            <span className="meta-sm jrow__date">{shortDate(entry.createdAt)}</span>
          </div>
        </div>
      )}
      {expanded && (
        <JournalEditor
          host="jrow"
          task={linkedTask}
          entry={entry}
          autoFocus={entry.title || entry.body ? "body" : "title"}
          onSave={onSave}
          onDelete={onDelete}
          onClose={onClose}
        />
      )}
    </li>
  );
}

// ─────────────────────────────────────────────────────────────────────────────

function JournalView({
  tasks,
  journals,
  pendingTaskId,
  onClearPendingTaskId,
  onAddJournal,
  onUpdateJournal,
  onDeleteJournal,
}) {
  const { useState, useEffect, useMemo, useRef } = React;
  const [expandedId, setExpandedId] = useState(null);
  const [pendingCloseId, setPendingCloseId] = useState(null);

  const tasksById = useMemo(() => {
    const m = {};
    tasks.forEach(t => { m[t.id] = t; });
    return m;
  }, [tasks]);

  // After an outside-click or external collapse, run blank-cleanup against
  // the fresh journals state (which now reflects the editor's onBlur save).
  useEffect(() => {
    if (!pendingCloseId) return;
    const current = journals.find(j => j.id === pendingCloseId);
    if (current) {
      const linkedTask = current.taskId ? tasksById[current.taskId] : null;
      if (entryIsBlank(current, linkedTask)) {
        onDeleteJournal(pendingCloseId);
      }
    }
    setPendingCloseId(null);
  }, [pendingCloseId, journals, tasksById, onDeleteJournal]);

  const sorted = useMemo(() => {
    return journals.slice().sort((a, b) => {
      const av = a.createdAt || "";
      const bv = b.createdAt || "";
      return bv.localeCompare(av);
    });
  }, [journals]);

  // Click-outside collapse for the expanded row. Esc is handled inside the
  // editor itself (which calls onClose with the latest title/body).
  useEffect(() => {
    if (!expandedId) return;
    const onDocDown = (e) => {
      if (!e.target.closest?.(".jrow--open")) {
        setPendingCloseId(expandedId);
        setExpandedId(null);
      }
    };
    document.addEventListener("mousedown", onDocDown);
    return () => document.removeEventListener("mousedown", onDocDown);
  }, [expandedId]);

  // Consume the pending task id from the Week-chip route flow.
  const consumed = useRef(false);
  useEffect(() => {
    if (consumed.current) return;
    if (!pendingTaskId) return;
    consumed.current = true;
    const task = tasksById[pendingTaskId];
    const seed = seedFromTask(task);
    const id = onAddJournal({
      taskId: pendingTaskId,
      linkedTitleSnapshot: task ? task.title : null,
      title: seed.title,
      body: "",
    });
    setExpandedId(id);
    onClearPendingTaskId && onClearPendingTaskId();
  }, [pendingTaskId, tasksById]);

  const handleSave = (id) => ({ title, body }) => {
    onUpdateJournal(id, { title, body });
  };

  const handleClose = (id) => (latest) => {
    const current = journals.find(j => j.id === id);
    if (current) {
      const snap = latest && typeof latest === "object"
        ? { title: latest.title, body: latest.body }
        : { title: current.title, body: current.body };
      const linkedTask = current.taskId ? tasksById[current.taskId] : null;
      if (entryIsBlank(snap, linkedTask)) {
        onDeleteJournal(id);
      }
    }
    setExpandedId(null);
  };

  const handleDelete = (id) => {
    onDeleteJournal(id);
    setExpandedId(null);
  };

  const handleNewEntry = () => {
    const id = onAddJournal({});
    setExpandedId(id);
  };

  const countLabel = sorted.length === 0
    ? "EMPTY"
    : `${String(sorted.length).padStart(2, "0")} ${sorted.length === 1 ? "ENTRY" : "ENTRIES"}`;

  return (
    <div className="shell journal">
      <div className="journal__head">
        <div>
          <h2 className="display journal__title">Journal</h2>
          <p className="meta journal__count">{countLabel}</p>
        </div>
        <button className="btn btn--ghost journal__new" onClick={handleNewEntry}>
          New entry
        </button>
      </div>

      {sorted.length === 0 && (
        <div className="journal__empty">
          <p className="journal__empty-line text-pretty">Nothing logged yet.</p>
          <button className="btn btn--ghost journal__empty-cta" onClick={handleNewEntry}>
            New entry →
          </button>
        </div>
      )}

      {sorted.length > 0 && (
        <ul className="journal__list">
          {sorted.map(entry => (
            <JRow
              key={entry.id}
              entry={entry}
              linkedTask={entry.taskId ? tasksById[entry.taskId] : null}
              expanded={expandedId === entry.id}
              onToggle={() => setExpandedId(expandedId === entry.id ? null : entry.id)}
              onSave={handleSave(entry.id)}
              onDelete={() => handleDelete(entry.id)}
              onClose={handleClose(entry.id)}
            />
          ))}
        </ul>
      )}
    </div>
  );
}

// Helper exposed on window for other views to query note state.
window.CAIRN_JOURNAL = {
  derivePromptFromTitle,
  entriesForTask,
  noteCountForTask,
  isEmptyEntry,
  entryIsBlank,
  seedFromTask,
};

window.JournalEditor = JournalEditor;
window.JournalView = JournalView;
