let members = [];
let isAdmin = false;
let selectedDept = '전체';
let editingMembers = [];
let collapsedUnits = new Set();
let isListMode = false;
let emptyStateMessage = '서버에 조직 데이터가 없습니다. 상단의 업로드 버튼으로 초기 데이터를 넣어주세요.';
let photoPreviewObjectUrl = null;
let seatMapLayoutCache = null;
let activeAsOfDate = '';
let isHistoricalSnapshot = false;
const listViewState = {
  mode: 'current',
  snapshotDate: '',
  compareFromDate: '',
  compareToDate: '',
  snapshotMembers: [],
  compareItems: [],
};
const seatMapOfficeKeys = ['technical-development-center', 'hanmac-building-6f', 'hanmac-building-7f'];

const levelOrder = ['부서', '그룹', '디비전', '팀', '셀'];
const dropdownFields = ['소속회사', '직급', '직책', ...levelOrder];

function pad(value) {
  return String(value).padStart(2, '0');
}

function updateTimestamp() {
  const now = new Date();
  const dateStr = `${now.getFullYear()}.${pad(now.getMonth() + 1)}.${pad(now.getDate())}`;
  const timeStr = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
  document.getElementById('last-updated').innerText = `WSL 서버 기준 동기화: ${dateStr} ${timeStr}`;
}

function rebuildMemberPath(member) {
  member._path = levelOrder
    .map((level) => ({ level, name: member[level] || '' }))
    .filter((item) => item.name !== '');
  return member;
}

function cloneMembers(items) {
  return JSON.parse(JSON.stringify(items));
}

function getPhotoPlaceholder(name = '') {
  return `https://via.placeholder.com/160?text=${encodeURIComponent(name || 'Profile')}`;
}

function escapeHtml(value) {
  return String(value ?? '')
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;');
}

function resetPhotoPreviewObjectUrl() {
  if (photoPreviewObjectUrl) {
    URL.revokeObjectURL(photoPreviewObjectUrl);
    photoPreviewObjectUrl = null;
  }
}

function toLegacyMember(item) {
  return rebuildMemberPath({
    _id: String(item.id),
    id: item.id,
    이름: item.name || '',
    사번: item.employee_id || '',
    소속회사: item.company || '',
    직급: item.rank || '',
    직책: item.role || '',
    부서: item.department || '',
    그룹: item.grp || '',
    디비전: item.division || '',
    팀: item.team || '',
    셀: item.cell || '',
    근무상태: item.work_status || '',
    근무시간: item.work_time || '',
    전화번호: item.phone || '',
    이메일: item.email || '',
    자리위치: item.seat_label || '',
    사진: item.photo_url || '',
    sort_order: item.sort_order ?? 0,
  });
}

function toApiMember(member, sortOrder) {
  return {
    name: member['이름'] || '',
    employee_id: member['사번'] || '',
    company: member['소속회사'] || '',
    rank: member['직급'] || '',
    role: member['직책'] || '',
    department: member['부서'] || '',
    grp: member['그룹'] || '',
    division: member['디비전'] || '',
    team: member['팀'] || '',
    cell: member['셀'] || '',
    work_status: member['근무상태'] || '',
    work_time: member['근무시간'] || '',
    phone: member['전화번호'] || '',
    email: member['이메일'] || '',
    seat_label: member['자리위치'] || '',
    photo_url: member['사진'] || '',
    sort_order: sortOrder,
  };
}

async function apiFetch(url, options = {}) {
  const config = { ...options };
  config.headers = { ...(options.headers || {}) };
  if (config.body && !(config.body instanceof FormData) && !config.headers['Content-Type']) {
    config.headers['Content-Type'] = 'application/json';
  }

  const response = await fetch(url, config);
  const contentType = response.headers.get('content-type') || '';
  const payload = contentType.includes('application/json') ? await response.json() : null;
  if (!response.ok) {
    const detail = payload?.detail || '요청 처리에 실패했습니다.';
    throw new Error(detail);
  }
  return payload;
}

function withAsOf(url) {
  if (!activeAsOfDate) {
    return url;
  }
  const separator = url.includes('?') ? '&' : '?';
  return `${url}${separator}as_of=${encodeURIComponent(activeAsOfDate)}`;
}

function getDefaultHistoryDate() {
  if (activeAsOfDate) {
    return activeAsOfDate;
  }
  const now = new Date();
  return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
}

async function uploadProfilePhoto(file, memberName) {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('member_name', memberName || '');
  const payload = await apiFetch('/api/uploads/profile-photo', {
    method: 'POST',
    body: formData,
  });
  return payload.url || '';
}

function setMembers(items) {
  members = items.map(toLegacyMember);
  if (selectedDept !== '전체' && !members.some((member) => member['부서'] === selectedDept)) {
    selectedDept = '전체';
  }
  updateTimestamp();
}

async function loadMembers(message) {
  if (message) {
    emptyStateMessage = message;
  }
  const payload = await apiFetch(withAsOf('/api/members'));
  setMembers(payload.items || []);
  if (!members.length) {
    emptyStateMessage = '서버에 조직 데이터가 없습니다. 상단의 업로드 버튼으로 초기 데이터를 넣어주세요.';
  }
  render();
}

async function loadSeatMapLayouts(force = false) {
  if (seatMapLayoutCache && !force) {
    return seatMapLayoutCache;
  }
  try {
    const layouts = (await Promise.all(seatMapOfficeKeys.map(async (officeKey) => {
      try {
        const activePayload = await apiFetch(`/api/seat-maps/active?office_key=${encodeURIComponent(officeKey)}`);
        const seatMap = activePayload?.item;
        if (!seatMap?.id) {
          return null;
        }
        return await apiFetch(withAsOf(`/api/seat-maps/${seatMap.id}/layout`));
      } catch {
        return null;
      }
    }))).filter(Boolean);
    seatMapLayoutCache = layouts;
    return layouts;
  } catch {
    seatMapLayoutCache = null;
    return [];
  }
}

function handleSeatMapLayoutUpdated() {
  seatMapLayoutCache = null;
  loadMembers().catch(() => { });
}

function getMemberSeatInfo(layouts, memberId) {
  if (!Array.isArray(layouts) || !memberId) {
    return null;
  }
  for (const layout of layouts) {
    const placement = (layout.placements || []).find((item) => Number(item.member_id) === Number(memberId));
    if (!placement) {
      continue;
    }
    const slot = (layout.slots || []).find((item) => Number(item.id) === Number(placement.seat_slot_id));
    return {
      layout,
      seatMapId: layout.seat_map?.id || null,
      seatMapName: layout.seat_map?.name || '자리배치도',
      seatLabel: placement.seat_label || slot?.label || '',
      slotKey: slot?.slot_key || '',
      assigned: true,
    };
  }
  return null;
}

function buildSeatAssignments(layout) {
  if (!layout || !Array.isArray(layout.placements) || !Array.isArray(layout.members) || !Array.isArray(layout.slots)) {
    return [];
  }
  return layout.placements.map((placement) => {
    const slot = layout.slots.find((item) => Number(item.id) === Number(placement.seat_slot_id));
    const memberItem = layout.members.find((item) => Number(item.id) === Number(placement.member_id));
    if (!slot || !memberItem) return null;
    return {
      key: String(slot.slot_key || ''),
      member_id: Number(memberItem.id),
      name: memberItem.name || '-',
      rank: memberItem.rank || '-',
    };
  }).filter(Boolean);
}

function applySeatPreviewFrameState(frame, seatInfo, layout) {
  if (!frame?.contentWindow || !seatInfo?.slotKey) {
    return;
  }
  const postState = () => {
    if (!frame.contentWindow) {
      return;
    }
    frame.contentWindow.postMessage({
      type: 'seatmap-set-assignments',
      items: buildSeatAssignments(layout),
    }, window.location.origin);
    frame.contentWindow.postMessage({ type: 'seatmap-set-mode', mode: 'compact' }, window.location.origin);
    frame.contentWindow.postMessage({ type: 'seatmap-focus-chair', key: seatInfo.slotKey, padding: 2600 }, window.location.origin);
  };
  postState();
  setTimeout(postState, 120);
}

async function syncMembers(nextMembers) {
  const payload = await apiFetch('/api/members/bulk-sync', {
    method: 'PUT',
    body: JSON.stringify({
      items: nextMembers.map((member, index) => toApiMember(member, index)),
    }),
  });
  setMembers(payload.items || []);
  render();
}

function jsString(value) {
  return String(value).replaceAll('\\', '\\\\').replaceAll("'", "\\'");
}

function updateDeptTabs(deptList) {
  const tabsContainer = document.getElementById('dept-tabs');
  tabsContainer.innerHTML = deptList.map((dept) => `
    <div class="dept-tab ${selectedDept === dept ? 'active' : ''}" onclick="selectDept('${jsString(dept)}')">${dept}</div>
  `).join('');
}

function selectDept(dept) {
  selectedDept = dept;
  render();
}

function calculateTotalCount(node) {
  let count = node.members.length;
  if (node.children) {
    node.children.forEach((child) => {
      count += calculateTotalCount(child);
    });
  }
  node.totalCount = count;
  return count;
}

