;;; dag-draw-render-gknv-compliant.el --- GKNV-compliant 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:

;; This file implements proper GKNV-compliant ASCII rendering that respects
;; the 4-pass algorithm and doesn't violate coordinate system authority.

;;; Code:

(require 'dag-draw-core)
(require 'dag-draw-ascii-grid)
(require 'dag-draw-ascii-nodes)
(require 'dag-draw-ascii-edges)

(defun dag-draw--convert-gknv-to-ascii-grid (graph)
  "Pure conversion of GKNV final coordinates to ASCII grid.

GRAPH is a `dag-draw-graph' structure with positioned nodes and spline points.

Performs coordinate conversion only - does NOT modify graph coordinates or
regenerate splines.  Calculates grid size, creates grid, draws nodes and edges
using GKNV final coordinates, and converts to string.

Returns a string containing the ASCII representation."

  (let* (;; Step 1: Calculate conversion parameters from GKNV final coordinates
         (bounds (dag-draw-get-graph-bounds graph))
         (min-x (nth 0 bounds))
         (min-y (nth 1 bounds))
         (max-x (nth 2 bounds))
         (max-y (nth 3 bounds))
         (scale (dag-draw--estimate-ascii-scale graph))

         ;; Step 2: Calculate grid size needed for GKNV coordinates
         (width (- max-x min-x))
         (height (- max-y min-y))
         (grid-width (max 20 (+ 10 (ceiling (* width scale)))))
         ;; GKNV AESTHETIC A3: Complex graphs need sufficient space for clarity
         (complexity-min-height (if (> (dag-draw-node-count graph) 2)
                                    (min 50 (+ 20 (* 5 (dag-draw-node-count graph))))
                                  10))
         (grid-height (max complexity-min-height (+ 5 (ceiling (* height scale)))))

         ;; Step 3: Create ASCII grid
         (grid (dag-draw--create-ascii-grid grid-width grid-height)))

    ;; Step 4: Draw nodes using GKNV final coordinates
    (dag-draw--draw-nodes-gknv-compliant graph grid min-x min-y scale)

    ;; Step 5: Draw edges using GKNV final splines
    (dag-draw--draw-edges-gknv-compliant graph grid min-x min-y scale)

    ;; Step 6: Convert grid to string
    (dag-draw--ascii-grid-to-string grid)))

(defun dag-draw--draw-nodes-gknv-compliant (graph grid min-x min-y scale)
  "Draw nodes using GKNV final coordinates without modification.

GRAPH is a `dag-draw-graph' structure.
GRID is a 2D vector representing the ASCII character grid (modified in place).
MIN-X and MIN-Y are numbers representing minimum world coordinates.
SCALE is a float representing the coordinate scale factor.

For each node, converts world coordinates to grid coordinates and draws
a box with the node label."

  (ht-each (lambda (_node-id node)
             (let* (;; Use GKNV final coordinates directly
                    (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))
                    (node-label (dag-draw-node-label 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 grid position (top-left corner)
                    (grid-x (round (- grid-center-x (/ grid-width 2))))
                    (grid-y (round (- grid-center-y (/ grid-height 2)))))


               ;; Draw node box at calculated position
               (dag-draw--draw-node-box grid grid-x grid-y grid-width grid-height node-label)))
           (dag-draw-graph-nodes graph)))

(defun dag-draw--draw-edges-gknv-compliant (graph grid min-x min-y scale)
  "Draw edges using GKNV final splines without regeneration.

GRAPH is a `dag-draw-graph' structure.
GRID is a 2D vector representing the ASCII character grid (modified in place).
MIN-X and MIN-Y are numbers representing minimum world coordinates.
SCALE is a float representing the coordinate scale factor.

For each edge with spline points, converts spline endpoints to grid
coordinates and draws the edge with proper ports."

  (dolist (edge (dag-draw-graph-edges graph))
    (let ((spline-points (dag-draw-edge-spline-points edge)))
      (when spline-points
        (let* ((start-point (car spline-points))
               (end-point (car (last spline-points)))

               ;; Convert GKNV spline endpoints to grid coordinates
               (start-world-x (dag-draw-point-x start-point))
               (start-world-y (dag-draw-point-y start-point))
               (end-world-x (dag-draw-point-x end-point))
               (end-world-y (dag-draw-point-y end-point))

               (start-grid-x (round (dag-draw--world-to-grid-coord start-world-x min-x scale)))
               (start-grid-y (round (dag-draw--world-to-grid-coord start-world-y min-y scale)))
               (end-grid-x (round (dag-draw--world-to-grid-coord end-world-x min-x scale)))
               (end-grid-y (round (dag-draw--world-to-grid-coord end-world-y min-y scale))))


          ;; Draw edge using proper port-based connection
          (dag-draw--draw-edge-with-proper-ports graph edge grid start-grid-x start-grid-y
                                                 end-grid-x end-grid-y min-x min-y scale))))))

(defun dag-draw--draw-edge-with-proper-ports (graph edge grid _start-x _start-y _end-x _end-y min-x min-y scale)
  "Draw EDGE with ports calculated from GKNV node boundaries.

GRAPH is a `dag-draw-graph' structure.
EDGE is a `dag-draw-edge' structure.
GRID is a 2D vector representing the ASCII character grid (modified in place).
START-X, START-Y, END-X, END-Y are integers representing
edge endpoints in grid coordinates.
MIN-X and MIN-Y are numbers representing minimum world coordinates.
SCALE is a float representing the coordinate scale factor.

Calculates proper port positions on node boundaries and draws a simple line
between them."

  (let* ((nodes (dag-draw--edge-nodes graph edge))
         (from-node (car nodes))
         (to-node (cdr nodes))

         ;; Calculate actual node boundaries in grid coordinates
         (from-world-x (dag-draw-node-x-coord from-node))
         (from-world-y (dag-draw-node-y-coord from-node))
         (from-world-width (dag-draw-node-x-size from-node))
         (from-world-height (dag-draw-node-y-size from-node))

         (to-world-x (dag-draw-node-x-coord to-node))
         (to-world-y (dag-draw-node-y-coord to-node))
         (to-world-width (dag-draw-node-x-size to-node))
         (to-world-height (dag-draw-node-y-size to-node))

         ;; Convert to grid boundaries
         (from-grid-center-x (dag-draw--world-to-grid-coord from-world-x min-x scale))
         (from-grid-center-y (dag-draw--world-to-grid-coord from-world-y min-y scale))
         (from-grid-width (dag-draw--world-to-grid-size from-world-width scale))
         (from-grid-height (dag-draw--world-to-grid-size from-world-height scale))

         (to-grid-center-x (dag-draw--world-to-grid-coord to-world-x min-x scale))
         (to-grid-center-y (dag-draw--world-to-grid-coord to-world-y min-y scale))
         (to-grid-width (dag-draw--world-to-grid-size to-world-width scale))
         (to-grid-height (dag-draw--world-to-grid-size to-world-height scale))

         ;; Calculate proper ports on node boundaries
         (from-port (dag-draw--calculate-boundary-port from-grid-center-x from-grid-center-y
                                                       from-grid-width from-grid-height 'bottom))
         (to-port (dag-draw--calculate-boundary-port to-grid-center-x to-grid-center-y
                                                     to-grid-width to-grid-height 'top)))


    ;; Draw simple line between proper ports
    (dag-draw--draw-simple-line grid (nth 0 from-port) (nth 1 from-port)
                                (nth 0 to-port) (nth 1 to-port))))

(defun dag-draw--calculate-boundary-port (center-x center-y width height side)
  "Calculate port position on node boundary for given SIDE.
Argument CENTER-X .
Argument CENTER-Y .
Argument WIDTH .
Argument HEIGHT ."
  (let ((left (round (- center-x (/ width 2))))
        (right (round (+ center-x (/ width 2))))
        (top (round (- center-y (/ height 2))))
        (bottom (round (+ center-y (/ height 2)))))

    (cond
     ((eq side 'top) (list (round center-x) top))
     ((eq side 'bottom) (list (round center-x) bottom))
     ((eq side 'left) (list left (round center-y)))
     ((eq side 'right) (list right (round center-y)))
     (t (list (round center-x) (round center-y))))))

(defun dag-draw--get-box-chars (selected-p)
  "Return box-drawing characters based on SELECTED-P.

If SELECTED-P is non-nil, returns double-line box characters.
Otherwise returns single-line box characters.

Returns a plist with keys:
  :top-left :top-right :bottom-left :bottom-right
  :horizontal :vertical"
  (if selected-p
      (list :top-left ?╔
            :top-right ?╗
            :bottom-left ?╚
            :bottom-right ?╝
            :horizontal ?═
            :vertical ?║)
    (list :top-left ?┌
          :top-right ?┐
          :bottom-left ?└
          :bottom-right ?┘
          :horizontal ?─
          :vertical ?│)))

(defun dag-draw--draw-node-box (grid x y width height label &optional selected-p)
  "Draw a node box with LABEL at specified GRID position.
Returns list of (X . Y) cons cells representing node boundary positions.
Argument WIDTH .
Argument HEIGHT .
Argument SELECTED-P indicates if node should be rendered as selected."
  (let ((grid-height (length grid))
        (grid-width (if (> (length grid) 0) (length (aref grid 0)) 0))
        (boundaries nil)
        (chars (dag-draw--get-box-chars selected-p)))

    ;; Only draw if within grid bounds
    (when (and (>= x 0) (>= y 0)
               (< (+ x width) grid-width)
               (< (+ y height) grid-height))

      ;; Draw top border and record boundary positions
      (dotimes (i width)
        (let ((pos-x (+ x i)))
          (dag-draw--set-char grid pos-x y
                             (cond ((= i 0) (plist-get chars :top-left))
                                   ((= i (1- width)) (plist-get chars :top-right))
                                   (t (plist-get chars :horizontal))))
          (push (cons pos-x y) boundaries)))

      ;; Draw middle rows with label
      (dotimes (row (- height 2))
        (let ((actual-row (+ y row 1)))
          ;; Left border
          (dag-draw--set-char grid x actual-row (plist-get chars :vertical))
          (push (cons x actual-row) boundaries)
          ;; Content area
          (dotimes (col (- width 2))
            (let ((char-pos (+ x col 1)))
              (if (and (= row 0) (< col (length label)))
                  (dag-draw--set-char grid char-pos actual-row (aref label col))
                (dag-draw--set-char grid char-pos actual-row ?\s))))
          ;; Right border
          (dag-draw--set-char grid (+ x width -1) actual-row (plist-get chars :vertical))
          (push (cons (+ x width -1) actual-row) boundaries)))

      ;; Draw bottom border and record boundary positions
      (dotimes (i width)
        (let ((pos-x (+ x i))
              (pos-y (+ y height -1)))
          (dag-draw--set-char grid pos-x pos-y
                             (cond ((= i 0) (plist-get chars :bottom-left))
                                   ((= i (1- width)) (plist-get chars :bottom-right))
                                   (t (plist-get chars :horizontal))))
          (push (cons pos-x pos-y) boundaries))))

    ;; Return list of boundary positions
    boundaries))

(defun dag-draw--draw-simple-line (grid x1 y1 x2 y2)
  "Draw a simple line from (X1,y1) to (x2,y2) with arrow.
Argument GRID ."
  (let ((grid-height (length grid))
        (grid-width (if (> (length grid) 0) (length (aref grid 0)) 0)))

    ;; Draw vertical line first
    (if (not (= y1 y2))
        (let ((start-y (min y1 y2))
              (end-y (max y1 y2)))
          (dotimes (i (1+ (- end-y start-y)))
            (let ((y (+ start-y i)))
              (when (and (>= x1 0) (< x1 grid-width) (>= y 0) (< y grid-height))
                (dag-draw--set-char grid x1 y ?│))))))

    ;; Draw horizontal line
    (if (not (= x1 x2))
        (let ((start-x (min x1 x2))
              (end-x (max x1 x2)))
          (dotimes (i (1+ (- end-x start-x)))
            (let ((x (+ start-x i)))
              (when (and (>= x 0) (< x grid-width) (>= y2 0) (< y2 grid-height))
                (dag-draw--set-char grid x y2 ?─))))))

    ;; Add arrow at end point
    (when (and (>= x2 0) (< x2 grid-width) (>= y2 0) (< y2 grid-height))
      (let ((arrow-char (cond ((> y2 y1) ?▼)  ; Down arrow
                              ((< y2 y1) ?▲)  ; Up arrow
                              ((> x2 x1) ?▶)  ; Right arrow
                              ((< x2 x1) ?◀)  ; Left arrow
                              (t ?●))))       ; Point
        (dag-draw--set-char grid x2 y2 arrow-char)))))

(defun dag-draw--set-char (grid x y char)
  "Safely set character in GRID at position (X,Y).
Argument CHAR ."
  (let ((grid-height (length grid))
        (grid-width (if (> (length grid) 0) (length (aref grid 0)) 0)))
    (when (and (>= x 0) (< x grid-width) (>= y 0) (< y grid-height))
      (aset (aref grid y) x char))))

(provide 'dag-draw-render-gknv-compliant)

;;; dag-draw-render-gknv-compliant.el ends here
