const state = {
    catalog: {
        version: 1,
        profiles: []
    },
    meta: null,
    selectedProfileId: null,
    dirty: false,
    distributionEditor: {
        document: null
    }
}

const profileList = document.getElementById('profileList')
const profileCount = document.getElementById('profileCount')
const localCatalogUrl = document.getElementById('localCatalogUrl')
const launcherCatalogPath = document.getElementById('launcherCatalogPath')
const emptyState = document.getElementById('emptyState')
const profileEditorForm = document.getElementById('profileEditorForm')
const statusBanner = document.getElementById('statusBanner')
const editorHint = document.getElementById('editorHint')
const saveCatalogButton = document.getElementById('saveCatalogButton')
const duplicateProfileButton = document.getElementById('duplicateProfileButton')
const deleteProfileButton = document.getElementById('deleteProfileButton')
const addProfileButton = document.getElementById('addProfileButton')
const serverSection = document.getElementById('serverSection')
const editDistributionButton = document.getElementById('editDistributionButton')
const createDistributionButton = document.getElementById('createDistributionButton')
const distributionEditorModal = document.getElementById('distributionEditorModal')
const distributionEditorHint = document.getElementById('distributionEditorHint')
const distributionEditorStatus = document.getElementById('distributionEditorStatus')
const distributionEditorForm = document.getElementById('distributionEditorForm')
const distributionModuleCount = document.getElementById('distributionModuleCount')
const distributionAdditionalServerCount = document.getElementById('distributionAdditionalServerCount')
const closeDistributionEditorButton = document.getElementById('closeDistributionEditorButton')
const loadDistributionTemplateButton = document.getElementById('loadDistributionTemplateButton')
const saveDistributionFileButton = document.getElementById('saveDistributionFileButton')

const fieldElements = {
    id: document.getElementById('field-id'),
    name: document.getElementById('field-name'),
    description: document.getElementById('field-description'),
    details: document.getElementById('field-details'),
    modsEnabled: document.getElementById('field-modsEnabled'),
    pluginsEnabled: document.getElementById('field-pluginsEnabled'),
    serverEnabled: document.getElementById('field-serverEnabled'),
    distributionUrl: document.getElementById('field-distributionUrl'),
    worldArchiveUrl: document.getElementById('field-worldArchiveUrl'),
    worldDirectoryName: document.getElementById('field-worldDirectoryName'),
    serverJarUrl: document.getElementById('field-serverJarUrl'),
    serverPort: document.getElementById('field-serverPort'),
    serverMemoryMb: document.getElementById('field-serverMemoryMb'),
    serverMaxPlayers: document.getElementById('field-serverMaxPlayers'),
    serverWhitelistEnabled: document.getElementById('field-serverWhitelistEnabled')
}

const distributionFieldElements = {
    version: document.getElementById('distribution-field-version'),
    rss: document.getElementById('distribution-field-rss'),
    serverId: document.getElementById('distribution-field-serverId'),
    serverName: document.getElementById('distribution-field-serverName'),
    serverDescription: document.getElementById('distribution-field-serverDescription'),
    serverVersion: document.getElementById('distribution-field-serverVersion'),
    minecraftVersion: document.getElementById('distribution-field-minecraftVersion'),
    mainServer: document.getElementById('distribution-field-mainServer'),
    autoconnect: document.getElementById('distribution-field-autoconnect')
}

function slugify(value){
    return String(value ?? '')
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '')
}

function isRemoteUrl(value){
    return /^https?:\/\//i.test(String(value ?? '').trim())
}

function deepClone(value){
    return value == null ? value : JSON.parse(JSON.stringify(value))
}

function getDistributionTemplateProfileDefaults(profile){
    const safeId = slugify(profile?.id || profile?.name || 'example-profile') || 'example-profile'
    const fallbackPort = Number.isFinite(Number(profile?.serverPort)) ? Number(profile.serverPort) : 25565
    return {
        version: '1.0.0',
        rss: '',
        servers: [
            {
                id: safeId,
                name: profile?.name || '새 프로필',
                description: profile?.description || '관리자 사이트에서 만든 distribution 템플릿입니다.',
                version: '1.0.0',
                minecraftVersion: '1.20.1',
                address: `127.0.0.1:${fallbackPort}`,
                mainServer: true,
                autoconnect: false,
                modules: []
            }
        ]
    }
}