function buildHierarchy(data, depth) {
  if (!data || data.length === 0) {
    return [];
  }

  const orderedGroups = [];
  const groupMap = {};

  data.forEach((member) => {
    const currentStep = member._path[depth];
    if (!currentStep) {
      return;
    }

    const currentName = currentStep.name;
    if (!groupMap[currentName]) {
      groupMap[currentName] = {
        name: currentName,
        level: currentStep.level,
        members: [],
        subData: [],
      };
      orderedGroups.push(groupMap[currentName]);
    }

    if (member._path.length === depth + 1) {
      groupMap[currentName].members.push(member);
    } else {
      groupMap[currentName].subData.push(member);
    }
  });

  return orderedGroups.map((group) => ({
    ...group,
    children: buildHierarchy(group.subData, depth + 1),
  }));
}

function createMemberCard(member, isFullWidth = false) {
  const card = document.createElement('div');
  card.id = `card-${member._id}`;
  card.className = `member-card co-${member['소속회사'] || 'default'} transition-all duration-200 mb-1 last:mb-0`;
  if (isFullWidth) {
    card.classList.add('full-width');
  }

  card.onclick = (event) => {
    event.stopPropagation();
    openModal(member._id);
  };

  if (isAdmin) {
    card.setAttribute('draggable', 'true');
    card.ondragstart = (event) => handleDragStart(event, 'member', member._id);
    card.ondragend = (event) => handleDragEnd(event);
    card.ondragover = (event) => handleDragOverMember(event);
    card.ondragleave = (event) => handleDragLeaveMember(event);
    card.ondrop = (event) => handleDropMember(event, member._id);
  }

  const isLeave = member['근무상태'] === '휴직';
  const roleDisplay = isLeave
    ? '<span class="m-role" style="color:#ef4444; border-color:#fee2e2; background:#fff1f2;">휴직</span>'
    : ((member['직책'] && member['직책'] !== '팀원') ? `<span class="m-role">${member['직책']}</span>` : '');

  card.innerHTML = `<div class="m-top"><span class="m-name">${member['이름']}</span>${roleDisplay}<span class="m-rank">${member['직급'] || ''}</span></div>`;
  return card;
}

function getAllSubMembers(node) {
  let memberList = [...node.members];
  node.children.forEach((child) => {
    memberList = memberList.concat(getAllSubMembers(child));
  });
  return memberList;
}

function collectTeamItems(teamNode) {
  let items = [...teamNode.members];
  teamNode.children.forEach((cell) => {
    items.push({ isCellHeader: true, name: cell.name });
    items = items.concat(getAllSubMembers(cell));
  });
  return items;
}

function createNodeDOM(node, parentId) {
  const nodeItem = document.createElement('div');
  nodeItem.className = `node-item${node.children.length || node.members.length ? ' has-children' : ''}`;

  const myId = `node-${encodeURIComponent(`${node.level}_${node.name}`)}`;
  const box = document.createElement('div');
  box.className = 'box transition-all duration-200';
  box.id = myId;
  box.setAttribute('data-level', node.level);
  if (parentId) {
    box.setAttribute('data-parent', parentId);
  }

  if (isAdmin) {
    box.ondragover = (event) => handleDragOver(event);
    box.ondragleave = (event) => handleDragLeave(event);
    box.ondrop = (event) => handleDrop(event, node.level, node.name);
  }

  box.classList.add(`box-level-${node.level}`);
  if (node.level === '팀') {
    box.classList.add('box-team');
  }

  const displayTitle = `${node.name} (${node.totalCount || 0})`;
  const nodeTitleClass = isAdmin ? 'clickable-title' : '';
  box.innerHTML = `<div class="box-name ${nodeTitleClass}" ${isAdmin ? `onclick="openOrgEditModal('${jsString(node.level)}', '${jsString(node.name)}')"` : ''}>${displayTitle}</div>`;

  const memberGrid = document.createElement('div');
  const isHighLevel = node.level !== '팀' && node.level !== '셀';
  memberGrid.className = isHighLevel ? 'flex flex-col w-full' : 'member-grid';

  if (node.level === '팀') {
    const teamItems = collectTeamItems(node);
    const leaderIdx = teamItems.findIndex((item) => item['직책'] === '팀장');
    const finalItems = [];
    if (leaderIdx !== -1) {
      finalItems.push(teamItems.splice(leaderIdx, 1)[0]);
    } else if (teamItems.length > 0) {
      finalItems.push(teamItems.shift());
    }

    while (teamItems.length > 0) {
      const nextIndex = finalItems.length;
      if (nextIndex > 0 && nextIndex % 10 === 0) {
        finalItems.push({ isSpacer: true });
        continue;
      }
      if (nextIndex % 10 === 9 && teamItems[0].isCellHeader) {
        finalItems.push({ isSpacer: true });
        continue;
      }
      finalItems.push(teamItems.shift());
    }

    finalItems.forEach((item) => {
      if (item.isSpacer) {
        const spacer = document.createElement('div');
        spacer.className = 'spacer-box';
        memberGrid.appendChild(spacer);
      } else if (item.isCellHeader) {
        const label = document.createElement('div');
        label.className = `cell-label${isAdmin ? ' clickable-title' : ''}`;
        label.innerText = item.name;
        if (isAdmin) {
          label.onclick = () => openOrgEditModal('셀', item.name);
        }
        memberGrid.appendChild(label);
      } else {
        memberGrid.appendChild(createMemberCard(item));
      }
    });
  } else {
    const isFullWidth = node.level !== '팀' && node.level !== '셀';
    node.members.forEach((member) => memberGrid.appendChild(createMemberCard(member, isFullWidth)));
  }

  box.appendChild(memberGrid);
  nodeItem.appendChild(box);

  if (node.level !== '팀' && node.children && node.children.length > 0) {
    const childrenWrapper = document.createElement('div');
    childrenWrapper.className = 'node-group';
    node.children.forEach((child) => childrenWrapper.appendChild(createNodeDOM(child, myId)));
    nodeItem.appendChild(childrenWrapper);
  }

  return nodeItem;
}

function drawLines() {
  const container = document.getElementById('tree-root');
  const svg = document.getElementById('svg-canvas');
  if (!svg || !container) {
    return;
  }

  const containerRect = container.getBoundingClientRect();
  let paths = '';

  container.querySelectorAll('[data-parent]').forEach((box) => {
    const parentId = box.getAttribute('data-parent');
    const parentBox = document.getElementById(parentId);
    if (!parentBox) {
      return;
    }

    const parentLevel = parentBox.getAttribute('data-level');
    const childLevel = box.getAttribute('data-level');
    if (parentLevel === '부서' && childLevel === '그룹') {
      return;
    }

    const parentRect = parentBox.getBoundingClientRect();
    const childRect = box.getBoundingClientRect();
    const startX = parentRect.left + parentRect.width / 2 - containerRect.left;
    const startY = parentRect.bottom - containerRect.top;
    const endX = childRect.left + childRect.width / 2 - containerRect.left;
    const endY = childRect.top - containerRect.top;
    const curveY = endY - 20;

    paths += `<path d="M ${startX} ${startY} L ${startX} ${curveY} L ${endX} ${curveY} L ${endX} ${endY}" stroke="#cbd5e1" stroke-width="2" fill="none" stroke-linejoin="round" />`;
  });

  svg.innerHTML = paths;
}

function updateStatsTable() {
  if (!members.length) {
    document.getElementById('stats-table-container').innerHTML = '';
    document.getElementById('total-count-badge').innerText = '0명';
    return;
  }

  const companies = ['한맥', '삼안', '피티씨', '바론'];
  const rankGroups = {
    경영진: ['사장', '부사장'],
    수석: ['수석'],
    책임: ['책임'],
    선임: ['선임'],
    연구: ['연구'],
  };

  const columns = Object.keys(rankGroups);
  const stats = {};
  const companyLabelHtml = (company) => `
    <span class="stats-company-label">
      <span class="stats-company-dot co-${company}"></span>
      <span>${company}</span>
    </span>
  `;

  companies.forEach((company) => {
    stats[company] = {};
    columns.forEach((column) => {
      stats[company][column] = 0;
    });
    stats[company]._total = 0;
  });

  const targetMembers = selectedDept === '전체'
    ? members
    : members.filter((member) => member['부서'] === selectedDept);

  targetMembers.forEach((member) => {
    const company = companies.find((item) => (member['소속회사'] || '').includes(item));
    if (!company) {
      return;
    }

    const rank = member['직급'] || '';
    for (const [groupName, keywords] of Object.entries(rankGroups)) {
      if (keywords.some((keyword) => rank.includes(keyword))) {
        stats[company][groupName] += 1;
        break;
      }
    }
    stats[company]._total += 1;
  });

  let html = `<table class="stats-table"><thead><tr><th class="row-label">구분</th>${columns.map((column) => `<th>${column}</th>`).join('')}<th>합계</th></tr></thead><tbody>`;
  const colSums = {};
  columns.forEach((column) => {
    colSums[column] = 0;
  });
  let grandTotal = 0;

  companies.forEach((company) => {
    html += `<tr><td class="row-label">${companyLabelHtml(company)}</td>${columns.map((column) => {
      colSums[column] += stats[company][column];
      return `<td>${stats[company][column] || '-'}</td>`;
    }).join('')}<td class="total-cell">${stats[company]._total}</td></tr>`;
    grandTotal += stats[company]._total;
  });

  html += `<tr class="sum-row"><td class="row-label">전체 합계</td>${columns.map((column) => `<td>${colSums[column]}</td>`).join('')}<td class="total-cell">${grandTotal}</td></tr></tbody></table>`;

  document.getElementById('stats-table-container').innerHTML = html;
  document.getElementById('total-count-badge').innerText = `${grandTotal}명`;
}

