/* ═══════════════════════════════════════════════════════════════════════════
   ═══ ★ 제어 모드 — 같은 캔버스에서 테이블 관계 + 비즈니스 룰 시각화 ★ ═══
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── 더미 DB 테이블 메타 데이터 (실제 구현 시 백엔드 API 로 대체) ─── */
const DB_TABLES = {
  'USER_INFO': {
    icon:'👤', label:'사원',
    cols:[
      {name:'USER_ID',type:'VARCHAR',mark:'PK', dname:'사원번호',dtype:'auto'},
      {name:'USER_NAME',type:'VARCHAR', dname:'사원명',dtype:'text'},
      {name:'COMPANY_CODE',type:'VARCHAR',mark:'FK', dname:'소속회사',dtype:'select'},
      {name:'USER_TYPE',type:'VARCHAR', dname:'유형',dtype:'select'},
      {name:'STATUS',type:'VARCHAR', dname:'상태',dtype:'select'},
      {name:'CREATED_DATE',type:'TIMESTAMP', dname:'등록일',dtype:'date'},
    ]
  },
  'DEPARTMENT': {
    icon:'🏢', label:'부서',
    cols:[
      {name:'DEPT_CODE',type:'VARCHAR',mark:'PK', dname:'부서코드',dtype:'auto'},
      {name:'DEPT_NAME',type:'VARCHAR', dname:'부서명',dtype:'text'},
      {name:'COMPANY_CODE',type:'VARCHAR',mark:'FK', dname:'소속회사',dtype:'select'},
      {name:'PARENT_DEPT',type:'VARCHAR',mark:'FK', dname:'상위부서',dtype:'select'},
    ]
  },
  'ROLE_INFO': {
    icon:'🛡', label:'역할',
    cols:[
      {name:'ROLE_ID',type:'VARCHAR',mark:'PK', dname:'역할ID',dtype:'auto'},
      {name:'ROLE_NAME',type:'VARCHAR', dname:'역할명',dtype:'text'},
      {name:'COMPANY_CODE',type:'VARCHAR',mark:'FK', dname:'소속회사',dtype:'select'},
    ]
  },
  'ORDER_MASTER': {
    icon:'📦', label:'수주/발주',
    cols:[
      {name:'ORDER_ID',type:'VARCHAR',mark:'PK', dname:'주문번호',dtype:'auto'},
      {name:'USER_ID',type:'VARCHAR',mark:'FK', dname:'담당자',dtype:'select'},
      {name:'COMPANY_CODE',type:'VARCHAR',mark:'FK', dname:'회사',dtype:'select'},
      {name:'ORDER_TYPE',type:'VARCHAR', dname:'주문유형',dtype:'select'},
      {name:'AMOUNT',type:'NUMERIC', dname:'금액',dtype:'number'},
      {name:'STATUS',type:'VARCHAR', dname:'상태',dtype:'select'},
      {name:'CREATED_DATE',type:'TIMESTAMP', dname:'주문일',dtype:'date'},
    ]
  },
  'PROJECT': {
    icon:'📋', label:'프로젝트',
    cols:[
      {name:'PROJECT_ID',type:'VARCHAR',mark:'PK', dname:'프로젝트ID',dtype:'auto'},
      {name:'ORDER_ID',type:'VARCHAR',mark:'FK', dname:'주문번호',dtype:'select'},
      {name:'PROJECT_NAME',type:'VARCHAR', dname:'프로젝트명',dtype:'text'},
      {name:'STATUS',type:'VARCHAR', dname:'상태',dtype:'select'},
      {name:'BUDGET',type:'NUMERIC', dname:'예산',dtype:'number'},
    ]
  },
  'BOM_HEADER': {
    icon:'🔧', label:'BOM',
    cols:[
      {name:'BOM_ID',type:'VARCHAR',mark:'PK', dname:'BOM번호',dtype:'auto'},
      {name:'PROJECT_ID',type:'VARCHAR',mark:'FK', dname:'프로젝트',dtype:'select'},
      {name:'REVISION',type:'INT', dname:'리비전',dtype:'number'},
      {name:'STATUS',type:'VARCHAR', dname:'상태',dtype:'select'},
      {name:'CREATED_DATE',type:'TIMESTAMP', dname:'생성일',dtype:'date'},
    ]
  },
  'AUDIT_LOG': {
    icon:'📜', label:'감사로그',
    cols:[
      {name:'LOG_ID',type:'VARCHAR',mark:'PK', dname:'로그ID',dtype:'auto'},
      {name:'USER_ID',type:'VARCHAR',mark:'FK', dname:'사용자',dtype:'select'},
      {name:'ACTION',type:'VARCHAR', dname:'동작',dtype:'text'},
      {name:'TARGET_TABLE',type:'VARCHAR', dname:'대상 테이블',dtype:'text'},
      {name:'CREATED_DATE',type:'TIMESTAMP', dname:'시각',dtype:'date'},
    ]
  },
  'INVENTORY': {
    icon:'📦', label:'재고',
    cols:[
      {name:'INV_ID',type:'VARCHAR',mark:'PK', dname:'재고ID',dtype:'auto'},
      {name:'ORDER_ID',type:'VARCHAR',mark:'FK', dname:'주문',dtype:'select'},
      {name:'ITEM_NAME',type:'VARCHAR', dname:'품목명',dtype:'text'},
      {name:'QTY',type:'INT', dname:'수량',dtype:'number'},
      {name:'STATUS',type:'VARCHAR', dname:'상태',dtype:'select'},
    ]
  },
};

/* ─── 연결 트리 (루트=카드, 자식=테이블, tree 확산 애니메이션용) ─── */
const CTRL_TREE = [
  {from:'CARD',        to:'USER_INFO',     type:'source', label:'데이터 소스'},
  {from:'USER_INFO',   to:'DEPARTMENT',    type:'fk',     label:'FK 부서'},
  {from:'USER_INFO',   to:'ROLE_INFO',     type:'fk',     label:'FK 역할'},
  {from:'USER_INFO',   to:'ORDER_MASTER',  type:'auto',   label:'INSERT 수주→발주'},
  {from:'ORDER_MASTER',to:'PROJECT',       type:'cond',   label:'금액>1000만 → 자동생성'},
  {from:'PROJECT',     to:'BOM_HEADER',    type:'auto',   label:'INSERT 프로젝트→BOM'},
];

