/-
  Infoductor.Foundation.Restructure — the universal macro
  ==========================================================
  Phase B of `docs/ALGEBRA_PLAN.md`.  Defines the `restructure`
  operation — a function with the same five-field shape as
  `comp i A φ u t`, lifted to the meta-source-code level.

  Per ALGEBRA_PLAN §0:
  > One macro.  Built from `comp`.  Aliases accrue by usage; tactics
  > are search over a library that grows under structural-Path
  > declarations alone.

  All 32 enumerated macros from the original sketch are *frozen
  partial applications* of this single `restructure`; the alias
  layer in `Algebra/MacroAlias.lean` lets users (or the system)
  name recurring patterns.
-/

import Infoductor.Foundation.Edit

namespace Infoductor

-- ── The universal `restructure` operation ───────────────────────────────────

/-- `restructure i ctx φ witness fallback` — the universal source-
    mutation operation.  Five fields, each mirroring a field of
    `comp i A φ u t`:

      · `i        : MetaPosition`    — where in source.
      · `ctx      : MetaCType`       — meta-type of the artifact.
      · `φ        : MetaClassifier`  — when this restructuring applies.
      · `witness  : MetaArtifact`    — new content valid on φ.
      · `fallback : MetaArtifact`    — existing content off-φ.

    The result is an `Edit Unit`: a single `EditOp` whose content is
    `witness` (when `φ` matches at `i`) or `fallback` (otherwise).

    The `ctx` argument is currently used only for documentation /
    diagnostics — Phase C's `@[macroAlias]` attribute consumes it
    when binding aliases.  Future Phase D' may use it to dispatch
    different rendering / preview modes in the widget. -/
def restructure (i : MetaPosition) (_ctx : MetaCType)
    (φ : MetaClassifier) (witness fallback : MetaArtifact) : Edit Unit :=
  let active := φ.atPosition i
  let chosen := if active then witness else fallback
  let label := if active then "hit" else "miss"
  Edit.emit
    { position := i
    , newContent := chosen
    , oldContent := none
    , description := s!"restructure φ={label} at {repr i}" }

-- ── The 32 macros as frozen aliases ─────────────────────────────────────────
-- Per ALGEBRA_PLAN §2.3 every "macro" in the original 32-item list
-- is a frozen partial application.  These are the foundational
-- aliases; a downstream user / agent can register more via
-- `@[macroAlias]` (Phase C).

/-- Frozen alias: `transport_artifact i ctx w` — `restructure` with
    `φ = .always`, `witness = fallback = w`.  Always applies; just
    relocates `w` to `i`. -/
@[reducible]
def transport_artifact (i : MetaPosition) (ctx : MetaCType) (w : MetaArtifact) :
    Edit Unit :=
  restructure i ctx .always w w

/-- Frozen alias: `relocate_invariant i src dst` — moves a file's
    content from `src` to `dst`, classifier set to "in source file." -/
@[reducible]
def relocate_invariant (decl : Lean.Name) (src dst : String) : Edit Unit :=
  restructure { declName := decl, filePath := dst, range := none }
    .file (.inFile src) (.refTo decl) .empty

/-- Frozen alias: `rename_throughout x y` — emit a rename edit for
    declaration `x` (witness on `.atDecl x`, fallback `.empty`).  -/
@[reducible]
def rename_throughout (oldName newName : Lean.Name) : Edit Unit :=
  restructure { declName := oldName, filePath := "", range := none }
    .definition (.atDecl oldName) (.refTo newName) .empty

/-- Frozen alias: `define_question_shape S` — register a new question
    schema as an inductive declaration. -/
@[reducible]
def define_question_shape (declName : Lean.Name) (witness : MetaArtifact) :
    Edit Unit :=
  restructure { declName := declName, filePath := "", range := none }
    .inductive_ .always witness .empty

/-- Frozen alias: `compose_proof_fragments` — pure `restructure` with
    no freezing.  This is the "raw" `restructure` exposed under a
    documentary name; identical in behaviour. -/
@[reducible]
def compose_proof_fragments
    (i : MetaPosition) (ctx : MetaCType) (φ : MetaClassifier)
    (w f : MetaArtifact) : Edit Unit :=
  restructure i ctx φ w f