function render() {
  const container = document.getElementById('tree-root');
  container.innerHTML = '<svg id="svg-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;"></svg>';

  if (!members.length) {
    container.innerHTML += `<div class="text-slate-400 font-bold mt-20 text-xs text-center">${emptyStateMessage}</div>`;
    updateStatsTable();
    return;
  }

  const allDepts = Array.from(new Set(members.map((member) => member['부서']).filter(Boolean))).sort();
  updateDeptTabs(['전체', ...allDepts]);

  const deptNames = selectedDept === '전체' ? allDepts : [selectedDept];
  deptNames.forEach((deptName) => {
    const deptData = members.filter((member) => member['부서'] === deptName);
    const hierarchy = buildHierarchy(deptData, 0);
    const deptNode = hierarchy[0] || null;
    if (deptNode) {
      calculateTotalCount(deptNode);
    }

    const deptSection = document.createElement('div');
    deptSection.className = 'dept-section';
    const deptId = `node-${encodeURIComponent(`부서_${deptName}`)}`;
    const hasMembers = Boolean(deptNode && deptNode.members && deptNode.members.length > 0);
    const totalCount = deptNode ? deptNode.totalCount : deptData.length;

    const deptBox = document.createElement('div');
    deptBox.id = deptId;
    deptBox.className = 'dept-box';
    deptBox.setAttribute('data-level', '부서');
    deptBox.innerHTML = `<div class="dept-header ${isAdmin ? 'clickable-title' : ''} ${hasMembers ? 'has-members' : ''}" ${isAdmin ? `onclick="openOrgEditModal('부서', '${jsString(deptName)}')"` : ''}>${deptName} (${totalCount})</div>`;

    if (hasMembers) {
      const memberGrid = document.createElement('div');
      memberGrid.className = 'flex flex-col w-full';
      memberGrid.style.padding = '0 15px 15px 15px';
      deptNode.members.forEach((member) => memberGrid.appendChild(createMemberCard(member, true)));
      deptBox.appendChild(memberGrid);
    }

    deptSection.appendChild(deptBox);
    const groupContainer = document.createElement('div');
    groupContainer.className = 'node-group';
    if (deptNode && deptNode.children) {
      deptNode.children.forEach((child) => groupContainer.appendChild(createNodeDOM(child, deptId)));
    }
    deptSection.appendChild(groupContainer);
    container.appendChild(deptSection);
  });

  updateStatsTable();
  setTimeout(drawLines, 50);
}

function toggleAdminMode(checked) {
  if (checked && isHistoricalSnapshot) {
    alert('월말 히스토리 조회 중에는 수정할 수 없습니다. 최신 월로 돌아간 뒤 수정해주세요.');
    return;
  }
  isAdmin = checked;
  const button = document.getElementById('admin-mode-btn');
  if (isAdmin) {
    button.classList.add('is-admin');
    button.innerText = '🔓';
    button.setAttribute('data-label', '관리자 모드: ON');
  } else {
    button.classList.remove('is-admin');
    button.innerText = '🔐';
    button.setAttribute('data-label', '관리자 모드: OFF');
  }
  updateFabMenu();
  render();
}

function toggleFab(event) {
  if (event) {
    event.stopPropagation();
  }
  document.getElementById('fab-container').classList.toggle('active');
}

function updateFabMenu() {
  const menu = document.getElementById('fab-menu');
  let html = '<button class="fab-sub shadow-xl" data-label="리스트" onclick="openListViewModal(event)">📋</button>';
  html += '<button class="fab-sub shadow-xl" data-label="조직도 인쇄(A3)" onclick="printA3()">🖨️</button>';
  html += '<button class="fab-sub shadow-xl" data-label="자리배치도" onclick="openSeatMapView(event)">🪑</button>';
  if (isAdmin && !isHistoricalSnapshot) {
    html += '<button class="fab-sub shadow-xl" data-label="조직현황 업로드" onclick="triggerUpload(event)">⬆️</button>';
    html += '<button class="fab-sub shadow-xl" data-label="신규 구성원" onclick="openAddModal(event)">👤</button>';
    html += '<button class="fab-sub shadow-xl" data-label="신규 팀/그룹/셀" onclick="openUnitAddModal(event)">🏢</button>';
  }
  menu.innerHTML = html;
}

async function openHistoryCompareModal(fromDate, toDate) {
  openListViewModal();
  const fromInput = document.getElementById('list-compare-from');
  const toInput = document.getElementById('list-compare-to');
  if (fromInput) {
    fromInput.value = fromDate || '';
  }
  if (toInput) {
    toInput.value = toDate || '';
  }
  await loadCompareListView();
}

function openSeatMapView(event) {
  event.stopPropagation();
  document.getElementById('fab-container').classList.remove('active');
  if (window.parent && window.parent !== window) {
    window.parent.postMessage({ type: 'open-seatmap', readOnly: !isAdmin }, '*');
  }
}

window.addEventListener('message', (event) => {
  const data = event.data;
  if (!data || typeof data !== 'object') {
    return;
  }
  if (data.type === 'date-range') {
    activeAsOfDate = String(data.endDate || '').slice(0, 10);
    return;
  }
  if (data.type === 'organization-history-view') {
    activeAsOfDate = String(data.asOfDate || '').slice(0, 10);
    isHistoricalSnapshot = Boolean(data.historical);
    if (isHistoricalSnapshot && isAdmin) {
      toggleAdminMode(false);
    } else {
      updateFabMenu();
      render();
    }
    seatMapLayoutCache = null;
    loadMembers().catch(() => { });
    return;
  }
  if (data.type === 'open-history-compare') {
    openHistoryCompareModal(String(data.fromDate || ''), String(data.toDate || '')).catch((error) => {
      alert(error.message || '변경 비교를 불러오지 못했습니다.');
    });
    return;
  }
  if (data.type === 'seatmap-layout-updated') {
    handleSeatMapLayoutUpdated();
  }
});

function triggerUpload(event) {
  if (event) {
    event.stopPropagation();
  }
  document.getElementById('upload-excel').click();
}

function printA3() {
  window.print();
}

function toggleStats() {
  const container = document.getElementById('stats-table-container');
  const icon = document.getElementById('stats-toggle-icon');
  const area = document.getElementById('stats-area');
  if (container.style.display === 'none') {
    container.style.display = 'block';
    icon.style.transform = 'rotate(0deg)';
    area.style.padding = '15px';
  } else {
    container.style.display = 'none';
    icon.style.transform = 'rotate(-90deg)';
    area.style.padding = '10px 15px';
  }
}

function handleSearch(value) {
  const query = value.trim().toLowerCase();
  if (!query) {
    return;
  }

  document.querySelectorAll('.search-target').forEach((element) => element.classList.remove('search-target'));
  let targetEl = null;

  const memberMatch = members.find((member) => (member['이름'] || '').toLowerCase().includes(query));
  if (memberMatch) {
    targetEl = document.getElementById(`card-${memberMatch._id}`);
  }

  if (!targetEl) {
    for (const level of levelOrder) {
      const orgName = Array.from(new Set(members.map((member) => member[level]).filter(Boolean)))
        .find((name) => name.toLowerCase().includes(query));
      if (orgName) {
        targetEl = document.getElementById(`node-${encodeURIComponent(`${level}_${orgName}`)}`);
      }
      if (targetEl) {
        break;
      }
    }
  }

  if (!targetEl) {
    alert('검색 결과를 찾을 수 없습니다.');
    return;
  }

  targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
  targetEl.classList.add('search-target');
}

async function importMemberFile(file) {
  const formData = new FormData();
  formData.append('file', file);
  const payload = await apiFetch('/api/members/import', {
    method: 'POST',
    body: formData,
  });
  emptyStateMessage = '서버에 조직 데이터가 없습니다. 상단의 업로드 버튼으로 초기 데이터를 넣어주세요.';
  setMembers(payload.items || []);
  render();
}

function openAddModal(event) {
  if (event) {
    event.stopPropagation();
  }
  openModal(null);
}

function updateParentList() {
  const type = document.getElementById('new-unit-type').value;
  const parentSelect = document.getElementById('new-unit-parent');
  const typeIdx = levelOrder.indexOf(type);
  const parentType = levelOrder[typeIdx - 1];
  const parents = Array.from(new Set(members.map((member) => member[parentType]).filter(Boolean))).sort();
  parentSelect.innerHTML = '<option value="">-- 선택 안 함 (기본) --</option>' + parents.map((parent) => `<option value="${parent}">${parent}</option>`).join('');
}

