using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using AGVNavigationCore.Models;
using AGVNavigationCore.Utils;
using AGVNavigationCore.PathFinding.Core;
using AGVNavigationCore.PathFinding.Analysis;

namespace AGVNavigationCore.PathFinding.Planning
{
    /// <summary>
    /// AGV 경로 계획기
    /// 물리적 제약사항과 마그넷 센서를 고려한 실제 AGV 경로 생성
    /// </summary>
    public class AGVPathfinder
    {

        private readonly List<MapNode> _mapNodes;
        private readonly AStarPathfinder _basicPathfinder;
        private readonly DirectionalPathfinder _directionPathfinder;
        private readonly JunctionAnalyzer _junctionAnalyzer;
        private readonly DirectionChangePlanner _directionChangePlanner;

        public AGVPathfinder(List<MapNode> mapNodes)
        {
            _mapNodes = mapNodes ?? new List<MapNode>();
            _basicPathfinder = new AStarPathfinder();
            _basicPathfinder.SetMapNodes(_mapNodes);
            _junctionAnalyzer = new JunctionAnalyzer(_mapNodes);
            _directionChangePlanner = new DirectionChangePlanner(_mapNodes);
            _directionPathfinder = new DirectionalPathfinder();
        }

        /// <summary>
        /// 지정한 노드에서 가장 가까운 교차로(3개 이상 연결된 노드)를 찾는다.
        /// </summary>
        /// <param name="startNode">기준이 되는 노드</param>
        /// <returns>가장 가까운 교차로 노드 (또는 null)</returns>
        public MapNode FindNearestJunction(MapNode startNode)
        {
            if (startNode == null || _mapNodes == null || _mapNodes.Count == 0)
                return null;

            // 교차로: 3개 이상의 노드가 연결된 노드
            var junctions = _mapNodes.Where(n =>
                n.IsActive &&
                n.IsNavigationNode() &&
                n.ConnectedNodes != null &&
                n.DisableCross == false &&
                n.ConnectedNodes.Count >= 3 &&
                n.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false &&
                n.Id != startNode.Id
            ).ToList();

            // docking 포인트가 연결된 노드는 제거한다.


            if (junctions.Count == 0)
                return null;

            // 직선 거리 기반으로 가장 가까운 교차로 찾기
            MapNode nearestJunction = null;
            float minDistance = float.MaxValue;

            foreach (var junction in junctions)
            {
                float dx = junction.Position.X - startNode.Position.X;
                float dy = junction.Position.Y - startNode.Position.Y;
                float distance = (float)Math.Sqrt(dx * dx + dy * dy);

                if (distance < minDistance)
                {
                    minDistance = distance;
                    nearestJunction = junction;
                }
            }

            return nearestJunction;
        }

        /// <summary>
        /// 지정한 노드에서 경로상 가장 가까운 교차로를 찾는다.
        /// (최단 경로 내에서 3개 이상 연결된 교차로를 찾음)
        /// </summary>
        /// <param name="startNode">시작 노드</param>
        /// <param name="targetNode">목적지 노드</param>
        /// <returns>경로상의 가장 가까운 교차로 노드 (또는 null)</returns>
        public MapNode FindNearestJunctionOnPath(AGVPathResult pathResult)
        {
            if (pathResult == null || !pathResult.Success || pathResult.Path == null || pathResult.Path.Count == 0)
                return null;

            // 경로상의 모든 노드 중 교차로(3개 이상 연결) 찾기
            var StartNode = pathResult.Path.First();
            foreach (var pathNode in pathResult.Path)
            {
                if (pathNode != null &&
                    pathNode.IsActive &&
                    pathNode.IsNavigationNode() &&
                    pathNode.DisableCross == false &&
                    pathNode.ConnectedNodes != null &&
                    pathNode.ConnectedNodes.Count >= 3 &&
                    pathNode.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false)
                {
                    if (pathNode.Id.Equals(StartNode.Id) == false)
                        return pathNode;
                }
            }

            return null;
        }


        public AGVPathResult FindPathAStar(MapNode startNode, MapNode targetNode)
        {
            // 기본값으로 경로 탐색 (이전 위치 = 현재 위치, 방향 = 전진)
            return _basicPathfinder.FindPathAStar(startNode, targetNode);
        }


        /// <summary>
        /// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
        /// </summary>

        /// <summary>
        /// 단순 경로 찾기 (복잡한 제약조건/방향전환 로직 없이 A* 결과만 반환)
        /// </summary>
        public AGVPathResult FindBasicPath(MapNode startNode, MapNode targetNode, MapNode _prevNode, AgvDirection prevDirection)
        {
            // 1. 입력 검증
            if (startNode == null || targetNode == null)
                return AGVPathResult.CreateFailure("노드 정보 오류", 0, 0);

            // 2. A* 경로 탐색
            var pathResult = _basicPathfinder.FindPathAStar(startNode, targetNode);
            pathResult.PrevNode = _prevNode;
            //이전노드가 새로생서된 노드에 포함되어있다면  방향을 전환해줘야한다.
            if (pathResult.Path.Where(t => t.RfidId == _prevNode.RfidId).Any() == false)
                pathResult.PrevDirection = prevDirection;
            else
            {
                if (prevDirection == AgvDirection.Forward)
                    pathResult.PrevDirection = AgvDirection.Backward;
                else
                    pathResult.PrevDirection = AgvDirection.Forward;
            }



            if (!pathResult.Success)
                return AGVPathResult.CreateFailure(pathResult.Message ?? "경로 없음", 0, 0);

            // 3. 상세 데이터 생성 (갈림길 마그넷 방향 계산 포함)
            // 3. 상세 데이터 생성 (갈림길 마그넷 방향 계산 포함)
            if (pathResult.Path != null && pathResult.Path.Count > 0)
            {
                var detailedPath = new List<NodeMotorInfo>();
                for (int i = 0; i < pathResult.Path.Count; i++)
                {
                    var node = pathResult.Path[i];
                    var nextNode = (i + 1 < pathResult.Path.Count) ? pathResult.Path[i + 1] : null;

                    // 마그넷 방향 계산 (갈림길인 경우)
                    // 마그넷 방향 계산 (갈림길인 경우)
                    MagnetDirection magnetDirection = MagnetDirection.Straight;

                    //갈림길에 있다면 미리 방향을 저장해준다.
                    if ((node.ConnectedNodes?.Count ?? 0) > 2 && nextNode != null)
                    {
                        //다음 노드ID를 확인해서 마그넷 방향 데이터를 찾는다.
                        if (node.MagnetDirections.ContainsKey(nextNode.Id) == false)
                        {
                            return AGVPathResult.CreateFailure($"{node.ID2}->{nextNode.ID2} 의 (목표)갈림길 방향이 입력되지 않았습니다", 0, 0);
                        }
                        else
                        {
                            var magdir = node.MagnetDirections[nextNode.Id].ToString();
                            if (magdir == "L") magnetDirection = MagnetDirection.Left;
                            else if (magdir == "R") magnetDirection = MagnetDirection.Right;

                        }


                    }

                    var nodeInfo = new NodeMotorInfo(i + 1, node.Id, node.RfidId, pathResult.PrevDirection, nextNode, magnetDirection);

                    // 속도 설정
                    var mapNode = _mapNodes.FirstOrDefault(n => n.Id == node.Id);
                    if (mapNode != null)
                    {
                        nodeInfo.Speed = mapNode.SpeedLimit;
                        detailedPath.Add(nodeInfo);
                    }


                }
                pathResult.DetailedPath = detailedPath;
            }

            return pathResult;
        }




        #region Helper Classes & Methods for CalculatePath_new

        private class SearchState
        {
            public MapNode CurrentNode { get; set; }
            public MapNode PreviousNode { get; set; }
            public AgvDirection CurrentDirection { get; set; } // 현재 모터 방향 (Forward/Backward)

            public float GCost { get; set; }
            public float HCost { get; set; }
            public float FCost => GCost + HCost;

            public SearchState Parent { get; set; }
            public string TurnType { get; set; } // Debug info
        }

        private struct MoveResult
        {
            public bool IsPossible;
            public float Cost;
            public AgvDirection NextDirection;
            public string TurnType;
        }

        private string GetStateKey(SearchState state)
        {
            string prevId = state.PreviousNode?.Id ?? "null";
            // 모터 방향도 상태에 포함해야 함 (같은 노드, 같은 진입이라도 모터방향 다르면 다른 상태 - 스위치백 때문)
            return $"{state.CurrentNode.Id}_{prevId}_{state.CurrentDirection}";
        }

        private SearchState GetLowestFCostState(List<SearchState> openSet)
        {
            SearchState lowest = openSet[0];
            for (int i = 1; i < openSet.Count; i++)
            {
                if (openSet[i].FCost < lowest.FCost)
                    lowest = openSet[i];
            }
            return lowest;
        }

        private float CalculateHeuristic(MapNode from, MapNode to)
        {
            // 유클리드 거리
            float dx = from.Position.X - to.Position.X;
            float dy = from.Position.Y - to.Position.Y;
            return (float)Math.Sqrt(dx * dx + dy * dy);
        }