/* 테이블 위치 — 고정 3열×2행 그리드 (카드 위치와 무관, 캔버스 % 기반) */
function calcTablePositions(){
  const cv=document.getElementById('canvas');
  const cw=cv?cv.clientWidth:1200;
  const ch=cv?cv.clientHeight:700;

  /* 3열 × 2행 고정 그리드. 캔버스의 30%/55%/80% 위치 */
  const col1=Math.round(cw*0.22);
  const col2=Math.round(cw*0.48);
  const col3=Math.round(cw*0.74);
  const row1=20;
  const row2=Math.max(Math.round(ch*0.5),280);

  return {
    'USER_INFO':    {x:col1,      y:row1},
    'DEPARTMENT':   {x:col1-30,   y:row2},
    'ROLE_INFO':    {x:col1+220,  y:row2+20},
    'ORDER_MASTER': {x:col2,      y:row1},
    'INVENTORY':    {x:col2,      y:row2},
    'PROJECT':      {x:col3,      y:row1},
    'BOM_HEADER':   {x:col3,      y:row2},
    'AUDIT_LOG':    {x:col3-30,   y:row2-140},
  };
}

/* ─── 동적 CTRL_TREE 생성 (카드의 data-source-table 기반) ─── */
function buildCtrlTree(cv){
  const tree=[];
  /* 1. 카드 → 소스 테이블 (data-source-table 에서 읽기, 콤마 구분 지원) */
  cv.querySelectorAll('.tpl-card[data-source-table]').forEach(card=>{
    const tables=card.dataset.sourceTable.split(',');
    tables.forEach(tbl=>{
      const t=tbl.trim();
      if(t&&DB_TABLES[t]){
        tree.push({from:'CARD:'+card.id, to:t, type:'source', label:'데이터 소스'});
      }
    });
  });
  /* 2. 테이블 간 FK / 비즈니스 룰 (정적 정의 — 나중에 DB 메타에서 가져올 것) */
  /* ★ FK 제거 — DB 설계 레벨은 화면에 안 보여줌. 비즈니스 룰만 */
  const TABLE_RELATIONS=[
    {from:'USER_INFO',   to:'ORDER_MASTER',  type:'auto', label:'수주→발주 자동생성'},
    {from:'ORDER_MASTER',to:'PROJECT',       type:'cond', label:'금액>1000만 → 프로젝트'},
    {from:'PROJECT',     to:'BOM_HEADER',    type:'auto', label:'프로젝트→BOM 자동'},
    {from:'ORDER_MASTER',to:'INVENTORY',     type:'auto', label:'재고 자동 반영'},
    {from:'USER_INFO',   to:'AUDIT_LOG',     type:'auto', label:'변경 시 감사로그'},
  ];
  /* 캔버스에 관련 소스 테이블이 있는 관계만 포함 */
  const sourceTables=new Set(tree.map(e=>e.to));
  TABLE_RELATIONS.forEach(rel=>{
    if(sourceTables.has(rel.from)||sourceTables.has(rel.to)){
      tree.push(rel);
    }
  });
  return tree;
}

let _controlMode = false;

/* ═══ 제어 모드 토글 ═══ */
function toggleControlMode(){
  _controlMode = !_controlMode;
  const cv = document.getElementById('canvas');
  const btn = document.getElementById('btn-control-mode');
  if(_controlMode){
    enterControlMode();
    btn.classList.add('control-on');
    btn.innerHTML = '제어 ✓';
    // 편집 모드는 끄기
    cv.classList.remove('edit-mode');
    document.getElementById('btn-edit-mode')?.classList.remove('on');
    toast('제어 모드 — 테이블 관계 + 비즈니스 룰','info');
  } else {
    exitControlMode();
    btn.classList.remove('control-on');
    btn.innerHTML = '⚡ 제어';
    toast('일반 모드','info');
  }
}

/* ═══ 제어 모드 진입 (★ 트리 확산 애니메이션) ═══
   카드 축소 → 선이 뻗어나감 → 선 끝에 노드 생성 → 그 노드에서 또 선 → 또 노드
   = 나무가 자라는 듯한 연쇄 애니메이션
   ═══ */
function enterControlMode(){
  const cv = document.getElementById('canvas');
  cv.classList.add('control-mode');

  /* SVG 오버레이 (먼저 생성) */
  const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');
  svg.classList.add('ctrl-svg');svg.id='ctrl-svg';
  svg.setAttribute('width','100%');svg.setAttribute('height','100%');svg.style.overflow='visible';
  svg.innerHTML=`<defs>
    <marker id="arr-fk" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--cyan)" opacity=".7"/></marker>
    <marker id="arr-auto" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--primary)" opacity=".8"/></marker>
    <marker id="arr-cond" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--amber)" opacity=".8"/></marker>
    <marker id="arr-src" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--pink)" opacity=".8"/></marker>
  </defs>`;
  cv.appendChild(svg);

  /* ── Step 0: 모든 카드 원래 크기 유지 + 반투명 (★ 축소 안 함) ── */
  const allCards = Array.from(cv.querySelectorAll('.tpl-card'));
  allCards.forEach(card=>{
    card.dataset.autoCollapsed='1';
    card.style.transition='opacity .4s';
    card.style.opacity='.5';
  });

  /* 테이블 위치 계산 (캔버스 우측 영역 균등 분산) */
  const tblPos = calcTablePositions();
  window._ctrlTblPos = tblPos;

  /* ★ 테이블/연결선은 아직 안 그림 — 카드 클릭하면 그 카드의 흐름만 표시 */

  /* 카드 클릭 이벤트 등록 */
  allCards.forEach(card=>{
    card._ctrlClick=function(){
      if(!_controlMode)return;
      showCardFlow(card.id);
    };
    card.addEventListener('click',card._ctrlClick);
  });

  /* 사이드바 → 팔레트 */
  renderCtrlPalette();

  /* 규칙 빌더 초기화 (드래그앤드롭 활성화) */
  initRuleBuilder();

  toast('카드 클릭 = 흐름 보기 / 팔레트에서 드래그 = 규칙 편집','info');
}

/* ═══ 카드 클릭 → 해당 카드의 흐름만 표시 ═══ */
let _activeFlowCardId=null;
let _activeFlowTree=null; /* 현재 활성 흐름의 엣지 배열 (드래그 시 재그리기용) */