function openUnitAddModal(event) {
  if (event) {
    event.stopPropagation();
  }

  if (!members.length) {
    alert('먼저 조직 데이터를 업로드해주세요.');
    return;
  }

  const modal = document.getElementById('modal');
  modal.querySelector('.modal-content').classList.remove('wide');
  document.getElementById('modal-title').innerText = '신규 조직 단위 추가';
  const fieldsArea = document.getElementById('modal-fields');
  fieldsArea.className = 'grid grid-cols-2 gap-x-8 gap-y-5';
  fieldsArea.style.maxHeight = 'none';
  fieldsArea.innerHTML = `
    <div class="col-span-2">
      <label class="text-[11px] font-black text-slate-600 block">만들고 싶은 조직 종류</label>
      <select id="new-unit-type" onchange="updateParentList()" class="w-full bg-white p-3 rounded-xl border text-sm font-bold outline-none">
        ${['그룹', '디비전', '팀', '셀'].map((unit) => `<option value="${unit}">${unit}</option>`).join('')}
      </select>
    </div>
    <div class="col-span-2">
      <label class="text-[11px] font-black text-slate-600 block">상위 위치 선택</label>
      <select id="new-unit-parent" class="w-full bg-white p-3 rounded-xl border text-sm font-bold outline-none"></select>
    </div>
    <div class="col-span-2">
      <label class="text-[11px] font-black text-slate-600 block">신규 명칭 입력</label>
      <input id="new-unit-name" placeholder="예: 신규개발팀" class="w-full bg-slate-50 p-3 rounded-xl border font-bold text-sm outline-none">
    </div>
  `;
  updateParentList();
  document.getElementById('modal-footer-area').innerHTML = `
    <button onclick="closeModal()" class="flex-1 bg-slate-100 py-3.5 rounded-xl font-bold text-sm">취소</button>
    <button onclick="saveNewUnit()" class="flex-1 bg-indigo-600 text-white py-3.5 rounded-xl font-bold text-sm">저장</button>
  `;
  modal.style.display = 'flex';
}

async function saveNewUnit() {
  const type = document.getElementById('new-unit-type').value;
  const parentName = document.getElementById('new-unit-parent').value;
  const name = document.getElementById('new-unit-name').value.trim();

  if (!name) {
    alert('이름을 입력해주세요.');
    return;
  }

  const typeIdx = levelOrder.indexOf(type);
  const parentType = levelOrder[typeIdx - 1];
  const template = (parentName && parentType)
    ? (members.find((member) => member[parentType] === parentName) || members[0])
    : members[0];

  const newMember = {
    ...cloneMembers([template])[0],
    _id: `virtual-${Date.now()}`,
    이름: '공석(신규)',
    직급: '',
    직책: '',
    소속회사: template?.소속회사 || '',
    전화번호: '',
    이메일: '',
    자리위치: '',
    사진: '',
    근무상태: '근무',
    근무시간: '09~18',
  };

  if (!parentName) {
    for (let index = 1; index < typeIdx; index += 1) {
      newMember[levelOrder[index]] = '';
    }
  } else {
    newMember[parentType] = parentName;
  }

  newMember[type] = name;
  for (let index = typeIdx + 1; index < levelOrder.length; index += 1) {
    newMember[levelOrder[index]] = '';
  }

  rebuildMemberPath(newMember);
  await syncMembers([...members, newMember]);
  closeModal();
}

function openOrgEditModal(level, oldName) {
  const modal = document.getElementById('modal');
  modal.querySelector('.modal-content').classList.remove('wide');
  document.getElementById('modal-title').innerText = `${level} 이름 수정`;
  const fieldsArea = document.getElementById('modal-fields');
  fieldsArea.className = 'grid grid-cols-2 gap-x-8 gap-y-5';
  fieldsArea.style.maxHeight = 'none';
  fieldsArea.innerHTML = `
    <div class="col-span-2">
      <label class="text-[11px] font-black text-slate-400 block">새로운 ${level} 명칭</label>
      <input id="new-org-name" value="${oldName}" class="w-full bg-slate-50 p-3 rounded-xl border font-bold text-sm outline-none">
    </div>
  `;
  document.getElementById('modal-footer-area').innerHTML = `
    <button onclick="deleteOrg('${jsString(level)}', '${jsString(oldName)}')" class="bg-red-50 text-red-600 py-3.5 px-6 rounded-xl font-bold text-sm border border-red-100 hover:bg-red-100 transition-colors">삭제</button>
    <button onclick="closeModal()" class="flex-1 bg-slate-100 py-3.5 rounded-xl font-bold text-sm">취소</button>
    <button onclick="saveOrgName('${jsString(level)}', '${jsString(oldName)}')" class="flex-1 bg-indigo-600 text-white py-3.5 rounded-xl font-bold text-sm">저장</button>
  `;
  modal.style.display = 'flex';
}

async function deleteOrg(level, name) {
  if (!confirm(`'${name}' ${level}과 소속된 모든 인원 정보가 삭제됩니다. 정말 삭제하시겠습니까?`)) {
    return;
  }
  const nextMembers = members.filter((member) => member[level] !== name);
  await syncMembers(nextMembers);
  closeModal();
}

async function saveOrgName(level, oldName) {
  const newName = document.getElementById('new-org-name').value.trim();
  if (!newName) {
    alert('이름을 입력해주세요.');
    return;
  }

  if (newName === oldName) {
    closeModal();
    return;
  }

  const nextMembers = cloneMembers(members);
  nextMembers.forEach((member) => {
    if (member[level] === oldName) {
      member[level] = newName;
      rebuildMemberPath(member);
    }
  });
  await syncMembers(nextMembers);
  closeModal();
}

function toggleManualInput(field) {
  document.getElementById(`manual-${field}`).classList.toggle('hidden', document.getElementById(`sel-${field}`).value !== '__NEW__');
}

function toggleFlexibleTime(value) {
  document.getElementById('flexible-time-area').classList.toggle('hidden', value !== '유연근무제');
}

function updatePhotoPreview(src, fallbackName) {
  const preview = document.getElementById('m-photo-preview');
  if (!preview) {
    return;
  }
  preview.src = src || getPhotoPlaceholder(fallbackName);
}

function syncPhotoPreviewFromUrl() {
  const name = document.getElementById('m-name')?.value?.trim() || '';
  const url = document.getElementById('m-photo-hidden')?.value?.trim() || '';
  updatePhotoPreview(url, name);
}

function handlePhotoFileChange(event) {
  const file = event.target.files?.[0];
  const fileName = document.getElementById('m-photo-file-name');
  const name = document.getElementById('m-name')?.value?.trim() || '';
  resetPhotoPreviewObjectUrl();

  if (!file) {
    if (fileName) {
      fileName.textContent = '선택된 파일 없음';
    }
    syncPhotoPreviewFromUrl();
    return;
  }

  if (fileName) {
    fileName.textContent = file.name;
  }
  photoPreviewObjectUrl = URL.createObjectURL(file);
  updatePhotoPreview(photoPreviewObjectUrl, name);
}

function renderSeatPreviewCard(seatInfo) {
  const assigned = Boolean(seatInfo?.assigned);
  const safeLabel = escapeHtml(seatInfo?.seatLabel || '');
  const safeSeatMapName = escapeHtml(seatInfo?.seatMapName || '자리배치도');
  const safeSlotKey = escapeHtml(seatInfo?.slotKey || '');
  const badge = assigned
    ? `<span class="seat-preview-badge">${safeLabel || '배치완료'}</span>`
    : '<span class="seat-preview-badge seat-preview-badge-muted">미배치</span>';
  const body = assigned
    ? `
      <iframe
        id="member-seat-preview-frame"
        class="seat-preview-frame"
        src="/api/seat-maps/${Number(seatInfo.seatMapId || 0)}/viewer"
        title="${safeSeatMapName} 좌석 미리보기"
        loading="eager"
        referrerpolicy="same-origin"
      ></iframe>
    `
    : `
      <div class="seat-preview-placeholder">
        <span class="seat-preview-placeholder-icon">⌖</span>
        <span>현재 공석 또는 미배치 상태입니다.</span>
      </div>
    `;

  return `
    <div class="seat-preview-card">
      <div class="seat-preview-head">
        <div>
          <strong>재석위치</strong>
          <p>${assigned ? '현재 자리배치도 기준으로 배치된 좌석 정보를 표시합니다.' : '현재 자리배치도에서 배치된 좌석이 없습니다.'}</p>
        </div>
        ${badge}
      </div>
      <div class="seat-preview-canvas">
        ${body}
      </div>
    </div>
  `;
}

