import {
  startTransition,
  useEffect,
  useEffectEvent,
  useMemo,
  useRef,
  useState,
} from 'react'
import seedData from './data/seed-data.json'
import * as api from './api.js'

const STORAGE_KEY = 'bcmnc-local-state'
const PLAYER_KEY = 'bcmnc-local-player'
const SAVE_SLOTS_KEY = 'bhb-save-slots'
const ADMIN_AUTH_KEY = 'bhb-admin-auth'
const APP_NAME = 'Bách Hồn Bang'
const DEFAULT_ADMIN_AUTH = {
  salt: 'Ob9TOfgnhDdAegATkXRr+Q==',
  hash: 'rc1QiTpZE2BmnA8qYOWYxqu3rj42jUsRWwiB6PZkTK0=',
  iterations: 210000,
}
const TEAM_COUNT = 10
const TEAM_SIZE = 6
const RESERVE_SIZE = 30
const MAX_SKILLS = 5
const SAVE_SLOT_COUNT = 3

const CLASS_META = {
  'Toái Mộng': { color: '#80C7E6', accent: '#0a2a3a' },
  'Thiết Y': { color: '#E6A35C', accent: '#2f1f10' },
  'Huyết Hà': { color: '#A3534A', accent: '#321414' },
  'Thần Tương': { color: '#5E7FAF', accent: '#16243f' },
  'Tố Vấn': { color: '#F28E99', accent: '#3b1930' },
  'Cửu Linh': { color: '#B36BB3', accent: '#281744' },
  'Long Ngâm': { color: '#8CB36B', accent: '#223211' },
}

const GROUP_META = {
  'Nhóm 1': {
    bg: 'rgba(255, 255, 255, 0.05)',
    border: '#7cd826',
    glow: 'rgba(124, 216, 38, 0.15)',
  },
  'Nhóm 2': {
    bg: 'rgba(0, 255, 255, 0.12)',
    border: '#00ffff',
    glow: 'rgba(0, 255, 255, 0.15)',
  },
  'Nhóm 3': {
    bg: 'rgba(255, 215, 0, 0.12)',
    border: '#ffd700',
    glow: 'rgba(255, 215, 0, 0.15)',
  },
  'Nhóm 4': {
    bg: 'rgba(255, 69, 0, 0.15)',
    border: '#ff4500',
    glow: 'rgba(255, 69, 0, 0.15)',
  },
}

const EMPTY_FORM = {
  char_name: '',
  class_name: 'Toái Mộng',
  team_slot: null,
  type: 'Chính thức',
}

const teamNumbers = Array.from({ length: TEAM_COUNT }, (_, index) => index + 1)
const reserveNumbers = Array.from({ length: RESERVE_SIZE }, (_, index) => index + 1)

function cloneRows(rows) {
  return rows.map((row) => ({ ...row }))
}

function buildSeedState() {
  return {
    members: cloneRows(seedData.members),
    memberSkills: cloneRows(seedData.memberSkills),
    skillLibrary: seedData.skillLibrary
      .map((item) => ({ ...item, url: item.url.trim() }))
      .filter((item) => item.url),
    teamGroups: seedData.teamGroups.reduce((groups, item) => {
      groups[item.team_id] = item.group_name
      return groups
    }, {}),
    teamPositions: seedData.teamPositions.reduce((positions, item) => {
      positions[item.team_id] = {
        x: Number(item.pos_x),
        y: Number(item.pos_y),
      }
      return positions
    }, {}),
    registrationLocked: true,
  }
}

function loadAppState() {
  const fallback = buildSeedState()

  if (typeof window === 'undefined') {
    return fallback
  }

  try {
    const raw = window.localStorage.getItem(STORAGE_KEY)
    if (!raw) {
      return fallback
    }

    const parsed = JSON.parse(raw)
    return {
      members: Array.isArray(parsed.members) ? parsed.members : fallback.members,
      memberSkills: Array.isArray(parsed.memberSkills)
        ? parsed.memberSkills
        : fallback.memberSkills,
      skillLibrary: Array.isArray(parsed.skillLibrary)
        ? parsed.skillLibrary.filter((item) => item?.url)
        : fallback.skillLibrary,
      teamGroups: { ...fallback.teamGroups, ...(parsed.teamGroups ?? {}) },
      teamPositions:
        parsed.teamPositions &&
        typeof parsed.teamPositions === 'object' &&
        !Array.isArray(parsed.teamPositions)
          ? parsed.teamPositions
          : fallback.teamPositions,
      registrationLocked:
        typeof parsed.registrationLocked === 'boolean'
          ? parsed.registrationLocked
          : fallback.registrationLocked,
    }
  } catch {
    return fallback
  }
}

function saveAppState(state) {
  if (typeof window === 'undefined') {
    return
  }

  window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
}

function getEmptySaveSlots() {
  return Array.from({ length: SAVE_SLOT_COUNT }, () => null)
}

function normalizeSaveSlots(slots) {
  const normalizedSlots = getEmptySaveSlots()

  if (!Array.isArray(slots)) {
    return normalizedSlots
  }

  slots.slice(0, SAVE_SLOT_COUNT).forEach((slot, index) => {
    if (slot?.savedAt && slot?.state && typeof slot.state === 'object') {
      normalizedSlots[index] = slot
    }
  })

  return normalizedSlots
}

function loadSaveSlots() {
  if (typeof window === 'undefined') {
    return getEmptySaveSlots()
  }

  try {
    return normalizeSaveSlots(JSON.parse(window.localStorage.getItem(SAVE_SLOTS_KEY) ?? '[]'))
  } catch {
    return getEmptySaveSlots()
  }
}

function saveSaveSlots(slots) {
  if (typeof window === 'undefined') {
    return
  }

  window.localStorage.setItem(SAVE_SLOTS_KEY, JSON.stringify(slots))
}

function formatSavedAt(value) {
  if (!value) {
    return ''
  }

  const date = new Date(value)
  if (Number.isNaN(date.getTime())) {
    return ''
  }

  return new Intl.DateTimeFormat('vi-VN', {
    dateStyle: 'short',
    timeStyle: 'medium',
  }).format(date)
}