function normalizeDistributionDocument(rawDocument, profile){
    const baseDocument = deepClone(rawDocument) ?? {}
    const defaultDocument = getDistributionTemplateProfileDefaults(profile)
    const servers = Array.isArray(baseDocument.servers) && baseDocument.servers.length > 0
        ? baseDocument.servers
        : defaultDocument.servers
    const primaryServer = {
        ...defaultDocument.servers[0],
        ...(servers[0] ?? {})
    }

    baseDocument.version = typeof baseDocument.version === 'string' && baseDocument.version.trim().length > 0
        ? baseDocument.version.trim()
        : defaultDocument.version
    baseDocument.rss = typeof baseDocument.rss === 'string' ? baseDocument.rss.trim() : ''
    baseDocument.servers = [primaryServer, ...servers.slice(1)]

    return baseDocument
}

function populateDistributionEditorForm(documentData, profile){
    const normalized = normalizeDistributionDocument(documentData, profile)
    const primaryServer = normalized.servers[0]

    state.distributionEditor.document = normalized
    distributionFieldElements.version.value = normalized.version ?? ''
    distributionFieldElements.rss.value = normalized.rss ?? ''
    distributionFieldElements.serverId.value = primaryServer.id ?? ''
    distributionFieldElements.serverName.value = primaryServer.name ?? ''
    distributionFieldElements.serverDescription.value = primaryServer.description ?? ''
    distributionFieldElements.serverVersion.value = primaryServer.version ?? ''
    distributionFieldElements.minecraftVersion.value = primaryServer.minecraftVersion ?? ''
    distributionFieldElements.mainServer.checked = primaryServer.mainServer !== false
    distributionFieldElements.autoconnect.checked = primaryServer.autoconnect === true

    const moduleCount = Array.isArray(primaryServer.modules) ? primaryServer.modules.length : 0
    const additionalServerCount = Math.max(0, normalized.servers.length - 1)
    distributionModuleCount.textContent = `${moduleCount}개`
    distributionAdditionalServerCount.textContent = `${additionalServerCount}개`
}

function buildDistributionDocumentFromForm(profile){
    const baseDocument = normalizeDistributionDocument(state.distributionEditor.document, profile)
    const primaryServer = {
        ...baseDocument.servers[0]
    }
    const fallbackPort = Number.isFinite(Number(profile?.serverPort)) ? Number(profile.serverPort) : 25565

    baseDocument.version = distributionFieldElements.version.value.trim() || '1.0.0'
    baseDocument.rss = distributionFieldElements.rss.value.trim()

    primaryServer.id = distributionFieldElements.serverId.value.trim() || slugify(profile?.id || profile?.name || 'example-profile') || 'example-profile'
    primaryServer.name = distributionFieldElements.serverName.value.trim() || profile?.name || '새 프로필'
    primaryServer.description = distributionFieldElements.serverDescription.value.trim()
    primaryServer.version = distributionFieldElements.serverVersion.value.trim() || '1.0.0'
    primaryServer.minecraftVersion = distributionFieldElements.minecraftVersion.value.trim() || '1.20.1'
    primaryServer.address = typeof primaryServer.address === 'string' && primaryServer.address.trim().length > 0
        ? primaryServer.address.trim()
        : `127.0.0.1:${fallbackPort}`
    primaryServer.mainServer = distributionFieldElements.mainServer.checked
    primaryServer.autoconnect = distributionFieldElements.autoconnect.checked
    primaryServer.modules = Array.isArray(primaryServer.modules) ? primaryServer.modules : []

    baseDocument.servers = [primaryServer, ...baseDocument.servers.slice(1)]
    state.distributionEditor.document = baseDocument
    return baseDocument
}