        private MoveResult CheckMove(SearchState current, MapNode next)
        {
            var res = new MoveResult { IsPossible = false, Cost = float.MaxValue };

            // 1. 기본 거리 비용
            float dist = (float)Math.Sqrt(Math.Pow(current.CurrentNode.Position.X - next.Position.X, 2) + Math.Pow(current.CurrentNode.Position.Y - next.Position.Y, 2));

            // 2. 회전 각도 계산
            // Vector Prev -> Curr
            // Vector Curr -> Next

            double angle = 180.0; // Straight
            if (current.PreviousNode != null)
            {
                // Vector 1 (Prev -> Curr)
                double v1x = current.CurrentNode.Position.X - current.PreviousNode.Position.X;
                double v1y = current.CurrentNode.Position.Y - current.PreviousNode.Position.Y;

                // Vector 2 (Curr -> Next)
                double v2x = next.Position.X - current.CurrentNode.Position.X;
                double v2y = next.Position.Y - current.CurrentNode.Position.Y;

                // 각도 계산 (0 ~ 180)
                // 내적 이용: a·b = |a||b|cosθ
                double dot = v1x * v2x + v1y * v2y;
                double mag1 = Math.Sqrt(v1x * v1x + v1y * v1y);
                double mag2 = Math.Sqrt(v2x * v2x + v2y * v2y);

                if (mag1 > 0 && mag2 > 0)
                {
                    double cosTheta = dot / (mag1 * mag2);
                    // 부동소수점 오차 보정
                    if (cosTheta > 1.0) cosTheta = 1.0;
                    if (cosTheta < -1.0) cosTheta = -1.0;

                    double rad = Math.Acos(cosTheta);
                    // 외적을 이용해 좌/우 판별이 가능하지만, 여기서는 "꺾인 정도"만 중요하므로 내적각(0~180)만 사용
                    // 180도 = 직진, 90도 = 직각, 0도 = U턴(완전 뒤로)
                    // 주의: 벡터 방향 기준임. 
                    // Prev->Curr 가 (1,0) 이고 Curr->Next가 (1,0) 이면 각도 0도? 아니면 180?
                    // 위 내적 계산에서 같은 방향이면 cos=1 -> acos=0. 즉 0도가 직진임.
                    // 180도가 U턴(역방향).

                    // 변환: 사용자가 "120도 이상 완만해야" 라고 했음.
                    // 그림상 11->3->4는 예각(Sharp turn).
                    // 직선 주행시 각도 변화량(Deviation)으로 생각하면:
                    // 직진 = 0도 변화.
                    // 90도 턴 = 90도 변화.
                    // U턴 = 180도 변화.

                    angle = rad * (180.0 / Math.PI); // 0(직진) ~ 180(U턴)
                }
            }
            else
            {
                angle = 0; // 시작점에서는 직진으로 간주 (또는 자유)
            }

            // 제약 조건 체크
            // 조건 2: 120도 미만 예각 회전 불가? 
            // 사용자 멘트: "11->3->4 ... 120도 이상 완만해야 이동 가능... 3->4는 각도가 훨씬 작아서 커브 못틈"
            // 여기서 "각도"가 내각(Inner Angle)을 말하는지, 진행 경로의 꺾임각을 말하는지 중요.
            // 11->3->4 그림을 보면 "V"자 형태에 가까움. (Sharp Turn)
            // 즉, "진행 방향의 꺾임각"이 크다(예: 150도 꺾임) = "내각"이 작다(30도).
            // "120도 이상 완만해야" -> 내각이 120도 이상(점잖은 턴)이어야 한다.
            // 즉, 꺾임각(Deviation)은 60도 이하여야 한다.

            // 내 알고리즘의 `angle`은 "꺾임각(Deviation)"임. (0=직진, 180=U턴)
            // 따라서 "내각 120도 이상" == "꺾임각 60도 이하".
            // 그러므로 angle <= 60 이어야 Normal Turn 가능.

            // 3번 노드 (RFID 3) 특수성:
            // "3번 노드에서만 180도 턴 가능"
            // "3->4 처럼 각이 좁은 경우 3번으로 가서 모터방향 바꿔서 4번으로 감"

            bool isRfid3 = current.CurrentNode.RfidId == 3;
            bool isSharpTurn = angle > 60.0; // 60도보다 많이 꺾이면 Sharp Turn (내각 120도 미만)

            // Case A: Normal Move (완만한 턴)
            if (!isSharpTurn)
            {
                res.IsPossible = true;
                res.Cost = dist;
                res.NextDirection = current.CurrentDirection; // 방향 유지
                res.TurnType = "Normal";
                return res;
            }

            // Case B: Sharp Turn or U-Turn
            if (isSharpTurn)
            {
                if (isRfid3)
                {
                    // 3번 노드에서는 무슨 짓이든(?) 가능하다. (Switch Back)
                    // 멈춰서 방향을 바꾸므로, 모터 방향도 바뀜 (FWD -> BWD or BWD -> FWD)
                    // 비용 패널티 추가 (멈추고 바꾸는 시간 등)
                    res.IsPossible = true;
                    res.Cost = dist + 5000; // 큰 비용 (Switch Penalty)
                    res.NextDirection = (current.CurrentDirection == AgvDirection.Forward)
                                        ? AgvDirection.Backward
                                        : AgvDirection.Forward;
                    res.TurnType = "SwitchBack";
                    return res;
                }
                else
                {
                    // 3번 노드가 아닌데 Sharp Turn 하려고 함 -> 불가
                    res.IsPossible = false;
                    return res;
                }
            }

            return res;
        }

        private bool IsDockingDirectionValid(SearchState state, MapNode target)
        {
            if (target.DockDirection == DockingDirection.DontCare) return true;

            // 도착 시 모터 방향 확인
            // SearchState에 저장된 CurrentDirection이 도착 시 모터 방향임.

            if (target.DockDirection == DockingDirection.Forward)
                return state.CurrentDirection == AgvDirection.Forward;

            if (target.DockDirection == DockingDirection.Backward)
                return state.CurrentDirection == AgvDirection.Backward;

            return true;
        }

        private AGVPathResult ReconstructPath_New(SearchState endState)
        {
            var result = new AGVPathResult { Success = true, TotalDistance = endState.GCost };
            var pathList = new List<MapNode>();
            var detailedList = new List<NodeMotorInfo>();

            var current = endState;
            var pathStack = new Stack<SearchState>();

            while (current != null)
            {
                pathStack.Push(current);
                current = current.Parent;
            }

            // Path 재구성
            int seq = 1;
            while (pathStack.Count > 0)
            {
                var step = pathStack.Pop();
                pathList.Add(step.CurrentNode);

                // Detailed Info (마그넷 방향 계산 등은 후처리 필요할 수도 있으나 여기선 단순화)
                // SearchState에는 "어떤 방향으로 왔는지"가 저장되어 있음.

                // 마그넷 방향 계산 (다음 노드가 있을 때)
                MagnetDirection magDir = MagnetDirection.Straight;
                if (pathStack.Count > 0)
                {
                    var nextStep = pathStack.Peek();
                    // Current -> NextStep 이동 시 마그넷 방향
                    if (step.CurrentNode.MagnetDirections.ContainsKey(nextStep.CurrentNode.Id))
                    {
                        var magPos = step.CurrentNode.MagnetDirections[nextStep.CurrentNode.Id];
                        if (magPos == MagnetPosition.L) magDir = MagnetDirection.Left;
                        else if (magPos == MagnetPosition.R) magDir = MagnetDirection.Right;

                        // 만약 SwitchBack 상황이라면? 
                        // 모터 방향이 바뀌었다면 마그넷 방향도 그에 맞춰야 하나?
                        // 기존 로직 참고: MagnetDirection은 "진행 방향 기준" 좌/우 인가? 아님 절대적?
                        // 보통 "갈림길에서 어느 쪽" 인지 나타냄.
                    }
                }

                detailedList.Add(new NodeMotorInfo(seq++, step.CurrentNode.Id, step.CurrentNode.RfidId, step.CurrentDirection, null, magDir)); // NextNode는 일단 null
            }

            // NextNode 정보 채우기
            for (int i = 0; i < detailedList.Count - 1; i++)
            {
                detailedList[i].NextNode = _mapNodes.FirstOrDefault(n => n.Id == detailedList[i + 1].NodeId);
            }

            result.Path = pathList;
            result.DetailedPath = detailedList;

            return result;
        }

        #endregion
        public enum MonDir
        {
            LeftTop,
            RightBtm,
        }
        public enum MapZone
        {
            None,
            Buffer,   // 91 ~ 07
            Charger,  // 73 ~ 10
            Plating,  // 72 ~ 05
            Loader,   // 71 ~ 04
            Cleaner,  // 70 ~ 01
            Junction,  // Hub (11, 12, etc)
            Turn,
        }
        public class PathData
        {
            public Station NodeSta { get; set; }
            public Station NodeEnd { get; set; }
            public MonDir Monitor { get; set; }
            public List<string> Path { get; set; }
            public PathData(params string[] _path)
            {
                Path = new List<string>();
                Path.AddRange(_path);
            }
            
        }

