/-
  Infoductor.Foundation.Methodology — `@[methodology]` + `cubical_search`
  ==========================================================================
  Phase D' of `docs/ALGEBRA_PLAN.md`.  Provides the autodiscovery
  layer that crowns the metacoding stack:

  - `@[methodology]` attribute: registers a Lean theorem as a "proof
    fragment" indexed by a classifier shape.  When `cubical_search`
    encounters a question matching that shape, it tries the
    registered theorem.

  - `cubical_search` tactic: walks the methodology library by
    classifier dispatch and applies matches; on miss, attempts
    methodology-transport along declared structural Paths
    (`transp`-at-the-meta-level).

  Per ALGEBRA_PLAN §4.3:
  > Once a small library of base methodologies exists, every new
  > structural Path declared in the codebase **automatically generates
  > new methodology candidates** by transporting existing methodologies
  > across the path.

  This module ships the registry + the basic tactic; the
  methodology-transport clause is scaffolded as `deriveByTransport`
  (a stub returning `[]` until the structural-Path infrastructure
  arrives in REL2.6+).
-/

import Lean
import Infoductor.Foundation.MacroAlias
import Infoductor.Foundation.MetaPath

namespace Infoductor

open Lean Lean.Elab Lean.Elab.Tactic

-- ── Methodology entries ─────────────────────────────────────────────────────

/-- A registered methodology.  Indexes a Lean theorem by the
    classifier shape it solves.

    `classifierName` is a `Name` referring to a `MetaClassifier`-
    valued `def` (or one of the syntactic classifier predicates from
    `Question.lean`).  `theoremName` is the `Name` of the theorem
    that `cubical_search` will try when the question matches.

    Priority is used for ordering: higher priority methodologies are
    tried first.  Default 100; specialised methodologies bump higher,
    catch-alls run lower. -/
structure MethodologyEntry where
  classifierName : Name
  theoremName    : Name
  priority       : Nat := 100
  description    : String := ""
  deriving Repr, Inhabited

-- ── The methodology registry ────────────────────────────────────────────────

/-- Global registry of methodologies, indexed by classifier name.
    Built up via `@[methodology]`-tagged declarations.

    Implementation: `EnvExtension` so adds are visible across
    modules.  Lookup is linear in the registry size (acceptable for
    REL2.5; a discrtree-indexed version is REL2.6 work per
    ALGEBRA_PLAN §10.4). -/