function createProfile(){
    const timestamp = Date.now()
    return {
        id: `profile-${timestamp}`,
        name: '새 프로필',
        description: '',
        details: '',
        modsEnabled: false,
        pluginsEnabled: false,
        serverEnabled: false,
        distributionUrl: '',
        worldArchiveUrl: '',
        worldDirectoryName: '',
        serverJarUrl: '',
        serverPort: 25565,
        serverMemoryMb: 4096,
        serverMaxPlayers: 20,
        serverWhitelistEnabled: false
    }
}

function getSelectedProfile(){
    return state.catalog.profiles.find((profile) => profile.id === state.selectedProfileId) ?? null
}

function markDirty(nextDirty = true){
    state.dirty = nextDirty
    saveCatalogButton.textContent = nextDirty ? '카탈로그 저장 *' : '카탈로그 저장'
}

function showStatus(message, tone = 'info'){
    statusBanner.hidden = false
    statusBanner.dataset.tone = tone
    statusBanner.textContent = message
}

function clearStatus(){
    statusBanner.hidden = true
    statusBanner.textContent = ''
    delete statusBanner.dataset.tone
}

function showDistributionEditorStatus(message, tone = 'info'){
    if(distributionEditorStatus == null){
        return
    }
    distributionEditorStatus.hidden = false
    distributionEditorStatus.dataset.tone = tone
    distributionEditorStatus.textContent = message
}

function clearDistributionEditorStatus(){
    if(distributionEditorStatus == null){
        return
    }
    distributionEditorStatus.hidden = true
    distributionEditorStatus.textContent = ''
    delete distributionEditorStatus.dataset.tone
}

function selectProfile(profileId){
    state.selectedProfileId = profileId
    renderSidebar()
    populateEditor()
}

function describeProfileFeatures(profile){
    const badges = ['맵']
    if(profile.modsEnabled){
        badges.push('모드')
    }
    if(profile.pluginsEnabled){
        badges.push('플러그인')
    }
    if(profile.serverEnabled){
        badges.push('서버')
    }
    return badges
}

function renderSidebar(){
    profileList.innerHTML = ''
    profileCount.textContent = `${state.catalog.profiles.length}개`

    for(const profile of state.catalog.profiles){
        const button = document.createElement('button')
        button.type = 'button'
        button.className = 'profileListItem'
        if(profile.id === state.selectedProfileId){
            button.setAttribute('selected', 'true')
        }

        const title = document.createElement('div')
        title.className = 'profileListName'
        title.textContent = profile.name || profile.id

        const meta = document.createElement('div')
        meta.className = 'profileListMeta'
        meta.innerHTML = describeProfileFeatures(profile)
            .map((label) => `<span class="badge">${label}</span>`)
            .join('')

        const description = document.createElement('div')
        description.className = 'profileListDescription'
        description.textContent = profile.description || '설명이 없습니다.'

        button.appendChild(title)
        button.appendChild(meta)
        button.appendChild(description)
        button.addEventListener('click', () => {
            selectProfile(profile.id)
        })

        profileList.appendChild(button)
    }
}

function syncFeatureDependencies(profile, showMessage = false){
    if(profile.pluginsEnabled && !profile.serverEnabled){
        profile.serverEnabled = true
        if(showMessage){
            showStatus('플러그인 사용을 켜면 서버 사용도 자동으로 같이 켜집니다.', 'info')
        }
    }
}

function syncServerSection(profile){
    serverSection.hidden = !profile?.serverEnabled
    fieldElements.serverEnabled.disabled = profile?.pluginsEnabled === true
}

function updateDistributionEditorHint(profile, pathOverride){
    if(!profile){
        distributionEditorHint.textContent = '프로필에 연결할 distribution.json 내용을 사이트 안에서 직접 관리합니다.'
        return
    }

    const currentPath = String(pathOverride ?? profile.distributionUrl ?? '').trim()

    if(currentPath.length === 0){
        distributionEditorHint.textContent = '연결된 distribution 파일이 없습니다. 샘플을 불러오거나 새로 저장해서 이 프로필에 연결하세요.'
        return
    }

    if(isRemoteUrl(currentPath)){
        distributionEditorHint.textContent = `현재 값은 원격 URL입니다: ${currentPath} | 내용을 불러와 수정할 수 있고, 저장하면 이 프로젝트 안의 로컬 distribution 파일로 복사됩니다.`
        return
    }

    distributionEditorHint.textContent = `현재 연결된 distribution 파일: ${currentPath}`
}