async function hydrateMemberSeatPreview(member) {
  const target = document.getElementById('member-seat-preview');
  if (!target) {
    return;
  }
  target.innerHTML = renderSeatPreviewCard({
    assigned: false,
    seatMapName: '자리배치도',
    seatLabel: member['자리위치'] || '',
    slotKey: '',
  });
  const layouts = await loadSeatMapLayouts(true);
  if (!document.getElementById('member-seat-preview')) {
    return;
  }
  const seatInfo = getMemberSeatInfo(layouts, member.id) || {
    layout: null,
    seatMapName: '자리배치도',
    seatLabel: member['자리위치'] || '',
    slotKey: '',
    assigned: false,
  };
  target.innerHTML = renderSeatPreviewCard(seatInfo);
  if (!seatInfo.assigned || !seatInfo.seatMapId || !seatInfo.slotKey) {
    return;
  }
  const frame = document.getElementById('member-seat-preview-frame');
  if (!frame) {
    return;
  }
  frame.addEventListener('load', () => {
    applySeatPreviewFrameState(frame, seatInfo, seatInfo.layout);
  }, { once: true });
}

function switchModalTab(tab) {
  const isBasic = tab === 'basic';
  document.getElementById('modal-sec-basic').classList.toggle('hidden', !isBasic);
  document.getElementById('modal-sec-org').classList.toggle('hidden', isBasic);
  document.getElementById('modal-tab-basic').className = isBasic
    ? 'flex-1 py-3 font-bold border-b-2 border-indigo-600 text-indigo-600 text-sm transition-all'
    : 'flex-1 py-3 font-bold border-b-2 border-transparent text-slate-400 text-sm transition-all';
  document.getElementById('modal-tab-org').className = !isBasic
    ? 'flex-1 py-3 font-bold border-b-2 border-indigo-600 text-indigo-600 text-sm transition-all'
    : 'flex-1 py-3 font-bold border-b-2 border-transparent text-slate-400 text-sm transition-all';
}

function openModal(id) {
  const sourceList = isListMode ? editingMembers : members;
  const modal = document.getElementById('modal');
  modal.querySelector('.modal-content').classList.remove('wide');
  const fieldsArea = document.getElementById('modal-fields');
  const footer = document.getElementById('modal-footer-area');
  const member = id ? (sourceList.find((item) => item._id === id) || {}) : {};

  if (!isAdmin && id) {
    document.getElementById('modal-title').innerText = '구성원 상세 프로필';
    fieldsArea.className = 'flex flex-col items-center gap-6 py-4';
    fieldsArea.style.maxHeight = 'none';
    fieldsArea.innerHTML = `
      <div class="member-detail-top-row">
        <div class="relative w-32 h-32 rounded-full overflow-hidden border-4 border-indigo-100 shadow-lg">
          <img src="${member['사진'] || 'https://via.placeholder.com/120?text=Profile'}" class="w-full h-full object-cover">
        </div>
        <div class="member-detail-summary">
          <div>
            <h2 class="text-2xl font-black text-slate-800">${member['이름'] || ''}</h2>
            <p class="text-indigo-600 font-bold">${member['직급'] || '-'} / ${member['직책'] || '팀원'}</p>
            <p class="text-slate-400 text-xs mt-1 font-medium">${(member._path || []).map((path) => path.name).join(' > ')}</p>
          </div>
          <div class="member-inline-info-grid">
            <div class="member-inline-info-card">
              <label>전화번호</label>
              <strong>${member['전화번호'] || '정보 없음'}</strong>
            </div>
            <div class="member-inline-info-card">
              <label>이메일</label>
              <strong>${member['이메일'] || '정보 없음'}</strong>
            </div>
          </div>
        </div>
      </div>
      <div class="w-full mt-2">
        <div id="member-seat-preview">${renderSeatPreviewCard({ assigned: false, seatLabel: member['자리위치'] || '', seatMapName: '자리배치도', slotKey: '' })}</div>
      </div>
    `;
    footer.innerHTML = '<button onclick="closeModal()" class="w-full bg-slate-800 text-white py-4 rounded-xl font-bold text-sm shadow-lg">닫기</button>';
    modal.style.display = 'flex';
    hydrateMemberSeatPreview(member);
    return;
  }

  document.getElementById('modal-title').innerText = id ? '구성원 정보 수정' : '신규 구성원 추가';
  modal.querySelector('.modal-content').classList.add('wide');
  fieldsArea.className = 'flex flex-col w-full';
  fieldsArea.style.maxHeight = 'none';
  fieldsArea.style.overflowY = 'visible';

  const sourceValues = isListMode ? editingMembers : members;
  let orgFields = '<div id="modal-sec-org" class="hidden grid grid-cols-2 gap-3 modal-form-grid">';
  dropdownFields.forEach((field) => {
    const uniqueValues = Array.from(new Set(sourceValues.map((item) => item[field]).filter(Boolean))).sort();
    const currentValue = member[field] || '';
    orgFields += `
      <div class="col-span-1">
        <label class="text-[11px] font-black text-slate-600 block">${field}</label>
        <select id="sel-${field}" onchange="toggleManualInput('${field}')" class="w-full bg-white p-3 rounded-xl border text-sm font-bold text-slate-700 outline-none">
          <option value="__NEW__" class="text-indigo-600 font-bold">+ 직접/신규 입력</option>
          <option value="__NONE__" ${currentValue === '' ? 'selected' : ''}>-- 선택 안 함 --</option>
          ${uniqueValues.map((value) => `<option value="${value}" ${value === currentValue ? 'selected' : ''}>${value}</option>`).join('')}
        </select>
        <div id="manual-${field}" class="hidden mt-2">
          <input id="input-${field}" placeholder="직접 입력" class="w-full bg-indigo-50 p-3 rounded-xl border-indigo-200 border text-sm font-bold">
        </div>
      </div>
    `;
  });

  const isFlexible = member['근무시간'] === '유연근무제';
  orgFields += `
    <div class="col-span-1">
      <label class="text-[11px] font-black text-slate-600 block">근무 상태</label>
      <select id="m-status" class="w-full bg-white p-3 rounded-xl border text-sm font-bold outline-none">
        <option value="근무" ${member['근무상태'] !== '휴직' ? 'selected' : ''}>근무</option>
        <option value="휴직" ${member['근무상태'] === '휴직' ? 'selected' : ''}>휴직</option>
      </select>
    </div>
    <div class="col-span-1">
      <label class="text-[11px] font-black text-slate-600 block">근무 시간</label>
      <select id="m-worktime" onchange="toggleFlexibleTime(this.value)" class="w-full bg-white p-3 rounded-xl border text-sm font-bold outline-none">
        <option value="09~18" ${!isFlexible ? 'selected' : ''}>09~18</option>
        <option value="유연근무제" ${isFlexible ? 'selected' : ''}>유연근무제</option>
      </select>
      <div id="flexible-time-area" class="${isFlexible ? '' : 'hidden'} mt-2 flex items-center gap-2">
        <input type="time" id="m-work-start" value="${member['유연근무_시작'] || '09:00'}" class="bg-indigo-50 p-2 rounded-lg border border-indigo-100 text-xs font-bold w-full">
        <input type="time" id="m-work-end" value="${member['유연근무_종료'] || '18:00'}" class="bg-indigo-50 p-2 rounded-lg border border-indigo-100 text-xs font-bold w-full">
      </div>
    </div>
  </div>`;

  fieldsArea.innerHTML = `
    <div class="flex border-b mb-6 sticky top-0 bg-white z-10">
      <button id="modal-tab-basic" onclick="switchModalTab('basic')" class="flex-1 py-3 font-bold border-b-2 border-indigo-600 text-indigo-600 text-sm transition-all">기본 정보</button>
      <button id="modal-tab-org" onclick="switchModalTab('org')" class="flex-1 py-3 font-bold border-b-2 border-transparent text-slate-400 text-sm transition-all">조직 및 근무</button>
    </div>
    <div id="modal-sec-basic" class="grid grid-cols-2 gap-3 modal-form-grid">
      <input type="hidden" id="m-id" value="${id || ''}">
      <input type="hidden" id="m-photo-hidden" value="${member['사진'] || ''}">
      <input type="hidden" id="m-seat-hidden" value="${member['자리위치'] || ''}">
      <div class="col-span-2 member-edit-layout">
        <div class="member-edit-left-pane">
          <div class="member-edit-profile-card">
            <label class="text-[11px] font-black text-slate-600 block">프로필 사진</label>
            <div class="member-photo-upload-card member-photo-upload-card-compact">
              <div class="member-photo-preview-wrap">
                <img id="m-photo-preview" src="${member['사진'] || getPhotoPlaceholder(member['이름'] || '')}" alt="프로필 미리보기" class="member-photo-preview">
              </div>
              <div class="member-photo-upload-controls">
                <label class="member-photo-file-label" for="m-photo-file">
                  <input id="m-photo-file" type="file" accept="image/png,image/jpeg,image/webp,image/gif" onchange="handlePhotoFileChange(event)">
                  <span>사진 파일 선택</span>
                </label>
                <strong id="m-photo-file-name" class="member-photo-file-name">선택된 파일 없음</strong>
              </div>
            </div>
            <div class="member-name-field member-name-field-compact">
              <label class="text-[11px] font-black text-slate-600 block">이름 (필수)</label>
              <input id="m-name" value="${member['이름'] || ''}" oninput="syncPhotoPreviewFromUrl()" class="w-full bg-slate-50 p-3 rounded-xl border font-bold text-sm outline-none">
            </div>
            <div class="member-inline-info-grid member-inline-info-grid-stacked">
              <div class="member-inline-info-card member-inline-info-card-full">
                <label>사번</label>
                <input id="m-employee-id" value="${member['사번'] || ''}" class="w-full bg-white p-3 rounded-xl border font-bold text-sm outline-none">
              </div>
              <div class="member-inline-info-card member-inline-info-card-full">
                <label>전화번호</label>
                <input id="m-phone" value="${member['전화번호'] || ''}" class="w-full bg-white p-3 rounded-xl border font-bold text-sm outline-none">
              </div>
              <div class="member-inline-info-card member-inline-info-card-full">
                <label>이메일</label>
                <input id="m-email" value="${member['이메일'] || ''}" class="w-full bg-white p-3 rounded-xl border font-bold text-sm outline-none">
              </div>
            </div>
          </div>
        </div>
        <div class="member-edit-right-pane">
          <div class="member-seat-field member-seat-field-emphasis">
            <div id="member-seat-preview">${renderSeatPreviewCard({ assigned: false, seatLabel: member['자리위치'] || '', seatMapName: '자리배치도', slotKey: '' })}</div>
          </div>
        </div>
      </div>
    </div>
    ${orgFields}
  `;

  resetPhotoPreviewObjectUrl();

  const deleteBtn = id ? `<button onclick="deleteMember('${id}')" class="bg-red-50 text-red-600 py-3.5 px-6 rounded-xl font-bold text-sm border border-red-100 hover:bg-red-100 transition-colors">삭제</button>` : '';
  footer.innerHTML = `
    ${deleteBtn}
    <button onclick="closeModal()" class="flex-1 bg-slate-100 py-3.5 rounded-xl font-bold text-sm">취소</button>
    <button onclick="saveMember()" class="flex-1 bg-indigo-600 text-white py-3.5 rounded-xl font-bold text-sm">저장</button>
  `;
  modal.style.display = 'flex';
  if (id) {
    hydrateMemberSeatPreview(member);
  }
}

