(vl-load-com)

(defun c:ATXT ( / *error* doc entPt ent obj entType ang p1 p2 ss i txtEnt txtObj 
                  curveMode offsetDist isCurved ptClick param deriv tanAng normAng insPt )
  (setq doc (vla-get-ActiveDocument (vlax-get-acad-object)))
  
  ;; Error handler to close Undo mark if user presses Esc
  (defun *error* (msg)
    (if (not (wcmatch (strcase msg t) "*break,*cancel*,*exit*"))
      (princ (strcat "\nError: " msg))
    )
    (vla-EndUndoMark doc)
    (princ)
  )

  ;; Helper function to keep text plan-readable (prevent upside-down text)
  (defun MakeReadable ( a )
    (setq a (rem a (* 2.0 pi)))
    (if (< a 0.0) (setq a (+ a (* 2.0 pi))))
    (if (and (> a (/ pi 2.0)) (<= a (* 1.5 pi)))
      (setq a (- a pi))
    )
    a
  )

  (vla-StartUndoMark doc)

  ;; 1. Choose alignment reference: Object (Line/Polyline/Arc/Curve) or 2-Points with Mouse
  (while (not (or ent ang))
    (initget "Points")
    (setq entPt (entsel "\nSelect Line/Polyline/Arc/Circle/Spline to align with or [Points]: "))
    (cond
      ;; Option A: User chose 2-Points with mouse
      ((= entPt "Points")
        (setq p1 (getpoint "\nSpecify first point with mouse: "))
        (if p1
          (progn
            (setq p2 (getpoint p1 "\nSpecify second point with mouse: "))
            (if p2
              (setq ang (MakeReadable (angle (trans p1 1 0) (trans p2 1 0))))
            )
          )
        )
      )
      ;; Option B: User selected an object
      ((listp entPt)
        (setq ent (car entPt))
        (setq entType (cdr (assoc 0 (entget ent))))
        (if (wcmatch entType "LINE,*POLYLINE,ARC,CIRCLE,ELLIPSE,SPLINE")
          (progn
            (setq obj (vlax-ename->vla-object ent))
            (setq ptClick (vlax-curve-getClosestPointTo obj (trans (cadr entPt) 1 0)))
            (setq param (vlax-curve-getParamAtPoint obj ptClick))
            
            ;; Check if the clicked segment is curved or straight
            (setq isCurved nil)
            (cond
              ((wcmatch entType "ARC,CIRCLE,ELLIPSE,SPLINE") (setq isCurved T))
              ((wcmatch entType "*POLYLINE")
                (if (/= 0.0 (vla-getBulge obj (fix param)))
                  (setq isCurved T)
                )
              )
            )

            ;; Ask for offset distance (default to 0.0 so it sits right on the curve/line)
            (setq offsetDist (getdist ptClick "\nSpecify offset distance from curve/line <0.0>: "))
            (if (not offsetDist) (setq offsetDist 0.0))
          )
          (progn
            (princ "\nInvalid object. Please select a Line, Polyline, Arc, Circle, Ellipse, or Spline.")
            (setq ent nil)
          )
        )
      )
      (t
        (princ "\nNothing selected. Try again or type P for Points.")
      )
    )
  )

  ;; 2. Select Text / MText objects
  (princ "\nSelect Text or MText objects to align: ")
  (setq ss (ssget '((0 . "TEXT,MTEXT"))))

  (if ss
    (progn
      (setq i 0)
      (repeat (sslength ss)
        (setq txtEnt (ssname ss i))
        (setq txtObj (vlax-ename->vla-object txtEnt))

        (cond
          ;; Case 1: 2-Points mouse alignment (Rotate only)
          ((not obj)
            (vla-put-Rotation txtObj ang)
          )

          ;; Case 2: Object alignment (Snap whole text and align tangent without exploding)
          (t
            (setq insPt (vlax-safearray->list (vlax-variant-value (vla-get-InsertionPoint txtObj))))
            ;; If 1 text selected, use clicked point as reference, otherwise use closest point to each text
            (if (= (sslength ss) 1)
              (setq ptOnCurve ptClick)
              (setq ptOnCurve (vlax-curve-getClosestPointTo obj insPt))
            )
            (setq param (vlax-curve-getParamAtPoint obj ptOnCurve))
            (setq deriv (vlax-curve-getFirstDeriv obj param))
            (setq tanAng (MakeReadable (atan (cadr deriv) (car deriv))))
            (setq normAng (+ tanAng (/ pi 2.0)))
            
            ;; Apply offset and position
            (setq insPt (polar ptOnCurve normAng offsetDist))
            (vla-put-InsertionPoint txtObj (vlax-3d-point insPt))
            (vla-put-Rotation txtObj tanAng)
          )
        )
        (setq i (1+ i))
      )
      (princ (strcat "\nSuccessfully aligned " (itoa (sslength ss)) " text object(s)."))
    )
    (princ "\nNo text objects selected.")
  )

  (vla-EndUndoMark doc)
  (princ)
)

(princ "\nType ATXT to run the Align Text command.")
(princ)