/-- Frozen alias: `materialize` — emit a piece of raw Lean source at
    a position.  The leaf operation; everything else composes from
    here. -/
@[reducible]
def materialize (i : MetaPosition) (ctx : MetaCType) (src : String) :
    Edit Unit :=
  restructure i ctx .always (.source src) .empty

-- ── Headless interpreter: apply edits to an in-memory buffer ────────────────
-- Phase 5.3 No-LSP fallback: same operations as `lake exe
-- algebra-restructure` subcommands.  Useful for batch CI runs.

/-- A simple in-memory source buffer.  Maps file paths to current
    contents.  `applyOp` mutates the buffer in place. -/
abbrev SourceBuffer := Std.HashMap String String

/-- Apply a single `EditOp` to a source buffer.  For now this is a
    crude full-file overwrite when `position.range` is `none`; range-
    based partial overwrites are scheduled for the LSP-backed
    integration in `Algebra/EditLSP.lean`.

    Returns the updated buffer.  Diagnostic lines printed to
    `IO.stderr` describe what changed. -/
def EditOp.apply (op : EditOp) (buf : SourceBuffer) : IO SourceBuffer := do
  let path := op.position.filePath
  match op.newContent with
  | .source s =>
      IO.eprintln s!"[restructure] writing {path} (decl {op.position.declName})"
      return buf.insert path s
  | .empty =>
      IO.eprintln s!"[restructure] removing {path} (decl {op.position.declName})"
      return buf.erase path
  | .refTo n =>
      IO.eprintln s!"[restructure] {path}: rename / link → {n}"
      return buf
  | .declAt _ =>
      IO.eprintln s!"[restructure] {path}: emit syntax (LSP-bound; headless skipped)"
      return buf

/-- Run an `Edit` headless: apply every emitted op in order to an
    initial source buffer.  Returns the final buffer plus the
    computation's value. -/
def Edit.runHeadless {α : Type} (e : Edit α) (initial : SourceBuffer) :
    IO (α × SourceBuffer) := do
  let mut buf := initial
  for op in e.ops do
    buf ← op.apply buf
  return (e.result, buf)

-- ── Soundness guard scaffold ────────────────────────────────────────────────
-- ALGEBRA_PLAN §3.3: every Edit passes through `preserve_typing`.
-- The full integration requires invoking `lean` on a fresh source
-- buffer; the data-level guard here checks that no edit deletes
-- a declaration that other edits in the same batch reference.

/-- The names referenced by an `EditOp` (as a `MetaArtifact.refTo`). -/
def EditOp.referenced : EditOp → List Lean.Name
  | { newContent := .refTo n, .. } => [n]
  | _ => []

/-- The decl-name *removed* by an `EditOp` (when content is `.empty`). -/
def EditOp.removed : EditOp → List Lean.Name
  | { newContent := .empty, position := p, .. } => [p.declName]
  | _ => []

/-- A "broken reference" predicate: does this batch reference any
    name that the same batch removes?  The structural check that
    runs in any environment, headless or not. -/
def Edit.brokenRefs {α : Type} (e : Edit α) : List Lean.Name :=
  let removed := e.ops.flatMap EditOp.removed
  let referenced := e.ops.flatMap EditOp.referenced
  referenced.filter (· ∈ removed)

/-- The structural soundness check: an `Edit` is *self-consistent*
    if no op references a name another op in the batch removes.
    Full type-checking of the post-edit buffer is the LSP-integrated
    layer's job (`Algebra/EditLSP.lean`). -/
def Edit.selfConsistent {α : Type} (e : Edit α) : Bool :=
  e.brokenRefs.isEmpty

/-- Guarded wrapper: aborts the edit if its self-consistency check
    fails.  Mirrors the `Edit.guarded` shape in ALGEBRA_PLAN §3.3. -/
def Edit.guarded {α : Type} [Inhabited α] (e : Edit α) :
    Except String (Edit α) :=
  if e.selfConsistent then .ok e
  else .error s!"restructure batch breaks references: {repr e.brokenRefs}"

end Infoductor