function closeModal() {
  resetPhotoPreviewObjectUrl();
  document.getElementById('modal').style.display = 'none';
  document.getElementById('modal-fields').className = 'grid grid-cols-2 gap-x-8 gap-y-5';
  document.getElementById('modal-fields').style.maxHeight = 'none';
  document.querySelector('.modal-content').classList.remove('wide');
  isListMode = false;
}

async function saveMember() {
  const id = document.getElementById('m-id')?.value || '';
  const name = document.getElementById('m-name').value.trim();
  if (!name) {
    alert('이름을 입력해주세요.');
    return;
  }

  const targetList = isListMode ? editingMembers : members;
  let member = id ? targetList.find((item) => item._id === id) : { _id: `virtual-${Date.now()}` };
  if (!member) {
    member = { _id: `virtual-${Date.now()}` };
  }

  member['이름'] = name;
  member['사번'] = document.getElementById('m-employee-id').value.trim();
  dropdownFields.forEach((field) => {
    const selectValue = document.getElementById(`sel-${field}`).value;
    if (selectValue === '__NEW__') {
      member[field] = document.getElementById(`input-${field}`).value.trim();
    } else if (selectValue === '__NONE__') {
      member[field] = '';
    } else {
      member[field] = selectValue;
    }
  });

  member['근무상태'] = document.getElementById('m-status').value;
  member['근무시간'] = document.getElementById('m-worktime').value;
  member['전화번호'] = document.getElementById('m-phone').value.trim();
  member['이메일'] = document.getElementById('m-email').value.trim();
  member['자리위치'] = document.getElementById('m-seat-hidden').value.trim();
  member['사진'] = document.getElementById('m-photo-hidden').value.trim();
  const photoFile = document.getElementById('m-photo-file')?.files?.[0];
  if (photoFile) {
    member['사진'] = await uploadProfilePhoto(photoFile, member['이름']);
  }
  if (member['근무시간'] === '유연근무제') {
    member['유연근무_시작'] = document.getElementById('m-work-start').value;
    member['유연근무_종료'] = document.getElementById('m-work-end').value;
  } else {
    member['유연근무_시작'] = '';
    member['유연근무_종료'] = '';
  }
  rebuildMemberPath(member);

  if (isListMode) {
    if (!id) {
      targetList.push(member);
    }
    renderListViewTable();
    closeModal();
    return;
  }

  if (id && member.id) {
    await apiFetch(`/api/members/${member.id}`, {
      method: 'PUT',
      body: JSON.stringify({
        ...toApiMember(member, member.sort_order || 0),
        id: member.id,
      }),
    });
  } else {
    await apiFetch('/api/members', {
      method: 'POST',
      body: JSON.stringify(toApiMember(member, members.length)),
    });
  }

  await loadMembers();
  closeModal();
}

async function deleteMember(id) {
  if (!confirm('해당 구성원을 삭제하시겠습니까?')) {
    return;
  }

  if (isListMode) {
    const idx = editingMembers.findIndex((member) => member._id === id);
    if (idx !== -1) {
      editingMembers.splice(idx, 1);
    }
    renderListViewTable();
    return;
  }

  const member = members.find((item) => item._id === id);
  if (!member?.id) {
    return;
  }
  await apiFetch(`/api/members/${member.id}`, { method: 'DELETE' });
  await loadMembers();
  closeModal();
}

function openListViewModal(event) {
  if (event) {
    event.stopPropagation();
  }

  const defaultDate = getDefaultHistoryDate();
  listViewState.mode = 'current';
  listViewState.snapshotDate = defaultDate;
  listViewState.compareFromDate = defaultDate;
  listViewState.compareToDate = defaultDate;
  listViewState.snapshotMembers = [];
  listViewState.compareItems = [];

  const modal = document.getElementById('modal');
  modal.querySelector('.modal-content').classList.add('wide');
  document.getElementById('modal-title').innerText = '인원 명단';
  const fieldsArea = document.getElementById('modal-fields');
  fieldsArea.className = 'flex flex-col w-full overflow-hidden';
  fieldsArea.style.maxHeight = '75vh';
  isListMode = true;
  editingMembers = cloneMembers(members);
  fieldsArea.innerHTML = `
    <div class="list-toolbar">
      <div class="list-toolbar-row">
        <div class="list-toolbar-group">
          <button type="button" onclick="showCurrentListView()" class="list-mode-btn">현재 명단</button>
        </div>
        <div class="list-toolbar-divider" aria-hidden="true"></div>
        <div class="list-toolbar-group list-date-group">
          <input type="date" id="list-snapshot-date" value="${escapeHtml(defaultDate)}" class="list-date-input">
          <button type="button" onclick="loadSnapshotListView()" class="list-mode-btn">기준일 조회</button>
        </div>
        <div class="list-toolbar-divider" aria-hidden="true"></div>
        <div class="list-toolbar-group list-date-group">
          <input type="date" id="list-compare-from" value="${escapeHtml(defaultDate)}" class="list-date-input">
          <span class="list-date-separator">~</span>
          <input type="date" id="list-compare-to" value="${escapeHtml(defaultDate)}" class="list-date-input">
          <button type="button" onclick="loadCompareListView()" class="list-mode-btn">변경 비교</button>
        </div>
      </div>
      <div class="list-toolbar-row">
        <input type="text" id="list-search-input" placeholder="이름 또는 부서/팀 검색 (Enter 시 이동)" class="flex-1 bg-slate-50 border-2 border-slate-100 p-3 rounded-xl text-sm outline-none font-bold focus:border-indigo-400 transition-all" onkeydown="if(event.key==='Enter') handleListSearch(this.value)">
        <button type="button" onclick="handleListSearch(document.getElementById('list-search-input').value)" class="bg-indigo-600 text-white px-5 rounded-xl font-bold text-sm">검색</button>
      </div>
      <div id="list-view-status" class="list-view-status"></div>
    </div>
    <div id="list-table-container" class="overflow-y-auto flex-1 border rounded-xl"></div>
  `;
  renderListViewModalContent();
  modal.style.display = 'flex';
}

async function applyListViewChanges() {
  if (listViewState.mode !== 'current') {
    closeModal();
    return;
  }
  if (!confirm('리스트의 변경 사항을 메인 화면에 반영하시겠습니까?')) {
    return;
  }
  await syncMembers(editingMembers);
  isListMode = false;
  closeModal();
}

function renderListViewFooter() {
  const footer = document.getElementById('modal-footer-area');
  if (!footer) {
    return;
  }
  if (listViewState.mode === 'current' && isAdmin) {
    footer.innerHTML = `
      <div class="flex gap-2 w-full justify-between items-center">
        <div class="flex gap-2">
          <button onclick="openAddModal(event)" class="bg-indigo-50 text-indigo-600 px-4 py-2 rounded-lg text-xs font-bold border border-indigo-100">+ 구성원 추가</button>
          <button onclick="openUnitAddModal(event)" class="bg-indigo-50 text-indigo-600 px-4 py-2 rounded-lg text-xs font-bold border border-indigo-100">+ 조직 추가</button>
        </div>
        <div class="flex gap-2 items-center">
          <p class="text-[10px] text-slate-400 font-bold mr-4">항목을 드래그하여 순서를 바꿀 수 있습니다.</p>
          <button onclick="closeModal()" class="bg-slate-100 text-slate-600 px-6 py-2 rounded-lg text-xs font-bold">취소</button>
          <button onclick="applyListViewChanges()" class="bg-indigo-600 text-white px-8 py-2 rounded-lg text-xs font-bold shadow-lg shadow-indigo-200">반영하기</button>
        </div>
      </div>
    `;
    return;
  }
  footer.innerHTML = '<div class="flex gap-2 w-full justify-end items-center"><button onclick="closeModal()" class="bg-indigo-600 text-white px-10 py-2.5 rounded-lg text-xs font-bold shadow-lg shadow-indigo-200">닫기</button></div>';
}