function decodeBase64Bytes(value) {
  const binary = window.atob(value)
  const bytes = new Uint8Array(binary.length)

  for (let index = 0; index < binary.length; index += 1) {
    bytes[index] = binary.charCodeAt(index)
  }

  return bytes
}

function encodeBase64Bytes(bytes) {
  let binary = ''

  bytes.forEach((byte) => {
    binary += String.fromCharCode(byte)
  })

  return window.btoa(binary)
}

function areBytesEqual(left, right) {
  if (left.length !== right.length) {
    return false
  }

  let diff = 0
  for (let index = 0; index < left.length; index += 1) {
    diff |= left[index] ^ right[index]
  }

  return diff === 0
}

function isAdminAuthRecord(value) {
  return (
    value &&
    typeof value.salt === 'string' &&
    typeof value.hash === 'string' &&
    Number.isInteger(value.iterations) &&
    value.iterations > 0
  )
}

function loadAdminAuth() {
  if (typeof window === 'undefined') {
    return DEFAULT_ADMIN_AUTH
  }

  try {
    const parsed = JSON.parse(window.localStorage.getItem(ADMIN_AUTH_KEY) ?? 'null')
    return isAdminAuthRecord(parsed) ? parsed : DEFAULT_ADMIN_AUTH
  } catch {
    return DEFAULT_ADMIN_AUTH
  }
}

function saveAdminAuth(auth) {
  if (typeof window === 'undefined') {
    return
  }

  window.localStorage.setItem(ADMIN_AUTH_KEY, JSON.stringify(auth))
}

async function deriveAdminHash(password, salt, iterations) {
  const keyMaterial = await window.crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(password),
    'PBKDF2',
    false,
    ['deriveBits'],
  )
  const derivedBits = await window.crypto.subtle.deriveBits(
    {
      name: 'PBKDF2',
      salt,
      iterations,
      hash: 'SHA-256',
    },
    keyMaterial,
    256,
  )

  return new Uint8Array(derivedBits)
}

async function createAdminAuth(password) {
  if (!window.crypto?.subtle) {
    return null
  }

  const salt = window.crypto.getRandomValues(new Uint8Array(16))
  const hash = await deriveAdminHash(password, salt, DEFAULT_ADMIN_AUTH.iterations)

  return {
    salt: encodeBase64Bytes(salt),
    hash: encodeBase64Bytes(hash),
    iterations: DEFAULT_ADMIN_AUTH.iterations,
  }
}

async function verifyAdminPassword(password, auth) {
  if (!window.crypto?.subtle) {
    return false
  }

  const hash = await deriveAdminHash(
    password,
    decodeBase64Bytes(auth.salt),
    auth.iterations,
  )

  return areBytesEqual(hash, decodeBase64Bytes(auth.hash))
}

function loadPlayerName() {
  if (typeof window === 'undefined') {
    return ''
  }

  return window.localStorage.getItem(PLAYER_KEY) ?? ''
}

function clamp(value, min, max) {
  return Math.min(max, Math.max(min, value))
}

function getNextId(rows) {
  return rows.reduce((maxValue, row) => Math.max(maxValue, Number(row.id) || 0), 0) + 1
}

function createPanelPosition(mapRect, event) {
  const x = ((event.clientX - mapRect.left) / mapRect.width) * 100
  const y = ((event.clientY - mapRect.top) / mapRect.height) * 100

  return {
    x: clamp(x, 6, 94),
    y: clamp(y, 8, 92),
  }
}