function populateEditor(){
    const profile = getSelectedProfile()
    const hasSelection = profile != null

    emptyState.hidden = hasSelection
    profileEditorForm.hidden = !hasSelection
    duplicateProfileButton.disabled = !hasSelection
    deleteProfileButton.disabled = !hasSelection

    if(!profile){
        editorHint.textContent = '왼쪽에서 프로필을 선택하거나 새 프로필을 추가하세요.'
        syncServerSection(null)
        updateDistributionEditorHint(null)
        return
    }

    syncFeatureDependencies(profile)

    editorHint.textContent = '맵은 기본이고, 모드/플러그인/서버를 체크해서 조합하는 프로필입니다. 저장하면 런처 카탈로그도 같이 갱신됩니다.'

    fieldElements.id.value = profile.id ?? ''
    fieldElements.name.value = profile.name ?? ''
    fieldElements.description.value = profile.description ?? ''
    fieldElements.details.value = profile.details ?? ''
    fieldElements.modsEnabled.checked = profile.modsEnabled === true
    fieldElements.pluginsEnabled.checked = profile.pluginsEnabled === true
    fieldElements.serverEnabled.checked = profile.serverEnabled === true
    fieldElements.distributionUrl.value = profile.distributionUrl ?? ''
    fieldElements.worldArchiveUrl.value = profile.worldArchiveUrl ?? ''
    fieldElements.worldDirectoryName.value = profile.worldDirectoryName ?? ''
    fieldElements.serverJarUrl.value = profile.serverJarUrl ?? ''
    fieldElements.serverPort.value = profile.serverPort ?? 25565
    fieldElements.serverMemoryMb.value = profile.serverMemoryMb ?? 4096
    fieldElements.serverMaxPlayers.value = profile.serverMaxPlayers ?? 20
    fieldElements.serverWhitelistEnabled.checked = profile.serverWhitelistEnabled === true

    syncServerSection(profile)
    updateDistributionEditorHint(profile)
}

function updateSelectedProfile(patch){
    const profile = getSelectedProfile()
    if(!profile){
        return
    }

    Object.assign(profile, patch)
    syncFeatureDependencies(profile)
    markDirty(true)
    renderSidebar()
    syncServerSection(profile)

    if(Object.prototype.hasOwnProperty.call(patch, 'distributionUrl')){
        updateDistributionEditorHint(profile, patch.distributionUrl)
    }
}

function bindTextField(fieldName){
    fieldElements[fieldName].addEventListener('input', (event) => {
        if(fieldName === 'id'){
            const profile = getSelectedProfile()
            if(!profile){
                return
            }

            const previousId = profile.id
            profile.id = event.target.value
            state.selectedProfileId = profile.id
            markDirty(true)
            renderSidebar()

            if(previousId !== profile.id){
                showStatus('프로필 ID를 바꿨습니다. 저장하면 이 ID로 반영됩니다.', 'info')
            }
            return
        }

        updateSelectedProfile({ [fieldName]: event.target.value })
    })
}

function bindNumberField(fieldName, fallback){
    fieldElements[fieldName].addEventListener('input', (event) => {
        const value = Number.parseInt(event.target.value || String(fallback), 10)
        updateSelectedProfile({
            [fieldName]: Number.isFinite(value) ? value : fallback
        })
    })
}

function bindCheckboxField(fieldName){
    fieldElements[fieldName].addEventListener('change', (event) => {
        updateSelectedProfile({
            [fieldName]: event.target.checked
        })

        if(fieldName === 'pluginsEnabled' || fieldName === 'serverEnabled'){
            populateEditor()
        }
    })
}