function clearFlow(){
  const cv=document.getElementById('canvas');
  /* 테이블/선/뱃지만 제거 — 카드 opacity 는 건드리지 않음 (showCardFlow 에서 관리) */
  cv.querySelectorAll('.tbl-node:not([data-rule]),.ctrl-badge,.ctrl-diamond').forEach(el=>el.remove());
  document.getElementById('ctrl-svg')?.remove();
  cv.querySelectorAll('.tpl-card').forEach(c=>{
    c.classList.remove('flow-active');
    /* 위치 복원 (좌측 이동한 거) */
    if(c.dataset.origLeft2){
      c.style.transition='all .3s cubic-bezier(.16,1,.3,1)';
      c.style.left=c.dataset.origLeft2;
      c.style.top=c.dataset.origTop2;
    }
  });
  _activeFlowCardId=null;
  _activeFlowTree=null;
}

/* ─── 드래그 시 현재 흐름의 선 + 뱃지만 다시 그리기 ─── */
function redrawFlowLines(){
  const cv=document.getElementById('canvas');
  const svg=document.getElementById('ctrl-svg');
  if(!svg||!cv||!_activeFlowTree)return;
  /* 기존 path + badge 제거 (defs 유지) */
  svg.querySelectorAll('path').forEach(p=>p.remove());
  cv.querySelectorAll('.ctrl-badge').forEach(b=>b.remove());
  /* 현재 흐름 엣지만 다시 그리기 (DOM 좌표) */
  _activeFlowTree.forEach(edge=>{
    let x1,y1,x2,y2;
    if(edge.from.startsWith('CARD:')){
      const c=document.getElementById(edge.from.split(':')[1]);
      if(!c)return; x1=c.offsetLeft+c.offsetWidth; y1=c.offsetTop+c.offsetHeight/2;
    } else {
      const fn=cv.querySelector(`.tbl-node[data-table="${edge.from}"]`);
      if(!fn)return; x1=fn.offsetLeft+fn.offsetWidth; y1=fn.offsetTop+fn.offsetHeight/2;
    }
    const tn=cv.querySelector(`.tbl-node[data-table="${edge.to}"]`);
    if(!tn)return; x2=tn.offsetLeft; y2=tn.offsetTop+tn.offsetHeight/2;
    /* 선 */
    const dx=x2-x1;
    const path=document.createElementNS('http://www.w3.org/2000/svg','path');
    path.setAttribute('d',`M${x1},${y1} C${x1+dx*0.5},${y1} ${x1+dx*0.5},${y2} ${x2},${y2}`);
    const cls=edge.type==='source'?'ctrl-line-tpl':edge.type==='auto'?'ctrl-line-auto':edge.type==='cond'?'ctrl-line-cond':'ctrl-line';
    const marker=edge.type==='source'?'url(#arr-src)':edge.type==='auto'?'url(#arr-auto)':edge.type==='cond'?'url(#arr-cond)':'url(#arr-fk)';
    path.classList.add(cls);path.setAttribute('marker-end',marker);
    svg.appendChild(path);
    /* 뱃지 */
    const mx=(x1+x2)/2,my=(y1+y2)/2;
    const badge=document.createElement('div');
    const bcls=edge.type==='source'?'tpl-link':edge.type==='auto'?'auto':edge.type==='cond'?'cond':'';
    badge.className='ctrl-badge '+bcls;
    badge.style.left=mx+'px';badge.style.top=my+'px';badge.style.transform='translate(-50%,-50%)';
    if(edge.type==='cond'){
      const ct=(edge.label.split('→')[0]||'조건').trim();
      const at=(edge.label.split('→')[1]||'실행').trim();
      badge.innerHTML=`<div class="cb-head"><div class="cb-icon">◇</div>조건 분기</div><div class="cb-cond">${ct}</div><div class="cb-paths"><span class="cb-yes">Yes → ${at}</span><span class="cb-no">No → 스킵</span></div>`;
    } else { badge.textContent=edge.label; }
    cv.appendChild(badge);
  });
}

