/-
  Infoductor.Foundation.Edit — Edit monad + Context comonad
  ============================================================
  Phase B of `docs/ALGEBRA_PLAN.md`.  Defines the abstract `Edit`
  monad (a thread of source mutations) and the abstract `Context`
  comonad (ambient information at a code position), plus the
  distributive law that lets `Context`-aware functions produce
  `Edit`-valued results.

  This module is *pure data*: it does not depend on the Lean LSP,
  Lean.Elab APIs, or any I/O backend.  LSP-specific bindings
  (a real `MakeEditLinkProps.ofReplaceRange` integration; a Code
  Action provider; a `Lean.Server.CodeActionContext` consumer) live
  in a separate `Algebra/EditLSP.lean` module and import this one.

  The headless interpreter `runHeadless` applies an `Edit` to an
  in-memory source buffer and emits the post-edit text.  This is
  what `lake exe algebra-restructure` uses (per ALGEBRA_PLAN §5.3
  No-LSP fallback).
-/

import Infoductor.Foundation.Meta

namespace Infoductor

-- ── EditOp — the leaf of every edit ──────────────────────────────────────────

/-- A single source-mutation request.  The `position` field names the
    spot in source; `newContent` is what to write there; `oldContent`
    (if known) is recorded for audit / undo.

    `EditOp` is the meta-CTerm leaf — emitted by `restructure` as part
    of an `Edit α` computation. -/
structure EditOp where
  position    : MetaPosition
  newContent  : MetaArtifact
  oldContent  : Option MetaArtifact := none
  description : String := ""
  deriving Inhabited

instance : Repr EditOp where
  reprPrec e _ := s!"EditOp(at={repr e.position}, new={e.newContent.toString}, desc={e.description})"

-- ── The Edit monad ──────────────────────────────────────────────────────────
-- A computation that may emit zero or more `EditOp`s.  Pure value α
-- is the result of the computation; the list is the side-effect.

/-- The Edit monad: a value-and-edit-list pair.  Composition appends
    edit lists; `pure` emits nothing.

    Implementation note: we use the bare-bones `α × List EditOp`
    representation rather than the IO-threaded `run :
    CodeActionContext → IO (...)` form proposed in ALGEBRA_PLAN §3.1.
    The IO and LSP threading are *implementation choices* that don't
    affect the algebraic structure; lifting from this pure form into
    the LSP form is a `MonadLift Edit (ReaderT … IO)` instance in
    `EditLSP.lean`.

    See `docs/ALGEBRA_PLAN.md` §3.1 for the design discussion. -/
structure Edit (α : Type) where
  result : α
  ops    : List EditOp
  deriving Inhabited

namespace Edit

/-- Lift a value into the trivial edit (zero ops). -/
def pure {α : Type} (a : α) : Edit α := { result := a, ops := [] }

/-- Sequence: concatenate edit lists. -/
def bind {α β : Type} (m : Edit α) (f : α → Edit β) : Edit β :=
  let n := f m.result
  { result := n.result, ops := m.ops ++ n.ops }

/-- Emit a single edit op with no result. -/
def emit (op : EditOp) : Edit Unit := { result := (), ops := [op] }

/-- Emit several edit ops. -/
def emitMany (ops : List EditOp) : Edit Unit := { result := (), ops := ops }

instance : Monad Edit where
  pure := Edit.pure
  bind := Edit.bind

/-- Functor instance.  Maps the result; leaves ops untouched. -/
instance : Functor Edit where
  map f m := { result := f m.result, ops := m.ops }

end Edit

-- ── The Context comonad ─────────────────────────────────────────────────────
-- "What's around here?"  At each meta-position, expose ambient info:
-- the current artifact, the position, the namespace, and whatever
-- additional metadata the elaborator pass populates.

/-- Ambient information at a meta-position.

    `here` is the focal artifact; `pos` is its location; `siblings`
    is a list of nearby artifacts that the restructuring might wish
    to consult (e.g., to rename a definition consistently with its
    consumers).

    Per ALGEBRA_PLAN §3.1 the full Context carries `Lean.Environment`
    and `QuestionGraph` fields too; those are populated only when
    running inside the elaborator (post-import).  The pure-data form
    here works headless. -/
structure Context (α : Type) where
  here     : α
  pos      : MetaPosition
  siblings : List MetaArtifact := []
  deriving Inhabited

namespace Context

/-- Comonad `extract`: the focal artifact at the current position. -/
def extract {α : Type} (c : Context α) : α := c.here

/-- Comonad `extend`: relocate an `α` value to a context-dependent
    `β` value, i.e. apply a context-aware function pointwise. -/
def extend {α β : Type} (f : Context α → β) (c : Context α) : Context β :=
  { here := f c, pos := c.pos, siblings := c.siblings }

instance : Functor Context where
  map f c := { here := f c.here, pos := c.pos, siblings := c.siblings }

end Context

-- ── The distributive law: Context → Edit ────────────────────────────────────
-- The standard "comonad-to-monad" distributive setup.  A context-
-- aware decision produces an edit; lifting it into a Context-keyed
-- Edit yields the contextualized edit.

/-- Lift a context-aware decision into an `Edit`.  Per ALGEBRA_PLAN
    §3.2 this is the *distributive law* between the Context comonad
    and the Edit monad. -/
def Context.contextualEdit {α β : Type}
    (decide : Context α → Edit β) (c : Context α) : Edit β :=
  decide c

-- ── Classifier-at-position evaluation ───────────────────────────────────────
-- Decide whether a `MetaClassifier` matches a `MetaPosition`.  Used
-- by `restructure` to pick `witness` vs `fallback`.

/-- Decide whether `φ` matches `pos`, syntactically.  For
    `.underAttribute` and `.dependencyOf` we conservatively answer
    `false` here — those require Lean.Environment and live in the
    LSP integration module.

    The lattice operations `.meet` and `.join` are the obvious
    Boolean combinations; `.always` is `true`, `.never` is `false`. -/
def MetaClassifier.atPosition : MetaClassifier → MetaPosition → Bool
  | .always, _ => true
  | .never,  _ => false
  | .atDecl n, p => decide (n = p.declName)
  | .inFile s, p => decide (s = p.filePath)
  | .underAttribute _, _ => false   -- needs environment; resolved in LSP module
  | .dependencyOf _, _   => false   -- needs dep graph; resolved in LSP module
  | .inNamespace n, p =>
      -- syntactic prefix check: is `p.declName` lexically under `n`?
      n.isPrefixOf p.declName
  | .meet a b, p => a.atPosition p && b.atPosition p
  | .join a b, p => a.atPosition p || b.atPosition p

/-- Lattice laws on `atPosition` — the meta-mirror of the cubical
    face-formula laws (`FaceFormula.eval`).  `meet` is conjunction,
    `join` is disjunction, `always`/`never` are top/bottom. -/
theorem MetaClassifier.atPosition_always (p : MetaPosition) :
    MetaClassifier.always.atPosition p = true := rfl

theorem MetaClassifier.atPosition_never (p : MetaPosition) :
    MetaClassifier.never.atPosition p = false := rfl

theorem MetaClassifier.atPosition_meet (a b : MetaClassifier) (p : MetaPosition) :
    (a.meet b).atPosition p = (a.atPosition p && b.atPosition p) := rfl

theorem MetaClassifier.atPosition_join (a b : MetaClassifier) (p : MetaPosition) :
    (a.join b).atPosition p = (a.atPosition p || b.atPosition p) := rfl

end Infoductor