function bindProfileForm(){
    profileEditorForm.addEventListener('submit', (event) => {
        event.preventDefault()
    })

    bindTextField('id')
    bindTextField('name')
    bindTextField('description')
    bindTextField('details')
    bindTextField('distributionUrl')
    bindTextField('worldArchiveUrl')
    bindTextField('worldDirectoryName')
    bindTextField('serverJarUrl')
    bindNumberField('serverPort', 25565)
    bindNumberField('serverMemoryMb', 4096)
    bindNumberField('serverMaxPlayers', 20)
    bindCheckboxField('modsEnabled')
    bindCheckboxField('pluginsEnabled')
    bindCheckboxField('serverEnabled')
    bindCheckboxField('serverWhitelistEnabled')

    for(const button of document.querySelectorAll('.uploadButton')){
        button.addEventListener('click', async () => {
            const targetField = button.dataset.uploadTarget
            const accept = button.dataset.uploadAccept
            await uploadIntoField(targetField, accept)
        })
    }
}

async function uploadIntoField(targetField, accept){
    const profile = getSelectedProfile()
    if(!profile){
        return
    }

    const picker = document.createElement('input')
    picker.type = 'file'
    picker.accept = accept || ''
    picker.addEventListener('change', async () => {
        const file = picker.files?.[0]
        if(!file){
            return
        }

        const formData = new FormData()
        formData.append('file', file)

        try {
            showStatus(`${file.name} 업로드 중...`, 'info')

            const response = await fetch('/api/upload', {
                method: 'POST',
                body: formData
            })
            const result = await response.json()
            if(!response.ok || result.ok !== true){
                throw new Error(result.message || '업로드에 실패했습니다.')
            }

            profile[targetField] = result.file.path
            fieldElements[targetField].value = result.file.path
            markDirty(true)
            renderSidebar()

            if(targetField === 'distributionUrl'){
                updateDistributionEditorHint(profile, result.file.path)
            }

            showStatus(`업로드 완료: ${result.file.path}`, 'success')
        } catch (error) {
            console.error(error)
            showStatus(error instanceof Error ? error.message : '업로드에 실패했습니다.', 'error')
        }
    })

    picker.click()
}

function openDistributionEditorModal(){
    distributionEditorModal.hidden = false
    distributionEditorModal.style.display = 'flex'
    document.body.style.overflow = 'hidden'
    clearDistributionEditorStatus()
}

function closeDistributionEditorModal(){
    distributionEditorModal.hidden = true
    distributionEditorModal.style.display = 'none'
    document.body.style.overflow = ''
    clearDistributionEditorStatus()
}

async function loadDistributionTemplate(){
    const response = await fetch('/api/distribution/template')
    const result = await response.json()
    if(!response.ok || result.ok !== true){
        throw new Error(result.message || 'distribution 샘플을 불러오지 못했습니다.')
    }

    return JSON.parse(result.content)
}

async function loadDistributionContent(requestedPath){
    const response = await fetch(`/api/distribution/content?path=${encodeURIComponent(requestedPath)}`)
    const result = await response.json()
    if(!response.ok || result.ok !== true){
        throw new Error(result.message || 'distribution 파일을 불러오지 못했습니다.')
    }

    return JSON.parse(result.content)
}

async function openDistributionEditor(mode){
    const profile = getSelectedProfile()
    if(!profile){
        showStatus('먼저 프로필을 선택하세요.', 'error')
        return
    }

    openDistributionEditorModal()
    distributionEditorForm.reset()
    state.distributionEditor.document = null
    updateDistributionEditorHint(profile)
    showDistributionEditorStatus('distribution 내용을 준비하는 중...', 'info')

    try {
        const currentPath = String(profile.distributionUrl ?? '').trim()

        if(mode === 'create' || currentPath.length === 0){
            const templateDocument = await loadDistributionTemplate()
            populateDistributionEditorForm(templateDocument, profile)
            updateDistributionEditorHint(profile, '')
            showStatus('distribution 템플릿을 불러왔습니다.', 'success')
            showDistributionEditorStatus('샘플을 불러왔습니다. 바로 수정한 뒤 저장하면 됩니다.', 'success')
            return
        }

        if(isRemoteUrl(currentPath)){
            const remoteDocument = await loadDistributionContent(currentPath)
            populateDistributionEditorForm(remoteDocument, profile)
            updateDistributionEditorHint(profile, currentPath)
            showStatus('원격 distribution 내용을 불러왔습니다.', 'success')
            showDistributionEditorStatus('원격 distribution 내용을 불러왔습니다. 저장하면 로컬 파일로 복사됩니다.', 'success')
            return
        }

        showStatus('distribution 파일을 불러오는 중...', 'info')
        const localDocument = await loadDistributionContent(currentPath)
        populateDistributionEditorForm(localDocument, profile)
        updateDistributionEditorHint(profile, currentPath)
        showStatus('distribution 파일을 불러왔습니다.', 'success')
        showDistributionEditorStatus('현재 연결된 distribution 파일을 불러왔습니다.', 'success')
    } catch (error) {
        console.error(error)
        showStatus(error instanceof Error ? error.message : 'distribution 파일을 불러오지 못했습니다.', 'error')
        showDistributionEditorStatus(error instanceof Error ? error.message : 'distribution 파일을 불러오지 못했습니다.', 'error')
    }
}

