;;; dag-draw-ascii-junctions.el --- Junction character enhancement for ASCII rendering -*- 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:

;; Junction Character Enhancement - ASCII Rendering Context
;;
;; BOUNDED CONTEXT: ASCII Rendering Context
;; UPSTREAM CONTEXT: GKNV Algorithm Context (via Coordinate Transform Layer)
;; LAYER: Visual Enhancement
;; AUTHORITY: CLAUDE.md "Implementation Principles: Junction characters for
;;            ASCII graphs"
;;
;; This module implements junction character enhancement for ASCII graph
;; rendering, as specified in CLAUDE.md "Implementation Principles: Junction
;; characters for ASCII graphs".
;;
;; Junction characters are CRITICAL for ASCII visual quality.  They transform basic
;; edge routing (─ │) into visually polished diagrams with proper semigraphics
;; at connection points (┬ ┴ ├ ┤ ┌ ┐ └ ┘ ┼).
;;
;; Five Junction Types (CLAUDE.md):
;; 1. Port Start/End - at node boundaries where edges begin/end
;; 2. Direction Changes - corners where edges turn
;; 3. Edge Joins - T-junctions where edges merge
;; 4. Edge Splits - T-junctions where edges separate
;; 5. Edge Crossings - where two edges cross (┼)
;;
;; Algorithm: Walk-based local context analysis
;; - Walk through grid positions
;; - Analyze connectivity in all 4 directions
;; - Determine junction type based on connection pattern
;; - Select appropriate semigraphic character
;;
;; Bounded Context: ASCII Rendering Context
;; - Input: Grid with basic edge characters (─ │)
;; - Output: Grid with enhanced junction characters
;; - Authority: CLAUDE.md junction specifications
;;
;; See CLAUDE.md section "Junction characters for ASCII graphs" for full
;; specifications.

;;; Code:

