var gojs = (function () {
    "use strict";

    var myDiagram;
    let isLinkDeletion = false;
    let armsStateArray;

    function init() {
        // Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make
        // For details, see https://gojs.net/latest/intro/buildingObjects.html
        const $ = go.GraphObject.make; // for conciseness in defining templates
        myDiagram = new go.Diagram('myDiagramDiv', {
            allowCopy: false,
            layout: $(go.LayeredDigraphLayout, {
                setsPortSpots: false, // Links already know their fromSpot and toSpot
                columnSpacing: 5,
                isInitial: false,
                isOngoing: false,
            }),
            validCycle: go.CycleMode.NotDirected,
            'undoManager.isEnabled': true,
            "maxSelectionCount": 1,  // �ㅼ쨷 �좏깮 鍮꾪솢��
        });

        // when the document is modified, add a "*" to the title and enable the "Save" button
        myDiagram.addDiagramListener('Modified', (e) => {
            const button = document.getElementById('mapping_save_button');
            if (button) button.disabled = !myDiagram.isModified;
            const idx = document.title.indexOf('*');
            if (myDiagram.isModified) {
                if (idx < 0) document.title += '*';
            } else {
                if (idx >= 0) document.title = document.title.slice(0, idx);
            }
        });

        // const graygrad = $(go.Brush, 'Linear', { 0: 'white', 0.1: 'whitesmoke', 0.9: 'whitesmoke', 1: 'lightgray' });

        // �몃뱶 諛뺤뒪 諛곌꼍��
        const graygrad = $(go.Brush, 'Linear', {
            0: 'rgba(51, 51, 51, 0)',
            0.1: 'rgba(51, 51, 51, 0.1)',
            0.9: 'rgba(51, 51, 51, 0.9)',
            1: 'rgba(51, 51, 51, 1)'
        });

        // �몃뱶 諛뺤뒪 �뚮몢由�
        const lightGray = 'rgba(128, 128, 128, 0.5)'; // �먮┛ �뚯깋 (�щ챸�� 50%)

        myDiagram.nodeTemplate = // the default node template
            $(go.Node,
                { movable: false },
                'Spot',
                { selectionAdorned: false, textEditable: false, locationObjectName: 'BODY' },
                new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
                // {
                //     doubleClick: (e, node) => {
                //         // �붾툝 �대┃ �� �ㅽ뻾�� 硫붿냼�� �몄텧
                //         console.log(node.data.c_id);
                //         popup_modal('update_popup', node.data.c_id);
                //     }
                // },
                $(go.Panel,
                    'Auto',
                    { name: 'BODY' },
                    $(go.Shape,
                        'RoundedRectangle',
                        { fill: graygrad, stroke: lightGray, minSize: new go.Size(100, 30) },
                        new go.Binding('fill', 'isSelected', (s) => (s ? 'dodgerblue' : graygrad)).ofObject()
                    ),
                    $(go.Panel,
                        'Horizontal',
                        { alignment: go.Spot.Left, margin: 3 },
                        $(go.Picture, {
                            source: "/arms/img/arms.png",  // �꾩씠肄� �대�吏� 寃쎈줈
                            width: 20,
                            height: 20,
                            margin: new go.Margin(0, 0, 0, 4)
                        }),
                        $(go.TextBlock,
                        {
                                stroke: 'white',
                                font: '12px sans-serif',
                                margin: new go.Margin(3, 4, 3, 4), // �곸젅�� 留덉쭊 �ㅼ젙
                                alignment: go.Spot.Left,
                                editable: true,
                                textEdited: function(textBlock, oldText, newText) {
                                    // 以꾨컮轅� 臾몄옄瑜� �쒓굅
                                    textBlock.text = newText.replace(/\r?\n|\r/g, '');
                                    if (textBlock.text.trim() === "") {
                                        jNotify("鍮� 媛믪쑝濡쒕뒗 �곹깭紐낆쓣 蹂�寃쏀븷 �� �놁뒿�덈떎.");
                                        textBlock.text = oldText;
                                    }
                                },
                            },
                            new go.Binding('text').makeTwoWay()
                        ),
                        $(go.Shape, "Rectangle",  // �ㅽ럹�댁꽌 ��븷�� �� �щ챸�� �꾪삎 異붽�
                            {
                                stroke: null,
                                fill: "transparent",
                                stretch: go.GraphObject.Horizontal, // �⑤꼸�� �섎㉧吏� 怨듦컙�� 梨꾩썙�� 諛��대깂
                                width: 1, height: 1
                            }
                        ),
                        // $(go.Shape,
                        //     {
                        //         figure: "XLine",
                        //         width: 8,
                        //         height: 8,
                        //         stroke: "rgba(255, 255, 255, 0.45)",
                        //         strokeWidth: 2,  // �먭퍡
                        //         margin: new go.Margin(0, 0, 0, 0),
                        //         cursor: "pointer",
                        //         alignment: go.Spot.Right,
                        //         click: function(e, obj) {
                        //             // �곹깭 ��젣 �뺤씤 �앹뾽 �몄텧
                        //             const node = obj.part;
                        //             const state_name = node.data.text;
                        //             const state_c_id = node.data.c_id;
                        //             popup_modal('delete_popup', state_c_id, state_name);
                        //         }
                        //     }
                        // )
/*                        $(go.TextBlock,
                            {
                                stroke: 'white',
                                font: '12px sans-serif',
                                editable: true,
                                margin: new go.Margin(3, 4, 3, 4), // �곸젅�� 留덉쭊 �ㅼ젙
                                alignment: go.Spot.Left,
                            },
                            new go.Binding('text').makeTwoWay()
                        )*/
                    )
                ),
                // output port
                $(go.Panel,
                    'Auto',
                    { alignment: go.Spot.Right, portId: 'from', fromLinkable: true, cursor: 'pointer', click: (e, obj) => {
                            if (obj.part.data.category !== 'NoAdd') {
                                addNodeAndLink(e, obj);
                            }
                        }},
                    $(go.Shape, 'Diamond', { width: 11, height: 11, fill: 'white', stroke: graygrad, strokeWidth: 3 }),
                    // $(go.Shape, 'PlusLine', new go.Binding('visible', '', (data) => data.category !== 'NoAdd').ofObject(), { width: 11, height: 11, fill: null, stroke: 'dodgerblue', strokeWidth: 3 })
                ),
                // input port
                $(go.Panel,
                    'Auto',
                    { alignment: go.Spot.Left, portId: 'to', toLinkable: true },
                    $(go.Shape, 'Ellipse', { width: 9, height: 9, fill: 'white', stroke: graygrad, strokeWidth: 3 }),
                    $(go.Shape, 'Ellipse', { width: 6, height: 6, fill: 'white', stroke: null })
                )
            );

        // �고겢由� �대깽�� 由ъ뒪��
        myDiagram.addDiagramListener("ObjectContextClicked", function (e) {
            let element = e.subject.part;
            if (element) {
                let data = element.data;  // �좏깮�� �붿냼�� �곗씠��

                // �붿뒪 �곹깭留� 泥섎━
                if (data.type === "arms-state") {
                    let stateId = data.c_id;

                    // armsStateArray�먯꽌 �대떦 stateId媛� �덈뒗吏� �뺤씤
                    let foundState = armsStateArray.find(item => item.c_id == stateId);

                    if (foundState) {
                        console.log("State found in armsStateArray:", foundState);

                        let contextMenu = element.part.contextMenu;
                        let deleteNodeButton = contextMenu ? contextMenu.findObject("deleteNodeButton") : null;
                        let buttonShape = deleteNodeButton ? deleteNodeButton.findObject("buttonShape") : null;

                        if (!deleteNodeButton || !buttonShape) return;  // 踰꾪듉�� �놁쑝硫� 醫낅즺

                        // ��젣 踰꾪듉 鍮꾪솢�깊솕 �먮뒗 �쒖꽦��
                        if (foundState.c_check === "true") {
                            disableButton(deleteNodeButton, "deleteText");
                        } else {
                            enableButton(deleteNodeButton, deleteNode, "deleteText");
                        }
                    }
                }
                else if (element instanceof go.Link) {
                    let data = element.data;
                    let contextMenu = element.part.contextMenu;
                    let deleteLinkButton = contextMenu ? contextMenu.findObject("deleteLinkButton") : null;
                    if (!deleteLinkButton) return;  // 踰꾪듉�� �놁쑝硫� 醫낅즺

                    const isDisableButton = data.toNode.type === "arms-state" && data.toNode.c_check === "true";

                    if (isDisableButton) {
                        disableButton(deleteLinkButton, "deleteLinkText");
                    } else {
                        enableButton(deleteLinkButton, deleteLinkClick, "deleteLinkText");
                    }
                }

            }
        });

        // 踰꾪듉 鍮꾪솢�깊솕
        function disableButton(button, textObjectName) {
            button.click = null;  // �대┃ 鍮꾪솢�깊솕
            let textBlock = button.findObject(textObjectName);
            if (textBlock) {
                textBlock.stroke = "gray";  // �띿뒪�� 鍮꾪솢�깊솕 �됱긽
            }

            button.mouseEnter = null;
            button.mouseLeave = null;
        }

        // 踰꾪듉 �쒖꽦��
        function enableButton(button, clickHandler, textObjectName) {
            button.click = clickHandler;  // �대┃ �쒖꽦��
            let textBlock = button.findObject(textObjectName);
            if (textBlock) {
                textBlock.stroke = "white";  // �쒖꽦�붾맂 �띿뒪�� �됱긽
            }

            button.mouseEnter = (e, obj) => {
                let shape = obj.findObject("buttonShape");
                if (shape) shape.fill = "dodgerblue";  // 留덉슦�� �몃쾭 �� �됱긽
            };

            button.mouseLeave = (e, obj) => {
                let shape = obj.findObject("buttonShape");
                if (shape) shape.fill = "rgba(51, 51, 51, 0.8)";  // 湲곕낯 諛곌꼍 �됱긽
            };
        }

        myDiagram.nodeTemplate.contextMenu =
            $(go.Adornment, "Auto",
                $(go.Panel, "Vertical",
                    $("ContextMenuButton",
                        $(go.Shape, "Rectangle", {
                            fill: "rgba(51, 51, 51, 0.8)",
                            stroke: null,
                            name: "buttonShape",
                            width: 50,
                            height: 20,
                        }),
                        $(go.TextBlock, "�섏젙",
                            {
                                font: "12px",
                                stroke: "white",
                                margin: new go.Margin(0, 3, 0, 3),
                            }
                        ),
                        {
                            click: renameNode,
                            "ButtonBorder.stroke": null,
                            "ButtonBorder.fill": "rgba(51, 51, 51, 0.8)",
                            mouseEnter: (e, obj) => {
                                let shape = obj.findObject("buttonShape");
                                if (shape) shape.fill = "dodgerblue";
                            },
                            mouseLeave: (e, obj) => {
                                let shape = obj.findObject("buttonShape");
                                if (shape) shape.fill = "rgba(51, 51, 51, 0.8)";
                            }
                        }
                    ),
                    $("ContextMenuButton",
                        $(go.Shape, "Rectangle", {
                            fill: "rgba(51, 51, 51, 0.8)",
                            stroke: null,
                            name: "buttonShape",
                            width: 50,
                            height: 20
                        }),
                        $(go.TextBlock, "��젣",
                            {
                                name: "deleteText",
                                font: "12px",
                                stroke: "white",
                                margin: new go.Margin(0, 3, 0, 3),
                            }
                        ),
                        {
                            name: "deleteNodeButton",
                            click: deleteNode,
                            "ButtonBorder.stroke": null,
                            "ButtonBorder.fill": "rgba(51, 51, 51, 0.8)",
                            mouseEnter: (e, obj) => {
                                let shape = obj.findObject("buttonShape");
                                if (shape) shape.fill = "dodgerblue";
                            },
                            mouseLeave: (e, obj) => {
                                let shape = obj.findObject("buttonShape");
                                if (shape) shape.fill = "rgba(51, 51, 51, 0.8)";
                            }
                        }
                    )
                )
            );

        function renameNode(e, obj) {
            const node = obj.part.adornedPart;
            if (node) {
                console.log(node.data.c_id);
                let state_c_id = node.data.c_id;
                popup_modal("update_popup", state_c_id);
            }
        }

        function deleteNode(e, obj) {
            const node = obj.part.adornedPart;
            if (node) {
                let state_c_id = node.data.c_id;
                let state_name = node.data.text;
                console.log(state_name);

                popup_modal("delete_popup", state_c_id, state_name);
            }
        }

        // ARMS 移댄뀒怨좊━ �몃뱶 �ㅼ젙
        myDiagram.nodeTemplateMap.add(
            'Loading',
            $(go.Node,
                { movable: false },
                'Spot',
                { selectionAdorned: false, textEditable: true, locationObjectName: 'BODY' },
                new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
                // the main body consists of a Rectangle surrounding the text
                $(go.Panel,
                    'Auto',
                    { name: 'BODY' },
                    $(go.Shape,
                        'RoundedRectangle',
                        { fill: graygrad, stroke: lightGray, minSize: new go.Size(100, 30) },
                        new go.Binding('fill', 'isSelected', (s) => (s ? 'dodgerblue' : graygrad)).ofObject()
                    ),
                    $(go.Panel,
                        'Horizontal',
                        { alignment: go.Spot.Left, margin: 3 },
                        /*$(go.Picture, {
                            source: "/arms/img/arms.png",  // �꾩씠肄� �대�吏� 寃쎈줈
                            width: 20,
                            height: 20,
                            margin: new go.Margin(0, 0, 0, 0)
                        }),
                        $(go.TextBlock,
                            {
                                stroke: 'white',
                                font: '12px sans-serif',
                                editable: false,
                                margin: new go.Margin(3, 4, 3, 4), // �곸젅�� 留덉쭊 �ㅼ젙
                                alignment: go.Spot.Left,
                            },
                            new go.Binding('text').makeTwoWay()
                        )*/
                        $(go.TextBlock,
                            // 移댄뀒怨좊━ 蹂� �꾩씠肄� �ㅼ젙(�섎뱶肄붾뵫)
                            {
                                font: "12px FontAwesome, sans-serif",  // �꾩씠肄섏뿉 FontAwesome �ъ슜
                                margin: new go.Margin(0, 8, 0, 8),  // �꾩씠肄섍낵 �띿뒪�� �ъ씠�� 媛꾧꺽 �ㅼ젙
                            },
                            new go.Binding("text", "", function(data) {
                                let icon = "";
                                if (data.c_id === "3") {
                                    icon = "��";
                                }
                                else if (data.c_id === "4") {
                                    icon = "��";
                                }
                                else if (data.c_id === "5") {
                                    icon = "��";
                                }
                                else if (data.c_id === "6") {
                                    icon = "��";
                                }
                                return "   " + icon;
                            }),
                            // 移댄뀒怨좊━ 蹂� �됱긽 �ㅼ젙
                            new go.Binding("stroke", "", function(data) {
                                let color = "white";
                                if (data.c_id === "3") {
                                    color = "#DB2A34";
                                }
                                else if (data.c_id === "4") {
                                    color = "#E49400";
                                }
                                else if (data.c_id === "5") {
                                    color = "#2D8515";
                                }
                                else if (data.c_id === "6") {
                                    color = "#2477FF";
                                }
                                return color;
                            })
                        ),
                        // 移댄뀒怨좊━紐�
                        $(go.TextBlock,
                            {
                                stroke: "white",  // �띿뒪�� �됱긽
                                font: "12px sans-serif"
                            },
                            new go.Binding("text", "", function(data) {
                                return " " + data.text;
                            })
                        )
                    )
                ),
                // output port
                $(go.Panel,
                    'Auto',
                    { alignment: go.Spot.Right, portId: 'from', fromLinkable: true, cursor: 'pointer', click: (e, obj) => {
                            if (obj.part.data.category !== 'NoAdd') {
                                addNodeAndLink(e, obj);
                            }
                        }},
                    $(go.Shape, 'Diamond', { width: 11, height: 11, fill: 'white', stroke: graygrad, strokeWidth: 3 }),
                    // $(go.Shape, 'PlusLine', new go.Binding('visible', '', (data) => data.category !== 'Loading').ofObject(), { width: 11, height: 11, fill: null, stroke: 'dodgerblue', strokeWidth: 3 })
                ),
            )
        );

        myDiagram.nodeTemplateMap.add(
            'End',
            $(go.Node,
                { movable: false },
                'Spot',
                { selectionAdorned: false, textEditable: true, locationObjectName: 'BODY' },
                new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
                // the main body consists of a Rectangle surrounding the text
                $(go.Panel,
                    'Auto',
                    { name: 'BODY' },
                    $(go.Shape,
                        'RoundedRectangle',
                        { fill: graygrad, stroke: lightGray, minSize: new go.Size(100, 30) },
                        new go.Binding('fill', 'isSelected', (s) => (s ? 'dodgerblue' : graygrad)).ofObject()
                    ),
                    $(go.Panel,
                        'Horizontal',
                        { alignment: go.Spot.Left, margin: 3 },
                        $(go.Picture,
                            {
                                width: 20,
                                height: 20,
                                margin: new go.Margin(0, 0, 0, 4)
                            },
                            new go.Binding('source', 'server_type', function(type) {
                                console.log(type);
                                switch(type) {
                                    case '�대씪�곕뱶': return '/arms/img/commonIconPack/jira/mark-gradient-white-jira.svg';
                                    case '�⑦봽�덈���': return '/arms/img/commonIconPack/jira/mark-gradient-blue-jira.svg';
                                    case '�덈뱶留덉씤_�⑦봽�덈���': return '/arms/img/community_devtool/redmine_logo.png';
                                    default: return '';
                                }
                            })
                        ),
                        $(go.TextBlock,
                            {
                                stroke: 'white',
                                font: '12px sans-serif',
                                editable: false,
                                margin: new go.Margin(3, 3 + 11, 3, 3 + 4),
                                alignment: go.Spot.Left,
                            },
                            new go.Binding('text', 'text')
                        )
                    )
                ),
                // input port
                $(go.Panel,
                    'Auto',
                    { alignment: go.Spot.Left, portId: 'to', toLinkable: true },
                    $(go.Shape, 'Ellipse', { width: 9, height: 9, fill: 'white', stroke: graygrad, strokeWidth: 3 }),
                    $(go.Shape, 'Ellipse', { width: 6, height: 6, fill: 'white', stroke: null })
                )
            )
        );

        // �몃뱶 �곕젅湲고넻 �곸뿭 二쇱꽍
        /*myDiagram.nodeTemplateMap.add(
            'Recycle',
            $(go.Node,
                'Auto',
                {
                    portId: 'to',
                    toLinkable: true,
                    deletable: false,
                    layerName: 'Background',
                    locationSpot: go.Spot.Center,
                },
                new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
                {
                    dragComputation: (node, pt, gridpt) => pt,
                    mouseDrop: (e, obj) => e.diagram.commandHandler.deleteSelection(),
                },
                $(go.Shape, { fill: 'lightgray', stroke: 'gray' }),
                $(go.TextBlock, 'Drop Here\nTo Delete', { margin: 5, textAlign: 'center' })
            )
        );*/

        function addNodeAndLink(e, obj) {
            const fromNode = obj.part;
            const diagram = fromNode.diagram;
            diagram.startTransaction('Add State');
            // get the node data for which the user clicked the button
            const fromData = fromNode.data;
            const category = fromData.category;
            const c_id = fromData.c_id;
            const type = fromData.type;

            if (category === "NoAdd" || category === "Loading") {
                // NoAdd, Loading 移댄뀒怨좊━ �몃뱶�� 寃쎌슦 Node�� �앹꽦 紐삵븯�꾨줉 泥섎━
                diagram.commitTransaction('Add Node');
                return;
            }
            // else {
            //     if (!confirm(fromData.text + " 移댄뀒怨좊━ �곹깭瑜� 異붽��섏떆寃좎뒿�덇퉴?")) {
            //         return;
            //     }
            // }

            // create a new "State" data object, positioned off to the right of the fromNode
            const p = fromNode.location.copy();
            p.x += diagram.toolManager.draggingTool.gridSnapCellSize.width;
/*            const toData = {
                text: 'new',
                loc: go.Point.stringify(p),
            };*/

            let toData;

            if (type === 'arms-category') {
                toData = {
                    key: 'arms-state-' + (diagram.model.nodeDataArray.length + 1),
                    text: '�좉퇋 �곹깭',
                    isNew: true,
                    type: 'arms-state',
                    mapping_id: fromData.c_id,
                    category: 'NoAdd',
                    loc: go.Point.stringify(p),
                };
            }
            else {
                toData = {
                    text: '�좉퇋 �곹깭',
                    isNew: true,
                    loc: go.Point.stringify(p),
                };
            }

            // add the new node data to the model
            const model = diagram.model;
            model.addNodeData(toData);
            // create a link data from the old node data to the new node data
            const linkdata = {
                from: model.getKeyForNodeData(fromData),
                to: model.getKeyForNodeData(toData),
                isNew: true,
            };

            // and add the link data to the model
            model.addLinkData(linkdata);
            // select the new Node
            const newnode = diagram.findNodeForData(toData);
            diagram.select(newnode);
            // snap the new node to a valid location
            newnode.location = diagram.toolManager.draggingTool.computeMove(newnode, p);
            // then account for any overlap
            shiftNodesToEmptySpaces();
            diagram.commitTransaction('Add State');
        }

        // Highlight ports when they are targets for linking or relinking.
        let OldTarget = null; // remember the last highlit port
        function highlight(port) {
            if (OldTarget !== port) {
                lowlight(); // remove highlight from any old port
                OldTarget = port;
                port.scale = 1.3; // highlight by enlarging
            }
        }
        function lowlight() {
            // remove any highlight
            if (OldTarget) {
                OldTarget.scale = 1.0;
                OldTarget = null;
            }
        }

        // ARMS �곹깭 �몃뱶 �띿뒪�� �몄쭛 �대깽�� 由ъ뒪�� 異붽�
        myDiagram.addDiagramListener('TextEdited', (e) => {
            const text_block = e.subject;  // �몄쭛�� �띿뒪�� 釉붾줉
            const node = text_block.part;  // �띿뒪�� 釉붾줉�� �ы븿�� �몃뱶
            if (node !== null) {
                const edited_text = text_block.text;
                const state_c_id = node.data.c_id;
                const state_category_mapping_id = node.data.mapping_id;
                console.log(node.data.c_id);
                console.log('Edited text: ', edited_text);

                // �곹깭紐� 蹂�寃� �몄텧
                update_arms_state(state_c_id, state_category_mapping_id, edited_text, null)
                    .then((result) => {
                        // API �몄텧 寃곌낵瑜� 泥섎━�⑸땲��.
                        jSuccess("�곹깭紐� 蹂�寃쎌씠 �꾨즺�섏뿀�듬땲��.");
                        console.log(result);
                    })
                    .catch((error) => {
                        console.error('Error fetching data:', error);
                        return;
                    });
            }
        });

        // �곌껐�� Point to Point �곌껐 �대깽�� 泥섎━
        myDiagram.addDiagramListener('LinkDrawn', (e) => {
            const link = e.subject;
            const from_node = link.fromNode;
            const to_node = link.toNode;

            link.data.fromNode = from_node.data;
            link.data.toNode = to_node.data;
            link.data.oldFromNode = from_node.data;
            link.data.oldToNode = to_node.data;

            // ARMS �곹깭 �몃뱶�먯꽌 ALM �곹깭濡� �대� �곌껐�� �덈뒗 寃쎌슦
            if (from_node.category === "NoAdd" && from_node.findLinksOutOf().count > 1) {
                const existing_link = from_node.findLinksOutOf().first();   //
                jNotify(from_node.data.text + "�� �대� �곌껐�� ALM �곹깭媛� �덉뒿�덈떎. �곌껐 ��젣 �� �곌껐 �댁<�몄슂.");
                isLinkDeletion = false;
                myDiagram.remove(link);
                return;
            }

            // ALM �곹깭 �몃뱶媛� �대� �곌껐�� ARMS �곹깭媛� �덈뒗 寃쎌슦
            if (to_node.category === 'End' && to_node.findLinksInto().count > 1) {
                const existing_link = to_node.findLinksInto().first();
                jNotify("ALM �곹깭(" + to_node.data.text + ")�� �대� "+ existing_link.data.fromNode.text +" �곹깭�� �곌껐�섏뼱�덉뒿�덈떎.");
                isLinkDeletion = false;
                myDiagram.remove(link);
                return;
            }

            // ARMS �곹깭 - ARMS �곹깭 �곌껐 留됯린
            if (from_node.category === 'NoAdd' && to_node.category === "NoAdd") {
                jNotify("ARMS �곹깭�쇰━�� �곌껐�� �� �놁뒿�덈떎.");
                isLinkDeletion = false;
                myDiagram.remove(link);
                return;
            }

            // ARMS �곹깭�� �곹깭 移댄뀒怨좊━ 1媛쒖뿉留� �곌껐 媛���
            if (to_node.category === "NoAdd" && to_node.findLinksInto().count > 1) {
                const existing_link = to_node.findLinksInto().first();
                jNotify(to_node.data.text + " �곹깭�� " + existing_link.data.fromNode.text +" 移댄뀒怨좊━媛� 吏��� �섏뼱�덉뒿�덈떎.");
                isLinkDeletion = false;
                myDiagram.remove(link);
                return;
            }

            // 移댄뀒怨좊━ - ALM �곹깭 �곌껐 留됯린
            if (from_node.category === 'Loading' && to_node.category === "End") {
                jNotify("移댄뀒怨좊━�� ARMS �곹깭�먮쭔 �곌껐�� �� �덉뒿�덈떎.");
                isLinkDeletion = false;
                myDiagram.remove(link);
                return;
            }

            let update_c_id = to_node.data.c_id;
            let update_mapping_id = from_node.data.c_id;

            if (to_node.data.type === "arms-state") {
                update_arms_state(update_c_id, update_mapping_id, null, null)
                    .then((result) => {
                        console.log(result);
                        jSuccess(to_node.data.text + " �곹깭媛� " + from_node.data.text + " 移댄뀒怨좊━�� �곌껐�섏뿀�듬땲��.");
                    })
                    .catch((error) => {
                        console.error('Error fetching data:', error)
                        myDiagram.remove(link);
                        return;
                    });
            }
            else if (to_node.data.type === "alm-status") {
                update_alm_status(update_c_id, update_mapping_id)
                    .then((result) => {
                        console.log(result);
                        jSuccess("ALM �곹깭(" + to_node.data.text + ")媛� " + from_node.data.text + " �곹깭�� �곌껐�섏뿀�듬땲��.");
                    })
                    .catch((error) => {
                        console.error('Error fetching data:', error);
                        myDiagram.remove(link);
                        return;
                    });
            }

            lowlight();
        });

        // �곌껐 �쒕옒洹몃줈 �곌껐�� 洹몃━�� �대깽�� 泥섎━
        myDiagram.addDiagramListener('LinkRelinked', (e) => {
            console.log(e);
            const link = e.subject;
            const from_node = link.fromNode;
            const to_node = link.toNode;

            // �쒕옒洹� �� 留곹겕 �곗씠�� 異붽�
            const old_from_node = link.data.oldFromNode;
            const old_to_node = link.data.oldToNode;

            // �좏슚�� 寃��ъ뿉 �ㅽ뙣�� 寃쎌슦 留곹겕瑜� �쒓굅�섍퀬 湲곗〈 �곹깭濡� 蹂듭썝
            const remove_new_link_and_restore = () => {
                isLinkDeletion = false;
                myDiagram.remove(link);
                if (old_from_node && old_to_node) {
                    const newLinkData = {
                        from: old_from_node.key,
                        to: old_to_node.key,
                        fromNode: old_from_node,
                        toNode: old_to_node,
                        oldFromNode: old_from_node,
                        oldToNode: old_to_node,
                    };
                    myDiagram.model.addLinkData(newLinkData);
                }
            };

            // ARMS �곹깭 �몃뱶�먯꽌 ALM �곹깭濡� �대� �곌껐�� �덈뒗 寃쎌슦
            if (from_node.category === "NoAdd" && from_node.findLinksOutOf().count > 1) {
                const existing_link = from_node.findLinksOutOf().first();   //
                jNotify(from_node.data.text + "�� �대� �곌껐�� ALM �곹깭媛� �덉뒿�덈떎. �곌껐 ��젣 �� �곌껐 �댁<�몄슂.");
                remove_new_link_and_restore();
                return;
            }

            // ALM �곹깭 �몃뱶媛� �대� �곌껐�� ARMS �곹깭媛� �덈뒗 寃쎌슦
            if (to_node.category === 'End' && to_node.findLinksInto().count > 1) {
                const existing_link = to_node.findLinksInto().first();
                jNotify("ALM �곹깭(" + to_node.data.text + ")�� �대� "+ existing_link.data.fromNode.text +" �곹깭�� �곌껐�섏뼱�덉뒿�덈떎.");
                remove_new_link_and_restore();
                return;
            }

            // ARMS �곹깭 - ARMS �곹깭 �곌껐 留됯린
            if (from_node.category === 'NoAdd' && to_node.category === "NoAdd") {
                jNotify("ARMS �곹깭�쇰━�� �곌껐�� �� �놁뒿�덈떎.");
                isLinkDeletion = false;
                myDiagram.remove(link);
                return;
            }

            // ARMS �곹깭�� �곹깭 移댄뀒怨좊━ 1媛쒖뿉留� �곌껐 媛���
            if (to_node.category === "NoAdd" && to_node.findLinksInto().count > 1) {
                const existing_link = to_node.findLinksInto().first();
                jNotify(to_node.data.text + " �곹깭�� " + existing_link.data.fromNode.text +" 移댄뀒怨좊━媛� 吏��� �섏뼱�덉뒿�덈떎.");
                remove_new_link_and_restore();
                return;
            }

            // 移댄뀒怨좊━ - ALM �곹깭 �곌껐 留됯린
            if (from_node.category === 'Loading' && to_node.category === "End") {
                jNotify("移댄뀒怨좊━�� ARMS �곹깭�먮쭔 �곌껐�� �� �덉뒿�덈떎.");
                remove_new_link_and_restore();
                return;
            }

            // �쒕옒洹� �� �쒕∼�쇰줈 �곌껐�� 蹂�寃� 泥섎━�� 寃쎌슦 湲곗〈 �곌껐 ��젣 update 泥섎━
            let old_c_id = old_to_node.c_id;
            let old_mapping_id = null;
            // let old_mapping_id = old_from_node.c_id;

            if (old_to_node.type === "arms-state") {
                // 湲곗〈 ARMS �곹깭 珥덇린��
                update_arms_state(old_c_id, old_mapping_id, null, null)
                    .then((result) => {
                        // API �몄텧 寃곌낵瑜� 泥섎━�⑸땲��.
                        console.log(result);
                    })
                    .catch((error) => {
                        console.error('Error fetching data:', error);
                        remove_new_link_and_restore();
                        return;
                    });
            }
            else if (old_to_node.type === "alm-status") {
                // 湲곗〈 ALM �곹깭 珥덇린��
                update_alm_status(old_c_id, old_mapping_id)
                    .then((result) => {
                        console.log(result);
                    })
                    .catch((error) => {
                        console.error('Error fetching data:', error);
                        remove_new_link_and_restore();
                        return;
                    });
            }

            // �쒕옒洹� �� �쒕∼�쇰줈 �곌껐�� 蹂�寃� 泥섎━�� 寃쎌슦 �덈줈�� �곌껐 link update 泥섎━
            let c_id = to_node.data.c_id;
            let mapping_id = from_node.data.c_id;

            if (to_node.data.type === "arms-state") {
                update_arms_state(c_id, mapping_id, null, null)
                    .then((result) => {
                        // API �몄텧 寃곌낵瑜� 泥섎━�⑸땲��.
                        jSuccess(to_node.data.text + " �곹깭媛� " + from_node.data.text + "移댄뀒怨좊━�� �곌껐�섏뿀�듬땲��.");
                        console.log(result);
                    })
                    .catch((error) => {
                        console.error('Error fetching data:', error);
                        remove_new_link_and_restore();
                        return;
                    });
            }
            else if (to_node.data.type === "alm-status") {
                console.log("ALM �곹깭 留ㅽ븨 蹂�寃�");
                update_alm_status(c_id, mapping_id)
                    .then((result) => {
                        jSuccess("ALM �곹깭(" + to_node.data.text + ")媛� " + from_node.data.text + " �곹깭�� �곌껐�섏뿀�듬땲��.");
                        console.log(result);
                    })
                    .catch((error) => {
                        console.error('Error fetching data:', error);
                        remove_new_link_and_restore();
                        return;
                    });
            }

            // old data 理쒖떊��
            link.data.oldFromNode = from_node.data;
            link.data.oldToNode = to_node.data;
        });

        myDiagram.commandHandler.canDeleteSelection = function() {

            // �좏깮�� �곗씠�� (�ㅼ쨷 �좏깮�� 鍮꾪솢�깊솕�섏뼱 �덉뼱 ��긽 �섎굹留� �좏깮��)
            const selectedNode = myDiagram.selection.first();

            // �좏깮�� �곗씠�곌� Node ���낆씪 ��
            if (selectedNode instanceof go.Node) {
                isLinkDeletion = false; // Link ��젣媛� �꾨땶 Node ��젣濡� 泥섎━

                // 移댄뀒怨좊━ �닿굅��, ALM �곹깭�� 寃쎌슦
                if (selectedNode && (selectedNode.data.type === "arms-category" || selectedNode.data.type === 'alm-status')) {
                    // ��젣 紐삵븯�꾨줉 泥섎━
                    let node_type = selectedNode.data.type === "arms-category" ? "移댄뀒怨좊━" : "ALM �곹깭";
                    alert(`${node_type} �좏삎�� �몃뱶�� ��젣�� �� �놁뒿�덈떎.`);
                    return false;
                }
                // ARMS�� �곹깭�� 寃쎌슦
                else if (selectedNode && (selectedNode.data.type === "arms-state")) {
                    // ��젣�좎� �щ��� ���� �뚮┝ 李� 異붽�
                    const state_name = selectedNode.data.text;
                    const state_c_id = selectedNode.data.c_id;

                    let foundState = armsStateArray.find(item => item.c_id === selectedNode.data.c_id);

                    if (!(foundState && foundState.c_check === "true")) {
                        popup_modal('delete_popup', state_c_id, state_name);
                        return;
                    }
                    alert('湲곕낯 �곹깭�� ��젣�� �� �놁뒿�덈떎.');
                    return false;
                    /*if (!confirm( state_name + " �곹깭瑜� ��젣�섏떆寃좎뒿�덇퉴?")) {
                        return false;
                    }
                    else {
                        remove_arms_state(state_c_id, state_name)
                            .then((result) => {
                                console.log(result);
                            })
                            .catch((error) => {
                                console.error('Error fetching data:', error);
                                return false;
                            });
                    }*/
                }
            }

            // 湲곕낯 ��젣 �숈옉 �섑뻾
            return go.CommandHandler.prototype.canDeleteSelection.call(this);
        };

        myDiagram.addDiagramListener("SelectionDeleting", function(e) {
            const selectedNode = e.diagram.selection.first();

            // �곌껐 �좏깮 �� ��젣 �뺤씤 �뚮┝ 李� �⑤룄濡� 泥섎━瑜� �꾪븳 �뚮옒洹�
            if (selectedNode instanceof go.Node) {
                isLinkDeletion = false;
            }
            else if (selectedNode instanceof go.Link) {
                isLinkDeletion = true;
            }
        });

        // �곗씠�� 蹂�寃� �대깽�� 由ъ뒪��
        myDiagram.addModelChangedListener(function(e) {
            // ��젣 �대깽�� 泥섎━(Remove)
            if (e.change === go.ChangedEvent.Remove) {
                // �곌껐(留곹겕)留� ��젣 - �몃뱶�� ��젣�� 寃쎌슦�� �곌껐�� �먮룞��젣
                if (e.propertyName === 'linkDataArray' && isLinkDeletion) {
                    const removedLinkData = e.oldValue;     // 湲곗〈 �곌껐
                    if (removedLinkData.toNode.type === "arms-state" && removedLinkData.toNode.c_check === "true") {
                        alert("湲곕낯 �곹깭 �곌껐�� ��젣�� �� �놁뒿�덈떎.");
                        myDiagram.model.addLinkData(removedLinkData);
                        return;
                    }

                    if (!confirm("�대떦 �곌껐�� ��젣�섏떆寃좎뒿�덇퉴?")) {
                        // ��젣 痍⑥냼 �� 湲곗〈 �곌껐 �щ벑濡�
                        myDiagram.model.addLinkData(removedLinkData);
                        return;
                    }

                    let c_id = removedLinkData.toNode.c_id;

                    if (removedLinkData.toNode.type === "arms-state") {
                        // amrs �곹깭 c_id�� ���� 留ㅽ븨 媛� null 泥섎━
                        let state_category_mapping_id = null;
                        update_arms_state(c_id, state_category_mapping_id, null, null)
                            .then((result) => {
                                // API �몄텧 寃곌낵瑜� 泥섎━�⑸땲��.
                                console.log(result);
                            })
                            .catch((error) => {
                                // �ㅻ쪟媛� 諛쒖깮�� 寃쎌슦 泥섎━�⑸땲��.
                                console.error('Error fetching data:', error);
                                myDiagram.model.addLinkData(removedLinkData);
                                return;
                            });
                    }
                    else if (removedLinkData.toNode.type === "alm-status") {
                        console.log("alm �곹깭 留곹겕 ��젣");
                        update_alm_status(c_id, null)
                            .then((result) => {
                                // API �몄텧 寃곌낵瑜� 泥섎━�⑸땲��.
                                console.log(result);
                            })
                            .catch((error) => {
                                // �ㅻ쪟媛� 諛쒖깮�� 寃쎌슦 泥섎━�⑸땲��.
                                console.error('Error fetching data:', error);
                                myDiagram.model.addLinkData(removedLinkData);
                                return;
                            });
                    }
                }
            }
        });

        myDiagram.linkTemplate = $(go.Link,
            {
                selectionAdorned: false,
                fromPortId: 'from',
                toPortId: 'to',
                relinkableTo: true,
                contextMenu:  // �곌껐 ��젣 �고겢由� 硫붾돱 異붽�
                    $(go.Adornment, "Auto",
                        $(go.Panel, "Vertical",
                            $("ContextMenuButton",
                                $(go.Shape, "Rectangle", {
                                    fill: "rgba(51, 51, 51, 0.8)",
                                    stroke: null,
                                    name: "buttonShape",
                                    width: 50,
                                    height: 20
                                }),
                                $(go.TextBlock, "��젣",
                                    {
                                        name: "deleteLinkText",
                                        font: "12px",
                                        stroke: "white",
                                        margin: new go.Margin(0, 0, 0, 0),
                                    }
                                ),
                                {
                                    name: "deleteLinkButton",
                                    click: deleteLinkClick,
                                    "ButtonBorder.stroke": null,
                                    "ButtonBorder.fill": "rgba(51, 51, 51, 0.8)",
                                    mouseEnter: (e, obj) => {
                                        let shape = obj.findObject("buttonShape");
                                        if (shape) shape.fill = "dodgerblue";
                                    },
                                    mouseLeave: (e, obj) => {
                                        let shape = obj.findObject("buttonShape");
                                        if (shape) shape.fill = "rgba(51, 51, 51, 0.8)";
                                    }
                                }
                            )
                        )
                    )
            },
            $(go.Shape,
                { stroke: 'lightgray', strokeWidth: 3 },
                {
                    mouseEnter: (e, obj) => {
                        obj.strokeWidth = 5;
                        obj.stroke = 'dodgerblue';
                    },
                    mouseLeave: (e, obj) => {
                        obj.strokeWidth = 3;
                        obj.stroke = 'lightgray';
                    },
                }
            )
        );

        function deleteLinkClick(e, obj) {
            e.diagram.commandHandler.deleteSelection();
        }

        function commonLinkingToolInit(tool) {
            // the temporary link drawn during a link drawing operation (LinkingTool) is thick and blue
            tool.temporaryLink = $(go.Link, { layerName: 'Tool' }, $(go.Shape, { stroke: 'dodgerblue', strokeWidth: 5 }));

            // change the standard proposed ports feedback from blue rectangles to transparent circles
            tool.temporaryFromPort.figure = 'Circle';
            tool.temporaryFromPort.stroke = null;
            tool.temporaryFromPort.strokeWidth = 0;
            tool.temporaryToPort.figure = 'Circle';
            tool.temporaryToPort.stroke = null;
            tool.temporaryToPort.strokeWidth = 0;

            // provide customized visual feedback as ports are targeted or not
            tool.portTargeted = (realnode, realport, tempnode, tempport, toend) => {
                if (realport === null) {
                    // no valid port nearby
                    lowlight();
                } else if (toend) {
                    highlight(realport);
                }
            };
        }

        const ltool = myDiagram.toolManager.linkingTool;
        commonLinkingToolInit(ltool);
        // do not allow links to be drawn starting at the "to" port
        ltool.direction = go.LinkingDirection.ForwardsOnly;

        const rtool = myDiagram.toolManager.relinkingTool;
        commonLinkingToolInit(rtool);
        // change the standard relink handle to be a shape that takes the shape of the link
        rtool.toHandleArchetype = $(go.Shape, { isPanelMain: true, fill: null, stroke: 'dodgerblue', strokeWidth: 5 });

        // use a special DraggingTool to cause the dragging of a Link to start relinking it
        myDiagram.toolManager.draggingTool = new DragLinkingTool();

        // detect when dropped onto an occupied cell
        myDiagram.addDiagramListener('SelectionMoved', shiftNodesToEmptySpaces);

        function shiftNodesToEmptySpaces() {
            myDiagram.selection.each((node) => {
                if (!(node instanceof go.Node)) return;
                // look for Parts overlapping the node
                while (true) {
                    const exist = myDiagram
                        .findObjectsIn(
                            node.actualBounds,
                            // only consider Parts
                            (obj) => obj.part,
                            // ignore Links and the dropped node itself
                            (part) => part instanceof go.Node && part !== node,
                            // check for any overlap, not complete containment
                            true
                        )
                        .first();
                    if (exist === null) break;
                    // try shifting down beyond the existing node to see if there's empty space
                    node.moveTo(node.actualBounds.x, exist.actualBounds.bottom + 10);
                }
            });
        }

        // prevent nodes from being dragged to the left of where the layout placed them
        myDiagram.addDiagramListener('LayoutCompleted', (e) => {
            myDiagram.nodes.each((node) => {
                if (node.category === 'Recycle') return;
                node.minLocation = new go.Point(node.location.x, -Infinity);
            });
        });
    }

    function isEncoded(str) {
        try {
            return str !== decodeURIComponent(str);
        } catch (e) {
            return false;
        }
    }

    // 媛앹껜�� 紐⑤뱺 臾몄옄�댁쓣 �붿퐫�⑺븯�� �ш� �⑥닔
    function decodeObject(obj) {
        for (let key in obj) {
            if (typeof obj[key] === 'string' && isEncoded(obj[key])) {
                obj[key] = decodeURIComponent(obj[key]);
            } else if (typeof obj[key] === 'object') {
                decodeObject(obj[key]);
            }
        }
    }

    function load(data) {

        // let data = document.getElementById('mySavedModel').value;
        console.log(data);
        armsStateArray = data.nodeDataArray.filter(item => item.type === "arms-state");

        myDiagram.model = go.Model.fromJson(data);
        // if any nodes don't have a real location, explicitly do a layout
        if (myDiagram.nodes.any((n) => !n.location.isReal())) layout();
    }

    function layout() {
        myDiagram.layoutDiagram(true);
    }

    // Define a custom tool that changes a drag operation on a Link to a relinking operation,
    // but that operates like a normal DraggingTool otherwise.
    class DragLinkingTool extends go.DraggingTool {
        constructor() {
            super();
            this.isGridSnapEnabled = true;
            this.isGridSnapRealtime = false;
            this.gridSnapCellSize = new go.Size(182, 1);
            this.gridSnapOrigin = new go.Point(5.5, 0);
        }

        // Handle dragging a link specially -- by starting the RelinkingTool on that Link
        doActivate() {
            const diagram = this.diagram;
            if (diagram === null) return;
            this.standardMouseSelect();
            const main = this.currentPart; // this is set by the standardMouseSelect
            if (main instanceof go.Link) {
                // maybe start relinking instead of dragging
                const relinkingtool = diagram.toolManager.relinkingTool;
                // tell the RelinkingTool to work on this Link, not what is under the mouse
                relinkingtool.originalLink = main;
                // start the RelinkingTool
                diagram.currentTool = relinkingtool;
                // can activate it right now, because it already has the originalLink to reconnect
                relinkingtool.doActivate();
                relinkingtool.doMouseMove();
            } else {
                super.doActivate();
            }
        }
    }
    // end DragLinkingTool

    function save() {
        let data = myDiagram.model.toJson();
        console.log(data);
        let jsonData = JSON.parse(data);
        decodeObject(jsonData);

        console.log(jsonData);

        document.getElementById('mySavedModel').value = JSON.stringify(jsonData, null, 2);
        myDiagram.isModified = false;
    }

    return {
        init, save, load, layout
    }

})(); //利됱떆�ㅽ뻾 �⑥닔