initialize methodologyRegistryExt :
    SimplePersistentEnvExtension MethodologyEntry (Array MethodologyEntry) ←
  registerSimplePersistentEnvExtension {
    name          := `Infoductor.methodologyRegistry
    addEntryFn    := fun arr e => arr.push e
    addImportedFn := fun arrs => arrs.foldl (init := #[]) Array.append
    asyncMode     := .sync
  }

def getMethodologies : CoreM (Array MethodologyEntry) := do
  let env ← getEnv
  return methodologyRegistryExt.getState env

def registerMethodology (entry : MethodologyEntry) : CoreM Unit := do
  modifyEnv (methodologyRegistryExt.addEntry · entry)

/-- Find every methodology indexed against a given classifier name. -/
def findMethodologies (classifier : Name) : CoreM (Array MethodologyEntry) := do
  let all ← getMethodologies
  return all.filter (·.classifierName == classifier)

-- ── The `@[methodology]` attribute ──────────────────────────────────────────
-- Attached to a theorem.  Attribute parser parses the classifier
-- name and optional priority.  Registration adds an entry to the
-- methodology registry pointing back to the tagged theorem.

/-- Parser-level data for the `@[methodology]` attribute.  We accept
    a plain identifier naming the classifier.  Priority configuration
    via `priority := n` is Phase D'.1 work; the v0 attribute uses a
    fixed default of 100.

    The syntax `name :=` matches the attribute name registered below;
    Lean's attribute system looks up attributes by syntax kind, not
    by leading keyword. -/
syntax (name := methodology) "methodology" ident : attr

/-- The `@[methodology]` attribute itself.  Records the tagged
    theorem under the named classifier (priority defaults to 100). -/
initialize methodologyAttr : Unit ←
  registerBuiltinAttribute {
    name := `methodology
    descr := "Register this theorem as a proof methodology indexed \
              by a classifier (Phase D' of ALGEBRA_PLAN.md).  Usage: \
              `@[methodology IsFullFace]`."
    add := fun declName stx _kind => do
      -- Parse classifier name from the attribute syntax.
      let classifierName? : Option Name :=
        match stx with
        | `(attr| methodology $cls:ident) => some cls.getId
        | _ => none
      let some classifierName := classifierName?
        | throwError "@[methodology] requires a classifier name"
      let env ← getEnv
      let docstring? := (← findDocString? env declName).getD ""
      registerMethodology
        { classifierName := classifierName
        , theoremName    := declName
        , priority       := 100
        , description    := docstring? }
  }

-- ── The methodology-transport clause (scaffold) ─────────────────────────────
-- ALGEBRA_PLAN §4.3: when a structural Path connects classifierA to
-- classifierB and the methodology library has an entry for
-- classifierA, transport derives a candidate for classifierB.
--
-- Full implementation requires (a) declared structural Paths in the
-- codebase, (b) reification of methodology bodies as CTerms, (c)
-- application of `transp` at the methodology level.  REL2.6+ work.
--
-- The scaffold below returns `[]` so the surrounding tactic remains
-- well-typed; uses are guarded by feature flags.

/-- Derive new methodology candidates by transporting existing ones
    along declared structural Paths.

    Per ALGEBRA_PLAN §4.3:
    > Twenty starting methodologies + a hundred declared paths →
    > potentially thousands of derived methodologies, each formally
    > certified-by-construction.

    Implementation: for every methodology M registered against a
    classifier C₁, and every MetaPath C₁ ↦ C₂ in the registry,
    derive a candidate methodology against C₂ whose witness is M's
    body composed with the path witness.

    For the v0 dispatch tactic, the "composed witness" is just M's
    theorem name — `cubical_search` will try both M and the path
    witness directly via apply/exact.  A future Phase D'.6 will use
    actual Lean term composition to produce a single derived
    theorem.  See `Algebra/MetaPath.lean` for the path attribute. -/
def deriveByTransport (targetClassifier : Name) : CoreM (Array MethodologyEntry) := do
  let paths ← findPathsToTarget targetClassifier
  let mut out := #[]
  for path in paths do
    -- For every methodology M against the source classifier of this
    -- path, produce a derived candidate against the target classifier.
    let sourceMethodologies ← findMethodologies path.sourceClassifier
    for M in sourceMethodologies do
      out := out.push
        { classifierName := targetClassifier
        , theoremName    := M.theoremName  -- v0: try the source method directly
        , priority       := M.priority - 10  -- derived; lower priority
        , description    := s!"derived via metaPath {path.sourceClassifier} ↦ {path.targetClassifier} from {M.theoremName}" }
      -- Also add the path witness itself as a candidate; cubical_search
      -- can apply it to convert the goal to the source classifier form.
      out := out.push
        { classifierName := targetClassifier
        , theoremName    := path.witnessName
        , priority       := M.priority - 20
        , description    := s!"path witness {path.witnessName} for transport" }
  return out

-- ── Tactic primitive: try one entry as a goal closer ───────────────────────
-- Foundation primitive for any methodology-dispatching tactic.
-- Anyone can build different orderings, retry strategies, or
-- failure-reporting on top of this.

/-- Try to close the current goal by applying the registered
    methodology entry's theorem.

    **Contract.** Attempts `exact entry.theoremName`, falling back
    to `apply entry.theoremName`.  Returns `true` if the goal closes
    completely (no subgoals remaining); `false` otherwise.

    Tactic state is restored on `false` — neither a partial-apply nor
    a thrown elaboration error leaks past the call.  Never throws.
    Order of attempts (`exact` before `apply`) is fixed by this
    primitive; alternative orderings can be implemented by composing
    different primitives.

    Use this as the building block for goal-closing dispatch loops;
    no opinion baked in beyond the fixed exact/apply order. -/
def tryEntryAsClosed (entry : MethodologyEntry) : TacticM Bool := do
  let stx := mkIdent entry.theoremName
  let initialState ← saveState
  try
    evalTactic (← `(tactic| (first | exact $stx | apply $stx)))
    if (← getUnsolvedGoals).isEmpty then
      return true
    else
      restoreState initialState
      return false
  catch _ =>
    restoreState initialState
    return false

