// diffGutterExtension.js — Phase 4 of the Contribute rewrite. Restores
// §8.10's paragraph-margin gutter accent against the CM6 raw pane in
// the Contribute split, the surface where editing happens. The matching
// inline tracked-change overlay (Phase 3) lives in the preview pane on
// the right; the two visual layers answer different questions —
// "did anything change in this region?" (gutter) vs. "what changed
// here?" (inline) — and live on the two halves of the split for that
// reason. Discuss mode has no editor, so no gutter.
//
// Surface model
// -------------
// CM6's natural unit is the line, not the paragraph, so the marker is
// per-line: every line whose text differs from the same-indexed line of
// the open-session baseline gets a thin amber bar in its own gutter
// (sat left of the line-numbers gutter). The spec talks
// "paragraph-margin marker"; collapsing line-marks back to
// paragraph-block ranges is more spec-faithful but adds code and risks
// hiding small inline changes inside large paragraphs. Per-line is the
// pre-fancy stance — a future pass can collapse if it reads noisy.
//
// Baseline reset
// --------------
// Baseline = the last server-confirmed branch body. Initialized to
// `initialDoc` at editor construct; re-set via the `setBaseline` effect
// on accept / decline / manual flush / branch switch — i.e. every time
// the parent re-pushes `initialDoc`. The marker thus reads as
// "what's in the buffer but not on the server."
//
// The reset must dispatch atomically with the doc replacement —
// otherwise there's a one-frame window where the new doc differs from
// the old baseline and every line lights up. MarkdownSourceEditor's
// initialDoc effect bundles both into a single dispatch.
//
// Caveats kept deliberately
// -------------------------
// Insert/delete shifts. Adding a line in the middle shifts every
// subsequent line's index and the naive index-compare lights up
// everything below the insertion. We tolerate it: the gutter is a
// scannable cue, not a precision diff, and "you've touched stuff below
// this point" remains honest. A future pass against a proper LCS diff
// could anchor the marks to stable lines, but Phase 4 doesn't need it.

import { StateEffect, StateField } from '@codemirror/state'
import { GutterMarker, gutter } from '@codemirror/view'

export const setBaseline = StateEffect.define()

class DiffMarker extends GutterMarker {
  toDOM() {
    const el = document.createElement('div')
    el.className = 'cm-diff-gutter-mark'
    return el
  }
}

const diffMarker = new DiffMarker()

// Baseline doc as a frozen line array. We split once on reset rather
// than on every gutter query — the doc itself can change far more
// often than the baseline.
const baselineField = StateField.define({
  create: () => [],
  update(value, tr) {
    for (const e of tr.effects) {
      if (e.is(setBaseline)) return splitLines(e.value || '')
    }
    return value
  },
})

function splitLines(text) {
  return (text ?? '').split('\n')
}

export function diffGutterExtension() {
  return [
    baselineField,
    gutter({
      class: 'cm-diff-gutter',
      lineMarker(view, line) {
        const baseline = view.state.field(baselineField, false)
        if (!baseline || baseline.length === 0) return null
        const lineNumber = view.state.doc.lineAt(line.from).number
        const baselineLine = baseline[lineNumber - 1]
        const currentLine = view.state.doc.lineAt(line.from).text
        if (baselineLine === undefined) return diffMarker
        return currentLine === baselineLine ? null : diffMarker
      },
      lineMarkerChange(update) {
        if (update.docChanged) return true
        for (const tr of update.transactions) {
          for (const e of tr.effects) if (e.is(setBaseline)) return true
        }
        return false
      },
    }),
  ]
}