async function saveDistributionFile(){
    const profile = getSelectedProfile()
    if(!profile){
        showStatus('먼저 프로필을 선택하세요.', 'error')
        return
    }

    try {
        const distributionDocument = buildDistributionDocumentFromForm(profile)
        saveDistributionFileButton.disabled = true
        showStatus('distribution 파일 저장 중...', 'info')
        showDistributionEditorStatus('distribution 파일 저장 중...', 'info')

        const response = await fetch('/api/distribution/save', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                profileId: profile.id,
                content: JSON.stringify(distributionDocument, null, 2)
            })
        })
        const result = await response.json()
        if(!response.ok || result.ok !== true){
            throw new Error(result.message || 'distribution 파일 저장에 실패했습니다.')
        }

        profile.distributionUrl = result.file.path
        fieldElements.distributionUrl.value = result.file.path
        markDirty(true)
        renderSidebar()
        populateEditor()
        closeDistributionEditorModal()
        showStatus('distribution 파일을 저장했습니다. 카탈로그 저장을 누르면 런처에 반영됩니다.', 'success')
    } catch (error) {
        console.error(error)
        showStatus(error instanceof Error ? error.message : 'distribution 파일 저장에 실패했습니다.', 'error')
        showDistributionEditorStatus(error instanceof Error ? error.message : 'distribution 파일 저장에 실패했습니다.', 'error')
    } finally {
        saveDistributionFileButton.disabled = false
    }
}

function bindDistributionEditor(){
    distributionEditorForm.addEventListener('submit', (event) => {
        event.preventDefault()
    })

    distributionEditorModal.addEventListener('click', (event) => {
        if(event.target === distributionEditorModal){
            closeDistributionEditorModal()
        }
    })

    document.addEventListener('keydown', (event) => {
        if(event.key === 'Escape' && distributionEditorModal.hidden === false){
            closeDistributionEditorModal()
        }
    })
}

window.__launcherAdminOpenDistributionEditor = async (mode) => {
    await openDistributionEditor(mode)
}

window.__launcherAdminCloseDistributionEditor = () => {
    closeDistributionEditorModal()
}

window.__launcherAdminLoadDistributionTemplate = async () => {
    try {
        const templateDocument = await loadDistributionTemplate()
        populateDistributionEditorForm(templateDocument, getSelectedProfile())
        updateDistributionEditorHint(getSelectedProfile(), '')
        showStatus('distribution 템플릿을 다시 불러왔습니다.', 'success')
        showDistributionEditorStatus('샘플을 다시 불러왔습니다.', 'success')
    } catch (error) {
        console.error(error)
        showStatus(error instanceof Error ? error.message : 'distribution 템플릿을 불러오지 못했습니다.', 'error')
        showDistributionEditorStatus(error instanceof Error ? error.message : 'distribution 템플릿을 불러오지 못했습니다.', 'error')
    }
}

window.__launcherAdminSaveDistributionFile = async () => {
    await saveDistributionFile()
}

async function loadMeta(){
    const response = await fetch('/api/meta')
    const meta = await response.json()
    state.meta = meta
    launcherCatalogPath.textContent = meta.launcherCatalogPath
    localCatalogUrl.textContent = meta.localCatalogUrl
    localCatalogUrl.href = meta.localCatalogUrl
}

