;;; dag-draw-ports.el --- Port calculation logic 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:

;; GKNV Baseline Compliance:
;;
;; This module implements node port support as described in GKNV Section 4.2.
;; Ports allow edge endpoints to be offset in the X direction from node center.
;;
;; GKNV Reference: Section 4.2 (lines 1467-1488), Figure 4-3, Figure 4-4
;; Decision: D3.5 - X-offset node ports
;; Algorithm: Port calculation with δ adjustment
;;
;; Key Requirements:
;; - Ports specified as X-offsets from node center
;; - Auxiliary graph adjusts δ values for port positions
;; - Supports both tail and head port specifications
;; - Figure 4-4 shows δ calculation for ports
;;
;; Baseline Status: ✅ Compliant
;;
;; GKNV Section 4.2 states: "Using the auxiliary graph also permits the specification
;; of 'node ports,' or edge endpoints offset in the X direction from the center
;; of the node."
;;
;; See doc/implementation-decisions.md (D3.5) for decision rationale.

;;; Code:

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

;; dag-draw-point struct is now in dag-draw.el
;; Forward declaration for function in pass4-splines
(declare-function dag-draw--get-node-port "dag-draw-pass4-splines")

;;; Node Port Integration Functions

(defun dag-draw--get-node-port-grid (node side min-x min-y scale &optional graph)
  "Calculate grid coordinates for NODE port on SIDE with grid rounding.

NODE is a `dag-draw-node' structure with position and size data.
SIDE is a symbol: top, bottom, left, or right.
MIN-X is a number representing the minimum X coordinate in world space.
MIN-Y is a number representing the minimum Y coordinate in world space.
SCALE is a number for converting world coordinates to grid coordinates.
GRAPH is an optional `dag-draw-graph' containing adjusted positions.

Calculates ports based on actual grid positions after coordinate conversion
and rounding, ensuring precise alignment with rendered boxes.

Returns a `dag-draw-point' structure with grid coordinates (x, y)."
  (let* ((node-id (dag-draw-node-id node))
         ;; GKNV Pass 3 Authority: Only use algorithm-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))
         ;; Always prioritize adjusted coordinates when available
         ;; The adjusted-positions contain the final collision-resolved positions
         (adjusted-positions-table (and graph (dag-draw-graph-adjusted-positions graph)))
         (adjusted-coords (and adjusted-positions-table
                               (ht-get adjusted-positions-table node-id)))
         (x (if adjusted-coords
                ;; Adjusted coordinates are already in grid space: (x y width height)
                (float (nth 0 adjusted-coords))
              ;; Convert GKNV Pass 3 world coordinates to grid coordinates
              (dag-draw--world-to-grid-coord (or gknv-x 0) min-x scale)))
         (y (if adjusted-coords
                (float (nth 1 adjusted-coords))
              ;; Convert GKNV Pass 3 world coordinates to grid coordinates
              (dag-draw--world-to-grid-coord (or gknv-y 0) min-y scale)))
         (width (if adjusted-coords
                    (float (nth 2 adjusted-coords))
                  (dag-draw--world-to-grid-size (dag-draw-node-x-size node) scale)))
         (height (if adjusted-coords
                     (float (nth 3 adjusted-coords))
                   (dag-draw--world-to-grid-size (dag-draw-node-y-size node) scale)))
         ;; Both paths now use converted coordinates
         (grid-center-x (+ x (/ width 2.0)))
         (grid-center-y (+ y (/ height 2.0)))
         (grid-width width)
         (grid-height height)
         ;; Use unified coordinate system consistently
         (grid-x (round (- grid-center-x (/ grid-width 2))))
         (grid-y (round (- grid-center-y (/ grid-height 2))))
         (grid-x-end (+ grid-x grid-width -1))
         (grid-y-end (+ grid-y grid-height -1))
         ;; Ensure all coordinates are integer-quantized
         (actual-center-x (round grid-center-x))
         (actual-center-y (round grid-center-y)))

    ;; Port calculation using coordinate priority: adjusted > manual > default

    ;; Return grid coordinates directly to avoid double conversion
    (let ((result-port
           (cond
            ((eq side 'top)
             (dag-draw-point-create :x actual-center-x :y grid-y))  ; ON top boundary
            ((eq side 'bottom)
             (dag-draw-point-create :x actual-center-x :y grid-y-end))  ; ON bottom boundary
            ((eq side 'left)
             (dag-draw-point-create :x grid-x :y actual-center-y))  ; ON left boundary
            ((eq side 'right)
             (dag-draw-point-create :x grid-x-end :y actual-center-y))  ; ON right boundary
            (t
             (dag-draw-point-create :x actual-center-x :y actual-center-y)))))

      ;; Port calculation complete

      result-port)))

(defun dag-draw--determine-port-side (node port min-x min-y scale &optional graph)
  "Determine which side of NODE the PORT is located on.

NODE is a `dag-draw-node' structure.
PORT is a `dag-draw-point' representing the port position.
MIN-X is a number representing the minimum X coordinate in world space.
MIN-Y is a number representing the minimum Y coordinate in world space.
SCALE is a number for converting world coordinates to grid coordinates.
GRAPH is an optional `dag-draw-graph' containing adjusted positions.

Helps identify port orientation for proper connection logic.

Returns a symbol: top, bottom, left, right, or center."
  (let* ((node-center (dag-draw--get-node-center-grid node min-x min-y scale graph))
         (center-x (dag-draw-point-x node-center))
         (center-y (dag-draw-point-y node-center))
         (port-x (dag-draw-point-x port))
         (port-y (dag-draw-point-y port))
         (dx (- port-x center-x))
         (dy (- port-y center-y)))

    ;; Determine which side based on the largest directional difference
    (cond
     ((and (>= (abs dy) (abs dx)) (< dy 0)) 'top)
     ((and (>= (abs dy) (abs dx)) (> dy 0)) 'bottom)
     ((and (> (abs dx) (abs dy)) (< dx 0)) 'left)
     ((and (> (abs dx) (abs dy)) (> dx 0)) 'right)
     (t 'center)))) ; Fallback for center ports

(defun dag-draw--calculate-edge-ports (from-node to-node &optional graph edge)
  "Calculate appropriate ports for edge between FROM-NODE and TO-NODE.

FROM-NODE is a `dag-draw-node' representing the edge source.
TO-NODE is a `dag-draw-node' representing the edge destination.
GRAPH is an optional `dag-draw-graph' for distributed port calculation.
EDGE is an optional `dag-draw-edge' for port distribution.

Selects ports based on edge direction (vertical, horizontal, or diagonal).
Uses adaptive thresholds based on node sizes.

Returns list (from-port to-port) of `dag-draw-point' structures, or nil
if either node lacks coordinates."
  (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)))

    ;; Return nil if any coordinate is missing - prevents arithmetic errors
    (when (and from-x from-y to-x to-y)
      (let* ((dx (- to-x from-x))
             (dy (- to-y from-y))
             ;; Calculate node-size-aware thresholds instead of hard-coded 20
             (horizontal-threshold (/ (+ (dag-draw-node-x-size from-node) (dag-draw-node-x-size to-node)) 4.0))
             (vertical-threshold (/ (+ (dag-draw-node-y-size from-node) (dag-draw-node-y-size to-node)) 4.0)))

        ;; Determine primary direction using adaptive thresholds
        (cond
         ;; Vertical edge (down)
         ((and (< (abs dx) horizontal-threshold) (> dy 0))
          (list (dag-draw--get-node-port from-node 'bottom graph edge)
                (dag-draw--get-node-port to-node 'top graph edge)))
         ;; Vertical edge (up)
         ((and (< (abs dx) horizontal-threshold) (< dy 0))
          (list (dag-draw--get-node-port from-node 'top graph edge)
                (dag-draw--get-node-port to-node 'bottom graph edge)))
         ;; Horizontal edge (right)
         ((and (< (abs dy) vertical-threshold) (> dx 0))
          (list (dag-draw--get-node-port from-node 'right graph edge)
                (dag-draw--get-node-port to-node 'left graph edge)))
         ;; Horizontal edge (left)
         ((and (< (abs dy) vertical-threshold) (< dx 0))
          (list (dag-draw--get-node-port from-node 'left graph edge)
                (dag-draw--get-node-port to-node 'right graph edge)))
         ;; Diagonal edge - prefer vertical direction (including equal distances)
         ((>= (abs dy) (abs dx))
          (if (> dy 0)
              (list (dag-draw--get-node-port from-node 'bottom graph edge)
                    (dag-draw--get-node-port to-node 'top graph edge))
            (list (dag-draw--get-node-port from-node 'top graph edge)
                  (dag-draw--get-node-port to-node 'bottom graph edge))))
         ;; Diagonal edge - prefer horizontal direction
         (t
          (if (> dx 0)
              (list (dag-draw--get-node-port from-node 'right graph edge)
                    (dag-draw--get-node-port to-node 'left graph edge))
            (list (dag-draw--get-node-port from-node 'left graph edge)
                  (dag-draw--get-node-port to-node 'right graph edge)))))))))

(defun dag-draw--calculate-edge-ports-grid (from-node to-node min-x min-y scale &optional graph)
  "Calculate edge ports using grid coordinates.

FROM-NODE is a `dag-draw-node' representing the edge source.
TO-NODE is a `dag-draw-node' representing the edge destination.
MIN-X is a number representing the minimum X coordinate in world space.
MIN-Y is a number representing the minimum Y coordinate in world space.
SCALE is a number for converting world coordinates to grid coordinates.
GRAPH is an optional `dag-draw-graph' containing adjusted positions.

Uses grid-aware positioning for precise ASCII rendering alignment.

Returns list (from-port to-port) of `dag-draw-point' structures in grid space."
  (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)))

    ;; Return nil if any coordinate is missing - prevents arithmetic errors
    (when (and from-x from-y to-x to-y)
      (let* ((dx (- to-x from-x))
             (dy (- to-y from-y))
             ;; Calculate node-size-aware thresholds instead of hard-coded 20
             (horizontal-threshold (/ (+ (dag-draw-node-x-size from-node) (dag-draw-node-x-size to-node)) 4.0))
             (vertical-threshold (/ (+ (dag-draw-node-y-size from-node) (dag-draw-node-y-size to-node)) 4.0)))

        ;; Determine primary direction using adaptive thresholds (same logic as original)
        (cond
         ;; Vertical edge (down)
         ((and (< (abs dx) horizontal-threshold) (> dy 0))
          (list (dag-draw--get-node-port-grid from-node 'bottom min-x min-y scale graph)
                (dag-draw--get-node-port-grid to-node 'top min-x min-y scale graph)))
         ;; Vertical edge (up)
         ((and (< (abs dx) horizontal-threshold) (< dy 0))
          (list (dag-draw--get-node-port-grid from-node 'top min-x min-y scale graph)
                (dag-draw--get-node-port-grid to-node 'bottom min-x min-y scale graph)))
         ;; Horizontal edge (right)
         ((and (< (abs dy) vertical-threshold) (> dx 0))
          (let ((from-port (dag-draw--get-node-port-grid from-node 'right min-x min-y scale graph))
                (to-port (dag-draw--get-node-port-grid to-node 'left min-x min-y scale graph)))
            (list from-port to-port)))
         ;; Horizontal edge (left)
         ((and (< (abs dy) vertical-threshold) (< dx 0))
          (list (dag-draw--get-node-port-grid from-node 'left min-x min-y scale graph)
                (dag-draw--get-node-port-grid to-node 'right min-x min-y scale graph)))
         ;; Diagonal edge - prefer vertical direction (including equal distances)
         ((>= (abs dy) (abs dx))
          (if (> dy 0)
              (list (dag-draw--get-node-port-grid from-node 'bottom min-x min-y scale graph)
                    (dag-draw--get-node-port-grid to-node 'top min-x min-y scale graph))
            (list (dag-draw--get-node-port-grid from-node 'top min-x min-y scale graph)
                  (dag-draw--get-node-port-grid to-node 'bottom min-x min-y scale graph))))
         ;; Diagonal edge - prefer horizontal direction
         (t
          (if (> dx 0)
              (list (dag-draw--get-node-port-grid from-node 'right min-x min-y scale graph)
                    (dag-draw--get-node-port-grid to-node 'left min-x min-y scale graph))
            (list (dag-draw--get-node-port-grid from-node 'left min-x min-y scale graph)
                  (dag-draw--get-node-port-grid to-node 'right min-x min-y scale graph)))))))))

(defun dag-draw--calculate-distributed-edge-ports (graph _edge from-node to-node min-x min-y scale)
  "Calculate edge ports using GKNV-compliant direction-based approach.

GRAPH is a `dag-draw-graph' structure.
EDGE is a `dag-draw-edge' being routed.
FROM-NODE is a `dag-draw-node' representing the edge source.
TO-NODE is a `dag-draw-node' representing the edge destination.
MIN-X is a number representing the minimum X coordinate in world space.
MIN-Y is a number representing the minimum Y coordinate in world space.
SCALE is a number for converting world coordinates to grid coordinates.

Aligns with GKNV Section 5.1.1: route to appropriate side.
Simplified approach without complex multi-edge distribution.

Returns list (from-port to-port) of `dag-draw-point' structures."
  ;; Simplified: always use standard grid-based port calculation
  ;; This aligns with GKNV Section 5.1.1: \"route to appropriate side\"
  (dag-draw--calculate-edge-ports-grid from-node to-node min-x min-y scale graph))



(defun dag-draw--get-edge-connection-points (graph edge &optional min-x min-y scale)
  "Get connection points for EDGE in ASCII rendering context.

GRAPH is a `dag-draw-graph' structure containing nodes and edges.
EDGE is a `dag-draw-edge' for which to calculate connection points.
MIN-X is an optional number for minimum X coordinate in world space.
MIN-Y is an optional number for minimum Y coordinate in world space.
SCALE is an optional number for world-to-grid coordinate conversion.

If grid parameters (MIN-X, MIN-Y, SCALE) are provided, uses grid-aware
port calculation for precise ASCII alignment.

Returns list (from-port to-port) of `dag-draw-point' structures."
  (let* ((nodes (dag-draw--edge-nodes graph edge))
         (from-node (car nodes))
         (to-node (cdr nodes))
         (result (if (and min-x min-y scale)
                     ;; Use simplified GKNV-compliant port calculation
                     (dag-draw--calculate-distributed-edge-ports graph edge from-node to-node min-x min-y scale)
                   (dag-draw--calculate-edge-ports from-node to-node graph edge))))
    result))

(provide 'dag-draw-ports)

;;; dag-draw-ports.el ends here