function showCardFlow(cardId){
  const cv=document.getElementById('canvas');
  const card=document.getElementById(cardId);
  if(!card||!card.dataset.sourceTable)return;

  /* 이전 흐름 제거 */
  clearFlow();
  _activeFlowCardId=cardId;

  /* ★ 선택된 카드만 좌측 고정 + 나머지 fade out → 테이블 공간 확보 */
  cv.querySelectorAll('.tpl-card').forEach(c=>{
    /* 원래 위치 저장 (clearFlow 에서 복원용) */
    if(!c.dataset.origLeft2)c.dataset.origLeft2=c.style.left;
    if(!c.dataset.origTop2)c.dataset.origTop2=c.style.top;
    if(c.id===cardId){
      c.classList.add('flow-active');
      c.style.transition='all .4s cubic-bezier(.16,1,.3,1)';
      c.style.opacity='1';
      c.style.left='20px';
      c.style.top=Math.max(20,cv.clientHeight/2-70)+'px';
    } else {
      c.style.transition='opacity .3s';
      c.style.opacity='0.08';
    }
  });

  /* SVG 생성 */
  const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');
  svg.classList.add('ctrl-svg');svg.id='ctrl-svg';
  svg.setAttribute('width','100%');svg.setAttribute('height','100%');svg.style.overflow='visible';
  svg.innerHTML=`<defs>
    <marker id="arr-fk" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--cyan)" opacity=".7"/></marker>
    <marker id="arr-auto" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--primary)" opacity=".8"/></marker>
    <marker id="arr-cond" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--amber)" opacity=".8"/></marker>
    <marker id="arr-src" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--pink)" opacity=".8"/></marker>
  </defs>`;
  cv.appendChild(svg);

  /* ★ 카드의 전체 경로 한 번에 표시 (depth 제한 없음) */
  const fullTree=buildCtrlTree(cv);
  const rootKey='CARD:'+cardId;

  /* BFS: 이 카드에서 도달 가능한 전체 체인 */
  const reachable=new Set([rootKey]);
  const queue=[rootKey];
  while(queue.length){
    const cur=queue.shift();
    fullTree.filter(e=>e.from===cur).forEach(e=>{
      if(!reachable.has(e.to)){reachable.add(e.to);queue.push(e.to);}
    });
  }
  const tree=fullTree.filter(e=>reachable.has(e.from)&&reachable.has(e.to));

  /* depth 계산 */
  const depths={};
  depths[rootKey]=0;
  const q2=[rootKey];
  while(q2.length){
    const cur=q2.shift();
    tree.filter(e=>e.from===cur).forEach(e=>{
      if(depths[e.to]===undefined){depths[e.to]=depths[cur]+1;q2.push(e.to);}
    });
  }

  /* 엣지를 depth 별 그룹핑 */
  const depthEdges={};
  tree.forEach(edge=>{
    const fd=depths[edge.from]!==undefined?depths[edge.from]:0;
    const d=fd+1;
    if(!depthEdges[d])depthEdges[d]=[];
    depthEdges[d].push(edge);
  });
  const maxDepth=Math.max(0,...Object.keys(depthEdges).map(Number));

  /* 테이블 위치 — 카드 우측으로 트리 형태 */
  const tblPos=calcFlowPositions(card,tree,depths);

  _activeFlowTree=tree; /* 드래그 시 재그리기용 저장 */

  /* ★ Step A: 모든 테이블 노드를 먼저 숨긴 상태로 생성 (DOM 좌표 확보) */
  const allTables=new Set();
  tree.forEach(e=>{if(!e.to.startsWith('CARD:')&&DB_TABLES[e.to])allTables.add(e.to);});
  allTables.forEach(name=>{
    if(cv.querySelector(`.tbl-node[data-table="${name}"]`))return;
    const pos=tblPos[name]||{x:500,y:200};
    const node=buildTableNode(name,DB_TABLES[name],pos);
    node.style.opacity='0';node.style.transform='scale(0.3)';node.style.pointerEvents='none';
    cv.appendChild(node);
  });

  /* ★ Step B: 선 → 노드 reveal (from/to 모두 실제 DOM 좌표로 정확) */
  const STEP=500, NODE_D=350;
  for(let d=1;d<=maxDepth;d++){
    const edges=depthEdges[d]||[];
    const base=300+(d-1)*STEP;
    edges.forEach((edge,i)=>{
      const lineT=base+i*120;
      const nodeT=lineT+NODE_D;
      /* 선 — 실제 DOM 좌표 */
      setTimeout(()=>{
        let x1,y1,x2,y2;
        if(edge.from.startsWith('CARD:')){
          const c=document.getElementById(edge.from.split(':')[1]);
          if(!c)return; x1=c.offsetLeft+c.offsetWidth; y1=c.offsetTop+c.offsetHeight/2;
        } else {
          const fn=cv.querySelector(`.tbl-node[data-table="${edge.from}"]`);
          if(!fn)return; x1=fn.offsetLeft+fn.offsetWidth; y1=fn.offsetTop+fn.offsetHeight/2;
        }
        const tn=cv.querySelector(`.tbl-node[data-table="${edge.to}"]`);
        if(!tn)return; x2=tn.offsetLeft; y2=tn.offsetTop+tn.offsetHeight/2;
        /* bezier */
        const dx=x2-x1;
        const path=document.createElementNS('http://www.w3.org/2000/svg','path');
        path.setAttribute('d',`M${x1},${y1} C${x1+dx*0.5},${y1} ${x1+dx*0.5},${y2} ${x2},${y2}`);
        const cls=edge.type==='source'?'ctrl-line-tpl':edge.type==='auto'?'ctrl-line-auto':edge.type==='cond'?'ctrl-line-cond':'ctrl-line';
        const marker=edge.type==='source'?'url(#arr-src)':edge.type==='auto'?'url(#arr-auto)':edge.type==='cond'?'url(#arr-cond)':'url(#arr-fk)';
        path.classList.add(cls);path.setAttribute('marker-end',marker);
        svg.appendChild(path);
        /* draw 애니메이션 */
        const len=path.getTotalLength();
        path.style.strokeDasharray=len;path.style.strokeDashoffset=len;path.style.animation='none';
        path.getBoundingClientRect();
        path.style.transition='stroke-dashoffset 0.4s ease-out';path.style.strokeDashoffset='0';
        setTimeout(()=>{path.style.transition='none';path.style.strokeDasharray='';path.style.strokeDashoffset='';path.style.animation='';},500);
      },lineT);
      /* 노드 reveal + 뱃지 */
      setTimeout(()=>{
        const node=cv.querySelector(`.tbl-node[data-table="${edge.to}"]`);
        if(node&&node.style.opacity==='0'){
          node.style.transition='opacity .35s ease-out,transform .35s cubic-bezier(.16,1,.3,1)';
          node.style.pointerEvents='';
          requestAnimationFrame(()=>{node.style.opacity='1';node.style.transform='scale(1)';});
        }
        /* 뱃지 — 실제 DOM 좌표 */
        let bx1,by1;
        if(edge.from.startsWith('CARD:')){
          const c=document.getElementById(edge.from.split(':')[1]);
          if(!c)return; bx1=c.offsetLeft+c.offsetWidth; by1=c.offsetTop+c.offsetHeight/2;
        } else {
          const fn=cv.querySelector(`.tbl-node[data-table="${edge.from}"]`);
          if(!fn)return; bx1=fn.offsetLeft+fn.offsetWidth; by1=fn.offsetTop+fn.offsetHeight/2;
        }
        const tn=cv.querySelector(`.tbl-node[data-table="${edge.to}"]`);
        if(!tn)return;
        const bx2=tn.offsetLeft, by2=tn.offsetTop+tn.offsetHeight/2;
        const mx=(bx1+bx2)/2, my=(by1+by2)/2;
        const badge=document.createElement('div');
        const bcls=edge.type==='source'?'tpl-link':edge.type==='auto'?'auto':edge.type==='cond'?'cond':'';
        badge.className='ctrl-badge '+bcls;
        badge.style.left=mx+'px';badge.style.top=my+'px';badge.style.transform='translate(-50%,-50%)';
        if(edge.type==='cond'){
          const ct=(edge.label.split('→')[0]||'조건').trim();
          const at=(edge.label.split('→')[1]||'실행').trim();
          badge.innerHTML=`<div class="cb-head"><div class="cb-icon">◇</div>조건 분기</div><div class="cb-cond">${ct}</div><div class="cb-paths"><span class="cb-yes">Yes → ${at}</span><span class="cb-no">No → 스킵</span></div>`;
        } else { badge.textContent=edge.label; }
        badge.style.opacity='0';
        badge.onclick=()=>toast('연결 설정 (Phase 2) — '+edge.label,'info');
        cv.appendChild(badge);
        requestAnimationFrame(()=>{badge.style.transition='opacity .3s';badge.style.opacity='1';});
      },nodeT);
    });
  }
}