function App() {
  const initialStateRef = useRef(loadAppState())
  const mapRef = useRef(null)
  const draggingTeamRef = useRef(null)

  const [members, setMembers] = useState(initialStateRef.current.members)
  const [memberSkills, setMemberSkills] = useState(initialStateRef.current.memberSkills)
  const [skillLibrary, setSkillLibrary] = useState(initialStateRef.current.skillLibrary)
  const [teamGroups, setTeamGroups] = useState(initialStateRef.current.teamGroups)
  const [teamPositions, setTeamPositions] = useState(initialStateRef.current.teamPositions)
  const [registrationLocked, setRegistrationLocked] = useState(
    initialStateRef.current.registrationLocked,
  )
  const [users, setUsers] = useState([])
  const [usersModalOpen, setUsersModalOpen] = useState(false)
  const [selectedMemberId, setSelectedMemberId] = useState(null)
  const [movingMemberId, setMovingMemberId] = useState(null)
  const [draggingTeamId, setDraggingTeamId] = useState(null)
  const [currentUser, setCurrentUser] = useState(null)
  const [loginMode, setLoginMode] = useState('login') // 'login' or 'register'

  const isManager = currentUser && ['ADMIN', 'BANG_CHU', 'BANG_PHO'].includes(currentUser.role)
  const isAdminOrBangChu = currentUser && ['ADMIN', 'BANG_CHU'].includes(currentUser.role)
  
  const adminEnabled = isManager
  const [adminAuth, setAdminAuth] = useState(loadAdminAuth)
  const [adminLoginOpen, setAdminLoginOpen] = useState(false)
  const [authForm, setAuthForm] = useState({ username: '', password: '' })
  const [adminLoginError, setAdminLoginError] = useState('')
  const [adminLoginBusy, setAdminLoginBusy] = useState(false)
  const [myCharName, setMyCharName] = useState(loadPlayerName)
  const [libraryInput, setLibraryInput] = useState('')
  const [form, setForm] = useState(EMPTY_FORM)
  const [currentTab, setCurrentTab] = useState('MAIN') // MAIN, RESERVE, MAP
  const [draggedMemberId, setDraggedMemberId] = useState(null)
  const [saveSlots, setSaveSlots] = useState(loadSaveSlots)
  const [saveModalOpen, setSaveModalOpen] = useState(false)
  const [loadModalOpen, setLoadModalOpen] = useState(false)
  const [saveStatus, setSaveStatus] = useState('')

  const initRef = useRef(false)
  useEffect(() => {
    api.getMe().then(data => setCurrentUser(data.user)).catch(() => setCurrentUser(null))
    api.fetchState().then(data => {
      setMembers(data.members || [])
      setMemberSkills(data.memberSkills || [])
      setSkillLibrary(data.skillLibrary || [])
      if (Object.keys(data.teamGroups || {}).length > 0) setTeamGroups(data.teamGroups)
      if (Object.keys(data.teamPositions || {}).length > 0) setTeamPositions(data.teamPositions)
      setRegistrationLocked(data.registrationLocked || false)
      initRef.current = true
    }).catch(console.error)

    api.getSaves().then(data => {
      const formatted = [null, null, null]
      data.forEach(s => { formatted[s.id - 1] = { savedAt: s.saved_at, state: JSON.parse(s.state_json) } })
      setSaveSlots(formatted)
    }).catch(console.error)
  }, [])

  useEffect(() => {
    if (initRef.current && isManager) {
      api.syncState({ teamGroups, teamPositions, registrationLocked }).catch(console.error)
    }
  }, [teamGroups, teamPositions, registrationLocked, isManager])

  useEffect(() => {
    if (!saveStatus) {
      return undefined
    }

    const timeoutId = window.setTimeout(() => setSaveStatus(''), 2500)
    return () => window.clearTimeout(timeoutId)
  }, [saveStatus])

  useEffect(() => {
    if (!myCharName) {
      window.localStorage.removeItem(PLAYER_KEY)
      return
    }

    window.localStorage.setItem(PLAYER_KEY, myCharName)
  }, [myCharName])

  useEffect(() => {
    if (selectedMemberId && !members.some((member) => member.id === selectedMemberId)) {
      setSelectedMemberId(null)
    }

    if (movingMemberId && !members.some((member) => member.id === movingMemberId)) {
      setMovingMemberId(null)
    }
  }, [members, movingMemberId, selectedMemberId])

  const syncDraggedTeam = useEffectEvent((event) => {
    if (!draggingTeamRef.current || !mapRef.current) {
      return
    }

    const rect = mapRef.current.getBoundingClientRect()
    const nextPosition = createPanelPosition(rect, event)

    setTeamPositions((currentPositions) => ({
      ...currentPositions,
      [draggingTeamRef.current]: nextPosition,
    }))
  })

  const stopDragging = useEffectEvent(() => {
    draggingTeamRef.current = null
    setDraggingTeamId(null)
  })

  useEffect(() => {
    if (!draggingTeamId) {
      return undefined
    }

    const handlePointerMove = (event) => syncDraggedTeam(event)
    const handlePointerUp = () => stopDragging()

    window.addEventListener('pointermove', handlePointerMove)
    window.addEventListener('pointerup', handlePointerUp)
    window.addEventListener('pointercancel', handlePointerUp)

    return () => {
      window.removeEventListener('pointermove', handlePointerMove)
      window.removeEventListener('pointerup', handlePointerUp)
      window.removeEventListener('pointercancel', handlePointerUp)
    }
  }, [draggingTeamId, stopDragging, syncDraggedTeam])

  const slotMap = useMemo(() => {
    const map = new Map()
    members.forEach((member) => {
      map.set(`${member.type}-${member.team_slot}`, member)
    })
    return map
  }, [members])

  const selectedMember = useMemo(
    () => members.find((member) => member.id === selectedMemberId) ?? null,
    [members, selectedMemberId],
  )

  const movingMember = useMemo(
    () => members.find((member) => member.id === movingMemberId) ?? null,
    [members, movingMemberId],
  )

  const selectedMemberSkills = useMemo(
    () =>
      memberSkills
        .filter((skill) => skill.member_id === selectedMemberId)
        .sort((left, right) => Number(left.pos_x) - Number(right.pos_x)),
    [memberSkills, selectedMemberId],
  )

  const officialCount = useMemo(
    () => members.filter((member) => member.type === 'Chính thức').length,
    [members],
  )

  const reserveCount = useMemo(
    () => members.filter((member) => member.type !== 'Chính thức').length,
    [members],
  )

  const classSummary = useMemo(
    () =>
      Object.entries(CLASS_META).map(([className, meta]) => ({
        className,
        color: meta.color,
        count: members.filter((member) => member.class_name === className).length,
      })),
    [members],
  )

  const canManageSelectedMember =
    !!selectedMember &&
    (adminEnabled || (currentUser && selectedMember.created_by === currentUser.id))

  function getCurrentAppState() {
    return {
      members,
      memberSkills,
      skillLibrary,
      teamGroups,
      teamPositions,
      registrationLocked,
    }
  }

  function openMember(member) {
    setMovingMemberId(null)
    setSelectedMemberId(member.id)
  }

  async function commitMove(targetType, targetSlot) {
    if (!movingMember) {
      return
    }

    const occupant = slotMap.get(`${targetType}-${targetSlot}`)

    if (occupant?.id === movingMember.id) {
      setMovingMemberId(null)
      return
    }

    try {
      if (occupant) {
        await api.moveMember(occupant.id, movingMember.team_slot, movingMember.type)
      }
      await api.moveMember(movingMember.id, targetSlot, targetType)
      const data = await api.fetchState()
      setMembers(data.members || [])
    } catch (err) {
      window.alert(err.message)
    }

    setMovingMemberId(null)
  }

  function handleSlotClick(type, slot) {
    const occupant = slotMap.get(`${type}-${slot}`)

    if (movingMemberId) {
      commitMove(type, slot)
      return
    }

    if (occupant) {
      openMember(occupant)
      return
    }

    setForm({
      char_name: '',
      class_name: 'Toái Mộng',
      type,
      team_slot: slot,
    })
  }

  // HTML5 Drag and Drop handlers
  function handleDragStart(event, memberId) {
    setDraggedMemberId(memberId)
    // Optional: make phantom look better
    event.dataTransfer.effectAllowed = 'move'
    // It requires data to drag in Firefox
    event.dataTransfer.setData('text/plain', memberId.toString())
  }

  async function handleDrop(event, targetType, targetSlot) {
    event.preventDefault()
    if (!draggedMemberId) return

    const draggingMember = members.find(m => m.id === draggedMemberId)
    if (!draggingMember) return

    const occupant = slotMap.get(`${targetType}-${targetSlot}`)
    if (occupant?.id === draggingMember.id) {
      setDraggedMemberId(null)
      return
    }

    const canManageDragging = adminEnabled || (myCharName && draggingMember.char_name === myCharName)
    if (!canManageDragging) {
      setDraggedMemberId(null)
      return
    }

    try {
      if (occupant) {
        await api.moveMember(occupant.id, draggingMember.team_slot, draggingMember.type)
      }
      await api.moveMember(draggingMember.id, targetSlot, targetType)
      const data = await api.fetchState()
      setMembers(data.members || [])
    } catch (err) {
      window.alert(err.message)
    }
    setDraggedMemberId(null)
  }

  async function handleRegister(event) {
    event.preventDefault()

    if (!currentUser) {
      window.alert('Bạn cần đăng nhập để đăng ký nhân vật.')
      return
    }

    const cleanName = form.char_name.trim()
    if (!cleanName) {
      window.alert('Vui lòng nhập tên nhân vật.')
      return
    }

    if (!form.team_slot) {
      window.alert('Hãy bấm vào một ô trống để chọn slot trước khi đăng ký.')
      return
    }

    if (slotMap.get(`${form.type}-${form.team_slot}`)) {
      window.alert('Ô này đã có người. Hãy chọn ô khác.')
      return
    }

    const nextMember = {
      char_name: cleanName,
      class_name: form.class_name,
      team_slot: form.team_slot,
      type: form.type,
      has_item: false,
    }

    try {
      await api.saveMember(nextMember)
      const data = await api.fetchState()
      setMembers(data.members || [])
      if (!adminEnabled) {
        setMyCharName(cleanName)
      }
      setForm({
        ...EMPTY_FORM,
        class_name: form.class_name,
      })
    } catch (err) {
      window.alert(err.message)
    }
  }

  function handleAdminAccess() {
    if (currentUser) {
      handleLogout()
      setMovingMemberId(null)
      return
    }

    setAuthForm({ username: '', password: '' })
    setAdminLoginError('')
    setAdminLoginOpen(true)
  }

  async function handleAdminLoginSubmit(event) {
    event.preventDefault()

    if (!authForm.username || !authForm.password) {
      setAdminLoginError('Vui lòng nhập tài khoản và mật khẩu.')
      return
    }

    setAdminLoginBusy(true)
    setAdminLoginError('')

    try {
      if (loginMode === 'login') {
        const data = await api.login(authForm.username, authForm.password)
        localStorage.setItem('bhb-token', data.token)
        setCurrentUser(data.user)
      } else {
        const data = await api.register(authForm.username, authForm.password)
        localStorage.setItem('bhb-token', data.token)
        setCurrentUser(data.user)
      }
      setAdminLoginOpen(false)
      setAuthForm({ username: '', password: '' })
    } catch (err) {
      setAdminLoginError(err.message)
    } finally {
      setAdminLoginBusy(false)
    }
  }

  function handleLogout() {
    localStorage.removeItem('bhb-token')
    setCurrentUser(null)
  }

  async function openUsersModal() {
    if (!isAdminOrBangChu) return
    try {
      const data = await api.getUsers()
      setUsers(data || [])
      setUsersModalOpen(true)
    } catch(err) {
      window.alert(err.message)
    }
  }

  async function handleRoleChange(userId, role) {
    try {
      await api.setRole(userId, role)
      setUsers(users.map(u => u.id === userId ? { ...u, role } : u))
    } catch (err) {
      window.alert(err.message)
    }
  }

  async function handleToggleSupply() {
    if (!selectedMember || !canManageSelectedMember) {
      return
    }

    try {
      await api.saveMember({ ...selectedMember, has_item: !selectedMember.has_item })
      const data = await api.fetchState()
      setMembers(data.members || [])
    } catch (err) {
      window.alert(err.message)
    }
  }

  async function handleDeleteSelectedMember() {
    if (!selectedMember || !canManageSelectedMember) {
      return
    }

    const confirmed = window.confirm(`Xóa nhân vật ${selectedMember.char_name}?`)
    if (!confirmed) {
      return
    }

    try {
      await api.deleteMember(selectedMember.id)
      const data = await api.fetchState()
      setMembers(data.members || [])
      setMemberSkills(data.memberSkills || [])
      setSelectedMemberId(null)
      setMovingMemberId(null)
    } catch (err) {
      window.alert(err.message)
    }
  }

  function handlePrepareMove() {
    if (!adminEnabled || !selectedMember) {
      return
    }

    setMovingMemberId(selectedMember.id)
    setSelectedMemberId(null)
  }

  async function handleAddEquippedSkill(url) {
    if (!selectedMember || !canManageSelectedMember) {
      return
    }

    const equipped = memberSkills.filter((skill) => skill.member_id === selectedMember.id)
    if (equipped.length >= MAX_SKILLS) {
      window.alert('Mỗi nhân vật chỉ gắn tối đa 5 kỹ năng.')
      return
    }

    const emptyIndex = Array.from({ length: MAX_SKILLS }, (_, index) => index).find(
      (index) => !equipped.some((skill) => Number(skill.pos_x) === index),
    )

    const skillObj = skillLibrary.find(s => s.url === url)
    if (!skillObj) return

    const newSkill = {
      skill_id: skillObj.id,
      pos_x: emptyIndex ?? equipped.length,
      pos_y: 0
    }
    const newSkills = [...equipped.map(s => ({ skill_id: s.skill_id, pos_x: s.pos_x, pos_y: s.pos_y })), newSkill]

    try {
      await api.saveMemberSkills(selectedMember.id, newSkills)
      const data = await api.fetchState()
      setMemberSkills(data.memberSkills || [])
    } catch (err) {
      window.alert(err.message)
    }
  }

  async function handleRemoveEquippedSkill(skillId) {
    if (!canManageSelectedMember) {
      return
    }

    const equipped = memberSkills.filter((skill) => skill.member_id === selectedMember.id)
    const newSkills = equipped.filter(s => s.id !== skillId).map(s => ({ skill_id: s.skill_id, pos_x: s.pos_x, pos_y: s.pos_y }))

    try {
      await api.saveMemberSkills(selectedMember.id, newSkills)
      const data = await api.fetchState()
      setMemberSkills(data.memberSkills || [])
    } catch (err) {
      window.alert(err.message)
    }
  }

  function pushSkillIntoLibrary(url) {
    const cleanUrl = url.trim()

    if (!cleanUrl) {
      return
    }

    if (skillLibrary.some((item) => item.url === cleanUrl)) {
      window.alert('Kỹ năng này đã có trong thư viện.')
      return
    }

    setSkillLibrary((currentLibrary) => [
      {
        id: getNextId(currentLibrary),
        url: cleanUrl,
        created_at: new Date().toISOString(),
      },
      ...currentLibrary,
    ])
  }

  function handleAddLibrarySkill() {
    if (!adminEnabled) {
      return
    }

    if (!libraryInput.trim()) {
      window.alert('Nhập link ảnh trước khi thêm.')
      return
    }

    pushSkillIntoLibrary(libraryInput)
    setLibraryInput('')
  }

  function handleUploadLibrarySkill(event) {
    if (!adminEnabled) {
      return
    }

    const file = event.target.files?.[0]
    event.target.value = ''

    if (!file) {
      return
    }

    if (file.size > 1024 * 1024) {
      window.alert('Ảnh quá lớn, hãy dùng file dưới 1MB.')
      return
    }

    const reader = new FileReader()
    reader.onload = () => {
      if (typeof reader.result === 'string') {
        pushSkillIntoLibrary(reader.result)
      }
    }
    reader.readAsDataURL(file)
  }

  function handleRemoveLibrarySkill(skillId) {
    if (!adminEnabled) {
      return
    }

    setSkillLibrary((currentLibrary) =>
      currentLibrary.filter((skill) => skill.id !== skillId),
    )
  }

  function handleSaveData() {
    if (!adminEnabled) {
      return
    }

    setSaveModalOpen(true)
  }

  async function handleSaveSlot(slotIndex) {
    if (!adminEnabled) {
      return
    }

    const state = getCurrentAppState()
    try {
      await api.saveSlotData(slotIndex, JSON.stringify(state))
      const data = await api.getSaves()
      const formatted = [null, null, null]
      data.forEach(s => { formatted[s.id - 1] = { savedAt: s.saved_at, state: JSON.parse(s.state_json) } })
      setSaveSlots(formatted)
      setSaveStatus(`Đã lưu vào ô ${slotIndex + 1}`)
    } catch (err) {
      window.alert(err.message)
    }
  }

  function handleLoadData() {
    if (!adminEnabled) {
      return
    }

    setLoadModalOpen(true)
  }

  function handleLoadSlot(slotIndex) {
    if (!adminEnabled) {
      return
    }

    const slot = saveSlots[slotIndex]
    if (!slot || !slot.state) {
      window.alert('Ô này chưa có dữ liệu.')
      return
    }

    const confirmed = window.confirm(`Bạn có chắc muốn tải dữ liệu từ ô ${slotIndex + 1}? Toàn bộ dữ liệu hiện tại sẽ bị ghi đè.`)
    if (!confirmed) {
      return
    }

    const state = slot.state
    saveAppState(state)

    startTransition(() => {
      setMembers(state.members || [])
      setMemberSkills(state.memberSkills || [])
      setSkillLibrary(state.skillLibrary || [])
      setTeamGroups(state.teamGroups || {})
      setTeamPositions(state.teamPositions || {})
      setRegistrationLocked(state.registrationLocked || false)
      setSelectedMemberId(null)
      setMovingMemberId(null)
      setDraggedMemberId(null)
      draggingTeamRef.current = null
      setDraggingTeamId(null)
      setMyCharName('')
      setSaveStatus(`Đã tải dữ liệu từ ô ${slotIndex + 1}`)
      setLoadModalOpen(false)
    })
  }

  async function clearData() {
    if (!adminEnabled) {
      return
    }

    const confirmed = window.confirm(
      'Clear dữ liệu sẽ xóa toàn bộ roster, skill đang gắn và vị trí team hiện tại. Tiếp tục?',
    )

    if (!confirmed) {
      return
    }

    try {
      await api.syncState({
        members: [],
        memberSkills: [],
        skillLibrary,
        teamGroups,
        teamPositions: {},
        registrationLocked,
      })
      const data = await api.fetchState()
      setMembers(data.members || [])
      setMemberSkills(data.memberSkills || [])
      setTeamPositions(data.teamPositions || {})
      setSelectedMemberId(null)
      setMovingMemberId(null)
      setDraggedMemberId(null)
      draggingTeamRef.current = null
      setDraggingTeamId(null)
      setMyCharName('')
      setSaveStatus('Đã clear dữ liệu roster và vị trí team')
    } catch(err) {
      window.alert(err.message)
    }
  }

  function handleTeamPointerDown(teamId, event) {
    if (!adminEnabled || !mapRef.current) {
      return
    }

    event.preventDefault()
    draggingTeamRef.current = teamId
    setDraggingTeamId(teamId)
    syncDraggedTeam(event)
  }

  function renderSlot(type, slotNumber) {
    const member = slotMap.get(`${type}-${slotNumber}`)
    const isSelectedSlot = form.team_slot === slotNumber && form.type === type
    const isLeaderSlot = type === 'Chính thức' && (slotNumber - 1) % TEAM_SIZE === 0
    const isMovingTarget = movingMember?.id === member?.id
    const isBeingDragged = draggedMemberId === member?.id
    const canManageDrag = !!member && (adminEnabled || (myCharName && member.char_name === myCharName))

    return (
      <div
        key={`${type}-${slotNumber}`}
        className={`slot-compact ${member ? 'slot-filled-compact' : ''} ${isSelectedSlot ? 'slot-pending-compact' : ''} ${
          isMovingTarget || isBeingDragged ? 'slot-moving-compact' : ''
        }`}
        style={{
          boxShadow: member
            ? `inset 9px 0 0 -5px ${CLASS_META[member.class_name]?.color}, 0 2px 8px rgba(0,0,0,0.2)`
            : undefined,
        }}
        onClick={() => handleSlotClick(type, slotNumber)}
        draggable={canManageDrag}
        onDragStart={(e) => handleDragStart(e, member.id)}
        onDragOver={(e) => e.preventDefault()}
        onDrop={(e) => handleDrop(e, type, slotNumber)}
        onDragEnd={() => setDraggedMemberId(null)}
      >
        {isLeaderSlot && <span className="slot-key">🔑</span>}
        {member ? (
          <>
            <div className="slot-class-dot" style={{ background: CLASS_META[member.class_name]?.color }}></div>
            <span
              className="slot-name"
              style={{
                color: CLASS_META[member.class_name]?.color || '#fff',
                textShadow: `0 0 6px ${CLASS_META[member.class_name]?.color}40`,
              }}
            >
              {member.char_name} {member.has_item && '📦'}
            </span>
          </>
        ) : (
          <span className="slot-empty-label">S{slotNumber}</span>
        )}
      </div>
    )
  }

  return (
    <main className="app-shell-compact">
      <img className="background-watermark" src="/nth-logo.png" alt="" aria-hidden="true" />
      {/* Admin controls - top right */}
      <div className="admin-corner">
        <button
          type="button"
          className={`admin-btn ${currentUser ? 'admin-on' : ''}`}
          onClick={handleAdminAccess}
        >
          {currentUser ? `${currentUser.username} (${currentUser.role}) [Thoát]` : 'ĐĂNG NHẬP'}
        </button>
        {isAdminOrBangChu && (
          <>
            <button
              type="button"
              className="admin-sub-btn"
              style={{ background: registrationLocked ? '#222' : 'red' }}
              onClick={() => setRegistrationLocked((value) => !value)}
            >
              GIỚI HẠN: {registrationLocked ? 'BẬT' : 'TẮT'}
            </button>
            <button
              type="button"
              className="admin-sub-btn"
              style={{ background: 'blue' }}
              onClick={clearData}
            >
              CLEAR DỮ LIỆU
            </button>
            <button
              type="button"
              className="admin-sub-btn save-data-btn"
              onClick={handleSaveData}
            >
              LƯU DỮ LIỆU
            </button>
            <button
              type="button"
              className="admin-sub-btn load-data-btn"
              style={{ background: '#7cd826', color: '#000' }}
              onClick={handleLoadData}
            >
              MỞ DỮ LIỆU
            </button>
            {isAdminOrBangChu && (
              <button
                type="button"
                className="admin-sub-btn"
                onClick={openUsersModal}
              >
                QUẢN LÝ QUYỀN
              </button>
            )}
            {saveStatus && <div className="save-status">{saveStatus}</div>}
          </>
        )}
      </div>

      {/* Logo & Title */}
      <div className="header-compact">
        <img className="brand-logo-compact" src="/nth-logo.png" alt={APP_NAME} />
        <h1 className="title-compact">{APP_NAME}</h1>
      </div>

      {adminLoginOpen && !currentUser ? (
        <div
          className="modal-overlay"
          role="presentation"
          onClick={() => setAdminLoginOpen(false)}
        >
          <div className="mini-modal fade-in" onClick={(event) => event.stopPropagation()}>
            <h3 className="mini-modal-title">
              {loginMode === 'login' ? 'ĐĂNG NHẬP' : 'ĐĂNG KÝ'}
            </h3>
            <div style={{ display: 'flex', gap: 10, marginBottom: 15, justifyContent: 'center' }}>
              <button 
                type="button" 
                className="admin-sub-btn" 
                style={{ background: loginMode === 'login' ? '#007bff' : '#222' }}
                onClick={() => setLoginMode('login')}
              >Đăng nhập</button>
              <button 
                type="button" 
                className="admin-sub-btn" 
                style={{ background: loginMode === 'register' ? '#007bff' : '#222' }}
                onClick={() => setLoginMode('register')}
              >Đăng ký</button>
            </div>
            <form onSubmit={handleAdminLoginSubmit} className="mini-form">
              <input
                className="reg-input"
                type="text"
                autoFocus
                value={authForm.username}
                onChange={(event) => setAuthForm(prev => ({ ...prev, username: event.target.value }))}
                placeholder="Tài khoản..."
                autoComplete="username"
              />
              <input
                className="reg-input"
                type="password"
                value={authForm.password}
                onChange={(event) => setAuthForm(prev => ({ ...prev, password: event.target.value }))}
                placeholder="Mật khẩu..."
                autoComplete={loginMode === 'login' ? "current-password" : "new-password"}
              />
              {adminLoginError && <div className="admin-login-error">{adminLoginError}</div>}
              <div className="modal-actions" style={{ marginTop: 10 }}>
                <button
                  type="submit"
                  className="reg-submit"
                  style={{ width: '100%' }}
                  disabled={adminLoginBusy}
                >
                  {adminLoginBusy ? 'ĐANG XỬ LÝ...' : (loginMode === 'login' ? 'ĐĂNG NHẬP' : 'ĐĂNG KÝ')}
                </button>
                <button
                  type="button"
                  className="modal-action-btn"
                  onClick={() => setAdminLoginOpen(false)}
                >
                  HỦY
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      {saveModalOpen && adminEnabled ? (
        <div
          className="modal-overlay"
          role="presentation"
          onClick={() => setSaveModalOpen(false)}
        >
          <div
            className="mini-modal save-slots-modal fade-in"
            onClick={(event) => event.stopPropagation()}
          >
            <h3 className="mini-modal-title">CHỌN Ô LƯU DỮ LIỆU</h3>
            <div className="save-slot-grid">
              {saveSlots.map((slot, index) => (
                <button
                  key={index}
                  type="button"
                  className="save-slot-card"
                  onClick={() => handleSaveSlot(index)}
                >
                  <span className="save-slot-title">Ô {index + 1}</span>
                  <span className="save-slot-date">
                    {slot ? `Đã lưu: ${formatSavedAt(slot.savedAt)}` : 'Chưa có dữ liệu'}
                  </span>
                  <span className="save-slot-action">Bấm để lưu vào ô này</span>
                </button>
              ))}
            </div>
            <div className="modal-actions" style={{ marginTop: 12 }}>
              <button
                type="button"
                className="modal-action-btn"
                onClick={() => setSaveModalOpen(false)}
              >
                ĐÓNG
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {loadModalOpen && adminEnabled ? (
        <div
          className="modal-overlay"
          role="presentation"
          onClick={() => setLoadModalOpen(false)}
        >
          <div
            className="mini-modal save-slots-modal fade-in"
            onClick={(event) => event.stopPropagation()}
          >
            <h3 className="mini-modal-title">CHỌN Ô MỞ DỮ LIỆU</h3>
            <div className="save-slot-grid">
              {saveSlots.map((slot, index) => (
                <button
                  key={index}
                  type="button"
                  className="save-slot-card"
                  onClick={() => handleLoadSlot(index)}
                  disabled={!slot}
                  style={{ opacity: slot ? 1 : 0.5 }}
                >
                  <span className="save-slot-title">Ô {index + 1}</span>
                  <span className="save-slot-date">
                    {slot ? `Đã lưu: ${formatSavedAt(slot.savedAt)}` : 'Chưa có dữ liệu'}
                  </span>
                  <span className="save-slot-action">{slot ? 'Bấm để tải dữ liệu' : 'Trống'}</span>
                </button>
              ))}
            </div>
            <div className="modal-actions" style={{ marginTop: 12 }}>
              <button
                type="button"
                className="modal-action-btn"
                onClick={() => setLoadModalOpen(false)}
              >
                ĐÓNG
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {usersModalOpen && isAdminOrBangChu ? (
        <div
          className="modal-overlay"
          role="presentation"
          onClick={() => setUsersModalOpen(false)}
        >
          <div className="mini-modal save-slots-modal fade-in" onClick={(event) => event.stopPropagation()} style={{ width: 450 }}>
            <h3 className="mini-modal-title">QUẢN LÝ QUYỀN THÀNH VIÊN</h3>
            <div style={{ maxHeight: 300, overflowY: 'auto', marginBottom: 15 }}>
              {users.map(u => (
                <div key={u.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0', borderBottom: '1px solid #333' }}>
                  <div style={{ color: '#fff', fontSize: 14 }}>{u.username}</div>
                  <select 
                    value={u.role} 
                    onChange={e => handleRoleChange(u.id, e.target.value)}
                    style={{ background: '#222', color: '#fff', border: '1px solid #444', padding: '2px 5px' }}
                    disabled={u.role === 'ADMIN'}
                  >
                    <option value="MEMBER">Thành viên</option>
                    <option value="BANG_PHO">Bang Phó</option>
                    <option value="BANG_CHU">Bang Chủ</option>
                    {u.role === 'ADMIN' && <option value="ADMIN">Admin</option>}
                  </select>
                </div>
              ))}
            </div>
            <div className="modal-actions">
              <button
                type="button"
                className="modal-action-btn"
                onClick={() => setUsersModalOpen(false)}
                style={{ width: '100%' }}
              >
                ĐÓNG
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {/* Stats bar */}
      <div className="stats-bar">
        {classSummary.map((item) => (
          <div key={item.className} className="stat-item">
            <div className="stat-name" style={{ color: item.color }}>
              {item.className}
            </div>
            <div className="stat-count">{item.count}</div>
          </div>
        ))}
        <div className="stat-item stat-total">
          <div className="stat-name" style={{ color: '#00FF00' }}>QUÂN SỐ</div>
          <div className="stat-count" style={{ color: '#00FF00' }}>
            {officialCount} / {TEAM_COUNT * TEAM_SIZE}
          </div>
        </div>
      </div>

      {/* Moving banner */}
      {movingMember ? (
        <div className="move-banner-compact">
          <span>
            ↔ Đang di chuyển <strong>{movingMember.char_name}</strong> — Chọn ô đích để hoán đổi
          </span>
          <button
            type="button"
            className="admin-sub-btn"
            style={{ background: '#dc3545' }}
            onClick={() => setMovingMemberId(null)}
          >
            HỦY
          </button>
        </div>
      ) : null}

      {/* Team grid - 10 columns */}
      <div className="team-grid-compact fade-in">
          {teamNumbers.map((teamId) => {
            const groupName = teamGroups[teamId] ?? 'Nhóm 1'
            const groupMeta = GROUP_META[groupName]

            return (
              <div
                key={teamId}
                className="team-col"
              >
                <div className="team-col-header">
                  <span style={{ color: groupMeta.border }}>TEAM {teamId}</span>
                  <select
                    className="group-select-compact"
                    style={{ borderColor: groupMeta.border }}
                    value={groupName}
                    disabled={!adminEnabled}
                    onChange={(event) =>
                      setTeamGroups((currentGroups) => ({
                        ...currentGroups,
                        [teamId]: event.target.value,
                      }))
                    }
                  >
                    {Object.keys(GROUP_META).map((option) => (
                      <option key={option} value={option}>
                        {option}
                      </option>
                    ))}
                  </select>
                </div>
                {Array.from({ length: TEAM_SIZE }, (_, index) =>
                  renderSlot('Chính thức', (teamId - 1) * TEAM_SIZE + index + 1),
                )}
              </div>
            )
          })}
        </div>

      {/* Reserve section */}
      <h2 className="section-title-compact">DỰ BỊ ({reserveCount}/{RESERVE_SIZE})</h2>
      <div className="reserve-grid-compact fade-in">
        {reserveNumbers.map((slotNumber) => renderSlot('Học việc', slotNumber))}
      </div>

      {/* Tactical map */}
      <div className="map-section-compact fade-in">
        <h3 className="section-title-compact">CHỈ ĐẠO CHIẾN THUẬT</h3>
        <div className="map-container-compact" ref={mapRef}>
            <img src="/nth-map.jpg" alt="Bản đồ chiến thuật" className="map-bg-compact" />
            {teamNumbers.map((teamId) => {
              const position = teamPositions[teamId] ?? {
                x: 10 + teamId * 7,
                y: 18 + teamId * 4,
              }
              const groupMeta = GROUP_META[teamGroups[teamId] ?? 'Nhóm 1']

              return (
                <button
                  key={teamId}
                  type="button"
                  className={`team-node-compact ${draggingTeamId === teamId ? 'team-node-active-compact' : ''}`}
                  style={{
                    left: `${position.x}%`,
                    top: `${position.y}%`,
                    backgroundColor: groupMeta.border,
                    cursor: adminEnabled ? 'move' : 'default',
                  }}
                  onPointerDown={(event) => handleTeamPointerDown(teamId, event)}
                >
                  T{teamId}
                </button>
              )
            })}
          </div>
        </div>

      {/* Mini Popover Dang Ky */}
      {form.team_slot && !selectedMemberId && (
        <div className="modal-overlay" role="presentation" onClick={() => setForm(EMPTY_FORM)}>
           <div className="mini-modal fade-in" onClick={e => e.stopPropagation()}>
              <h3 className="mini-modal-title">Đăng Ký ({form.type} - S{form.team_slot})</h3>
              <form onSubmit={handleRegister} className="mini-form">
                <input
                  className="reg-input"
                  autoFocus
                  value={form.char_name}
                  onChange={(event) =>
                    setForm((currentForm) => ({
                      ...currentForm,
                      char_name: event.target.value,
                    }))
                  }
                  placeholder="Nhập tên nhân vật..."
                  required
                />
                <select
                  className="reg-select"
                  value={form.class_name}
                  onChange={(event) =>
                    setForm((currentForm) => ({
                      ...currentForm,
                      class_name: event.target.value,
                    }))
                  }
                  style={{ color: CLASS_META[form.class_name]?.color || '#fff' }}
                >
                  {Object.keys(CLASS_META).map((className) => (
                    <option key={className} value={className} style={{ color: CLASS_META[className].color, fontWeight: 'bold', background: '#1e293b' }}>
                      {className}
                    </option>
                  ))}
                </select>
                <div className="modal-actions" style={{marginTop: 10}}>
                  <button type="submit" className="reg-submit" style={{width: '100%'}}>
                    XÁC NHẬN
                  </button>
                  <button type="button" className="modal-action-btn" onClick={() => setForm(EMPTY_FORM)}>
                    HỦY
                  </button>
                </div>
              </form>
           </div>
        </div>
      )}

      {/* Member Detail Modal */}
      {selectedMember ? (
        <div className="modal-overlay" role="presentation" onClick={() => setSelectedMemberId(null)}>
          <div className="modal-compact" onClick={(event) => event.stopPropagation()}>
            <div className="modal-header-compact">
              <div>
                <div
                  className="modal-class"
                  style={{ color: CLASS_META[selectedMember.class_name]?.color }}
                >
                  {selectedMember.class_name}
                </div>
                <div className="modal-name">{selectedMember.char_name}</div>
                <div className="modal-info">
                  {selectedMember.type} • Slot {selectedMember.team_slot}
                </div>
              </div>
              <button
                type="button"
                className="admin-sub-btn"
                onClick={() => setSelectedMemberId(null)}
              >
                ĐÓNG
              </button>
            </div>

            {/* Skill library */}
            <div className="skill-section">
              <div className="skill-section-label">CHỌN SKILL</div>
              <div className="skill-library-grid">
                {skillLibrary.map((skill) => (
                  <div key={skill.id} className="skill-lib-item">
                    <img
                      src={skill.url}
                      alt="Skill"
                      onClick={() => handleAddEquippedSkill(skill.url)}
                    />
                    {adminEnabled && (
                      <div
                        className="skill-remove-btn"
                        onClick={(e) => {
                          e.stopPropagation()
                          handleRemoveLibrarySkill(skill.id)
                        }}
                      >
                        ×
                      </div>
                    )}
                  </div>
                ))}
                {skillLibrary.length === 0 && (
                  <div style={{ fontSize: '10px', color: '#555' }}>Kho skill trống...</div>
                )}
              </div>

              {/* Admin: add skill */}
              {adminEnabled && (
                <div className="admin-skill-add">
                  <div style={{ fontSize: '9px', color: '#888', marginBottom: '5px' }}>
                    ADMIN: THÊM SKILL MỚI VÀO KHO
                  </div>
                  <div className="admin-skill-row">
                    <input
                      className="reg-input"
                      value={libraryInput}
                      onChange={(event) => setLibraryInput(event.target.value)}
                      placeholder="Link ảnh skill..."
                    />
                    <button type="button" className="admin-sub-btn" onClick={handleAddLibrarySkill}>
                      THÊM
                    </button>
                    <label className="admin-sub-btn" style={{ cursor: 'pointer' }}>
                      TẢI ẢNH
                      <input
                        type="file"
                        accept="image/*"
                        hidden
                        onChange={handleUploadLibrarySkill}
                      />
                    </label>
                  </div>
                </div>
              )}
            </div>

            {/* Equipped skills */}
            <div className="equipped-row">
              {Array.from({ length: MAX_SKILLS }, (_, index) => {
                const skill = selectedMemberSkills.find(
                  (item) => Number(item.pos_x) === index,
                )

                return (
                  <div key={index} className="equipped-slot">
                    {skill ? (
                      <>
                        <img src={skill.skill_url} alt={`Skill ${index + 1}`} />
                        {canManageSelectedMember && (
                          <div
                            className="skill-remove-btn"
                            onClick={() => handleRemoveEquippedSkill(skill.id)}
                          >
                            ×
                          </div>
                        )}
                      </>
                    ) : (
                      <div style={{fontSize: 20, color: '#4b5563', fontWeight: 300}}>+</div>
                    )}
                  </div>
                )
              })}
            </div>

            {/* Action buttons */}
            <div className="modal-actions">
              {(canManageSelectedMember || adminEnabled) && (
                <>
                  {adminEnabled && (
                    <button type="button" className="modal-action-btn" onClick={handlePrepareMove}>
                      ĐỔI SLOT
                    </button>
                  )}
                  {canManageSelectedMember && (
                    <>
                      <button
                        type="button"
                        className="modal-action-btn"
                        style={{ background: selectedMember.has_item ? '#444' : '#28a745' }}
                        onClick={handleToggleSupply}
                      >
                        {selectedMember.has_item ? 'BỎ VẬT TƯ' : 'VẬT TƯ 📦'}
                      </button>
                      <button
                        type="button"
                        className="modal-action-btn"
                        style={{ background: '#dc3545' }}
                        onClick={handleDeleteSelectedMember}
                      >
                        XÓA
                      </button>
                    </>
                  )}
                </>
              )}
              {!canManageSelectedMember && !adminEnabled && (
                <div style={{ color: '#888', fontSize: '11px' }}>
                  Bạn chỉ có thể chỉnh skill và vật tư cho nhân vật của chính mình.
                </div>
              )}
            </div>
          </div>
        </div>
      ) : null}
    </main>
  )
}

export default App