(require 'dag-draw-core)

;; Module created as part of Phase 1: DDD Refactoring
;; This file will contain all junction character logic extracted from dag-draw-ascii-grid.el

;;; Junction Character Selection

(defun dag-draw--get-enhanced-junction-char (context)
  "Determine the appropriate junction character based on CONTEXT.

CONTEXT is a plist containing junction information per CLAUDE.md specifications.
Expected keys include:
  `:type' - Symbol: junction type (port-start, port-end, direction-change, etc.)
  `:direction' - Symbol: directional information (up, down, left, right)
  `:from-direction' and `:to-direction' - Symbols: for direction changes
  `:main-direction' and `:branch-direction' - Symbols: for T-junctions

Returns the Unicode box-drawing character appropriate for the junction type,
or nil to keep the current character."
  (let ((junction-type (plist-get context :type)))
    (cond
     ;; Direction change junctions (CLAUDE.md: \"When edge requires direction change\")
     ((eq junction-type 'direction-change)
      (let ((from-dir (plist-get context :from-direction))
            (to-dir (plist-get context :to-direction)))
        (cond
         ((and (eq from-dir 'right) (eq to-dir 'down)) ?┐)  ; right→down corner
         ((and (eq from-dir 'left) (eq to-dir 'down)) ?┌)   ; left→down corner
         ((and (eq from-dir 'right) (eq to-dir 'up)) ?┘)    ; right→up corner
         ((and (eq from-dir 'left) (eq to-dir 'up)) ?└))))

     ;; Edge crossing junctions (CLAUDE.md: \"When two edges cross\")
     ((eq junction-type 'edge-cross) ?┼)  ; always use cross character

     ;; T-junction scenarios (CLAUDE.md example)
     ((eq junction-type 't-junction)
      (let ((main-dir (plist-get context :main-direction))
            (branch-dir (plist-get context :branch-direction)))
        (cond
         ((and (eq main-dir 'down) (eq branch-dir 'right)) ?├)   ; down + right branch
         ((and (eq main-dir 'down) (eq branch-dir 'left)) ?┤)    ; down + left branch
         ((and (eq main-dir 'right) (eq branch-dir 'down)) ?┬)   ; right + down branch
         ((and (eq main-dir 'right) (eq branch-dir 'up)) ?┴))))

     ;; Straight line: no junction needed, return nil to keep current character
     ((eq junction-type 'straight-line) nil)

     ;; Unknown/no junction: keep current character
     ((eq junction-type 'unknown) nil)

     ;; Fallback for any other unknown types
     (t nil))))

;;; Edge Analysis for Junction Detection

(defun dag-draw--detect-port-junctions (graph)
  "Detect junction points at node port boundaries in GRAPH.
CLAUDE.md: `At the start of the edge, at the port boundary'
Returns a list of port junction specifications."
  (when graph
    (let ((port-junctions '()))
      ;; Walk through all edges to find port boundary points
      (dolist (edge (dag-draw-graph-edges graph))
        (let* ((nodes (dag-draw--edge-nodes graph edge))
               (from-node (car nodes))
               (to-node (cdr nodes)))

          ;; Check if nodes have coordinates (positioned after GKNV layout)
          (when (and from-node to-node
                     (dag-draw-node-x-coord from-node) (dag-draw-node-y-coord from-node)
                     (dag-draw-node-x-coord to-node) (dag-draw-node-y-coord to-node))

            ;; Determine edge direction from source to target
            (let* ((from-x (dag-draw-node-x-coord from-node))
                   (from-y (dag-draw-node-y-coord from-node))
                   (to-x (dag-draw-node-x-coord to-node))
                   (to-y (dag-draw-node-y-coord to-node))
                   (direction (dag-draw--determine-edge-direction from-x from-y to-x to-y)))

              ;; Add port start junction (at source node boundary)
              (push (list :type 'port-start
                         :node (dag-draw-edge-from-node edge)
                         :x from-x :y from-y
                         :direction direction
                         :edge edge)
                    port-junctions)

              ;; Add port end junction (at target node boundary)
              (push (list :type 'port-end
                         :node (dag-draw-edge-to-node edge)
                         :x to-x :y to-y
                         :direction (dag-draw--reverse-direction direction)
                         :edge edge)
                    port-junctions)))))

      port-junctions)))

(defun dag-draw--determine-edge-direction (from-x from-y to-x to-y)
  "Determine primary direction of edge from (FROM-X,FROM-Y) to (TO-X,TO-Y).
Returns one of: `up', `down', `left', `right' based on dominant
coordinate change."
  (let ((dx (- to-x from-x))
        (dy (- to-y from-y)))
    (cond
     ;; Vertical movement dominates
     ((> (abs dy) (abs dx))
      (if (> dy 0) 'down 'up))
     ;; Horizontal movement dominates
     ((> (abs dx) (abs dy))
      (if (> dx 0) 'right 'left))
     ;; Equal movement - choose based on positive direction preference
     ((and (> dx 0) (> dy 0)) 'down)  ; Southeast
     ((and (< dx 0) (> dy 0)) 'down)  ; Southwest
     ((and (> dx 0) (< dy 0)) 'up)    ; Northeast
     ((and (< dx 0) (< dy 0)) 'up)    ; Northwest
     ;; Fallback
     (t 'down))))

(defun dag-draw--reverse-direction (direction)
  "Return the opposite direction of DIRECTION."
  (cond
   ((eq direction 'up) 'down)
   ((eq direction 'down) 'up)
   ((eq direction 'left) 'right)
   ((eq direction 'right) 'left)
   (t direction)))

(defun dag-draw--detect-direction-changes-in-path (edge-path)
  "Detect direction change (corners) in EDGE-PATH.
EDGE-PATH is a list of cons cells (x . y) representing the path.
Returns a list of corner specifications with position and directions.
CLAUDE.md: `When the edge requires a direction change'"
  (let ((corners '()))
    (when (>= (length edge-path) 3)
      ;; Walk through path, checking each point with its neighbors
      (let ((i 1))  ; Start at second point
        (while (< i (- (length edge-path) 1))
          (let* ((prev (nth (- i 1) edge-path))
                 (curr (nth i edge-path))
                 (next (nth (+ i 1) edge-path))
                 ;; Calculate direction vectors
                 (dx-in (- (car curr) (car prev)))
                 (dy-in (- (cdr curr) (cdr prev)))
                 (dx-out (- (car next) (car curr)))
                 (dy-out (- (cdr next) (cdr curr))))
            ;; Check if this is a direction change
            ;; Direction change occurs when we go from H to V or V to H
            (when (dag-draw--is-direction-change dx-in dy-in dx-out dy-out)
              (push (list :type 'direction-change
                         :x (car curr)
                         :y (cdr curr)
                         :from-direction (dag-draw--get-direction dx-in dy-in)
                         :to-direction (dag-draw--get-direction dx-out dy-out))
                    corners)))
          (setq i (1+ i)))))
    (nreverse corners)))

(defun dag-draw--is-direction-change (dx-in dy-in dx-out dy-out)
  "Check if the direction change occurs between incoming and outgoing vectors.
DX-IN, DY-IN: incoming direction vector.
DX-OUT, DY-OUT: outgoing direction vector.
Returns t if direction changes from horizontal to vertical or vice versa."
  (let ((is-h-in (and (not (zerop dx-in)) (zerop dy-in)))   ; Horizontal in
        (is-v-in (and (zerop dx-in) (not (zerop dy-in))))   ; Vertical in
        (is-h-out (and (not (zerop dx-out)) (zerop dy-out))) ; Horizontal out
        (is-v-out (and (zerop dx-out) (not (zerop dy-out)))) ; Vertical out
        )
    ;; Direction change if: H->V or V->H
    (or (and is-h-in is-v-out)
        (and is-v-in is-h-out))))

(defun dag-draw--get-direction (dx dy)
  "Get direction symbol from direction vector (DX, DY).
Returns one of: `up', `down', `left', `right'."
  (cond
   ((and (> dx 0) (zerop dy)) 'right)
   ((and (< dx 0) (zerop dy)) 'left)
   ((and (zerop dx) (> dy 0)) 'down)
   ((and (zerop dx) (< dy 0)) 'up)
   ;; Diagonal or zero - should not happen in orthogonal paths
   (t 'unknown)))

(defun dag-draw--detect-crossings-in-paths (edge-paths)
  "Detect crossing points between multiple EDGE-PATHS.
EDGE-PATHS is a list of edge paths, where each path is a list of
cons cells (x . y).
Returns a list of crossing specifications.
CLAUDE.md: `When two edges cross'"
  (let ((crossings '())
        (position-map (make-hash-table :test 'equal)))

    ;; Build a map of positions to edges that pass through them
    (let ((edge-index 0))
      (dolist (path edge-paths)
        (dolist (point path)
          (let* ((key (cons (car point) (cdr point)))
                 (current-list (gethash key position-map '())))
            (puthash key (cons edge-index current-list) position-map)))
        (setq edge-index (1+ edge-index))))

    ;; Find positions where multiple edges meet
    (maphash (lambda (pos edge-indices)
               (when (>= (length edge-indices) 2)
                 ;; Check if edges actually cross (not just touch)
                 ;; Two edges cross if they pass through same point from different directions
                 (let ((edge-dirs (dag-draw--get-edge-directions-at-point
                                  pos edge-paths edge-indices)))
                   (when (dag-draw--are-edges-crossing edge-dirs)
                     (push (list :type 'edge-cross
                                :x (car pos)
                                :y (cdr pos)
                                :edge-indices edge-indices)
                           crossings)))))
             position-map)

    (nreverse crossings)))

(defun dag-draw--get-edge-directions-at-point (pos edge-paths edge-indices)
  "Get the directions of edges at position POS.
POS is a cons cell (x . y).
EDGE-PATHS is the list of all edge paths.
EDGE-INDICES is the list of edge indices that pass through POS.
Returns a list of direction symbols for each edge."
  (let ((directions '()))
    (dolist (edge-idx edge-indices)
      (let* ((path (nth edge-idx edge-paths))
             ;; Find the point in the path
             (point-idx (dag-draw--find-point-index-in-path pos path)))
        (when point-idx
          ;; Determine direction at this point by looking at neighbors
          (let* ((prev (when (> point-idx 0) (nth (- point-idx 1) path)))
                 (next (when (< point-idx (- (length path) 1))
                        (nth (+ point-idx 1) path)))
                 (dir (cond
                      ;; Check incoming direction
                      ((and prev
                            (= (cdr prev) (cdr pos))
                            (/= (car prev) (car pos)))
                       'horizontal)
                      ((and prev
                            (= (car prev) (car pos))
                            (/= (cdr prev) (cdr pos)))
                       'vertical)
                      ;; Check outgoing direction
                      ((and next
                            (= (cdr next) (cdr pos))
                            (/= (car next) (car pos)))
                       'horizontal)
                      ((and next
                            (= (car next) (car pos))
                            (/= (cdr next) (cdr pos)))
                       'vertical)
                      (t 'unknown))))
            (push dir directions)))))
    (nreverse directions)))

(defun dag-draw--find-point-index-in-path (pos path)
  "Find the index of POS in PATH.
POS is a cons cell (x . y).
PATH is a list of cons cells.
Returns the index or nil if not found."
  (let ((idx 0)
        (found nil))
    (dolist (point path)
      (when (and (= (car point) (car pos))
                 (= (cdr point) (cdr pos)))
        (setq found idx))
      (setq idx (1+ idx)))
    found))

(defun dag-draw--are-edges-crossing (edge-dirs)
  "Check if edges are truly crossing based on their directions.
EDGE-DIRS is a list of direction symbols.
Two edges cross if they have different directions (horizontal vs vertical)."
  (let ((has-h nil)
        (has-v nil))
    (dolist (dir edge-dirs)
      (when (eq dir 'horizontal) (setq has-h t))
      (when (eq dir 'vertical) (setq has-v t)))
    (and has-h has-v)))

(defun dag-draw--detect-joins-in-paths (edge-paths)
  "Detect join points where edges merge or split.
EDGE-PATHS is a list of edge paths, where each path is a list of
cons cells (x . y).
Returns a list of join/split junction specifications.
CLAUDE.md: `When two edges join, or two edges separate'"
  (let ((joins '())
        (position-map (make-hash-table :test 'equal)))

    ;; Build a map of positions to edges that pass through them
    (let ((edge-index 0))
      (dolist (path edge-paths)
        (dolist (point path)
          (let* ((key (cons (car point) (cdr point)))
                 (current-list (gethash key position-map '())))
            (puthash key (cons edge-index current-list) position-map)))
        (setq edge-index (1+ edge-index))))

    ;; Find positions where multiple edges meet but don't cross
    ;; (i.e., they join or split - T-junctions)
    (maphash (lambda (pos edge-indices)
               (when (>= (length edge-indices) 2)
                 ;; Check if this is a join/split (T-junction) not a crossing
                 (let ((edge-dirs (dag-draw--get-edge-directions-at-point
                                  pos edge-paths edge-indices)))
                   ;; A join/split occurs when edges meet but don't cross
                   ;; This includes T-junctions where 3+ edges meet
                   (when (not (dag-draw--are-edges-crossing edge-dirs))
                     (push (list :type 'edge-join
                                :x (car pos)
                                :y (cdr pos)
                                :edge-indices edge-indices
                                :directions edge-dirs)
                           joins)))))
             position-map)

    (nreverse joins)))

;;; Local Grid Context Analysis

(defun dag-draw--analyze-local-grid-junction-context (grid x y _current-char new-char)
  "Analyze grid context at position (X,Y) to determine junction type.

GRID is a 2D vector representing the ASCII character grid.
X and Y are integers representing grid coordinates.
CURRENT-CHAR is the character already at the position (or space).
NEW-CHAR is the character being drawn.

This implements the D5.6-D5.8 context analysis requirements:
- Check for adjacent edges in all 4 directions
- Determine junction type based on connectivity
- Build proper context plist for character selection

Returns a context plist suitable for `dag-draw--get-enhanced-junction-char'."
  (let* ((grid-height (length grid))
         (_grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         ;; Check all 4 directions for edge characters
         (has-up (dag-draw--has-edge-in-direction grid x y 'up))
         (has-down (dag-draw--has-edge-in-direction grid x y 'down))
         (has-left (dag-draw--has-edge-in-direction grid x y 'left))
         (has-right (dag-draw--has-edge-in-direction grid x y 'right))
         ;; Count total connections
         (connection-count (+ (if has-up 1 0) (if has-down 1 0)
                             (if has-left 1 0) (if has-right 1 0)))
         ;; Determine if new character indicates direction
         (_new-is-horizontal (memq new-char '(?─)))
         (_new-is-vertical (memq new-char '(?│))))

    ;; Build context plist based on connectivity pattern
    (cond
     ;; Direction change: 2 connections in perpendicular directions
     ((and (= connection-count 2)
           (or (and has-left has-down)
               (and has-right has-down)
               (and has-left has-up)
               (and has-right has-up)))
      ;; For corners, determine from/to based on which two directions have edges
      ;; The character maps to the turn direction regardless of edge flow direction
      (let ((from-dir (cond
                       ;; Horizontal incoming
                       (has-left 'right)    ; Edge from left means traveling right
                       (has-right 'left)    ; Edge from right means traveling left
                       ;; Vertical incoming
                       (has-up 'down)       ; Edge from above means traveling down
                       (has-down 'up)))     ; Edge from below means traveling up
            (to-dir (cond
                     ;; Vertical outgoing
                     (has-down 'down)
                     (has-up 'up)
                     ;; Horizontal outgoing
                     (has-right 'right)
                     (has-left 'left))))
        (list :type 'direction-change
              :from-direction from-dir
              :to-direction to-dir
              :has-up has-up
              :has-down has-down
              :has-left has-left
              :has-right has-right)))

     ;; T-junction: 3 connections
     ((= connection-count 3)
      (list :type 't-junction
            :main-direction (cond ((and has-up has-down) 'down)
                                 ((and has-left has-right) 'right)
                                 (t 'down))
            ;; Branch is the direction that's NOT part of the straight line
            :branch-direction (cond
                              ;; Vertical line (up-down), branch is horizontal
                              ((and has-up has-down)
                               (cond (has-left 'left)
                                     (has-right 'right)))
                              ;; Horizontal line (left-right), branch is vertical
                              ((and has-left has-right)
                               (cond (has-up 'up)
                                     (has-down 'down)))
                              ;; Shouldn't reach here if t-junction detection is correct
                              (t 'right))
            :has-up has-up
            :has-down has-down
            :has-left has-left
            :has-right has-right))

     ;; Cross: 4 connections
     ((= connection-count 4)
      (list :type 'edge-cross
            :has-up has-up
            :has-down has-down
            :has-left has-left
            :has-right has-right))

     ;; Straight line: 2 connections in same direction (not perpendicular)
     ((and (= connection-count 2)
           (or (and has-left has-right)   ; Horizontal line
               (and has-up has-down)))    ; Vertical line
      (list :type 'straight-line
            :has-up has-up
            :has-down has-down
            :has-left has-left
            :has-right has-right))

     ;; Default: return minimal context
     (t
      (list :type 'unknown
            :has-up has-up
            :has-down has-down
            :has-left has-left
            :has-right has-right)))))

(defun dag-draw--has-edge-in-direction (grid x y direction)
  "Check if there's an edge character in DIRECTION from (X,Y) on GRID.

GRID is a 2D vector representing the ASCII character grid.
X and Y are integers representing grid coordinates.
DIRECTION is a symbol: one of `up', `down', `left', or `right'.

Key insight: Direction matters! A horizontal line (─) only connects left/right,
a vertical line (│) only connects up/down.  Only junction characters connect
in multiple directions.  Node border corners are decorations, not connections.

Returns t if an edge character is found AND compatible with DIRECTION,
nil otherwise."
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         (check-x (cond ((eq direction 'left) (- x 1))
                       ((eq direction 'right) (+ x 1))
                       (t x)))
         (check-y (cond ((eq direction 'up) (- y 1))
                       ((eq direction 'down) (+ y 1))
                       (t y)))
         ;; Direction-specific characters
         (vertical-chars (list ?│ ?\u25bc ?\u25b2))      ; │ ▼ ▲ (only connect up/down)
         (horizontal-chars (list ?─ ?\u25ba ?\u25c4))    ; ─ ► ◄ (only connect left/right)
         ;; Junction characters can connect in multiple directions
         (junction-chars (list ?┼ ?├ ?┤ ?┬ ?┴ ?+))
         ;; Note: Node border corners (?┌ ?┐ ?└ ?┘) are NOT included - they're decorations!
         (char-at-pos (if (and (>= check-x 0) (< check-x grid-width)
                              (>= check-y 0) (< check-y grid-height))
                         (aref (aref grid check-y) check-x)
                       nil)))

    ;; Check if character is compatible with the direction
    (cond
     ;; Checking vertical directions (up/down): only vertical chars and junctions
     ((or (eq direction 'up) (eq direction 'down))
      (and char-at-pos
           (or (memq char-at-pos vertical-chars)
               (memq char-at-pos junction-chars))))

     ;; Checking horizontal directions (left/right): only horizontal chars and junctions
     ((or (eq direction 'left) (eq direction 'right))
      (and char-at-pos
           (or (memq char-at-pos horizontal-chars)
               (memq char-at-pos junction-chars))))

     (t nil))))

;;; Junction Character Application (Priority 4: Integration)

(defun dag-draw--apply-junction-chars-to-grid (grid node-boundaries)
  "Apply junction characters throughout GRID, excluding NODE-BOUNDARIES.

GRID is a 2D vector representing the ASCII character grid (modified in place).
NODE-BOUNDARIES is a list of (x . y) cons cells marking node border
positions.

Walks through all grid positions, analyzes connectivity, and updates
characters.  This is the integration point for D5.1-D5.8 junction character
enhancement.  CLAUDE.md: `walks the edge in order to determine the
locally-relevant algorithm'

NODE-BOUNDARIES positions are excluded from junction enhancement to
prevent corruption of node box borders.  Arrow characters
\(▼ ▲ ► ◀) are never replaced."
  (when dag-draw-debug-output
    (message "DEBUG junction: Processing grid with %d boundaries" (length node-boundaries)))
  (let* ((grid-height (length grid))
         (grid-width (if (> grid-height 0) (length (aref grid 0)) 0))
         ;; Include ?+ as it's used as a fallback junction character
         (edge-chars '(?─ ?│ ?┼ ?┌ ?┐ ?└ ?┘ ?├ ?┤ ?┬ ?┴ ?+))
         ;; Arrow characters should NOT be replaced (CLAUDE.md: "the one possible exemption
         ;; is where an arrow is placed")
         (arrow-chars (list ?\u25bc ?\u25b2 ?\u25ba ?\u25c4))) ; ▼ ▲ ► ◄
    ;; Walk through entire grid
    (dotimes (y grid-height)
      (dotimes (x grid-width)
        (let ((current-char (aref (aref grid y) x)))
          ;; Skip if this position is a node boundary
          (unless (member (cons x y) node-boundaries)
            ;; Only process edge/junction characters, but NOT arrows
            (when (and (memq current-char edge-chars)
                      (not (memq current-char arrow-chars)))
              ;; Analyze local context and determine proper junction character
              (let* ((context (dag-draw--analyze-local-grid-junction-context
                             grid x y current-char current-char))
                     (junction-char (dag-draw--get-enhanced-junction-char context)))
                ;; Update grid with enhanced junction character
                (when junction-char
                  (aset (aref grid y) x junction-char))))))))))

(provide 'dag-draw-ascii-junctions)

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