/* ★ 카드 크기에 맞춘 동적 시작점 + 넉넉한 간격 */
function calcFlowPositions(card,tree,depths){
  const cv=document.getElementById('canvas');
  const cw=cv?cv.clientWidth:1200;
  const ch=cv?cv.clientHeight:700;

  /* 카드 우측 끝 + 80px 여유 = 테이블 시작 X */
  const cardW=card.offsetWidth||200;
  const startX=20+cardW+80;

  const depthNodes={};
  Object.entries(depths).forEach(([name,d])=>{
    if(name.startsWith('CARD:'))return;
    if(!depthNodes[d])depthNodes[d]=[];
    depthNodes[d].push(name);
  });
  const maxD=Math.max(1,...Object.keys(depthNodes).map(Number));
  /* 열 간격: 최소 270, 캔버스에 맞게 조정 */
  const colGap=Math.max(270,Math.min(350,(cw-startX-230)/maxD));
  /* 행 간격: 테이블 높이(~200) + 여유 40 = 240 */
  const rowGap=240;

  const pos={};
  Object.entries(depthNodes).forEach(([d,nodes])=>{
    const di=parseInt(d);
    const totalH=nodes.length*rowGap;
    const startY=Math.max(20,(ch-totalH)/2);
    nodes.forEach((name,i)=>{
      pos[name]={
        x: startX+(di-1)*colGap,
        y: startY+i*rowGap
      };
    });
  });
  return pos;
}

/* ═══ 테이블 노드 클릭 → 비즈니스 룰 1단계 확장 ═══ */
function expandTableRules(tableName){
  const cv=document.getElementById('canvas');
  const svg=document.getElementById('ctrl-svg');
  if(!cv||!svg)return;

  const fullTree=buildCtrlTree(cv);
  /* 이 테이블에서 나가는 비즈니스 룰만 (이미 표시된 노드 제외) */
  const newEdges=fullTree.filter(e=>
    e.from===tableName && !cv.querySelector(`.tbl-node[data-table="${e.to}"]`)
  );
  if(newEdges.length===0){
    toast('추가 비즈니스 룰 없음','info');
    return;
  }

  /* 기존 테이블 위치 기준으로 새 테이블 배치 */
  const fromNode=cv.querySelector(`.tbl-node[data-table="${tableName}"]`);
  if(!fromNode)return;
  const fx=fromNode.offsetLeft+fromNode.offsetWidth+80;
  const fy=fromNode.offsetTop;

  const tblPos={};
  newEdges.forEach((edge,i)=>{
    tblPos[edge.to]={x:fx, y:fy+i*240};
  });

  /* 순차 애니메이션 */
  newEdges.forEach((edge,i)=>{
    setTimeout(()=>drawTreeLine(cv,svg,edge,tblPos),i*150);
    setTimeout(()=>{
      if(!cv.querySelector(`.tbl-node[data-table="${edge.to}"]`)){
        const pos=tblPos[edge.to];
        const node=buildTableNode(edge.to,DB_TABLES[edge.to],pos);
        node.style.opacity='0';node.style.transform='scale(0.3)';
        node.style.transition='opacity .35s ease-out,transform .35s cubic-bezier(.16,1,.3,1)';
        cv.appendChild(node);
        requestAnimationFrame(()=>{node.style.opacity='1';node.style.transform='scale(1)';});
      }
      addEdgeBadge(cv,edge,tblPos);
      if(edge.type==='cond')addDiamond(cv,edge,tblPos);
    },i*150+300);
  });

  toast(tableName+' → '+newEdges.length+'개 비즈니스 룰 확장','success');
}

/* 캔버스 빈 영역 클릭 → 흐름 닫기 */
document.addEventListener('click',function(e){
  if(!_controlMode)return;
  if(e.target.closest('.tpl-card')||e.target.closest('.tbl-node')||e.target.closest('.ctrl-badge')||e.target.closest('.ctrl-diamond'))return;
  if(e.target.closest('.cv-toolbar')||e.target.closest('.side'))return;
  if(_activeFlowCardId){
    clearFlow();
    /* 빈 영역 클릭 = 흐름 닫기 → 모든 카드 반투명으로 */
    document.getElementById('canvas').querySelectorAll('.tpl-card').forEach(c=>{
      c.style.transition='opacity .3s';
      c.style.opacity='.5';
    });
  }
});

/* ─── 트리 연결선 1개 그리기 (선이 "그려지는" 애니메이션) ─── */
function drawTreeLine(cv, svg, edge, tblPos){
  /* from 좌표: CARD:xxx 면 해당 카드, 아니면 테이블 노드 */
  let x1,y1,x2,y2;
  if(edge.from.startsWith('CARD:')){
    const cardId=edge.from.split(':')[1];
    const card=document.getElementById(cardId);
    if(!card) return;
    x1=card.offsetLeft+card.offsetWidth;
    y1=card.offsetTop+card.offsetHeight/2;
  } else {
    const fn=cv.querySelector(`.tbl-node[data-table="${edge.from}"]`);
    if(!fn) return;
    x1=fn.offsetLeft+fn.offsetWidth;
    y1=fn.offsetTop+fn.offsetHeight/2;
  }
  const toPos=tblPos[edge.to]||{x:x1+200,y:y1};
  x2=toPos.x;
  y2=toPos.y+80; // 노드 중앙 근사

  /* SVG bezier path */
  const dx=x2-x1;
  const path=document.createElementNS('http://www.w3.org/2000/svg','path');
  path.setAttribute('d',`M${x1},${y1} C${x1+dx*0.5},${y1} ${x1+dx*0.5},${y2} ${x2},${y2}`);

  /* 타입별 스타일 */
  const cls = edge.type==='source'?'ctrl-line-tpl':
              edge.type==='auto'?'ctrl-line-auto':
              edge.type==='cond'?'ctrl-line-cond':'ctrl-line';
  const marker = edge.type==='source'?'url(#arr-src)':
                 edge.type==='auto'?'url(#arr-auto)':
                 edge.type==='cond'?'url(#arr-cond)':'url(#arr-fk)';
  path.classList.add(cls);
  path.setAttribute('marker-end',marker);

  /* ★ "선이 그려지는" 애니메이션 — stroke-dashoffset */
  svg.appendChild(path);
  const len=path.getTotalLength();
  path.style.strokeDasharray=len;
  path.style.strokeDashoffset=len;
  path.style.animation='none'; // 기존 pulse 잠시 끄기
  path.getBoundingClientRect(); // reflow
  path.style.transition=`stroke-dashoffset 0.4s ease-out`;
  path.style.strokeDashoffset='0';
  /* 선 다 그려지면 pulse 애니메이션 시작 */
  setTimeout(()=>{
    path.style.transition='none';
    path.style.strokeDasharray='';
    path.style.strokeDashoffset='';
    path.style.animation=''; // pulse 복원
  },500);
}