-- ── Reference dispatcher: `cubical_search` ─────────────────────────────────
-- A reference composition of `findMethodologies` + `deriveByTransport`
-- + `tryEntryAsClosed` in one obvious order.  This is a *demonstrator*,
-- not a normative tactic — anyone wanting different ordering, priority
-- handling, or failure reporting writes their own using the
-- primitives above.

/-- Reference-composition tactic: walk every registered methodology;
    on miss, walk the `@[metaPath]`-derived transport candidates;
    emit a structured failure report on full miss.

    Stages (fixed in this demonstrator; anyone can write a different
    composition):
      1. Direct registry: `getMethodologies`, try each via
         `tryEntryAsClosed` in registry order.
      2. Transport: walk `getMetaPaths` and try each path's source
         methodologies and the path witness itself.
      3. Failure: structured report with per-candidate "didn't fire"
         reasons.

    This is a tactic *demonstrator*, exposing the primitive composition
    pattern.  Tag your goal-closing methodologies with `@[methodology
    Classifier]` and they enter this dispatcher's stage 1; declare
    `@[metaPath A B]` and stage 2 lifts methodologies from A to B
    automatically.  No opinion about ordering, retry strategy, or
    priority interpretation beyond what's stated here. -/
syntax (name := cubicalSearch) "cubical_search" : tactic

elab_rules : tactic
  | `(tactic| cubical_search) => do
      let goal ← getMainGoal
      let goalType ← goal.getType
      let methodologies ← getMethodologies
      let mut tried : Array Name := #[]
      -- Stage 1: direct methodology dispatch.
      for entry in methodologies do
        tried := tried.push entry.theoremName
        if (← tryEntryAsClosed entry) then return
      -- Stage 2: methodology-transport candidates from every
      -- declared `@[metaPath]`.
      let allPaths ← getMetaPaths
      for path in allPaths do
        let sourceMethods ← findMethodologies path.sourceClassifier
        for M in sourceMethods do
          tried := tried.push M.theoremName
          if (← tryEntryAsClosed M) then return
        let witnessEntry : MethodologyEntry :=
          { classifierName := path.targetClassifier
          , theoremName := path.witnessName
          , priority := 0, description := "" }
        tried := tried.push path.witnessName
        if (← tryEntryAsClosed witnessEntry) then return
      -- Structured failure (demonstrator format; no normative
      -- commitment — alternative composers may emit any shape).
      let lines := tried.map (fun n => s!"  ✗ {n}")
      let report := String.intercalate "\n" lines.toList
      let pathCount := allPaths.size
      let methCount := methodologies.size
      throwError s!"cubical_search: no registered methodology closed the goal\n  {← Meta.ppExpr goalType}\n\ncandidates considered ({methCount} direct + {pathCount} paths):\n{report}\n\nregister a new @[methodology] (or build a custom dispatcher from `tryEntryAsClosed`)."

-- ── Diagnostics ─────────────────────────────────────────────────────────────

/-- Print the methodology registry — what's currently available to
    `cubical_search`. -/
def printMethodologies : CoreM Unit := do
  let entries ← getMethodologies
  IO.println s!"── Methodology registry ({entries.size}) ──"
  for entry in entries do
    let p := if entry.priority == 100 then "" else s!" (prio {entry.priority})"
    let d := if entry.description.isEmpty then "" else s!" — {entry.description}"
    IO.println s!"  {entry.classifierName} → {entry.theoremName}{p}{d}"

end Infoductor