function normalizeLoadedProfile(profile){
    return {
        ...createProfile(),
        ...profile,
        modsEnabled: profile.modsEnabled === true,
        pluginsEnabled: profile.pluginsEnabled === true,
        serverEnabled: profile.serverEnabled === true || profile.pluginsEnabled === true,
        serverWhitelistEnabled: profile.serverWhitelistEnabled === true
    }
}

async function loadCatalog(){
    const response = await fetch('/api/catalog')
    const catalog = await response.json()
    state.catalog = {
        version: 1,
        profiles: Array.isArray(catalog.profiles) ? catalog.profiles.map(normalizeLoadedProfile) : []
    }

    if(state.catalog.profiles.length > 0){
        state.selectedProfileId = state.catalog.profiles[0].id
    } else {
        state.selectedProfileId = null
    }

    markDirty(false)
    renderSidebar()
    populateEditor()
}

function addProfile(){
    const profile = createProfile()
    state.catalog.profiles.push(profile)
    markDirty(true)
    selectProfile(profile.id)
    showStatus(`${profile.name} 프로필을 추가했습니다.`, 'success')
}

function duplicateSelectedProfile(){
    const profile = getSelectedProfile()
    if(!profile){
        return
    }

    const clonedProfile = structuredClone(profile)
    clonedProfile.id = `${slugify(profile.id || profile.name) || 'profile'}-copy-${Date.now()}`
    clonedProfile.name = `${profile.name} 복제본`

    state.catalog.profiles.push(clonedProfile)
    markDirty(true)
    selectProfile(clonedProfile.id)
    showStatus('복제본을 만들었습니다.', 'success')
}

function deleteSelectedProfile(){
    const profile = getSelectedProfile()
    if(!profile){
        return
    }

    const confirmed = window.confirm(`'${profile.name}' 프로필을 삭제할까요?`)
    if(!confirmed){
        return
    }

    state.catalog.profiles = state.catalog.profiles.filter((entry) => entry.id !== profile.id)
    state.selectedProfileId = state.catalog.profiles[0]?.id ?? null
    markDirty(true)
    renderSidebar()
    populateEditor()
    showStatus('프로필을 삭제했습니다.', 'success')
}

async function saveCatalog(){
    try {
        clearStatus()
        saveCatalogButton.disabled = true
        showStatus('카탈로그 저장 중...', 'info')

        const response = await fetch('/api/catalog', {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(state.catalog)
        })
        const result = await response.json()
        if(!response.ok || result.ok !== true){
            throw new Error(result.message || '카탈로그 저장에 실패했습니다.')
        }

        state.catalog = {
            version: 1,
            profiles: result.catalog.profiles.map(normalizeLoadedProfile)
        }
        if(!state.catalog.profiles.some((profile) => profile.id === state.selectedProfileId)){
            state.selectedProfileId = state.catalog.profiles[0]?.id ?? null
        }

        markDirty(false)
        renderSidebar()
        populateEditor()
        showStatus('카탈로그를 저장했고 런처 카탈로그에도 동기화했습니다.', 'success')
    } catch (error) {
        console.error(error)
        showStatus(error instanceof Error ? error.message : '카탈로그 저장에 실패했습니다.', 'error')
    } finally {
        saveCatalogButton.disabled = false
    }
}

function bindTopLevelActions(){
    addProfileButton.addEventListener('click', () => {
        addProfile()
    })

    saveCatalogButton.addEventListener('click', async () => {
        await saveCatalog()
    })

    duplicateProfileButton.addEventListener('click', () => {
        duplicateSelectedProfile()
    })

    deleteProfileButton.addEventListener('click', () => {
        deleteSelectedProfile()
    })
}

async function bootstrap(){
    closeDistributionEditorModal()
    bindProfileForm()
    bindDistributionEditor()
    bindTopLevelActions()

    try {
        await Promise.all([
            loadMeta(),
            loadCatalog()
        ])
        showStatus('관리자 사이트를 불러왔습니다. 저장하면 런처 카탈로그가 같이 갱신됩니다.', 'info')
    } catch (error) {
        console.error(error)
        showStatus(error instanceof Error ? error.message : '관리자 사이트를 불러오지 못했습니다.', 'error')
    }
}

bootstrap()