/* ─── 연결 뱃지 (선 중간에 라벨) ─── */
function addEdgeBadge(cv, edge, tblPos){
  let x1,y1;
  if(edge.from.startsWith('CARD:')){
    const cardId=edge.from.split(':')[1];
    const card=document.getElementById(cardId);
    if(!card) return;
    x1=card.offsetLeft+card.offsetWidth;
    y1=card.offsetTop+card.offsetHeight/2;
  } else {
    const fn=cv.querySelector(`.tbl-node[data-table="${edge.from}"]`);
    if(!fn) return;
    x1=fn.offsetLeft+fn.offsetWidth;
    y1=fn.offsetTop+fn.offsetHeight/2;
  }
  const toPos=tblPos[edge.to]||{x:x1+200,y:y1};
  const x2=toPos.x, y2=toPos.y+80;
  const mx=(x1+x2)/2, my=(y1+y2)/2;

  const badge=document.createElement('div');
  const cls=edge.type==='source'?'tpl-link':edge.type==='auto'?'auto':edge.type==='cond'?'cond':'';
  badge.className='ctrl-badge '+cls;
  badge.style.left=mx+'px';badge.style.top=my+'px';
  badge.style.transform='translate(-50%,-50%)';

  /* ★ 조건분기면 확장형 뱃지 */
  if(edge.type==='cond'){
    const condText=(edge.label.split('→')[0]||'조건').trim();
    const actionText=(edge.label.split('→')[1]||'실행').trim();
    badge.innerHTML=`
      <div class="cb-head"><div class="cb-icon">◇</div>조건 분기</div>
      <div class="cb-cond">${condText}</div>
      <div class="cb-paths">
        <span class="cb-yes">Yes → ${actionText}</span>
        <span class="cb-no">No → 스킵</span>
      </div>`;
  } else {
    badge.textContent=edge.label;
  }

  badge.style.opacity='0';
  badge.onclick=()=>toast('연결 설정 (Phase 2) — '+edge.label,'info');
  cv.appendChild(badge);
  requestAnimationFrame(()=>{badge.style.transition='opacity .3s';badge.style.opacity='1';});
}

/* ─── 조건분기 카드 ─── */
function addDiamond(cv, edge, tblPos){
  let x1,y1;
  const fn=cv.querySelector(`.tbl-node[data-table="${edge.from}"]`);
  if(!fn) return;
  x1=fn.offsetLeft+fn.offsetWidth;
  y1=fn.offsetTop+fn.offsetHeight/2;
  const toPos=tblPos[edge.to]||{x:x1+200,y:y1};
  const mx=(x1+toPos.x)/2, my=(y1+toPos.y+80)/2;

  /* 조건식 추출 (label 에서) */
  const condText=edge.label.split('→')[0].trim()||'조건';
  const actionText=edge.label.split('→')[1]?.trim()||'실행';

  const diamond=document.createElement('div');
  diamond.className='ctrl-diamond';
  diamond.style.left=(mx-70)+'px';
  diamond.style.top=(my-30)+'px';
  diamond.style.opacity='0';diamond.style.transform='scale(0.3)';
  diamond.innerHTML=`
    <div class="ctrl-diamond-head">
      <div class="ctrl-diamond-icon">◇</div>
      <span class="ctrl-diamond-title">조건 분기</span>
    </div>
    <div class="ctrl-diamond-cond">${condText}</div>
    <div class="ctrl-diamond-paths">
      <span class="ctrl-diamond-yes">Yes → ${actionText}</span>
      <span class="ctrl-diamond-no">No → 스킵</span>
    </div>`;
  diamond.onclick=()=>toast('조건분기 설정 (Phase 2) — '+edge.label,'info');
  cv.appendChild(diamond);
  requestAnimationFrame(()=>{
    diamond.style.transition='opacity .3s,transform .3s cubic-bezier(.16,1,.3,1)';
    diamond.style.opacity='1';diamond.style.transform='scale(1)';
  });
}

/* ═══ 제어 모드 탈출 ═══ */
function exitControlMode(){
  const cv=document.getElementById('canvas');
  cv.classList.remove('control-mode');

  /* 1. 흐름 + 테이블 노드 / SVG / 뱃지 / 다이아몬드 전부 제거 */
  clearFlow();
  /* 카드 클릭 이벤트 제거 */
  cv.querySelectorAll('.tpl-card').forEach(card=>{
    if(card._ctrlClick){card.removeEventListener('click',card._ctrlClick);delete card._ctrlClick;}
    card.classList.remove('flow-active');
  });

  /* 2. ★ 모든 카드 원래 상태 복원 (opacity + 위치) */
  cv.querySelectorAll('.tpl-card').forEach(card=>{
    card.style.transition='all .4s cubic-bezier(.16,1,.3,1)';
    card.style.opacity='1';
    /* showCardFlow 에서 좌측 이동한 카드 원위치 */
    if(card.dataset.origLeft2){
      card.style.left=card.dataset.origLeft2;
      card.style.top=card.dataset.origTop2;
    }
    delete card.dataset.autoCollapsed;
    delete card.dataset.origLeft2;
    delete card.dataset.origTop2;
    setTimeout(()=>{card.style.transition='';},500);
  });

  /* 3. 사이드바 복원 */
  renderSidebar();

  /* 4. ★ 보험: side-sec 라벨 복원 */
  const sideSec=document.querySelector('#side .side-sec');
  if(sideSec&&sideSec.dataset.origText){
    sideSec.textContent=sideSec.dataset.origText;
    sideSec.style.color='';
  }
  const addBtn=document.querySelector('#side .side-add-btn');
  if(addBtn)addBtn.style.display='';

  /* 5. 규칙 빌더 정리 */
  if(typeof cleanupRuleBuilder==='function') cleanupRuleBuilder();
}

