;;; dag-draw-ascii-edges.el --- ASCII edge drawing for dag-draw -*- lexical-binding: t -*-

;; Copyright (C) 2024, 2025

;; Author: Generated by Claude
;; Keywords: internal

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;;; Commentary:

;; ASCII edge drawing, routing, and arrow placement for dag-draw graphs.
;; This module handles the complex logic of drawing edges between nodes
;; with proper collision detection and arrow placement.
;;
;; ASCII COORDINATE CONTEXT: Direct character placement with unified coordinates.
;; The ASCII coordinate context eliminates negative coordinates and provides
;; unified positioning, making defensive checks unnecessary.

;;; Code:

(require 'dash)
(require 'dag-draw)
(require 'dag-draw-core)
(require 'dag-draw-ascii-grid)
(require 'dag-draw-ports)
(require 'dag-draw-ascii-splines)

;; dag-draw-point struct is now in dag-draw.el

;; Global variables for current rendering context (needed for node interior detection)

(defvar dag-draw--current-graph nil
  "Current graph being rendered (for node interior detection).")
(defvar dag-draw--current-min-x nil
  "Current min-x coordinate for rendering context.")
(defvar dag-draw--current-min-y nil
  "Current min-y coordinate for rendering context.")
(defvar dag-draw--current-scale nil
  "Current scale factor for rendering context.");; Node boundary detection functions
(defun dag-draw--get-node-boundary-rect (node min-x min-y scale)
  "Calculate exact boundary coordinates for a NODE in grid space.
Returns (left top right bottom) coordinates.
Argument MIN-X .
Argument MIN-Y .
Argument SCALE ."
  (let* ((world-x (dag-draw-node-x-coord node))
         (world-y (dag-draw-node-y-coord node))
         (world-width (dag-draw-node-x-size node))
         (world-height (dag-draw-node-y-size node))         ;; Convert to grid coordinates
         (grid-center-x (dag-draw--world-to-grid-coord world-x min-x scale))
         (grid-center-y (dag-draw--world-to-grid-coord world-y min-y scale))
         (grid-width (dag-draw--world-to-grid-size world-width scale))
         (grid-height (dag-draw--world-to-grid-size world-height scale))         ;; Calculate boundary rectangle
         (left (round (- grid-center-x (/ grid-width 2))))
         (top (round (- grid-center-y (/ grid-height 2))))
         (right (+ left (round grid-width) -1))
         (bottom (+ top (round grid-height) -1)))    (list left top right bottom)))(defun dag-draw--point-inside-node-p (x y graph min-x min-y scale)
  "Check if coordinate (X,Y) is inside any node's interior (including boundary).
Returns the node if point is inside node area, nil otherwise."
  (catch 'found-node
    (ht-each (lambda (_node-id node)
               (let* ((rect (dag-draw--get-node-boundary-rect node min-x min-y scale))
                      (left (nth 0 rect))
                      (top (nth 1 rect))
                      (right (nth 2 rect))
                      (bottom (nth 3 rect)))

                 ;; Check if point is anywhere within node rectangle (interior + boundary)
                 (when (and (>= x left) (<= x right) (>= y top) (<= y bottom))
                   (throw 'found-node node))))
             (dag-draw-graph-nodes graph))
    nil))

;;; Basic Line Drawing

(defun dag-draw--ascii-draw-line (grid x1 y1 x2 y2 &optional)
  "Draw a simple line from (X1,Y1) to (X2,Y2) on ASCII GRID."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         (dx (- x2 x1))
         (dy (- y2 y1)))

    (cond
     ;; Horizontal line
     ((= dy 0)
      (let ((start-x (min x1 x2))
            (end-x (max x1 x2))
            (_direction (if (< x1 x2) 'right 'left)))
        ;; GKNV Section 5.2 COMPLIANCE: Avoid drawing through node interiors
        (dotimes (i (1+ (- end-x start-x)))
          (let ((x (+ start-x i)))
            (when (not (dag-draw--is-node-interior-position grid x y1))
              (dag-draw--draw-char grid x y1 ?─))))))

     ;; Vertical line
     ((= dx 0)
      (let ((start-y (min y1 y2))
            (end-y (max y1 y2))
            (_direction (if (< y1 y2) 'down 'up)))
        ;; GKNV Section 5.2 COMPLIANCE: Avoid drawing through node interiors
        (dotimes (i (1+ (- end-y start-y)))
          (let ((y (+ start-y i)))
            (when (and (>= x1 0) (< x1 grid-width)
                       (>= y 0) (< y grid-height)
                       (not (dag-draw--is-node-interior-position grid x1 y)))
              (dag-draw--draw-char grid x1 y ?│))))))

     ;; L-shaped line (horizontal then vertical)
     (t
      ;; Draw horizontal segment first
      (dag-draw--ascii-draw-line grid x1 y1 x2 y1)
      ;; Draw vertical segment, avoiding overlap at corner
      (when (/= y1 y2)
        (dag-draw--ascii-draw-line grid x2 (+ y1 (if (< y1 y2) 1 -1)) x2 y2))))))

(defvar dag-draw--arrow-positions nil
  "Track positions where arrows have been placed to prevent conflicts.")

(defun dag-draw--draw-arrow (grid x y arrow-char)
  "Draw arrow character with GKNV Section 5.2 boundary clipping.
Argument GRID .
Argument X .
Argument Y .
Argument ARROW-CHAR ."
  (when arrow-char
    (let* ((int-x (round x))
           (int-y (round y))
           (grid-height (length grid))
           (grid-width (if (> grid-height 0) (length (aref grid 0)) 0)))

      (when (and (>= int-x 0) (< int-x grid-width) (>= int-y 0) (< int-y grid-height))
        ;; GKNV Section 5.2 COMPLIANCE: Boundary-aware arrow placement
        (if (dag-draw--is-node-interior-position grid int-x int-y)
            (message "ARROW-BLOCKED: Arrow %c blocked at occupied position (%d,%d)" arrow-char int-x int-y)
          (let ((current-char (aref (aref grid int-y) int-x)))
            (cond
             ;; Empty space or edge characters - replace with arrow
             ((or (eq current-char ?\s)
                  (memq current-char '(?─ ?│ ?┼ ?┌ ?┐ ?└ ?┘ ?┬ ?┴ ?├ ?┤)))
              (aset (aref grid int-y) int-x arrow-char))
             ;; Skip if arrow already exists or node text
             ((or (memq current-char '(?▼ ?▲ ?▶ ?◀))
                  (and (>= current-char ?a) (<= current-char ?z))
                  (and (>= current-char ?A) (<= current-char ?Z))
                  (and (>= current-char ?0) (<= current-char ?9)))
              nil)
             ;; Default - place arrow
             (t
              (aset (aref grid int-y) int-x arrow-char)))))))))
(defun dag-draw--draw-char (grid x y char)
  "Draw character at GRID position with basic bounds checking.
ASCII coordinate context ensures coordinates are always valid.
Argument X .
Argument Y .
Argument CHAR ."
  (let* ((int-x (round x))
         (int-y (round y))
         (grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0)))    (when (and (>= int-x 0) (< int-x grid-width) (>= int-y 0) (< int-y grid-height))
      ;; GKNV Section 5.2 COMPLIANCE: Boundary-aware character placement
      (if (dag-draw--is-node-interior-position grid int-x int-y)
          (message "CHAR-BLOCKED: Character %c blocked at occupied position (%d,%d)" char int-x int-y)
        (let ((current-char (aref (aref grid int-y) int-x)))
          (cond
           ;; Empty space - check for nearby parallel lines before drawing
           ((eq current-char ?\s)
            (if (and (eq char ?│) (dag-draw--has-nearby-vertical-line grid int-x int-y))
                ;; Skip drawing this vertical line to prevent ││ artifacts
                nil
              (aset (aref grid int-y) int-x char)))
           ;; Never overwrite node text content
           ((or (and (>= current-char ?a) (<= current-char ?z))
                (and (>= current-char ?A) (<= current-char ?Z))
                (and (>= current-char ?0) (<= current-char ?9)))
            nil)
           ;; Edge characters - use enhanced junction logic with proper context analysis
           ((memq current-char '(?─ ?│ ?┼ ?┌ ?┐ ?└ ?┘ ?├ ?┤ ?┬ ?┴))
            ;; D5.6-D5.8: Analyze local grid context to determine proper junction character
            (let* ((context (dag-draw--analyze-local-grid-junction-context grid int-x int-y current-char char))
                   (junction-char (dag-draw--get-enhanced-junction-char context)))
              (when junction-char
                (aset (aref grid int-y) int-x junction-char))
              ;; PARALLEL LINE CONSOLIDATION: Check for adjacent parallel lines that should merge
              (dag-draw--consolidate-parallel-lines grid int-x int-y)))
           ;; Default - draw character
           (t
            (aset (aref grid int-y) int-x char))))))))


(defun dag-draw--is-node-interior-position (_grid x y)
  "Check if position (X,Y) is inside a node's interior area.
Returns t if position is in node interior, nil if boundary or empty space.
GKNV Section 5.2: Edges should not route through node text areas.
Argument GRID ."
  ;; CRITICAL FIX: Use direct node boundary calculation
  ;; This ensures consistency with GKNV edge routing requirements
  (when (and dag-draw--current-graph dag-draw--current-min-x dag-draw--current-min-y dag-draw--current-scale)
    (dag-draw--point-inside-node-p x y dag-draw--current-graph
                                   dag-draw--current-min-x dag-draw--current-min-y dag-draw--current-scale)))


(defun dag-draw--has-nearby-vertical-line (grid x y)
  "Check if there's a vertical line nearby that would create ││ artifacts.
Argument GRID .
Argument X .
Argument Y ."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         (nearby-found nil))    ;; Check within 2 positions left and right for existing vertical lines
    (dotimes (dx 5) ; Check positions -2 to +2
      (let ((check-x (+ x (- dx 2))))
        (when (and (>= check-x 0) (< check-x grid-width) (not nearby-found))
          (let ((check-char (aref (aref grid y) check-x)))
            (when (eq check-char ?│)
              (setq nearby-found t))))))    nearby-found))

(defun dag-draw--consolidate-parallel-lines (grid x y)
  "Consolidate parallel lines to prevent ││ artifacts.
Detects when vertical lines are adjacent and merges them into proper routing.
Argument GRID .
Argument X .
Argument Y ."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         (current-char (aref (aref grid y) x)))    ;; Only process vertical lines
    (when (eq current-char ?│)
      ;; Check for adjacent vertical lines that should be consolidated
      (let ((adjacent-positions '()))
        ;; Check left and right adjacent positions
        (dolist (dx '(-1 1))
          (let ((check-x (+ x dx)))
            (when (and (>= check-x 0) (< check-x grid-width))
              (let ((adjacent-char (aref (aref grid y) check-x)))
                (when (eq adjacent-char ?│)
                  (push check-x adjacent-positions))))))        ;; If we have adjacent vertical lines, consolidate them
        (when adjacent-positions
          ;; Simple consolidation: remove one of the parallel lines
          ;; Keep the leftmost vertical line, remove others
          (let ((positions (sort (cons x adjacent-positions) '<)))
            (dolist (pos (cdr positions))
              (when (and (>= pos 0) (< pos grid-width))
                (aset (aref grid y) pos ?\s)))))))))

;;; GKNV Section 5.2 Boundary Clipping Implementation

(defun dag-draw--find-node-boundary-intersection (graph node-id x1 y1 x2 y2 min-x min-y scale)
  "Find where line from (X1,Y1) to (X2,Y2) intersects NODE boundary.
Returns intersection point (x,y) on the node boundary, or nil if no
intersection.
Implements GKNV Section 5.2: `clips the spline to the boundaries of
the endpoint node shapes'.
Argument GRAPH .
Argument NODE-ID ."
  (let* ((node (dag-draw-get-node graph node-id))
         ;; GKNV Pass 3 Authority: Only use GKNV-assigned coordinates
         ;; Section 4: "The third pass finds optimal coordinates for nodes"
         ;; No manual coordinate override allowed - Pass 3 has sole authority
         (gknv-x (dag-draw-node-x-coord node))
         (gknv-y (dag-draw-node-y-coord node))
         (adjusted-positions (dag-draw-graph-adjusted-positions graph))
         (node-coords (if (and adjusted-positions (ht-get adjusted-positions node-id))
                          ;; Use adjusted positions if available (from layout algorithm)
                          (ht-get adjusted-positions node-id)
                        ;; Otherwise use GKNV Pass 3 coordinates
                        (let* ((x (or gknv-x 0))
                               (y (or gknv-y 0))
                               (width (dag-draw-node-x-size node))
                               (height (dag-draw-node-y-size node))
                               (grid-center-x (dag-draw--world-to-grid-coord x min-x scale))
                               (grid-center-y (dag-draw--world-to-grid-coord y min-y scale))
                               (grid-width-node (dag-draw--world-to-grid-size width scale))
                               (grid-height-node (dag-draw--world-to-grid-size height scale))
                               (grid-x (- grid-center-x (/ grid-width-node 2)))
                               (grid-y (- grid-center-y (/ grid-height-node 2))))
                          (list grid-x grid-y grid-width-node grid-height-node)))))
    (when node      (when node-coords
        (let* ((node-x (nth 0 node-coords))
               (node-y (nth 1 node-coords))
               (node-width (nth 2 node-coords))
               (node-height (nth 3 node-coords))
               ;; Node boundary coordinates
               (left node-x)
               (right (+ node-x node-width -1))
               (top node-y)
               (bottom (+ node-y node-height -1)))          ;; DEBUG: Log boundary intersection attempt
          (when dag-draw-debug-output
            (message "INTERSECTION-DEBUG: Node %s bounds: (%d,%d,%d,%d) line: (%d,%d)->(%d,%d)"
                     node-id left top right bottom x1 y1 x2 y2))          ;; Check intersection with each boundary edge
          (let ((result (or
                         ;; Left boundary (vertical line at x=left)
                         (dag-draw--line-intersects-vertical x1 y1 x2 y2 left top bottom)
                         ;; Right boundary (vertical line at x=right)
                         (dag-draw--line-intersects-vertical x1 y1 x2 y2 right top bottom)
                         ;; Top boundary (horizontal line at y=top)
                         (dag-draw--line-intersects-horizontal x1 y1 x2 y2 top left right)
                         ;; Bottom boundary (horizontal line at y=bottom)
                         (dag-draw--line-intersects-horizontal x1 y1 x2 y2 bottom left right))))
            (when result
              (message "INTERSECTION-FOUND: Node %s intersection at (%d,%d)" node-id (nth 0 result) (nth 1 result)))
            result))))))

(defun dag-draw--point-on-node-boundary (graph node-id x y &optional min-x min-y scale)
  "Check if point (X,Y) is exactly on the boundary of NODE-ID.
Returns t if point is on the node boundary, nil otherwise.
GKNV Section 5.2 FIX: Use same coordinate priority as visual rendering.
Argument GRAPH ."
  ;; GKNV FIX: Ensure integer coordinates to prevent type errors
  (let ((int-x (round x))
        (int-y (round y)))
    (let* ((node (dag-draw-get-node graph node-id))
           ;; GKNV Pass 3 Authority: Only use GKNV-assigned coordinates
           ;; Section 4: "The third pass finds optimal coordinates for nodes"
           (gknv-x (dag-draw-node-x-coord node))
           (gknv-y (dag-draw-node-y-coord node))
           (adjusted-positions (dag-draw-graph-adjusted-positions graph))
           ;; GKNV Section 5.2 FIX: Use GKNV coordinate authority for intersection detection
           ;; Only use algorithm-assigned coordinates for boundary calculations
           (node-coords (if (and adjusted-positions (ht-get adjusted-positions node-id))
                            (ht-get adjusted-positions node-id)
                          (let* ((coord-scale (or scale dag-draw-ascii-coordinate-scale))
                                 (coord-min-x (or min-x 0))
                                 (coord-min-y (or min-y 0))
                                 (x-coord (or gknv-x 0))
                                 (y-coord (or gknv-y 0))
                                 (width (dag-draw-node-x-size node))
                                 (height (dag-draw-node-y-size node))
                                 (grid-center-x (dag-draw--world-to-grid-coord x-coord coord-min-x coord-scale))
                                 (grid-center-y (dag-draw--world-to-grid-coord y-coord coord-min-y coord-scale))
                                 (grid-width-node (dag-draw--world-to-grid-size width coord-scale))
                                 (grid-height-node (dag-draw--world-to-grid-size height coord-scale))
                                 (grid-x (round (- grid-center-x (/ grid-width-node 2))))
                                 (grid-y (round (- grid-center-y (/ grid-height-node 2)))))
                            (list grid-x grid-y grid-width-node grid-height-node)))))
      (when node-coords
        (let* ((node-x (nth 0 node-coords))
               (node-y (nth 1 node-coords))
               (node-width (nth 2 node-coords))
               (node-height (nth 3 node-coords))
               (left node-x)
               (right (+ node-x node-width -1))
               (top node-y)
               (bottom (+ node-y node-height -1)))
          ;; Point is on boundary if it's on any edge of the rectangle
          (or (and (= int-x left) (>= int-y top) (<= int-y bottom)) ; Left edge
              (and (= int-x right) (>= int-y top) (<= int-y bottom)) ; Right edge
              (and (= int-y top) (>= int-x left) (<= int-x right)) ; Top edge
              (and (= int-y bottom) (>= int-x left) (<= int-x right))))))))
                                        ; Bottom edge

(defun dag-draw--line-intersects-vertical (x1 y1 x2 y2 boundary-x min-y max-y)
  "Find intersection of line (X1,Y1)→(X2,Y2) with vertical boundary at BOUNDARY-X.
Returns (x,y) intersection point or nil if no intersection within Y range.
Argument MIN-Y .
Argument MAX-Y ."
  (when (not (= x1 x2)) ; Avoid division by zero for vertical lines
    (let* ((t-param (/ (float (- boundary-x x1)) (- x2 x1)))
           (intersect-y (+ y1 (* t-param (- y2 y1)))))
      ;; Check if intersection is within the line segment and boundary range
      (when (and (>= t-param 0) (<= t-param 1)
                 (>= intersect-y min-y) (<= intersect-y max-y))
        (list boundary-x (round intersect-y))))))

(defun dag-draw--line-intersects-horizontal (x1 y1 x2 y2 boundary-y min-x max-x)
  "Find intersection of line (X1,Y1)→(X2,Y2) w/ horiz.  boundary at BOUNDARY-Y.
Returns (x,y) intersection point or nil if no intersection within X range.
Argument MIN-X .
Argument MAX-X ."
  (when (not (= y1 y2)) ; Avoid division by zero for horizontal lines
    (let* ((t-param (/ (float (- boundary-y y1)) (- y2 y1)))
           (intersect-x (+ x1 (* t-param (- x2 x1)))))
      ;; Check if intersection is within the line segment and boundary range
      (when (and (>= t-param 0) (<= t-param 1)
                 (>= intersect-x min-x) (<= intersect-x max-x))
        (list (round intersect-x) boundary-y)))))

(defun dag-draw--clip-edge-to-boundaries (graph edge x1 y1 x2 y2 min-x min-y scale)
  "Clip EDGE endpoints to node boundaries per GKNV Section 5.2.
Returns (start-x start-y end-x end-y) with boundary-clipped coordinates.
GKNV FIX: Handles edges that start/end on node boundaries
by preventing inward extension.
Argument GRAPH ."
  (let* ((from-node-id (dag-draw-edge-from-node edge))
         (to-node-id (dag-draw-edge-to-node edge))
         ;; Find boundary intersections
         (from-intersection (dag-draw--find-node-boundary-intersection graph from-node-id x1 y1 x2 y2 min-x min-y scale))
         (to-intersection (dag-draw--find-node-boundary-intersection graph to-node-id x2 y2 x1 y1 min-x min-y scale))
         ;; GKNV FIX: Check if points are already on node boundaries using same coordinate system
         (from-on-boundary (dag-draw--point-on-node-boundary graph from-node-id x1 y1 min-x min-y scale))
         (to-on-boundary (dag-draw--point-on-node-boundary graph to-node-id x2 y2 min-x min-y scale)))    ;; DEBUG: Log boundary status and intersection results
    (message "BOUNDARY-STATUS: Edge %s->%s from-boundary=%s to-boundary=%s"
             from-node-id to-node-id from-on-boundary to-on-boundary)    ;; Use intersection points if found, otherwise use original coordinates
    ;; If point is already on boundary, keep it (don't clip further)
    (list (if (and from-intersection (not from-on-boundary)) (nth 0 from-intersection) x1)
          (if (and from-intersection (not from-on-boundary)) (nth 1 from-intersection) y1)
          (if (and to-intersection (not to-on-boundary)) (nth 0 to-intersection) x2)
          (if (and to-intersection (not to-on-boundary)) (nth 1 to-intersection) y2))))
;;; GKNV-Compliant Boundary-Aware Edge Drawing

(defun dag-draw--ascii-draw-boundary-aware-path-with-arrow (graph grid x1 y1 x2 y2 port-side min-x min-y scale)
  "Draw path that respects node boundaries per GKNV Section 5.2.
This function ensures edges never extend beyond node boundaries
by checking each segment.
Argument GRAPH .
Argument GRID .
Argument X1 .
Argument Y1 .
Argument X2 .
Argument Y2 .
Argument PORT-SIDE .
Argument MIN-X .
Argument MIN-Y .
Argument SCALE ."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0)))    ;; Only draw if we can do so safely
    (when (and (>= x1 0) (< x1 grid-width) (>= y1 0) (< y1 grid-height)
               (>= x2 0) (< x2 grid-width) (>= y2 0) (< y2 grid-height))      ;; Choose routing direction based on edge orientation
      (let ((routing-direction (if (<= (abs (- x1 x2)) 4)
                                   'vertical-only ; Pure vertical edge
                                 (if (<= (abs (- y1 y2)) 4)
                                     'horizontal-only ; Pure horizontal edge
                                   'horizontal-first)))) ; L-shaped edge        ;; Draw the path with boundary awareness
        (dag-draw--draw-boundary-aware-l-path graph grid x1 y1 x2 y2 routing-direction min-x min-y scale))      ;; Add port-based directional arrow at the endpoint
      (dag-draw--add-port-based-arrow grid x1 y1 x2 y2 port-side))))

(defun dag-draw--draw-boundary-aware-l-path (graph grid x1 y1 x2 y2 direction min-x min-y scale)
  "Draw L-shaped path that never extend beyond node boundaries.
Implements GKNV Section 5.2 boundary clipping
by checking each segment against node boundaries.
Argument GRAPH .
Argument GRID .
Argument X1 .
Argument Y1 .
Argument X2 .
Argument Y2 .
Argument DIRECTION .
Argument MIN-X .
Argument MIN-Y .
Argument SCALE ."
  (let* ((grid-height (length grid))
         (_grid-width (if (> grid-height 0) (length (aref grid 0)) 0)))    (cond
     ;; Pure vertical line
     ((eq direction 'vertical-only)
      (dag-draw--draw-boundary-clipped-vertical-line graph grid x1 y1 x2 y2 min-x min-y scale))     ;; Pure horizontal line
     ((eq direction 'horizontal-only)
      (dag-draw--draw-boundary-clipped-horizontal-line graph grid x1 y1 x2 y2 min-x min-y scale))     ;; L-shaped path - horizontal first, then vertical
     ((eq direction 'horizontal-first)
      ;; Draw horizontal segment from (x1,y1) to (x2,y1) with boundary clipping
      (dag-draw--draw-boundary-clipped-horizontal-line graph grid x1 y1 x2 y1 min-x min-y scale)
      ;; Draw vertical segment from (x2,y1) to (x2,y2) with boundary clipping
      (dag-draw--draw-boundary-clipped-vertical-line graph grid x2 y1 x2 y2 min-x min-y scale)))))


(defun dag-draw--draw-boundary-clipped-horizontal-line (_graph grid x1 y1 x2 _y2 _min-x _min-y _scale)
  "Draw horizontal line segment that stops at node boundaries.
GKNV Section 5.2 FIX: Prevents drawing through node interiors
by smart boundary exit.
Argument GRAPH .
Argument GRID .
Argument X1 .
Argument Y1 .
Argument X2 .
Argument Y2 .
Argument MIN-X .
Argument MIN-Y .
Argument SCALE ."
  (let* ((_start-x (min x1 x2))
         (_end-x (max x1 x2))
         (y y1) ; Horizontal line uses y1
         (direction (if (< x1 x2) 1 -1))) ; Drawing direction: 1=right, -1=left    ;; GKNV Section 5.2 FIX: Smart boundary-aware drawing with obstacle avoidance
    ;; Route around nodes rather than through them
    (let ((current-x x1)) ; Start from actual start point, not min
      (while (if (> direction 0) (<= current-x x2) (>= current-x x2))
        (when (and (>= current-x 0) (< current-x (if (> (length grid) 0) (length (aref grid 0)) 0))
                   (>= y 0) (< y (length grid)))
          ;; GKNV compliant: Draw edge characters directly, clipped at boundaries
          (dag-draw--draw-char grid current-x y ?─))
        (setq current-x (+ current-x direction))))))

(defun dag-draw--draw-boundary-clipped-vertical-line (_graph grid x1 y1 _x2 y2 _min-x _min-y _scale)
  "Draw vertical line segment that stops at node boundaries.
Argument GRAPH .
Argument GRID .
Argument X1 .
Argument Y1 .
Argument X2 .
Argument Y2 .
Argument MIN-X .
Argument MIN-Y .
Argument SCALE ."
  (let* ((start-y (min y1 y2))
         (end-y (max y1 y2))
         (x x1)) ; Vertical line uses x1    ;; Draw each character of the vertical line, checking boundaries
    (dotimes (i (1+ (- end-y start-y)))
      (let ((y (+ start-y i)))
        (when (and (>= x 0) (< x (if (> (length grid) 0) (length (aref grid 0)) 0))
                   (>= y 0) (< y (length grid)))
          ;; GKNV compliant: Draw edge characters directly, clipped at boundaries
          (dag-draw--draw-char grid x y ?│))))))

;;; Enhanced Edge Drawing Functions

(defun dag-draw--ascii-draw-orthogonal-edge (graph edge grid min-x min-y scale)
  "Draw orthogonal EDGE with comprehensive collision avoidance.
Argument GRAPH ."
  ;; COORDINATE SYSTEM FIX: Use regenerated spline endpoints when available
  (let ((connection-points
         (if (dag-draw-edge-spline-points edge)
             ;; Extract endpoints from regenerated splines (post-collision coordinates)
             (let* ((spline-points (dag-draw-edge-spline-points edge))
                    (start-point (car spline-points))
                    (end-point (car (last spline-points))))
               (list start-point end-point))
           ;; Fall back to original port calculation when no splines exist
           (dag-draw--get-edge-connection-points graph edge min-x min-y scale))))
    ;; Connection points calculated    (if (and connection-points (= (length connection-points) 2))
        (let* ((from-port (car connection-points))
               (to-port (cadr connection-points))
               ;; COORDINATE SYSTEM FIX: Ports now return grid coordinates directly
               (from-x (dag-draw--center-aware-round (dag-draw-point-x from-port)))
               (from-y (dag-draw--center-aware-round (dag-draw-point-y from-port)))
               (to-x (dag-draw--center-aware-round (dag-draw-point-x to-port)))
               (to-y (dag-draw--center-aware-round (dag-draw-point-y to-port)))
               ;; Calculate port-based arrow direction
               (to-node (dag-draw-get-node graph (dag-draw-edge-to-node edge)))
               (target-port-side (dag-draw--determine-port-side to-node to-port min-x min-y scale graph)))
          ;; GKNV Section 5.2: Apply boundary clipping to ensure edges terminate at node boundaries
          (let ((clipped-coords (dag-draw--clip-edge-to-boundaries graph edge from-x from-y to-x to-y min-x min-y scale)))
            (let ((clipped-from-x (nth 0 clipped-coords))
                  (clipped-from-y (nth 1 clipped-coords))
                  (clipped-to-x (nth 2 clipped-coords))
                  (clipped-to-y (nth 3 clipped-coords)))              ;; DEBUG: Log boundary clipping results
              (message "ENHANCED: Edge %s->%s using calculated ports (%d,%d)->(%d,%d)"
                       (dag-draw-edge-from-node edge) (dag-draw-edge-to-node edge)
                       from-x from-y to-x to-y)
              (message "BOUNDARY-CHECK: Edge %s->%s clipping (%d,%d)->(%d,%d) result: (%d,%d)->(%d,%d)"
                       (dag-draw-edge-from-node edge) (dag-draw-edge-to-node edge)
                       from-x from-y to-x to-y
                       clipped-from-x clipped-from-y clipped-to-x clipped-to-y)
              (when (or (not (= clipped-from-x from-x)) (not (= clipped-from-y from-y))
                        (not (= clipped-to-x to-x)) (not (= clipped-to-y to-y)))
                (message "CLIPPED: Edge %s->%s clipped to boundaries (%d,%d)->(%d,%d)"
                         (dag-draw-edge-from-node edge) (dag-draw-edge-to-node edge)
                         clipped-from-x clipped-from-y clipped-to-x clipped-to-y))              ;; Draw the boundary-clipped edge using GKNV-compliant boundary-aware drawing
              (dag-draw--ascii-draw-boundary-aware-path-with-arrow
               graph grid clipped-from-x clipped-from-y clipped-to-x clipped-to-y target-port-side min-x min-y scale))))
      ;; CRITICAL FIX: Still try to draw something even if connection points fail
      (progn
        ;; DEBUG: Log fallback usage
        (message "FALLBACK: Edge %s->%s using node centers (connection-points: %s)"
                 (dag-draw-edge-from-node edge) (dag-draw-edge-to-node edge)
                 (if connection-points (format "%d points" (length connection-points)) "nil"))
        ;; Fallback to node center connections
        ;; Fallback: use node centers as connection points
        (dag-draw--ascii-draw-orthogonal-edge graph edge grid min-x min-y scale))))


(defun dag-draw--ascii-draw-path-with-port-arrow (grid x1 y1 x2 y2 port-side)
  "Draw path with absolute safety and port-based arrow direction.
Argument GRID .
Argument X1 .
Argument Y1 .
Argument X2 .
Argument Y2 .
Argument PORT-SIDE ."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))) ;; Only draw if we can do so safely
    (when (and (>= x1 0) (< x1 grid-width) (>= y1 0) (< y1 grid-height)
               (>= x2 0) (< x2 grid-width) (>= y2 0) (< y2 grid-height)) ;; Choose routing direction based on edge orientation
      (let ((routing-direction (if (<= (abs (- x1 x2)) 4)
                                   'vertical-only ; Pure vertical edge (allow 4-char tolerance)
                                 (if (<= (abs (- y1 y2)) 4)
                                     'horizontal-only ; Pure horizontal edge (allow 4-char tolerance)
                                   'horizontal-first)))) ; L-shaped edge        ;; Draw the path with appropriate direction
        (dag-draw--draw-l-path grid x1 y1 x2 y2 routing-direction)) ;; Add port-based directional arrow at the endpoint
      (dag-draw--add-port-based-arrow grid x1 y1 x2 y2 port-side))))

(defun dag-draw--draw-l-path (grid x1 y1 x2 y2 direction)
  "Draw L-shaped path with ultra-conservative safety checks."
  (let* ((grid-height (length grid))
         (_grid-width (if (> grid-height 0) (length (aref grid 0)) 0)))
    (cond
     ;; Pure vertical line
     ((eq direction 'vertical-only)
          (let ((start-y (min y1 y2))
                (end-y (max y1 y2)))
            (dotimes (i (1+ (- end-y start-y)))
              (let ((y (+ start-y i)))
                (dag-draw--draw-char grid x1 y ?│)))))
     ;; Pure horizontal line - GKNV spline conversion
     ((eq direction 'horizontal-only)
          (let ((start-x (min x1 x2))
                (end-x (max x1 x2)))
            ;; Draw continuous horizontal line as GKNV spline requires
            (dotimes (i (1+ (- end-x start-x)))
              (let ((x (+ start-x i)))
                (dag-draw--draw-char grid x y1 ?─))))) ;; L-shaped path: horizontal first - GKNV spline conversion
         ((eq direction 'horizontal-first)
          ;; Horizontal segment first: x1 to x2 at y1 as continuous GKNV spline
          (let ((start-x (min x1 x2))
                (end-x (max x1 x2)))
            (dotimes (i (1+ (- end-x start-x)))
              (let ((x (+ start-x i)))
                (dag-draw--draw-char grid x y1 ?─))))
          ;; Vertical segment: y1 to y2 at x2
          (let ((start-y (min y1 y2))
                (end-y (max y1 y2)))
            (dotimes (i (1+ (- end-y start-y)))
              (let ((y (+ start-y i)))
                (dag-draw--draw-char grid x2 y ?│))))
          ;; Add corner character at junction point (x2, y1)
          (let ((corner-char (cond
                              ((and (< x1 x2) (< y1 y2)) ?┐) ; Right then down
                              ((and (< x1 x2) (> y1 y2)) ?┘) ; Right then up
                              ((and (> x1 x2) (< y1 y2)) ?┌) ; Left then down
                              ((and (> x1 x2) (> y1 y2)) ?└) ; Left then up
                              (t ?┼)))) ; Fallback intersection
            (dag-draw--draw-char grid x2 y1 corner-char))) ;; L-shaped path: vertical first (default fallback)
         (t
          ;; Vertical segment first: y1 to y2 at x1
          (let ((start-y (min y1 y2))
                (end-y (max y1 y2)))
            (dotimes (i (1+ (- end-y start-y)))
              (let ((y (+ start-y i)))
                (dag-draw--draw-char grid x1 y ?│))))
          ;; Horizontal segment: x1 to x2 at y2 as continuous GKNV spline
          (let ((start-x (min x1 x2))
                (end-x (max x1 x2)))
            (dotimes (i (1+ (- end-x start-x)))
              (let ((x (+ start-x i)))
                (dag-draw--draw-char grid x y2 ?─))))
          ;; Add corner character at junction point (x1, y2)
          (let ((corner-char (cond
                              ((and (< y1 y2) (< x1 x2)) ?┌) ; Down then right
                              ((and (< y1 y2) (> x1 x2)) ?┐) ; Down then left
                              ((and (> y1 y2) (< x1 x2)) ?└) ; Up then right
                              ((and (> y1 y2) (> x1 x2)) ?┘) ; Up then left
                              (t ?┼)))) ; Fallback intersection
            (dag-draw--draw-char grid x1 y2 corner-char))))))

(defun dag-draw--find-actual-boundary-position (grid target-x target-y arrow-char)
  "Find actual boundary near TARGET for GKNV Section 5.2 compliance.
Returns (x y) of boundary position where arrow should be placed,
or nil if none found."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         (search-radius 20) ; EXPANDED: Increased from 12 to 20 for complex graphs
         (target-boundary-chars (cond
                                 ;; ENHANCED: Added junction characters for comprehensive boundary detection
                                 ((eq arrow-char ?▼) '(?─ ?┌ ?┬ ?┐ ?├ ?┤ ?┼)) ; Down arrow replaces top boundary + junctions
                                 ((eq arrow-char ?▲) '(?─ ?└ ?┴ ?┘ ?├ ?┤ ?┼)) ; Up arrow replaces bottom boundary + junctions
                                 ((eq arrow-char ?▶) '(?│ ?┌ ?├ ?└ ?┬ ?┴ ?┼)) ; Right arrow replaces left boundary + junctions
                                 ((eq arrow-char ?◀) '(?│ ?┐ ?┤ ?┘ ?┬ ?┴ ?┼)) ; Left arrow replaces right boundary + junctions
                                 (t '(?─ ?│ ?┌ ?┐ ?└ ?┘ ?├ ?┤ ?┬ ?┴ ?┼)))) ; Default: any boundary + junctions
         (best-pos nil)
         (best-distance most-positive-fixnum))    ;; Search in expanding radius for actual boundary characters
    (dotimes (dy (1+ (* 2 search-radius)))
      (dotimes (dx (1+ (* 2 search-radius)))
        (let* ((check-x (+ target-x (- dx search-radius)))
               (check-y (+ target-y (- dy search-radius)))
               (manhattan-dist (+ (abs (- check-x target-x)) (abs (- check-y target-y)))))          ;; Only check within grid bounds and search radius
          (when (and (>= check-x 0) (< check-x grid-width)
                     (>= check-y 0) (< check-y grid-height)
                     (<= manhattan-dist search-radius))            (let ((char-at-pos (aref (aref grid check-y) check-x)))
              ;; Check if this is a target boundary character
              (when (memq char-at-pos target-boundary-chars)
                (when (< manhattan-dist best-distance)
                  (setq best-pos (list check-x check-y))
                  (setq best-distance manhattan-dist))))))))
    best-pos))

(defun dag-draw--find-nearest-boundary-for-adjacent-placement (grid target-x target-y arrow-char)
  "Find nearest boundary to place arrow adjacent to.
Used when main boundary search fails.  This ensures arrows don't
float in space per GKNV Section 5.2."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         (search-radius 25) ; Wider search for fallback
         (any-boundary-chars '(?─ ?│ ?┌ ?┐ ?└ ?┘ ?├ ?┤ ?┬ ?┴ ?┼))
         (best-pos nil)
         (best-distance most-positive-fixnum))
    ;; Search for ANY boundary character nearby
    (dotimes (dy (1+ (* 2 search-radius)))
      (dotimes (dx (1+ (* 2 search-radius)))
        (let* ((check-x (+ target-x (- dx search-radius)))
               (check-y (+ target-y (- dy search-radius)))
               (manhattan-dist (+ (abs (- check-x target-x)) (abs (- check-y target-y)))))
          (when (and (>= check-x 0) (< check-x grid-width)
                     (>= check-y 0) (< check-y grid-height)
                     (<= manhattan-dist search-radius))
            (let ((char-at-pos (aref (aref grid check-y) check-x)))
              (when (memq char-at-pos any-boundary-chars)
                (when (< manhattan-dist best-distance)
                  (setq best-pos (list check-x check-y))
                  (setq best-distance manhattan-dist))))))))
    ;; If we found a boundary, place arrow adjacent to it
    (when best-pos
      (let* ((boundary-x (car best-pos))
             (boundary-y (cadr best-pos))
             ;; Calculate adjacent position based on arrow direction
             (adjacent-pos (cond
                            ((eq arrow-char ?▼) (list boundary-x (1+ boundary-y))) ; Below boundary
                            ((eq arrow-char ?▲) (list boundary-x (1- boundary-y))) ; Above boundary
                            ((eq arrow-char ?▶) (list (1+ boundary-x) boundary-y)) ; Right of boundary
                            ((eq arrow-char ?◀) (list (1- boundary-x) boundary-y)) ; Left of boundary
                            (t (list boundary-x boundary-y))))) ; Fallback to boundary itself
        ;; Ensure adjacent position is within grid bounds
        (let ((adj-x (car adjacent-pos))
              (adj-y (cadr adjacent-pos)))
          (when (and (>= adj-x 0) (< adj-x grid-width)
                     (>= adj-y 0) (< adj-y grid-height))
            adjacent-pos))))))

(defun dag-draw--add-port-based-arrow (grid x1 y1 x2 y2 port-side)
  "Add directional arrow based on actual coordinate direction.
PORT-SIDE is used as secondary hint."
  (let* ((dx (- x2 x1))
         (dy (- y2 y1))
         (grid-height (length grid))
         (_grid-width (if (> grid-height 0) (length (aref grid 0)) 0)))
    ;; DEBUG: Show coordinates and direction calculation for arrow placement
    (message " GRID-ARROW: (%d,%d)->(%d,%d) dx=%d dy=%d port-side=%s"
             x1 y1 x2 y2 dx dy port-side)
    (let* ((arrow-char (cond
                        ;; PRIORITY: Use coordinate-based direction for clear vertical/horizontal cases
                        ((and (= dx 0) (> dy 0)) ?▼) ; Pure vertical downward
                        ((and (= dx 0) (< dy 0)) ?▲) ; Pure vertical upward
                        ((and (= dy 0) (> dx 0)) ?▶) ; Pure horizontal rightward
                        ((and (= dy 0) (< dx 0)) ?◀) ; Pure horizontal leftward
                        ;; GKNV Section 5.2 FIX: For ambiguous cases, use COORDINATE DIRECTION not port orientation
                        ((eq port-side 'top) ?▼) ; Arrow pointing down to top side
                        ((eq port-side 'bottom) ?▼) ; Arrow pointing down to bottom side (DAG: downward flow)
                        ((eq port-side 'left) (if (< dx 0) ?◀ ?▶)) ; Use actual movement direction
                        ((eq port-side 'right) (if (> dx 0) ?▶ ?◀)) ; Use actual movement direction
                        ;; For remaining diagonal cases, use coordinate direction as fallback
                        ((> (abs dy) (abs dx))
                         (if (> dy 0) ?▼ ?▲)) ; Primarily vertical
                        ((> (abs dx) (abs dy))
                         (if (> dx 0) ?▶ ?◀)) ; Primarily horizontal
                        (t ?▶))) ; Final default
           ;; GKNV Section 5.2 FIX: Find actual boundary character to replace
           (actual-boundary-pos (dag-draw--find-actual-boundary-position grid (round x2) (round y2) arrow-char)))      (if actual-boundary-pos
          (progn
            (message " BOUNDARY-FOUND: Arrow %c at actual boundary (%d,%d) instead of calculated (%d,%d)"
                     arrow-char (car actual-boundary-pos) (cadr actual-boundary-pos) x2 y2)
            ;; Place arrow ON actual boundary per GKNV Section 5.2
            (dag-draw--draw-arrow grid (car actual-boundary-pos) (cadr actual-boundary-pos) arrow-char))
        ;; ENHANCED FALLBACK: Try to place arrow adjacent to nearest boundary
        (let ((adjacent-pos (dag-draw--find-nearest-boundary-for-adjacent-placement grid (round x2) (round y2) arrow-char)))
          (if adjacent-pos
              (progn
                (message " BOUNDARY-ADJACENT: Arrow %c placed adjacent to boundary at (%d,%d) instead of floating at (%d,%d)"
                         arrow-char (car adjacent-pos) (cadr adjacent-pos) x2 y2)
                (dag-draw--draw-arrow grid (car adjacent-pos) (cadr adjacent-pos) arrow-char))
            ;; Final fallback: use calculated position only if no boundaries found anywhere
            (progn
              (message " BOUNDARY-MISSING: No boundary found near (%d,%d), using calculated position" x2 y2)
              (dag-draw--draw-arrow grid x2 y2 arrow-char))))))))

(defun dag-draw--draw-sophisticated-spline-path (grid converted-points)
  "Draw spline path using GKNV-compliant approach.
Draws segments between consecutive spline points per GKNV Section 5.2."
  (when (>= (length converted-points) 2)
    ;; Draw segments between ALL consecutive points for complete spline path
    ;; This preserves the piecewise linear path calculated by the L-array
    (dotimes (i (1- (length converted-points)))
      (let* ((current-point (nth i converted-points))
             (next-point (nth (1+ i) converted-points))
             (x1 (nth 0 current-point))
             (y1 (nth 1 current-point))
             (x2 (nth 0 next-point))
             (y2 (nth 1 next-point)))
        (dag-draw--draw-continuous-path-segment grid x1 y1 x2 y2)))))

;;; L-shaped path drawing

(defun dag-draw--draw-continuous-path-segment (grid x1 y1 x2 y2)
  "Draw a continuous path segment ensuring no gaps or floating characters.
Uses orthogonal routing (horizontal then vertical) for clean
ASCII representation.
Argument GRID .
Argument X1 .
Argument Y1 .
Argument X2 .
Argument Y2 ."
  (let* ((grid-height (length grid))
         (_grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         (dx (- x2 x1))
         (dy (- y2 y1)))    (cond
     ;; Pure horizontal line
     ((= dy 0)
      (let ((start-x (min x1 x2))
            (end-x (max x1 x2)))
        (dotimes (i (1+ (- end-x start-x)))
          (let ((x (+ start-x i)))
            (dag-draw--draw-char grid x y1 ?─)))))     ;; Pure vertical line
     ((= dx 0)
      (let ((start-y (min y1 y2))
            (end-y (max y1 y2)))
        (dotimes (i (1+ (- end-y start-y)))
          (let ((y (+ start-y i)))
            (dag-draw--draw-char grid x1 y ?│)))))     ;; L-shaped path: horizontal first, then vertical
     (t
      ;; Horizontal segment: x1 to x2 at y1
      (let ((start-x (min x1 x2))
            (end-x (max x1 x2)))
        (dotimes (i (1+ (- end-x start-x)))
          (let ((x (+ start-x i)))
            (dag-draw--draw-char grid x y1 ?─))))      ;; Vertical segment: y1 to y2 at x2
      (let ((start-y (min y1 y2))
            (end-y (max y1 y2)))
        (dotimes (i (1+ (- end-y start-y)))
          (let ((y (+ start-y i)))
            (dag-draw--draw-char grid x2 y ?│))))      ;; Corner character at junction
      (let ((corner-char (cond
                          ((and (< x1 x2) (< y1 y2)) ?┐) ; Right then down
                          ((and (< x1 x2) (> y1 y2)) ?┘) ; Right then up
                          ((and (> x1 x2) (< y1 y2)) ?┌) ; Left then down
                          ((and (> x1 x2) (> y1 y2)) ?└) ; Left then up
                          (t ?┼)))) ; Fallback
        (dag-draw--draw-char grid x2 y1 corner-char))))));;; Boundary Port Calculation

(provide 'dag-draw-ascii-edges) ;;; dag-draw-ascii-edges.el ends here

(provide 'dag-draw-ascii-edges)

;;; dag-draw-ascii-edges.el ends here