function getRenderableListMembers() {
  if (listViewState.mode === 'snapshot') {
    return listViewState.snapshotMembers;
  }
  return editingMembers;
}

function getListSearchEntries() {
  if (listViewState.mode === 'compare') {
    return (listViewState.compareItems || []).map((item) => ({
      rowId: `list-compare-row-${item.member_id}`,
      name: String(item.name || ''),
      values: [String(item.name || ''), ...(item.before_lines || []), ...(item.after_lines || [])],
    }));
  }
  return getRenderableListMembers().map((member) => ({
    rowId: `list-row-${member._id}`,
    name: String(member['이름'] || ''),
    values: [
      String(member['이름'] || ''),
      ...levelOrder.map((level) => String(member[level] || '')),
    ],
  }));
}

function formatCompareChangedAt(value) {
  const raw = String(value || '').trim();
  if (!raw) {
    return '-';
  }
  const date = new Date(raw);
  if (Number.isNaN(date.getTime())) {
    return raw;
  }
  const year = date.getFullYear();
  const month = pad(date.getMonth() + 1);
  const day = pad(date.getDate());
  const hours = pad(date.getHours());
  const minutes = pad(date.getMinutes());
  return `${year}-${month}-${day} ${hours}:${minutes}`;
}

function renderListViewCompareTable() {
  const container = document.getElementById('list-table-container');
  if (!container) {
    return;
  }

  const rows = listViewState.compareItems || [];
  let html = `
    <table class="list-table list-compare-table">
      <thead>
        <tr>
          <th class="col-name">이름</th>
          <th class="col-compare-status">상태</th>
          <th class="col-compare-date">변경일시</th>
          <th class="col-compare-category">변경유형</th>
          <th>이전</th>
          <th>현재</th>
        </tr>
      </thead>
      <tbody>
  `;

  if (!rows.length) {
    html += '<tr><td colspan="6" class="list-empty-cell">선택한 기간 사이의 구성원 변경 내역이 없습니다.</td></tr>';
  } else {
    rows.forEach((item) => {
      const categories = (item.categories || []).map((category) => `<span class="list-compare-chip">${escapeHtml(category)}</span>`).join('');
      const beforeLines = (item.before_lines || []).map((line) => `<div class="list-compare-line">${escapeHtml(line)}</div>`).join('') || '<span class="text-slate-300">-</span>';
      const afterLines = (item.after_lines || []).map((line) => `<div class="list-compare-line">${escapeHtml(line)}</div>`).join('') || '<span class="text-slate-300">-</span>';
      html += `
        <tr id="list-compare-row-${item.member_id}">
          <td class="font-black text-slate-700">${escapeHtml(item.name || '-')}</td>
          <td><span class="list-compare-status list-compare-status-${escapeHtml(item.status || 'updated')}">${escapeHtml(item.status_label || '-')}</span></td>
          <td>${escapeHtml(formatCompareChangedAt(item.changed_at))}</td>
          <td><div class="list-compare-chip-group">${categories || '<span class="text-slate-300">-</span>'}</div></td>
          <td class="list-compare-cell">${beforeLines}</td>
          <td class="list-compare-cell">${afterLines}</td>
        </tr>
      `;
    });
  }

  html += '</tbody></table>';
  container.innerHTML = html;
}

function renderListViewModalContent() {
  const status = document.getElementById('list-view-status');
  if (status) {
    if (listViewState.mode === 'snapshot') {
      status.textContent = listViewState.snapshotDate
        ? `${listViewState.snapshotDate} 기준 인원 명단입니다.`
        : '기준일을 선택한 뒤 조회하세요.';
    } else if (listViewState.mode === 'compare') {
      status.textContent = (listViewState.compareFromDate && listViewState.compareToDate)
        ? `${listViewState.compareFromDate} ~ ${listViewState.compareToDate} 변경 내역입니다.`
        : '비교 시작일과 종료일을 선택하세요.';
    } else {
      status.textContent = '현재 조직 인원 명단입니다.';
    }
  }

  if (listViewState.mode === 'compare') {
    renderListViewCompareTable();
  } else {
    renderListViewTable();
  }
  renderListViewFooter();
}

function showCurrentListView() {
  listViewState.mode = 'current';
  renderListViewModalContent();
}

async function loadSnapshotListView() {
  const snapshotDate = document.getElementById('list-snapshot-date')?.value || '';
  if (!snapshotDate) {
    alert('기준일을 선택해주세요.');
    return;
  }
  const payload = await apiFetch(`/api/members?as_of=${encodeURIComponent(snapshotDate)}`);
  listViewState.snapshotDate = snapshotDate;
  listViewState.snapshotMembers = (payload.items || []).map(toLegacyMember);
  listViewState.mode = 'snapshot';
  renderListViewModalContent();
}

async function loadCompareListView() {
  const fromDate = document.getElementById('list-compare-from')?.value || '';
  const toDate = document.getElementById('list-compare-to')?.value || '';
  if (!fromDate || !toDate) {
    alert('비교 시작일과 종료일을 선택해주세요.');
    return;
  }
  const payload = await apiFetch(`/api/history/members/compare?from_date=${encodeURIComponent(fromDate)}&to_date=${encodeURIComponent(toDate)}`);
  listViewState.compareFromDate = fromDate;
  listViewState.compareToDate = toDate;
  listViewState.compareItems = Array.isArray(payload.items) ? payload.items : [];
  listViewState.mode = 'compare';
  renderListViewModalContent();
}

function renderListViewTable() {
  const container = document.getElementById('list-table-container');
  if (!container) {
    return;
  }

  const sourceMembers = getRenderableListMembers();
  const editable = isAdmin && listViewState.mode === 'current';
  const inspectable = !editable && listViewState.mode === 'current';
  const groupColumnCount = editable ? 11 : 10;
  let html = `<table class="list-table"><thead><tr>${editable ? '<th width="40">순서</th>' : ''}<th class="col-name">이름</th><th class="col-rank">직급</th><th class="col-pos">직책</th><th class="col-unit-sm">셀</th><th class="col-unit-sm">팀</th><th class="col-unit-lg">디비전</th><th class="col-unit-lg">그룹</th><th class="col-unit-lg">부서</th><th class="col-corp">소속</th><th class="col-action">${editable ? '관리' : '조회'}</th></tr></thead><tbody id="list-body">`;
  const lastValues = {};
  levelOrder.forEach((level) => {
    lastValues[level] = '';
  });

  sourceMembers.forEach((member, index) => {
    let isAnyParentCollapsed = false;
    levelOrder.forEach((level, depth) => {
      const value = (member[level] || '').trim();
      if (!value) {
        return;
      }
      const key = `${level}_${value}`;
      const parentLevels = levelOrder.slice(0, depth);
      if (parentLevels.some((parentLevel) => member[parentLevel] && collapsedUnits.has(`${parentLevel}_${member[parentLevel].trim()}`))) {
        isAnyParentCollapsed = true;
      }
      if (value !== lastValues[level]) {
        const isCollapsed = collapsedUnits.has(key);
        const dragAttr = editable ? `draggable="true" ondragstart="handleListGroupDragStart(event, '${jsString(level)}', '${jsString(value)}')" ondragover="event.preventDefault()" ondrop="handleListGroupDrop(event, '${jsString(level)}', '${jsString(value)}')"` : '';
        html += `<tr ${dragAttr} class="list-header-row lvl-${depth} ${isCollapsed ? 'collapsed' : ''} ${isAnyParentCollapsed ? 'hidden-row' : ''}"><td colspan="${groupColumnCount}" onclick="toggleUnitCollapse('${jsString(level)}', '${jsString(value)}')" style="padding-left: 15px !important;"><span class="collapse-icon">▼</span> ${escapeHtml(value)}</td></tr>`;
        lastValues[level] = value;
        levelOrder.slice(depth + 1).forEach((childLevel) => {
          lastValues[childLevel] = '';
        });
      }
    });

    const hidden = levelOrder.some((level) => member[level] && collapsedUnits.has(`${level}_${member[level].trim()}`)) || isAnyParentCollapsed;
    const rowDragAttr = editable ? `draggable="true" ondragstart="handleListDragStart(event, ${index})" ondragover="event.preventDefault()" ondrop="handleListDrop(event, ${index})"` : '';
    const actionCell = editable
      ? `<div class="flex gap-1 justify-center"><span class="list-action-btn btn-edit" onclick="openModal('${member._id}')">수정</span><span class="list-action-btn btn-delete" onclick="deleteMember('${member._id}')">삭제</span></div>`
      : inspectable
        ? `<span class="list-action-btn btn-edit bg-indigo-50 text-indigo-600 border border-indigo-100" onclick="openModal('${member._id}')">조회</span>`
        : '<span class="text-slate-300">-</span>';
    html += `
      <tr id="list-row-${member._id}" ${rowDragAttr} class="${hidden ? 'hidden-row' : ''}">
        ${editable ? '<td class="text-slate-300 cursor-move">☰</td>' : ''}
        <td class="font-black text-slate-700">${escapeHtml(member['이름'] || '-')}</td>
        <td>${escapeHtml(member['직급'] || '-')}</td>
        <td>${member['근무상태'] === '휴직' ? '<span class="text-red-500 font-black">휴직</span>' : escapeHtml(member['직책'] || '-')}</td>
        <td>${escapeHtml(member['셀'] || '-')}</td>
        <td>${escapeHtml(member['팀'] || '-')}</td>
        <td>${escapeHtml(member['디비전'] || '-')}</td>
        <td>${escapeHtml(member['그룹'] || '-')}</td>
        <td>${escapeHtml(member['부서'] || '-')}</td>
        <td>${escapeHtml(member['소속회사'] || '-')}</td>
        <td>${actionCell}</td>
      </tr>
    `;
  });

  html += '</tbody></table>';
  container.innerHTML = html;
}