/* ═══ 테이블 노드 DOM 생성 ═══ */
function buildTableNode(name,meta,pos,onMove){
  const node=document.createElement('div');
  node.className='tbl-node';
  node.dataset.table=name;
  node.style.left=pos.x+'px';
  node.style.top=pos.y+'px';

  /* 논리 타입 아이콘 매핑 */
  const dtypeIcons={text:'Aa',number:'#',date:'📅',select:'▼',check:'☑',file:'📎',auto:'⚡'};

  let colsHtml='';
  meta.cols.forEach(col=>{
    const portCls=col.mark==='PK'?'pk':col.mark==='FK'?'fk':'';
    const markHtml=col.mark?`<span class="tbl-col-mark ${col.mark.toLowerCase()}">${col.mark}</span>`:'';
    /* 논리 뷰 (기본) + 물리 뷰 (토글) */
    const dname=col.dname||col.name;
    const dtype=col.dtype||'text';
    const dtIcon=dtypeIcons[dtype]||'Aa';
    colsHtml+=`<div class="tbl-col" data-col="${col.name}">
      <div class="tbl-port ${portCls}"></div>
      <span class="tbl-col-name" data-phys="${col.name}">${dname}</span>
      <span class="tbl-col-type" data-phys="${col.type}">${dtIcon} ${dtype}</span>
      ${markHtml}
    </div>`;
  });

  node.innerHTML=`
    <div class="tbl-node-head">
      <div class="tbl-icon">${meta.icon}</div>
      <span class="tbl-name">${name}</span>
      <span class="tbl-badge">${meta.label}</span>
      <button class="tbl-view-toggle" onclick="event.stopPropagation();toggleTableView(this)" title="논리/물리 전환">{ }</button>
    </div>
    <div class="tbl-node-cols">${colsHtml}</div>`;

  /* 노드 드래그 */
  const head=node.querySelector('.tbl-node-head');
  head.addEventListener('mousedown',function(e){
    if(!_controlMode)return;
    e.preventDefault();
    const sx=e.clientX,sy=e.clientY;
    const sl=node.offsetLeft,st=node.offsetTop;
    node.style.zIndex='30';
    function move(ev){
      node.style.left=(sl+ev.clientX-sx)+'px';
      node.style.top=(st+ev.clientY-sy)+'px';
      if(onMove)onMove();else redrawFlowLines();
    }
    function up(){
      node.style.zIndex='20';
      document.removeEventListener('mousemove',move);
      document.removeEventListener('mouseup',up);
    }
    document.addEventListener('mousemove',move);
    document.addEventListener('mouseup',up);
  });

  return node;
}

/* ═══ 연결선 다시 그리기 (드래그 중 실시간 갱신용) ═══ */
function drawCtrlLines(){
  const svg=document.getElementById('ctrl-svg');
  const cv=document.getElementById('canvas');
  if(!svg||!cv)return;

  /* 기존 path + 뱃지 + 다이아몬드 제거 (defs 유지) */
  svg.querySelectorAll('path').forEach(p=>p.remove());
  cv.querySelectorAll('.ctrl-badge,.ctrl-diamond').forEach(el=>el.remove());

  /* 동적 트리 기반으로 모든 연결선 재그리기 (애니메이션 없이 즉시) */
  const tree=buildCtrlTree(cv);
  tree.forEach(edge=>{
    /* from 좌표 */
    let x1,y1;
    if(edge.from.startsWith('CARD:')){
      const cardId=edge.from.split(':')[1];
      const card=document.getElementById(cardId);
      if(!card) return;
      x1=card.offsetLeft+card.offsetWidth;
      y1=card.offsetTop+card.offsetHeight/2;
    } else {
      const fn=cv.querySelector(`.tbl-node[data-table="${edge.from}"]`);
      if(!fn) return;
      x1=fn.offsetLeft+fn.offsetWidth;
      y1=fn.offsetTop+fn.offsetHeight/2;
    }
    /* to 좌표 */
    const tn=cv.querySelector(`.tbl-node[data-table="${edge.to}"]`);
    if(!tn) return;
    const x2=tn.offsetLeft;
    const y2=tn.offsetTop+tn.offsetHeight/2;

    /* bezier */
    const dx=x2-x1;
    const path=document.createElementNS('http://www.w3.org/2000/svg','path');
    path.setAttribute('d',`M${x1},${y1} C${x1+dx*0.5},${y1} ${x1+dx*0.5},${y2} ${x2},${y2}`);

    const cls = edge.type==='source'?'ctrl-line-tpl':
                edge.type==='auto'?'ctrl-line-auto':
                edge.type==='cond'?'ctrl-line-cond':'ctrl-line';
    const marker = edge.type==='source'?'url(#arr-src)':
                   edge.type==='auto'?'url(#arr-auto)':
                   edge.type==='cond'?'url(#arr-cond)':'url(#arr-fk)';
    path.classList.add(cls);
    path.setAttribute('marker-end',marker);
    /* hover 매칭용 data 속성 */
    const fromTbl=edge.from.startsWith('CARD:')?'':edge.from;
    const toTbl=edge.to;
    path.dataset.from=fromTbl;path.dataset.to=toTbl;
    svg.appendChild(path);

    /* 뱃지 */
    const mx=(x1+x2)/2, my=(y1+y2)/2;
    const bcls=edge.type==='source'?'tpl-link':edge.type==='auto'?'auto':edge.type==='cond'?'cond':'';
    const badge=document.createElement('div');
    badge.className='ctrl-badge '+bcls;
    badge.dataset.from=fromTbl;badge.dataset.to=toTbl;
    badge.style.left=mx+'px';badge.style.top=my+'px';
    badge.style.transform='translate(-50%,-50%)';
    badge.textContent=edge.label;
    badge.onclick=()=>toast('연결 설정 (Phase 2) — '+edge.label,'info');
    cv.appendChild(badge);

    if(edge.type==='cond'){
      const diamond=document.createElement('div');
      diamond.className='ctrl-diamond';
      diamond.style.left=(mx-45)+'px';diamond.style.top=(my-10)+'px';
      diamond.innerHTML=`<div class="ctrl-diamond-inner"><div class="ctrl-diamond-label">조건<br>분기</div></div>`;
      cv.appendChild(diamond);
    }
  });
}