        public List<PathData> GetMapZonePathData()
        {
            var retval = new List<PathData>();
            // Buffer -> ...
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "36B", "35B", "31B", "32B", "33B", "34B", "20B", "9B", "8B", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Buffer, NodeEnd = Station.Charger },
                new PathData( "36B", "35B", "31B", "32B", "33B", "34B", "20B", "9B", "8B", "7B", "11B", "3T", "3B", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Buffer, NodeEnd = Station.Plating },
                new PathData( "36B", "35B", "31B", "32B", "33B", "34B", "20B", "9B", "8B", "7B", "11B", "3T", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Buffer, NodeEnd = Station.Loder },
                new PathData( "36B", "35B", "31B", "32B", "33B", "34B", "20B", "9B", "8B", "7B", "11B", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Buffer, NodeEnd = Station.Cleaner }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "36F", "35F", "31F", "32F", "33F", "34F", "20F", "9F", "8F", "7F", "11F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Buffer, NodeEnd = Station.Charger },
                new PathData( "36F", "35F", "31F", "32F", "33F", "34F", "20F", "9F", "8F", "7F", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Buffer, NodeEnd = Station.Plating },
                new PathData( "36F", "35F", "31F", "32F", "33F", "34F", "20F", "9F", "8F", "7F", "11F", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Buffer, NodeEnd = Station.Loder },
                new PathData( "36F", "35F", "31F", "32F", "33F", "34F", "20F", "9F", "8F", "7F", "11F", "3T", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Buffer, NodeEnd = Station.Cleaner }
            });


            // Loader -> ...
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "71F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Loder, NodeEnd = Station.Cleaner },
                new PathData( "71F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Loder, NodeEnd = Station.Charger },
                new PathData( "71F", "3B", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Loder, NodeEnd = Station.Plating },
                new PathData( "71F", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Loder, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "71B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Loder, NodeEnd = Station.Cleaner },
                new PathData( "71B", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Loder, NodeEnd = Station.Charger },
                new PathData( "71B", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Loder, NodeEnd = Station.Plating },
                new PathData( "71B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Loder, NodeEnd = Station.Buffer }
            });


            // Cleaner -> ...
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "70B", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Cleaner, NodeEnd = Station.Loder },
                new PathData( "70B", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Cleaner, NodeEnd = Station.Charger },
                new PathData( "70B", "3B", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Cleaner, NodeEnd = Station.Plating },
                new PathData( "70B", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Cleaner, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "70F", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Cleaner, NodeEnd = Station.Loder },
                new PathData( "70F", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Cleaner, NodeEnd = Station.Charger },
                new PathData( "70F", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Cleaner, NodeEnd = Station.Plating },
                new PathData( "70F", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Cleaner, NodeEnd = Station.Buffer }
            });


            // Plating -> ...
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "72B", "11B", "3T", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Plating, NodeEnd = Station.Loder },
                new PathData( "72B", "11B", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Plating, NodeEnd = Station.Charger },
                new PathData( "72B", "11B", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Plating, NodeEnd = Station.Cleaner },
                new PathData( "72B", "11B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Plating, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "72F", "11F", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Plating, NodeEnd = Station.Loder },
                new PathData( "72F", "11F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Plating, NodeEnd = Station.Charger },
                new PathData( "72F", "11F", "3T", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Plating, NodeEnd = Station.Cleaner },
                new PathData( "72F", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Plating, NodeEnd = Station.Buffer }
            });


            //일반노드도 일부 경로를 계산한다.
            // 일반노드 3 -> 
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "3B", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });


            // 일반노드 7 ->
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "7F", "11F", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "7F", "11F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "7F", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "7F", "11F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "7B", "11B", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "7B", "11B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "7B", "11B", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "7B", "11B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });


            //일반노드를 포함하여 모든 노드를 정의하자

            // 일반노드 6 ->
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "6B", "10B", "7F", "11F", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "6B", "10B", "7F", "11F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "6B", "10B", "7F", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "6B", "10B", "7F", "11F", "3T", "3F","11F","7B","10B","6B","73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "6B", "10B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "6F", "10F", "7B", "11B", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "6F", "10F", "7B", "11B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "6F", "10F", "7B", "11F", "72F") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "6F", "10F", "7B", "11B","3T","3B","11B","7B","8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // 여기까지 검증 완료.

            // 일반노드 10 ->
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "10B", "7F", "11F", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "10B", "7F", "11F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "10B", "7F", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "10B", "7F", "11F", "3T","3F", "11F","7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "10B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "10F", "7B", "11B", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "10F", "7B", "11B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "10F", "7B", "11B", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "10F", "7B", "11B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });


            // 일반노드 11 ->
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "11F", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "11F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "11F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData( "11B", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData( "11B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData( "11B", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData( "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData( "11B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });


            // 일반노드 8 ->
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData("8F", "7F", "11F", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData("8F", "7F", "11F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData("8F", "7F", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData("8F", "7F", "11F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData("8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData("8B", "7B", "11B", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData("8B", "7B", "11B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData("8B", "7B", "11B", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData("8B", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData("8B", "7B", "11B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });


            // 일반노드 9 ->
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData("9F", "8F", "7F", "11F", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData("9F", "8F", "7F", "11F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData("9F", "8F", "7F", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData("9F", "8F", "7F", "11F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData("9B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData("9B", "8B", "7B", "11B", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData("9B", "8B", "7B", "11B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData("9B", "8B", "7B", "11B", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData("9B", "8B", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData("9B", "8B", "7B", "11B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });


            // 일반노드 20 ->
            // [[ LeftTop ]]
            retval.AddRange(new List<PathData>
            {
                new PathData("20F", "9F", "8F", "7F", "11F", "3B", "71B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData("20F", "9F", "8F", "7F", "11F", "3T", "3B", "70B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData("20F", "9F", "8F", "7F", "11B", "72B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData("20F", "9F", "8F", "7F", "11F", "3T", "3F", "11F", "7B", "10B", "6B", "73B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData("20B", "9B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.LeftTop, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });

            // [[ RightBtm ]]
            retval.AddRange(new List<PathData>
            {
                new PathData("20B", "9B", "8B", "7B", "11B", "3T", "3B", "71B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Loder },
                new PathData("20B", "9B", "8B", "7B", "11B", "3B", "70B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Cleaner },
                new PathData("20B", "9B", "8B", "7B", "11B", "3T", "3B", "11B", "72B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Plating },
                new PathData("20B", "9B", "8B", "7B", "10B", "6B", "73B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Charger },
                new PathData("20B", "9B", "8B", "7B", "11B", "3T", "3B", "11B", "7B", "8B", "9B", "20B", "34B", "33B", "32B", "31B", "35B", "36B") { Monitor = MonDir.RightBtm, NodeSta = Station.Normal, NodeEnd = Station.Buffer }
            });


            return retval;
        }
        /// <summary>
        /// 해당 노드가 속하는 존을 반환한다.
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public MapZone GetMapZone(MapNode node)
        {
            if (node == null) return MapZone.None;
            int rfid = node.RfidId;

            Dictionary<MapZone, int[]> ZoneList = GetMapZoneNodeList();
            var zone = ZoneList.Where(t => t.Value.Contains(rfid)).FirstOrDefault();
            if (zone.Value == null) return MapZone.None;

            return zone.Key;
        }

        public Dictionary<MapZone, int[]> GetMapZoneNodeList()
        {
            Dictionary<MapZone, int[]> ZoneList = new Dictionary<MapZone, int[]>();
            ZoneList.Add(MapZone.Turn, new int[] { 3 });
            ZoneList.Add(MapZone.Buffer, new int[] { 91, 36, 35, 31, 32, 33, 34, 20, 9, 8, 7 });
            ZoneList.Add(MapZone.Charger, new int[] { 73, 6, 10 });
            ZoneList.Add(MapZone.Junction, new int[] { 11 });
            ZoneList.Add(MapZone.Plating, new int[] { 72 });
            ZoneList.Add(MapZone.Loader, new int[] { 71 });
            ZoneList.Add(MapZone.Cleaner, new int[] { 70 });
            return ZoneList;
        }


        public AGVPathResult CalculateScriptedPath(MapNode startNode, MapNode targetNode, MapNode prevNode, AgvDirection prevDir)
        {
            // 태그 내 숫자로 정확히 매칭하기 위한 람다 (예: 3을 찾을 때 "36"이 매칭되지 않도록)
            Func<string, int, bool> matchNode = (tag, rfid) =>
            {
                string idStr = "";
                foreach (char c in tag) if (char.IsDigit(c)) idStr += c;
                return idStr == rfid.ToString();
            };

            var startZone = GetMapZone(startNode);
            var targetZone = GetMapZone(targetNode);

            // 존이 확인되지 않는다면 오류
            if (startZone == MapZone.None || targetZone == MapZone.None)
            {
                // return AGVPathResult.CreateFailure($"Zone not found: {startNode.ID2}->{targetNode.ID2}");
            }

            var monitorMode = GetMonitorMode(startNode, prevNode, prevDir);
            var motDir = prevDir == AgvDirection.Forward ? 'F' : 'B';

            // 시작 태그 검색용 (예: "91F")
            string startTag = $"{startNode.RfidId}{motDir}";
            string targetTag = $"{targetNode.RfidId}";
            string targetTag1;
            if (targetNode.StationType != Station.Normal && targetNode.StationType != Station.Lmt)
            {
                targetTag += "B"; //모든 스테이션은 후진으로 도킹을 해야한다
                targetTag1 = "";
            }
            else
            {
                targetTag1 = targetTag + (motDir == 'F' ? "B" : "F");
                targetTag += motDir;
            }

            // 모니터방향이 일치하고 대상노드가 동일한 경로를 찾는다
            var zonepath = GetMapZonePathData();

            //목표가 일반노드라면 단순 경로 생성해서 반환한다.
            if (targetNode.StationType == Station.Normal)
            {
                var simplepath = this.FindBasicPath(startNode, targetNode, prevNode, prevDir);
                return simplepath;
            }

            IEnumerable<PathData> candidates;

            //시작과 목표가 포함된 경로르 모두 찾아서 이용하도록 한다(zonepath 내에서 모두 검색한다)
            //모니터의 방향이 동일하고 20F -> 70B (목표노드의 방향에 따라서 목적지으 ㅣFB는 결정한다 일반노드라면 방향상관없이 검색한다 - 기본은 시작이 방향과 동일하게 한다)
            //경로의 시작이 경로의 끝보다 index가 먼저 나와야한다.
            //현재 진행방향의 경로와, 반대방향의 경로를 2개 추출해서.. 이전에 지나온 경로를 체크한다.
            string SearchTagS, SearchTagE, SearchTagE1;

            for (int i = 0; i < 2; i++)
            {
                //진입순서에 따라서 검색하는 대상을 바꿔준다(진행방향 반대방향)
                if (i == 0)
                {
                    SearchTagS = $"{startNode.RfidId}{motDir}";
                    SearchTagE = $"{targetNode.RfidId}";
                    if (targetNode.StationType != Station.Normal && targetNode.StationType != Station.Lmt)
                    {
                        SearchTagE += "B"; //모든 스테이션은 후진으로 도킹을 해야한다
                        SearchTagE1 = "";
                    }
                    else
                    {
                        SearchTagE += motDir;
                        SearchTagE1 = targetTag + (motDir == 'F' ? "B" : "F");
                    }
                }
                else
                {
                    SearchTagS = $"{startNode.RfidId}{(motDir == 'F' ? 'B' : 'F')}";
                    SearchTagE = $"{targetNode.RfidId}";
                    if (targetNode.StationType != Station.Normal && targetNode.StationType != Station.Lmt)
                    {
                        SearchTagE += "B"; //모든 스테이션은 후진으로 도킹을 해야한다
                        SearchTagE1 = "";
                    }
                    else
                    {
                        SearchTagE += motDir;
                        SearchTagE1 = targetTag + (motDir == 'F' ? "B" : "F"); // 오히려 반대로 처리해준다.
                    }
                }



                candidates = zonepath.Where(d =>
                    d.Monitor == monitorMode &&
                    d.Path.Contains(SearchTagS) &&
                    d.Path.Contains(SearchTagE)
                ).Where(d =>
                {
                    int startIndex = d.Path.FindIndex(p => p.Equals(SearchTagS));
                    int endIndex = d.Path.FindLastIndex(p => p.Equals(SearchTagE));
                    if (endIndex == -1 && SearchTagE1 != "")
                        endIndex = d.Path.FindLastIndex(p => p.Equals(SearchTagE1));

                    return startIndex != -1 && endIndex != -1 && startIndex < endIndex;
                }).ToList();

                //찾아진 값이 있다면 slice 해서 그 경로를반환한다.
                if (candidates.Any())
                {
                    PathData bestPath = null;
                    int bestStartIndex = -1;
                    int bestEndIndex = -1;
                    int minPathLength = int.MaxValue;

                    foreach (var candidate in candidates)
                    {
                        int startIndex = candidate.Path.FindIndex(p => p.Equals(SearchTagS));
                        int endIndex = candidate.Path.FindLastIndex(p => p.Equals(SearchTagE));
                        if (endIndex == -1 && SearchTagE1 != "")
                            endIndex = candidate.Path.FindLastIndex(p => p.Equals(SearchTagE1));

                        int length = endIndex - startIndex;
                        if (length < minPathLength)
                        {
                            minPathLength = length;
                            bestPath = candidate;
                            bestStartIndex = startIndex;
                            bestEndIndex = endIndex;
                        }
                    }

                    if (bestPath != null)
                    {
                        var slicedPath = bestPath.Path.Skip(bestStartIndex).Take(bestEndIndex - bestStartIndex + 1).ToList();

                        // 검증: 첫 번째 이동이 이전 노드(prevNode) 방향으로 가는 것이라면, 모터 방향을 반전시켜야 할 수 있음.
                        if (slicedPath.Count > 1 && prevNode != null)
                        {
                            var nextNodeInPath = _mapNodes.FirstOrDefault(n => n.RfidId.ToString() == new string(slicedPath[1].Where(char.IsDigit).ToArray()));
                            if (nextNodeInPath != null && nextNodeInPath.Id == prevNode.Id)
                            {
                                // 되돌아가는 상황: 첫 노드의 방향 플래그를 반전시킨다.
                                string firstTag = slicedPath[0];
                                char currentFlag = firstTag.Last();
                                if (currentFlag == 'F' || currentFlag == 'B')
                                {
                                    char reversedFlag = currentFlag == 'F' ? 'B' : 'F';
                                    slicedPath[0] = firstTag.Substring(0, firstTag.Length - 1) + reversedFlag;
                                }
                            }
                        }

                        return ConvertHardcodedPathToResult(slicedPath, startNode, prevNode, prevDir);
                    }
                }


            }






            //이곳에서 시작,종료노드가 완전히 일치하는 경로를 찾고 있다면 그것을 바로 반환한다
            //그런경우는 복잡하게 추가 계산할 필요가 없으니까
            var exactMatchList = zonepath.Where(d =>
                d.NodeSta == startNode.StationType &&
                d.NodeEnd == targetNode.StationType &&
                d.Monitor == monitorMode);

            var exactMatch = exactMatchList.FirstOrDefault(d =>
                d.Path.First().StartsWith(startNode.RfidId.ToString()) &&
                d.Path.Last().StartsWith(targetNode.RfidId.ToString()));

            if (exactMatch != null)
            {
                int startIndex = exactMatch.Path.FindIndex(p => p == startTag);
                if (startIndex == -1) startIndex = exactMatch.Path.FindIndex(p => p.StartsWith(startNode.RfidId.ToString()));
                int endIndex = exactMatch.Path.FindLastIndex(p => p.StartsWith(targetNode.RfidId.ToString()));

                if (startIndex != -1 && endIndex != -1 && startIndex <= endIndex)
                {
                    var slicedPath = exactMatch.Path.Skip(startIndex).Take(endIndex - startIndex + 1).ToList();
                    return ConvertHardcodedPathToResult(slicedPath, startNode, prevNode, prevDir);
                }
            }



            // 시작Zone과 목표Zone이 다른 경우에만 하드코딩된 zonepath 검색
            if (startZone != targetZone)
            {
                // 목적지가 특정 장비(StationType)인 경우를 우선 검색
                candidates = zonepath.Where(d =>
                    d.Monitor == monitorMode &&
                    d.NodeEnd == targetNode.StationType &&
                    d.Path.Any(p => p.StartsWith(startNode.RfidId.ToString())) && // 시작 포인트 포함
                    d.Path.Any(p => p.StartsWith(targetNode.RfidId.ToString()))   // 끝 포인트 포함
                ).ToList();

                // 목적지가 일반 노드이거나 StationType이 매칭되지 않아 결과가 없을 경우,
                // StationType 조건 없이 모니터 방향과 노드 포함 여부만으로 경로 검색
                if (!candidates.Any())
                {
                    candidates = zonepath.Where(d =>
                        d.Monitor == monitorMode &&
                        d.Path.Any(p => p.StartsWith(startNode.RfidId.ToString())) && // 시작 포인트 포함
                        d.Path.Any(p => p.StartsWith(targetNode.RfidId.ToString()))   // 끝 포인트 포함
                    ).ToList();
                }

                if (candidates.Any())
                {
                    PathData bestPath = null;
                    int bestStartIndex = -1;
                    int bestEndIndex = -1;
                    int minPathLength = int.MaxValue;

                    foreach (var candidate in candidates)
                    {
                        // 시작 태그와 가장 일치하는 인덱스 찾기 (방향까지 고려 "91F")
                        int startIndex = candidate.Path.FindIndex(p => p == startTag);
                        if (startIndex == -1) // 방향이 안 맞으면 그냥 RFID로만 찾기
                            startIndex = candidate.Path.FindIndex(p => p.StartsWith(startNode.RfidId.ToString()));

                        // 끝 태그 인덱스 (뒤에서부터 찾기)
                        int endIndex = candidate.Path.FindLastIndex(p => p.StartsWith(targetNode.RfidId.ToString()));

                        if (startIndex != -1 && endIndex != -1 && startIndex < endIndex)
                        {
                            int length = endIndex - startIndex;
                            if (length < minPathLength)
                            {
                                minPathLength = length;
                                bestPath = candidate;
                                bestStartIndex = startIndex;
                                bestEndIndex = endIndex;
                            }
                        }
                    }

                    if (bestPath != null)
                    {
                        // 추출된 경로 조각
                        var slicedPath = bestPath.Path.Skip(bestStartIndex).Take(bestEndIndex - bestStartIndex + 1).ToList();
                        var a = ConvertHardcodedPathToResult(slicedPath, startNode, prevNode, prevDir);
                        return a;
                    }
                }
            }

            //추가로 처리해준다.
            if (startZone == MapZone.Buffer && targetZone == MapZone.Buffer)
            {
                //모니터가 왼쪽이라면 턴을 해야하낟.

                if (monitorMode == MonDir.LeftTop)
                {
                    //위치는 현재위치이나 모니터방향이 일치하지 않으므로 턴을 한후 경로를 다시 찾아야한다.  오버슛이 필요하지 않다
                    var BufferPath = ("36B,35B,31B,32B,33B,34B,20B,9B,8B,7B,11B,3T,3B,11B,7B,8B,9B,20B,34B,33B,32B,31B,35B,36B").Split(',');
                    var startTagB = startNode.RfidId + "B"; //이 경우에는 반드시 우측으로 가야하니 Back 이동을 해야 한다
                    var endTagB = targetNode.RfidId + "B";
                    int firstIdx = Array.IndexOf(BufferPath, startTagB);
                    int lastIdx = Array.LastIndexOf(BufferPath, endTagB);
                    if (firstIdx != -1 && lastIdx != -1 && firstIdx < lastIdx)
                    {
                        var slicedPath = BufferPath.Skip(firstIdx).Take(lastIdx - firstIdx + 1).ToList();
                        return ConvertHardcodedPathToResult(slicedPath, startNode, prevNode, prevDir);
                    }
                    return AGVPathResult.CreateFailure("버퍼 공용 경로에서 정기 턴 경로를 생성할 수 없습니다.");
                }
                else
                {
                    //여긴 모니터가 우측방향에 있는 경우이며, 우측에 있다면 큰 문제없이 좌로 이동해서 목적지를 설정하면 된다
                    if (startNode.Id == targetNode.Id)
                    {
                        //방향과 모두 일치하므로 더이상 이동할 필요가 없다 - 현재위치를 그대로 반환한다
                        var result = new AGVPathResult { Success = true };
                        result.Path = new List<MapNode> { startNode };
                        result.DetailedPath = new List<NodeMotorInfo> { new NodeMotorInfo(1, startNode.Id, startNode.RfidId, prevDir, null, MagnetDirection.Straight, false) };
                        result.TotalDistance = 0;
                        return result;
                    }
                    else
                    {
                        //버퍼위치에서 다른 버퍼위치로 이동하는 경우인데. 목표위치가 좌측에 있다면 그대로 이동하면된다.
                        bool isTargetLeft = targetNode.Position.X < startNode.Position.X;
                        if (isTargetLeft)
                        {
                            //대상이 좌측에 있으므로 기본 경로내에서 
                            var BufferPath = ("7B,8B,9B,20B,34B,33B,32B,31B,35B,36B").Split(',');
                            var startTagB = startNode.RfidId + "B";
                            var endTagB = targetNode.RfidId + "B";
                            int firstIdx = Array.IndexOf(BufferPath, startTagB);
                            int lastIdx = Array.LastIndexOf(BufferPath, endTagB);
                            if (firstIdx != -1 && lastIdx != -1 && firstIdx < lastIdx)
                            {
                                var slicedPath = BufferPath.Skip(firstIdx).Take(lastIdx - firstIdx + 1).ToList();
                                return ConvertHardcodedPathToResult(slicedPath, startNode, prevNode, prevDir);
                            }
                            return AGVPathResult.CreateFailure("버퍼 공용 경로에서 정기 턴 경로를 생성할 수 없습니다.");
                        }
                        else
                        {
                            // 목표위치가 우측에 있다면 목표위치보다 한번 더 우측으로 이동해서 좌측으로 다시 진입
                            var endBufferNode = _mapNodes.FirstOrDefault(n => n.RfidId == 7);
                            if (endBufferNode == null) return AGVPathResult.CreateFailure("버퍼 끝 노드(7)를 찾을 수 없습니다.");

                            var overPathFull = this.FindBasicPath(startNode, endBufferNode, prevNode, AgvDirection.Forward);
                            if (overPathFull == null || !overPathFull.Success)
                                return AGVPathResult.CreateFailure("Overshoot 전체 경로(7번 방향) 탐색 실패");

                            int targetIdx = overPathFull.Path.FindIndex(n => n.Id == targetNode.Id);
                            if (targetIdx == -1 || targetIdx == overPathFull.Path.Count - 1)
                                return AGVPathResult.CreateFailure("Overshoot를 위한 여유 공간(다음 노드)이 없습니다.");

                            // 목표 노드 다음 노드(오버슈트 지점)까지만 잘라내어 새 경로 구성
                            var overPath = new AGVPathResult
                            {
                                Success = true,
                                Path = overPathFull.Path.Take(targetIdx + 2).ToList(),
                                DetailedPath = overPathFull.DetailedPath.Take(targetIdx + 2).ToList()
                            };

                            var autoOverNode = overPath.Path.Last(); // 오버슈트 된 곳
                            var lastDet = overPath.DetailedPath.Last();
                            lastDet.MotorDirection = AgvDirection.Backward; //방향을 변경 해준다.

                            // 오버슈트 위치에서 다시 Backward로 뒤로 한 칸 이동해 targetNode에 최종 진입
                            overPath.Path.Add(targetNode);
                            overPath.DetailedPath.Add(new NodeMotorInfo(lastDet.seq + 1, targetNode.Id, targetNode.RfidId, AgvDirection.Backward)
                            {
                                Speed = SpeedLevel.L,
                            });

                            return overPath;
                        }

                    }
                }

            }
            else
            {
                //
            }

            return AGVPathResult.CreateFailure("경로를 계산할 수 없습니다");
        }


        private MonDir GetMonitorMode(MapNode startNode, MapNode prevNode, AgvDirection prevDir)
        {
            if (prevNode == null) return MonDir.RightBtm;

            //모니터방향도 상황에 따라 다른경우가 있다. 이것도 하드코딩하다.
            //prev -> start 와 모터방향(prevdir) 에 따라서 경우의 수를 입력한다.
            //일반노드가아닌 노드와, 일반노드중 7,11을 포함해서 모든 경우를 정의해야한다.

            //3 - junction(turn)
            if (startNode.RfidId == 3)
            {
                if (prevDir == AgvDirection.Forward)
                {
                    if (prevNode.RfidId == 70)
                        return MonDir.RightBtm;
                    else if (prevNode.RfidId == 11 || prevNode.RfidId == 71)
                        return MonDir.LeftTop;
                }
                else if (prevDir == AgvDirection.Backward)
                {
                    if (prevNode.RfidId == 70)
                        return MonDir.LeftTop;
                    else if (prevNode.RfidId == 11 || prevNode.RfidId == 71)
                        return MonDir.RightBtm;
                }
            }

            //7 - junction
            if (startNode.RfidId == 7)
            {
                if (prevDir == AgvDirection.Forward)
                {
                    if (prevNode.RfidId == 10 || prevNode.RfidId == 11)
                        return MonDir.RightBtm;
                    else if (prevNode.RfidId == 8)
                        return MonDir.LeftTop;
                }
                else if (prevDir == AgvDirection.Backward)
                {
                    if (prevNode.RfidId == 10 || prevNode.RfidId == 11)
                        return MonDir.LeftTop;
                    else if (prevNode.RfidId == 8)
                        return MonDir.RightBtm;
                }
            }

            //70 - cleaner
            if (startNode.RfidId == 70)
            {
                if (prevDir == AgvDirection.Forward)
                {
                    if (prevNode.RfidId == 3)
                        return MonDir.LeftTop;
                }
                else if (prevDir == AgvDirection.Backward)
                {
                    if (prevNode.RfidId == 3)
                        return MonDir.RightBtm;
                }
            }

            //71 - loader
            if (startNode.RfidId == 71)
            {
                if (prevDir == AgvDirection.Forward)
                {
                    if (prevNode.RfidId == 3)
                        return MonDir.RightBtm;
                }
                else if (prevDir == AgvDirection.Backward)
                {
                    if (prevNode.RfidId == 3)
                        return MonDir.LeftTop;
                }
            }


            //73 - charger
            if (startNode.RfidId == 73)
            {
                if (prevDir == AgvDirection.Forward)
                {
                    if (prevNode.RfidId == 6)
                        return MonDir.LeftTop;
                }
                else if (prevDir == AgvDirection.Backward)
                {
                    if (prevNode.RfidId == 6)
                        return MonDir.RightBtm;
                }
            }

            //72 -- plating
            if (startNode.RfidId == 72)
            {
                if (prevDir == AgvDirection.Forward)
                {
                    if (prevNode.RfidId == 11)
                        return MonDir.LeftTop;
                }
                else if (prevDir == AgvDirection.Backward)
                {
                    if (prevNode.RfidId == 11)
                        return MonDir.RightBtm;
                }
            }

            //31~34,35,36 --buffer
            if (startNode.RfidId >= 31 && startNode.RfidId <= 36)
            {
                if (prevDir == AgvDirection.Forward)
                {
                    if (prevNode.RfidId == 20)
                        return MonDir.LeftTop;
                    else
                    {
                        int bdx = startNode.Position.X - prevNode.Position.X;
                        if (bdx < 0) return MonDir.LeftTop;
                        else return MonDir.RightBtm;
                    }
                }
                else if (prevDir == AgvDirection.Backward)
                {
                    if (prevNode.RfidId == 20)
                        return MonDir.RightBtm;
                    else
                    {
                        int bdx = startNode.Position.X - prevNode.Position.X;
                        if (bdx < 0) return MonDir.RightBtm;
                        else return MonDir.LeftTop;
                    }
                }
            }

            if (startNode.RfidId == 8 || startNode.RfidId == 9 || startNode.RfidId == 20)
            {
                if (prevNode.Position.X > startNode.Position.X)  //오른쪽에서 왔다. 
                {
                    if (prevDir == AgvDirection.Forward) return MonDir.LeftTop; //오른쪽에서 전진으로 왔다면 모니터는 좌측에있다.
                    else return MonDir.RightBtm;    //오른쪽에서 후진으로 왔다면 모니터는 우측에 있다.
                }
                else //왼쪽에서 왔다
                {
                    if (prevDir == AgvDirection.Forward) return MonDir.RightBtm;
                    else return MonDir.LeftTop;
                }
            }


            int dx = startNode.Position.X - prevNode.Position.X;
            int dy = startNode.Position.Y - prevNode.Position.Y;

            bool isMonitorLeft = false;
            if (Math.Abs(dx) > Math.Abs(dy)) // Horizontal
            {
                isMonitorLeft = (prevDir == AgvDirection.Backward);
            }
            else // Vertical
            {
                isMonitorLeft = (prevDir == AgvDirection.Forward);
            }

            return isMonitorLeft ? MonDir.LeftTop : MonDir.RightBtm;





        }



        private AGVPathResult ConvertHardcodedPathToResult(List<string> pathStrings, MapNode startNode, MapNode prevNode, AgvDirection prevDir)
        {
            var result = new AGVPathResult { Success = true };
            var pathList = new List<MapNode>();
            var detailedList = new List<NodeMotorInfo>();

            int seq = 1;
            for (int i = 0; i < pathStrings.Count; i++)
            {
                string s = pathStrings[i];
                if (string.IsNullOrEmpty(s)) continue;

                string rfIdStr = "";
                char flag = ' ';

                foreach (char c in s)
                {
                    if (char.IsDigit(c)) rfIdStr += c;
                    else flag = c;
                }

                var node = _mapNodes.FirstOrDefault(n => n.RfidId.ToString() == rfIdStr);
                if (node == null) continue;

                // Determine Motor Direction from Flag or Maintain Previous
                AgvDirection motorDir = detailedList.Count > 0 ? detailedList.Last().MotorDirection : prevDir;
                bool isTurn = false;

                if (flag == 'F') motorDir = AgvDirection.Forward;
                else if (flag == 'B') motorDir = AgvDirection.Backward;
                else if (flag == 'T') isTurn = true;

                pathList.Add(node);

                // Magnet direction lookup
                MagnetDirection magDir = MagnetDirection.Straight;
                if (i + 1 < pathStrings.Count)
                {
                    var nextTag = pathStrings[i + 1];
                    string nextRfidStr = "";
                    foreach (char c in nextTag) if (char.IsDigit(c)) nextRfidStr += c;

                    var nextNode = _mapNodes.FirstOrDefault(n => n.RfidId.ToString() == nextRfidStr);
                    if (nextNode != null && node.MagnetDirections.ContainsKey(nextNode.Id))
                    {
                        var magPos = node.MagnetDirections[nextNode.Id];
                        if (magPos == MagnetPosition.R) magDir = MagnetDirection.Right;
                        else if (magPos == MagnetPosition.L) magDir = MagnetDirection.Left;
                    }
                }

                var info = new NodeMotorInfo(seq++, node.Id, node.RfidId, motorDir, null, magDir, isTurn);
                detailedList.Add(info);
            }

            // Connect NextNode pointers
            for (int i = 0; i < detailedList.Count - 1; i++)
            {
                detailedList[i].NextNode = pathList[i + 1];
            }

            result.Path = pathList;
            result.DetailedPath = detailedList;
            result.TotalDistance = CalculatePathDistance(pathList);

            return result;
        }

        private float CalculatePathDistance(List<MapNode> path)
        {
            float dist = 0;
            for (int i = 0; i < path.Count - 1; i++)
            {
                dist += (float)Math.Sqrt(Math.Pow(path[i].Position.X - path[i + 1].Position.X, 2) + Math.Pow(path[i].Position.Y - path[i + 1].Position.Y, 2));
            }
            return dist;
        }

        private MapNode GetZoneExitNode(MapZone zone)
        {
            int exitRfid = -1;
            switch (zone)
            {
                case MapZone.Buffer: exitRfid = 7; break;
                case MapZone.Charger: exitRfid = 10; break; // Or 6? Assuming 10 based on flow
                case MapZone.Plating: exitRfid = 72; break;
                case MapZone.Loader: exitRfid = 71; break;
                case MapZone.Cleaner: exitRfid = 70; break;
                case MapZone.Junction: return null;
            }
            return _mapNodes.FirstOrDefault(n => n.RfidId == exitRfid);
        }

        private MapNode GetZoneEntryNode(MapZone zone)
        {
            int entryRfid = -1;
            switch (zone)
            {
                case MapZone.Buffer: entryRfid = 7; break; // Bi-directional entry/exit? 
                // Usually Buffer entry might be different (e.g. 91?). 
                // But user didn't specify directional flow constraints for zones other than turn logic.
                // Let's assume Entry = Exit for single-lane spurs, or define specific entry points.
                // If Buffer is 91~07, maybe Entry is 7?
                case MapZone.Charger: entryRfid = 10; break;
                case MapZone.Plating: entryRfid = 72; break;
                case MapZone.Loader: entryRfid = 71; break;
                case MapZone.Cleaner: entryRfid = 70; break;
            }
            return _mapNodes.FirstOrDefault(n => n.RfidId == entryRfid);
        }

        /// <summary>
        /// 길목(Gateway) 기반 고급 경로 계산 (기존 SimulatorForm.CalcPath 이관)
        /// </summary>
        public AGVPathResult CalculatePath(MapNode startNode, MapNode targetNode, MapNode prevNode, AgvDirection prevDir)
        {
            AGVPathResult Retval;
            // var o_StartNode = startNode;
            // startNode, targetNode는 이미 인자로 받음

            if (startNode == null || targetNode == null) return AGVPathResult.CreateFailure("시작/종료노드가 지정되지 않음");

            try
            {
                // 종료노드라면 이전위치로 이동시켜야한다. (Simulator Logic)
                // 만약 시작노드가 끝단(ConnectedMapNodes.Count == 1)이라면, 
                // AGV가 해당 노드에 '도착'한 상태가 아니라 '작업' 중일 수 있으므로 
                // 이전 노드(진입점)로 위치를 보정하여 경로를 계산한다.
                AGVPathResult LimitPath = null;
                if (startNode.ConnectedMapNodes.Count == 1)
                {
                    // 시작점 -> 이전점 경로 (보통 후진이나 전진 1칸)
                    LimitPath = this.FindPathAStar(startNode, prevNode);
                    if (LimitPath.Success)
                    {
                        for (int i = 0; i < LimitPath.Path.Count; i++)
                        {
                            var nodeinfo = LimitPath.Path[i];
                            var dir = (prevDir == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward);
                            LimitPath.DetailedPath.Add(new NodeMotorInfo(i + 1, nodeinfo.Id, nodeinfo.RfidId, dir));
                        }

                        // 시작 위치 및 방향 변경
                        // var org_start = startNode; // Unused
                        startNode = prevNode;
                        prevNode = LimitPath.Path.First(); // startNode (original)
                        prevDir = (prevDir == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward);
                    }
                    else
                    {
                        // 경로 생성 실패 시 보정 없이 진행하거나 에러 처리
                        // 여기서는 일단 기존 로직대로 진행
                    }
                }

                // 2. Buffer-to-Buffer 예외 처리
                // 05~31 구간 체크
                var node_buff_sta = _mapNodes.Where(t => t.StationType == Station.Buffer).OrderBy(s => s.RfidId).FirstOrDefault();// (n => n.RfidId == 5);
                var node_buff_end = _mapNodes.Where(t => t.StationType == Station.Buffer).OrderByDescending(s => s.RfidId).FirstOrDefault();//

                bool fixpath = false;
                Retval = null;
                MapNode gatewayNode = null;

                if (node_buff_sta != null && node_buff_end != null)
                {
                    // 버퍼 구간 경로 테스트
                    var rlt = this.FindPathAStar(node_buff_sta, node_buff_end);
                    if (rlt.Success)
                    {
                        // 버퍼구간내에 시작과 종료가 모두 포함되어있다
                        if (rlt.Path.Find(n => n.Id == startNode.Id) != null &&
                            rlt.Path.Find(n => n.Id == targetNode.Id) != null)
                        {
                            Retval = CalcPathBufferToBuffer(startNode, targetNode, prevNode, prevDir);
                            fixpath = true;
                        }
                    }
                }

                if (!fixpath)
                {


                    if (targetNode.StationType == Station.Lmt || targetNode.StationType == Station.Normal)
                    {
                        //일반노드라면 방향 무관하게 그냥 이동하게한다.
                        Retval = this.FindBasicPath(startNode, targetNode, prevNode, prevDir);
                    }
                    else
                    {

                        //목적지까지 그대로 방향을 계산한다.
                        var pathToTaget = this.FindBasicPath(startNode, targetNode, prevNode, prevDir);
                        if (pathToTaget.Success == false)
                            return AGVPathResult.CreateFailure($"Target({targetNode.ID2})까지 경로 실패: {pathToTaget.Message}");

                        // 방향을 확인하여, 왔던 방향으로 되돌아가야 한다면 방향 반전
                        //다음이동방향이 이전노드와 동일하다면? 되돌아가야한다는것이다
                        var predictNext = pathToTaget.Path[1];
                        if (predictNext.Id == prevNode.Id)
                        {
                            var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward;
                            foreach (var item in pathToTaget.DetailedPath)
                                item.MotorDirection = reverseDir;
                        }

                        if (targetNode.DockDirection != DockingDirection.DontCare)
                        {
                            Retval = pathToTaget;
                        }
                        //현재 진행방향과 목적지의 도킹방향이 일치하는지 확인한다.
                        else if ((pathToTaget.DetailedPath.Last().MotorDirection == AgvDirection.Backward && targetNode.DockDirection == DockingDirection.Backward) ||
                           (pathToTaget.DetailedPath.Last().MotorDirection == AgvDirection.Forward && targetNode.DockDirection == DockingDirection.Forward))
                        {
                            //일치하는 경우 그대로 사용하낟.
                            Retval = pathToTaget;
                        }
                        else
                        {
                            //불일치하는경우라면  Turn 가능노드를 찾아서 이동한 후 턴을 한다.

                            // 3. 목적지별 Turn 및 진입 조건 확인
                            gatewayNode = GetTurnNode(targetNode);


                            // Gateway Node 찾음
                            // 4. Start -> Gateway 경로 계산 (A*)
                            var pathToGateway = this.FindBasicPath(startNode, gatewayNode, prevNode, prevDir);
                            if (pathToGateway.Success == false)
                                return AGVPathResult.CreateFailure($"Gateway({gatewayNode.ID2})까지 경로 실패: {pathToGateway.Message}");

                            // 방향을 확인하여, 왔던 방향으로 되돌아가야 한다면 방향 반전
                            if (pathToGateway.Path.Count > 1)
                            {
                                //다음이동방향이 이전노드와 동일하다면? 되돌아가야한다는것이다
                                predictNext = pathToGateway.Path[1];
                                if (predictNext.Id == prevNode.Id)
                                {
                                    var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward;
                                    foreach (var item in pathToGateway.DetailedPath)
                                        item.MotorDirection = reverseDir;
                                }
                            }

                            // 마지막 경로는 게이트웨이이므로 제거 (Gateway 진입 후 처리는 GetPathFromGateway에서 담당)
                            if (pathToGateway.Path.Count > 0 && pathToGateway.Path.Last().Id == gatewayNode.Id)
                            {
                                var idx = pathToGateway.Path.Count - 1;
                                pathToGateway.Path.RemoveAt(idx);
                                pathToGateway.DetailedPath.RemoveAt(idx);
                            }

                            // 5. Gateway -> Target 경로 계산 (회차 패턴 및 최종 진입 포함)
                            MapNode GateprevNode = pathToGateway.Path.LastOrDefault() ?? prevNode;
                            NodeMotorInfo GatePrevDetail = pathToGateway.DetailedPath.LastOrDefault();

                            var arrivalOrientation = GatePrevDetail?.MotorDirection ?? prevDir;
                            var gatewayPathResult = GetPathFromGateway(gatewayNode, targetNode, GateprevNode, arrivalOrientation);

                            if (!gatewayPathResult.Success)
                                return AGVPathResult.CreateFailure($"{gatewayPathResult.Message}");

                            Retval = CombinePaths(pathToGateway, gatewayPathResult);
                        }
                    }
                }

                //게이트웨이
                Retval.Gateway = gatewayNode;

                // 경로 오류 검사
                if (Retval == null || Retval.Success == false) return Retval ?? AGVPathResult.CreateFailure("경로 계산 결과 없음");

                if (LimitPath != null)
                {
                    Retval = CombinePaths(LimitPath, Retval);
                }

                // 해당 경로와 대상의 도킹포인트 방향 검사
                if (targetNode.DockDirection != DockingDirection.DontCare)
                {
                    var lastPath = Retval.DetailedPath.LastOrDefault();
                    if (lastPath != null)
                    {
                        if (targetNode.DockDirection == DockingDirection.Forward && lastPath.MotorDirection != AgvDirection.Forward)
                        {
                            return AGVPathResult.CreateFailure($"생성된 경로와 목적지의 도킹방향이 일치하지 않습니다(FWD) Target:{targetNode.DockDirection}");
                        }
                        if (targetNode.DockDirection == DockingDirection.Backward && lastPath.MotorDirection != AgvDirection.Backward)
                        {
                            return AGVPathResult.CreateFailure($"생성된 경로와 목적지의 도킹방향이 일치하지 않습니다(BWD) Target:{targetNode.DockDirection}");
                        }
                    }
                }

                // 경로 최적화: A -> B -> A 패턴 제거
                // 6[F][R] → 13[B][L] → 6[F][L] 같은 경우 제거
                while (fixpath == false)
                {
                    var updatecount = 0;
                    for (int i = 0; i < Retval.DetailedPath.Count - 2; i++)
                    {
                        var n1 = Retval.DetailedPath[i];
                        var n2 = Retval.DetailedPath[i + 1];
                        var n3 = Retval.DetailedPath[i + 2];

                        if (n1.NodeId == n3.NodeId)
                        {
                            bool isInverse = false;
                            // 1. 모터 방향이 반대인가? (F <-> B)
                            bool isMotorInverse = (n1.MotorDirection != n2.MotorDirection) &&
                                                  (n1.MotorDirection == AgvDirection.Forward || n1.MotorDirection == AgvDirection.Backward) &&
                                                  (n2.MotorDirection == AgvDirection.Forward || n2.MotorDirection == AgvDirection.Backward);

                            if (isMotorInverse)
                            {
                                // 2. 마그넷 방향이 반대인가? (L <-> R, S <-> S)
                                bool isMagnetInverse = false;
                                if (n1.MagnetDirection == MagnetDirection.Straight && n2.MagnetDirection == MagnetDirection.Straight) isMagnetInverse = true;
                                else if (n1.MagnetDirection == MagnetDirection.Left && n2.MagnetDirection == MagnetDirection.Right) isMagnetInverse = true;
                                else if (n1.MagnetDirection == MagnetDirection.Right && n2.MagnetDirection == MagnetDirection.Left) isMagnetInverse = true;

                                if (isMagnetInverse) isInverse = true;
                            }

                            if (isInverse)
                            {
                                // 제자리 회귀 경로 발견 -> 앞의 두 노드(n1, n2)를 제거하여 n3만 남김
                                Retval.DetailedPath.RemoveAt(i);
                                Retval.DetailedPath.RemoveAt(i);

                                if (Retval.Path.Count > i + 1)
                                {
                                    Retval.Path.RemoveAt(i);
                                    Retval.Path.RemoveAt(i);
                                }
                                i--; // 인덱스 재조정
                                updatecount += 1;
                            }
                        }
                    }
                    if (updatecount == 0) break;
                }

                // 불가능한 회전 경로 검사 (사용자 요청 로직 반영)
                for (int i = 0; i < Retval.DetailedPath.Count - 2; i++)
                {
                    var n1 = Retval.DetailedPath[i];
                    var n2 = Retval.DetailedPath[i + 1];
                    var n3 = Retval.DetailedPath[i + 2];

                    if (n1.NodeId == n3.NodeId &&
                        n1.MotorDirection == n3.MotorDirection &&
                        n1.MotorDirection == n2.MotorDirection) // Fix: 중간 노드 방향도 같을 때만 에러
                    {
                        return AGVPathResult.CreateFailure($"불가능한 회전 경로가 포함되어있습니다. {n1.RfidId}->{n2.RfidId}->{n3.RfidId}");
                    }
                }

                // 기타 검증 로직 (마지막 노드 도킹, 시작노드 일치 등)
                var lastnode = Retval.Path.Last();
                if (lastnode.StationType != Station.Normal)
                {
                    var lastnodePath = Retval.DetailedPath.Last();
                    if (lastnode.DockDirection == DockingDirection.Forward && lastnodePath.MotorDirection != AgvDirection.Forward)
                        return AGVPathResult.CreateFailure($"목적지의 모터방향({lastnode.DockDirection}) 불일치 경로방향({lastnodePath.MotorDirection})");
                    if (lastnode.DockDirection == DockingDirection.Backward && lastnodePath.MotorDirection != AgvDirection.Backward)
                        return AGVPathResult.CreateFailure($"목적지의 모터방향({lastnode.DockDirection}) 불일치 경로방향({lastnodePath.MotorDirection})");
                }

                // 첫번째 노드 일치 검사 - 필요시 수행 (startNode가 변경될 수 있어서 o_StartNode 등 필요할 수도 있음)
                // 여기서는 생략 혹은 간단히 체크

                // 되돌아가는 길 방향 일치 검사
                if (Retval.DetailedPath.Count > 1)
                {
                    var FirstDetailPath = Retval.DetailedPath[0];
                    var NextDetailPath = Retval.DetailedPath[1];
                    AgvDirection? PredictNextDir = null;

                    if (NextDetailPath.NodeId == prevNode.Id)
                    {
                        if (NextDetailPath.MagnetDirection == MagnetDirection.Straight)
                            PredictNextDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward;
                    }

                    if (PredictNextDir != null && (FirstDetailPath.MotorDirection != (AgvDirection)PredictNextDir))
                    {
                        // return AGVPathResult.CreateFailure($"되돌아가는 길인데 방향이 일치하지않음");
                        // 경고 수준이나 무시 가능한 경우도 있음
                    }
                }

                // 연결성 검사
                for (int i = 0; i < Retval.DetailedPath.Count - 1; i++)
                {
                    var cnode = Retval.Path[i];
                    var nnode = Retval.Path[i + 1];

                    if (cnode.ConnectedNodes.Contains(nnode.Id) == false && cnode.Id != nnode.Id)
                    {
                        return AGVPathResult.CreateFailure($"[{cnode.RfidId}] 노드에 연결되지 않은 [{nnode.RfidId}]노드가 지정됨");
                    }
                }

                //각 도킹포인트별로 절대 움직이면 안되는 조건확인
                var firstnode = Retval.Path.FirstOrDefault();
                var firstDet = Retval.DetailedPath.First();
                var failmessage = $"[{firstnode.ID2}] 노드의 시작모터 방향({firstDet.MotorDirection})이 올바르지 않습니다";
                if (firstnode.StationType == Station.Charger && firstDet.MotorDirection != AgvDirection.Forward)
                    return AGVPathResult.CreateFailure(failmessage);
                else if (firstnode.StationType == Station.Loder && firstDet.MotorDirection != AgvDirection.Backward)
                    return AGVPathResult.CreateFailure(failmessage);
                else if (firstnode.StationType == Station.Cleaner && firstDet.MotorDirection != AgvDirection.Backward)
                    return AGVPathResult.CreateFailure(failmessage);
                else if (firstnode.StationType == Station.Plating && firstDet.MotorDirection != AgvDirection.Backward)
                    return AGVPathResult.CreateFailure(failmessage);
                else if (firstnode.StationType == Station.Buffer)
                {
                    //버퍼는 도킹이되어잇느닞 확인하고. 그때 방향을 체크해야한다.
                }


                return Retval;
            }
            catch (Exception ex)
            {
                return AGVPathResult.CreateFailure($"[계산오류] {ex.Message}");
            }
        }

        private AGVPathResult CalcPathBufferToBuffer(MapNode start, MapNode target, MapNode prev, AgvDirection prevDir)
        {
            // Monitor Side 판단 및 Buffer 간 이동 로직
            int deltaX = 0;
            int deltaY = 0;
            if (prev == null) return AGVPathResult.CreateFailure("이전 노드 정보가 없습니다");
            else
            {
                deltaX = start.Position.X - prev.Position.X;
                deltaY = -(start.Position.Y - prev.Position.Y);
            }

            if (Math.Abs(deltaY) > Math.Abs(deltaX))
                deltaX = deltaY;

            bool isMonitorLeft = false;

            if (deltaX > 0) // 오른쪽(Forward)으로 이동해 옴
                isMonitorLeft = (prevDir == AgvDirection.Backward);
            else if (deltaX < 0) // 왼쪽(Reverse)으로 이동해 옴
                isMonitorLeft = (prevDir == AgvDirection.Forward);
            else
                return AGVPathResult.CreateFailure("이전 노드와의 방향을 알 수 없습니다");

            if (isMonitorLeft)
            {
                // Monitor Left -> Gateway 탈출
                var GateWayNode = _mapNodes.FirstOrDefault(n => n.RfidId == 6);
                var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward;

                AGVPathResult escPath = null;
                if (start.Position.X > prev.Position.X)
                    escPath = this.FindBasicPath(start, GateWayNode, prev, prevDir);
                else
                    escPath = this.FindBasicPath(start, GateWayNode, prev, reverseDir);

                if (!escPath.Success) return AGVPathResult.CreateFailure("버퍼 탈출 경로 실패");

                var lastNode = escPath.Path.Last();
                var lastPrev = escPath.Path[escPath.Path.Count - 2];
                var lastDir = escPath.DetailedPath.Last().MotorDirection;

                var gateToTarget = GetPathFromGateway(GateWayNode, target, lastPrev, lastDir);

                escPath.Path.RemoveAt(escPath.Path.Count - 1);
                escPath.DetailedPath.RemoveAt(escPath.DetailedPath.Count - 1);

                return CombinePaths(escPath, gateToTarget);
            }
            else
            {
                // Monitor Right -> 직접 진입 또는 Overshoot
                bool isTargetLeft = target.Position.X < start.Position.X;

                if (target == start)
                {
                    // 제자리 재정렬 (Same as Simulator logic)
                    var list = new List<MapNode>();
                    var retval = AGVPathResult.CreateSuccess(list, new List<AgvDirection>(), 0, 0);
                    var resversedir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward;

                    retval.Path.Add(target);

                    if (deltaX < 0)
                    {
                        var nextNode = start.ConnectedMapNodes.Where(t => t.Id != prev.Id && t.StationType == Station.Buffer).FirstOrDefault();
                        if (nextNode != null)
                        {
                            retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, prevDir));
                            retval.Path.Add(nextNode);
                            var lastDefailt = retval.DetailedPath.Last();
                            retval.DetailedPath.Add(new NodeMotorInfo(lastDefailt.seq + 1, nextNode.Id, nextNode.RfidId, AgvDirection.Forward)
                            {
                                Speed = SpeedLevel.M,
                            });
                            retval.Path.Add(target);
                            retval.DetailedPath.Add(new NodeMotorInfo((retval.DetailedPath.Max(t => t.seq) + 1), target.Id, target.RfidId, AgvDirection.Forward));
                            retval.Path.Add(target);
                            retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, AgvDirection.Backward));
                        }
                        else
                        {
                            retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, resversedir));
                            retval.Path.Add(prev);
                            retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Last().seq + 1, prev.Id, prev.RfidId, prevDir)
                            {
                                Speed = SpeedLevel.M,
                            });
                            retval.Path.Add(target);
                            retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, prevDir));
                        }
                    }
                    else
                    {
                        retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, prevDir));
                        var nextNode = start.ConnectedMapNodes.Where(t => t.Id != prev.Id && t.StationType == Station.Buffer).FirstOrDefault();
                        retval.Path.Add(nextNode);
                        var lastDefailt = retval.DetailedPath.Last();
                        retval.DetailedPath.Add(new NodeMotorInfo(lastDefailt.seq + 1, nextNode.Id, nextNode.RfidId, AgvDirection.Backward)
                        {
                            Speed = SpeedLevel.L,
                        });
                        retval.Path.Add(target);
                        retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, AgvDirection.Backward));
                    }
                    return retval;
                }
                else if (isTargetLeft)
                {
                    return this.FindBasicPath(start, target, prev, AgvDirection.Backward);
                }
                else
                {
                    // Overshoot
                    var path1 = this.FindBasicPath(start, target, prev, AgvDirection.Forward);
                    if (path1.Path.Count < 2) return AGVPathResult.CreateFailure("Overshoot 경로 생성 실패");

                    var last = path1.Path.Last();
                    var lastD = path1.DetailedPath.Last();
                    path1.Path.RemoveAt(path1.Path.Count - 1);
                    path1.DetailedPath.RemoveAt(path1.DetailedPath.Count - 1);

                    path1.Path.Add(last);
                    path1.DetailedPath.Add(new NodeMotorInfo(lastD.seq + 1, lastD.NodeId, lastD.RfidId, AgvDirection.Backward)
                    {
                        Speed = SpeedLevel.L,
                    });

                    return path1;
                }
            }
        }

        private AGVPathResult GetPathFromGateway(MapNode GTNode, MapNode targetNode, MapNode PrevNode, AgvDirection PrevDirection)
        {
            AGVPathResult resultPath = null;
            var deltaX = GTNode.Position.X - PrevNode.Position.X;
            var isMonitorLeft = false;

            if (deltaX > 0) isMonitorLeft = PrevDirection == AgvDirection.Backward;
            else isMonitorLeft = PrevDirection == AgvDirection.Forward;

            if (targetNode.StationType == Station.Loder)
            {
                deltaX = GTNode.Position.Y - PrevNode.Position.Y;
                if (deltaX < 0) isMonitorLeft = PrevDirection == AgvDirection.Backward;
                else isMonitorLeft = PrevDirection == AgvDirection.Forward;
            }

            switch (targetNode.StationType)
            {
                case Station.Loder:
                case Station.Charger:
                case Station.Cleaner:
                case Station.Plating:
                case Station.Buffer:
                    var rlt1 = new AGVPathResult();
                    rlt1.Success = true;

                    //단순 경로를 찾는다
                    var motdir = targetNode.DockDirection == DockingDirection.Backward ? AgvDirection.Backward : AgvDirection.Forward;
                    var pathtarget = this.FindBasicPath(GTNode, targetNode, PrevNode, motdir);

                    if ((targetNode.DockDirection == DockingDirection.Backward && isMonitorLeft) ||
                        (targetNode.DockDirection == DockingDirection.Forward && !isMonitorLeft))
                    {
                        var turnPatterns = GetTurnaroundPattern(GTNode, targetNode);
                        if (turnPatterns == null || !turnPatterns.Any()) return new AGVPathResult { Success = false, Message = $"회차 패턴 없음: Dir {PrevDirection}" };

                        foreach (var item in turnPatterns)
                        {
                            var rfidvalue = ushort.Parse(item.Substring(0, 4));
                            var node = _mapNodes.FirstOrDefault(t => t.RfidId == rfidvalue);
                            rlt1.Path.Add(node);

                            AgvDirection nodedir = item.Substring(4, 1) == "F" ? AgvDirection.Forward : AgvDirection.Backward;
                            MagnetDirection magnet = MagnetDirection.Straight;
                            var magchar = item.Substring(5, 1);
                            if (magchar == "L") magnet = MagnetDirection.Left;
                            else if (magchar == "R") magnet = MagnetDirection.Right;

                            rlt1.DetailedPath.Add(new NodeMotorInfo(rlt1.DetailedPath.Count, node.Id, node.RfidId, nodedir, null, magnet)
                            {
                                Speed = SpeedLevel.L,
                            });
                        }

                        if (pathtarget.DetailedPath.First().NodeId != rlt1.DetailedPath.Last().NodeId ||
                            pathtarget.DetailedPath.First().MotorDirection != rlt1.DetailedPath.Last().MotorDirection)
                        {
                            // Gateway 턴 마지막 주소 불일치 경고 (로깅 등)
                        }

                        pathtarget.Path.RemoveAt(0);
                        pathtarget.DetailedPath.RemoveAt(0);
                    }
                    return CombinePaths(rlt1, pathtarget);

                default:
                    return AGVPathResult.CreateFailure($"지원되지 않는 StationType: {targetNode.StationType}");
            }
        }

        private MapNode GetTurnNode(MapNode node)
        {
            var rfid = 0;
            if (node.StationType == Station.Cleaner) rfid = 3;
            else if (node.StationType == Station.Charger) rfid = 3;
            else if (node.StationType == Station.Plating) rfid = 3;
            else if (node.StationType == Station.Loder) rfid = 3;
            else if (node.StationType == Station.Buffer) rfid = 3;

            if (rfid == 0) return null;
            return _mapNodes.FirstOrDefault(t => t.RfidId == rfid);
        }

        private List<string> GetTurnaroundPattern(MapNode gatewayNode, MapNode targetNode)
        {
            switch (gatewayNode.RfidId)
            {
                case 6:
                    if (targetNode.StationType == Station.Buffer)
                        return new List<string> { "0006BL", "0007FS", "0013BL", "0006BL" };
                    else
                        return new List<string> { "0006BL", "0007FS", "0013BL", "0006BS" };
                case 9: return new List<string> { "0009FL", "0010BS", "0007FL", "0009FS" };
                case 10: return new List<string> { "0010BR", "0009FR", "0007BS", "0010BS" };
                case 13: return new List<string> { "0013BL", "0006FL", "0007BS", "0013BS" };
                default: return null;
            }
        }

        private AGVPathResult CombinePaths(AGVPathResult p1, AGVPathResult p2)
        {
            var res = new AGVPathResult();
            res.Success = true;

            var p1last = p1.DetailedPath.LastOrDefault();
            var p2fist = p2.DetailedPath.FirstOrDefault();

            if (p1last != null && p2fist != null &&
                (p1last.NodeId == p2fist.NodeId && p1last.MotorDirection == p2fist.MotorDirection && p1last.MagnetDirection == p2fist.MagnetDirection))
            {
                p1.Path.RemoveAt(p1.Path.Count - 1);
                p1.DetailedPath.RemoveAt(p1.DetailedPath.Count - 1);
            }

            foreach (var item in p1.Path) res.Path.Add(item);
            foreach (var item in p2.Path) res.Path.Add(item);

            foreach (var item in p1.DetailedPath)
            {
                var maxseq = res.DetailedPath.Count == 0 ? 0 : res.DetailedPath.Max(t => t.seq);
                item.seq = maxseq + 1;
                res.DetailedPath.Add(item);
            }
            foreach (var item in p2.DetailedPath)
            {
                var maxseq = res.DetailedPath.Count == 0 ? 0 : res.DetailedPath.Max(t => t.seq);
                item.seq = maxseq + 1;
                res.DetailedPath.Add(item);
            }

            return res;
        }
    }
}