function toggleUnitCollapse(level, name) {
  const key = `${level}_${name}`;
  if (collapsedUnits.has(key)) {
    collapsedUnits.delete(key);
  } else {
    collapsedUnits.add(key);
  }
  renderListViewTable();
}

let draggedGroup = null;
function handleListGroupDragStart(event, level, name) {
  draggedGroup = { level, name };
  event.dataTransfer.effectAllowed = 'move';
}

function handleListGroupDrop(event, targetLevel, targetName) {
  event.preventDefault();
  if (!draggedGroup || (draggedGroup.level === targetLevel && draggedGroup.name === targetName)) {
    return;
  }
  const movingMembers = editingMembers.filter((member) => member[draggedGroup.level] === draggedGroup.name);
  if (!movingMembers.length) {
    return;
  }
  editingMembers = editingMembers.filter((member) => member[draggedGroup.level] !== draggedGroup.name);
  let targetIdx = editingMembers.findIndex((member) => member[targetLevel] === targetName);
  if (targetIdx === -1) {
    targetIdx = editingMembers.length;
  }
  editingMembers.splice(targetIdx, 0, ...movingMembers);
  draggedGroup = null;
  renderListViewTable();
}

function handleListSearch(value) {
  const query = value.trim().toLowerCase();
  if (!query) {
    return;
  }
  document.querySelectorAll('.list-search-target').forEach((element) => element.classList.remove('list-search-target'));
  const targetEntry = getListSearchEntries().find((entry) => (
    entry.values.some((candidate) => String(candidate || '').toLowerCase().includes(query))
  ));
  if (!targetEntry) {
    alert('검색 결과가 없습니다.');
    return;
  }
  const row = document.getElementById(targetEntry.rowId);
  if (row) {
    row.scrollIntoView({ behavior: 'smooth', block: 'center' });
    row.classList.add('list-search-target');
    setTimeout(() => row.classList.remove('list-search-target'), 2000);
  }
}

let draggedIdx = null;
function handleListDragStart(event, index) {
  draggedIdx = index;
  event.dataTransfer.effectAllowed = 'move';
  event.target.classList.add('dragging');
}

function handleListDrop(event, targetIdx) {
  event.preventDefault();
  if (draggedIdx === null || draggedIdx === targetIdx) {
    return;
  }
  const moved = editingMembers.splice(draggedIdx, 1)[0];
  editingMembers.splice(targetIdx, 0, moved);
  draggedIdx = null;
  renderListViewTable();
}

function handleDragStart(event, type, id) {
  event.stopPropagation();
  if (type !== 'member') {
    return;
  }
  event.dataTransfer.setData('text/plain', JSON.stringify({ type, id }));
  event.dataTransfer.effectAllowed = 'move';
  setTimeout(() => event.target.classList.add('opacity-40', 'scale-95'), 0);
}

function handleDragEnd(event) {
  event.stopPropagation();
  event.target.classList.remove('opacity-40', 'scale-95');
}

function handleDragOver(event) {
  event.preventDefault();
  event.stopPropagation();
  event.dataTransfer.dropEffect = 'move';
  event.currentTarget.classList.add('ring-4', 'ring-indigo-400', 'bg-indigo-50');
}

function handleDragLeave(event) {
  event.stopPropagation();
  event.currentTarget.classList.remove('ring-4', 'ring-indigo-400', 'bg-indigo-50');
}

async function handleDrop(event, targetLevel, targetName) {
  event.preventDefault();
  event.stopPropagation();
  event.currentTarget.classList.remove('ring-4', 'ring-indigo-400', 'bg-indigo-50');

  try {
    const data = JSON.parse(event.dataTransfer.getData('text/plain'));
    if (data.type !== 'member') {
      return;
    }
    const targetMember = members.find((member) => member[targetLevel] === targetName);
    const targetLevelIndex = levelOrder.indexOf(targetLevel);
    const memberIndex = members.findIndex((item) => item._id === data.id);
    if (memberIndex === -1) {
      return;
    }
    const nextMembers = cloneMembers(members);
    const moved = nextMembers[memberIndex];
    for (let index = 0; index <= targetLevelIndex; index += 1) {
      moved[levelOrder[index]] = targetMember ? targetMember[levelOrder[index]] : targetName;
    }
    for (let index = targetLevelIndex + 1; index < levelOrder.length; index += 1) {
      moved[levelOrder[index]] = '';
    }
    rebuildMemberPath(moved);
    nextMembers.splice(memberIndex, 1);
    nextMembers.push(moved);
    await syncMembers(nextMembers);
  } catch (error) {
    console.error(error);
  }
}

function handleDragOverMember(event) {
  event.preventDefault();
  event.stopPropagation();
  event.dataTransfer.dropEffect = 'move';
  const rect = event.currentTarget.getBoundingClientRect();
  const relX = event.clientX - rect.left;
  if (relX < rect.width / 2) {
    event.currentTarget.classList.add('drop-left');
    event.currentTarget.classList.remove('drop-right');
  } else {
    event.currentTarget.classList.add('drop-right');
    event.currentTarget.classList.remove('drop-left');
  }
}

function handleDragLeaveMember(event) {
  event.stopPropagation();
  event.currentTarget.classList.remove('drop-left', 'drop-right');
}

async function handleDropMember(event, targetId) {
  event.preventDefault();
  event.stopPropagation();
  const rect = event.currentTarget.getBoundingClientRect();
  const insertAfter = event.clientX - rect.left >= rect.width / 2;
  event.currentTarget.classList.remove('drop-left', 'drop-right');

  try {
    const data = JSON.parse(event.dataTransfer.getData('text/plain'));
    if (data.type !== 'member' || data.id === targetId) {
      return;
    }
    const nextMembers = cloneMembers(members);
    let movingIdx = nextMembers.findIndex((member) => member._id === data.id);
    let targetIdx = nextMembers.findIndex((member) => member._id === targetId);
    if (movingIdx === -1 || targetIdx === -1) {
      return;
    }
    const moved = nextMembers[movingIdx];
    const target = nextMembers[targetIdx];
    levelOrder.forEach((level) => {
      moved[level] = target[level];
    });
    rebuildMemberPath(moved);
    nextMembers.splice(movingIdx, 1);
    targetIdx = nextMembers.findIndex((member) => member._id === targetId);
    nextMembers.splice(insertAfter ? targetIdx + 1 : targetIdx, 0, moved);
    await syncMembers(nextMembers);
  } catch (error) {
    console.error(error);
  }
}

window.addEventListener('resize', () => {
  requestAnimationFrame(drawLines);
});

window.addEventListener('click', () => {
  document.getElementById('fab-container').classList.remove('active');
});

document.addEventListener('DOMContentLoaded', async () => {
  document.getElementById('upload-excel').addEventListener('change', async (event) => {
    const file = event.target.files?.[0];
    if (!file) {
      return;
    }
    try {
      await importMemberFile(file);
      event.target.value = '';
    } catch (error) {
      alert(error.message || '업로드에 실패했습니다.');
    }
  });
  document.getElementById('admin-mode-btn').addEventListener('click', () => toggleAdminMode(!isAdmin));
  document.getElementById('fab-main').addEventListener('click', (event) => toggleFab(event));
  document.getElementById('search-input').addEventListener('keydown', (event) => {
    if (event.key === 'Enter') {
      handleSearch(event.target.value);
    }
  });
  document.getElementById('stats-header').addEventListener('click', toggleStats);
  document.getElementById('modal-cancel-btn').addEventListener('click', closeModal);

  updateFabMenu();
  try {
    await loadMembers('서버에서 조직 데이터를 불러오는 중입니다.');
  } catch (error) {
    console.error(error);
    emptyStateMessage = `WSL 서버 연결에 실패했습니다. ${error.message || ''}`;
    render();
  }
});