/* ─── Hover 강조: 비활성화 (제어 모드에서 불필요 — 흐름 표시와 충돌) ─── */
document.addEventListener('mouseover',function(e){
  return; /* ★ hover 강조 OFF */
  const node=e.target.closest('.tbl-node');
  const cv=document.getElementById('canvas');
  if(!cv)return;
  if(!node){
    /* 노드 밖으로 나가면 해제 */
    cv.classList.remove('hover-focus');
    cv.querySelectorAll('.hover-active').forEach(el=>el.classList.remove('hover-active'));
    return;
  }
  const tbl=node.dataset.table;
  if(!tbl)return;
  cv.classList.add('hover-focus');
  /* 기존 하이라이트 초기화 */
  cv.querySelectorAll('.hover-active').forEach(el=>el.classList.remove('hover-active'));
  /* 이 노드 강조 */
  node.classList.add('hover-active');
  /* 이 테이블과 관련된 선 + 뱃지 강조 */
  const svg=document.getElementById('ctrl-svg');
  if(svg){
    svg.querySelectorAll('path').forEach(p=>{
      if(p.dataset.from===tbl||p.dataset.to===tbl)p.classList.add('hover-active');
    });
  }
  cv.querySelectorAll('.ctrl-badge').forEach(b=>{
    if(b.dataset.from===tbl||b.dataset.to===tbl)b.classList.add('hover-active');
  });
  /* 연결된 노드도 강조 */
  cv.querySelectorAll('.tbl-node').forEach(n=>{
    const t=n.dataset.table;
    if(t===tbl)return;
    const svg2=document.getElementById('ctrl-svg');
    if(svg2){
      const related=Array.from(svg2.querySelectorAll('path')).some(p=>(p.dataset.from===tbl&&p.dataset.to===t)||(p.dataset.from===t&&p.dataset.to===tbl));
      if(related)n.classList.add('hover-active');
    }
  });
  /* 연결된 카드도 강조 */
  cv.querySelectorAll('.tpl-card').forEach(c=>{
    if(c.dataset.sourceTable&&c.dataset.sourceTable.includes(tbl))c.style.opacity='1';
  });
});

/* ─── 논리/물리 뷰 토글 (테이블 노드 헤더 버튼) ─── */
function toggleTableView(btn){
  const node=btn.closest('.tbl-node');
  if(!node) return;
  const isPhys=node.classList.toggle('phys-view');
  btn.textContent=isPhys?'Aa':'{ }';
  btn.title=isPhys?'논리 뷰로 전환':'물리 뷰로 전환';
  node.querySelectorAll('.tbl-col-name').forEach(el=>{
    const phys=el.dataset.phys;
    const logic=el.textContent;
    el.textContent=isPhys?phys:el.dataset.logic||logic;
    if(!isPhys) el.dataset.logic=logic;
    else el.dataset.logic=logic;
  });
  node.querySelectorAll('.tbl-col-type').forEach(el=>{
    const phys=el.dataset.phys;
    const logic=el.textContent;
    el.textContent=isPhys?phys:el.dataset.logic||logic;
    if(!isPhys) el.dataset.logic=logic;
  });
}

/* ═══ 사이드바 → 테이블/제어 팔레트 (드래그앤드롭 지원) ═══ */
function renderCtrlPalette(){
  const list=document.getElementById('dashboard-list');
  if(!list)return;
  list.innerHTML='';

  /* 테이블 섹션 + 데모 버튼 */
  const tblSec=document.createElement('div');
  tblSec.className='ctrl-palette-section';
  tblSec.style.cssText='display:flex;justify-content:space-between;align-items:center;';
  tblSec.innerHTML='DB 테이블 <button class="ctrl-demo-btn" onclick="loadDemoScenario()">데모</button>';
  list.appendChild(tblSec);

  Object.entries(DB_TABLES).forEach(([name,meta])=>{
    const item=document.createElement('div');
    item.className='ctrl-palette-item';
    item.innerHTML=`<span class="cp-icon">${meta.icon}</span><span>${name}</span>`;
    item.title=meta.label+' — 캔버스로 드래그';
    if(typeof makePaletteItemDraggable==='function')
      makePaletteItemDraggable(item,{kind:'table',name});
    list.appendChild(item);
  });

  /* 제어 노드 — 카테고리별 그룹핑 */
  const nodeTypes=typeof CTRL_NODE_TYPES!=='undefined'?CTRL_NODE_TYPES:{};
  const cats=['트리거','조건','액션','흐름','연동','기록'];
  const catLabels={트리거:'트리거',조건:'조건 / 분기',액션:'액션',흐름:'흐름 제어',연동:'외부 연동',기록:'기록'};
  cats.forEach(cat=>{
    const items=Object.entries(nodeTypes).filter(([,d])=>d.cat===cat);
    if(!items.length)return;
    const sec=document.createElement('div');
    sec.className='ctrl-palette-section';
    sec.textContent=catLabels[cat]||cat;
    list.appendChild(sec);
    items.forEach(([type,def])=>{
      const item=document.createElement('div');
      item.className='ctrl-palette-item';
      item.innerHTML=`<span class="cp-icon">${def.icon}</span><span>${def.label}</span>`;
      item.title=def.label+' — 캔버스로 드래그';
      if(typeof makePaletteItemDraggable==='function')
        makePaletteItemDraggable(item,{kind:'control',type});
      list.appendChild(item);
    });
  });

  /* side-sec 라벨 변경 */
  const sideSec=document.querySelector('#side .side-sec');
  if(sideSec){
    sideSec.dataset.origText=sideSec.dataset.origText||sideSec.textContent;
    sideSec.textContent='제어 팔레트';
    sideSec.style.color='var(--cyan)';
  }
  /* 새 대시보드 버튼 숨기기 */
  const addBtn=document.querySelector('#side .side-add-btn');
  if(addBtn)addBtn.style.display='none';
}
