import { nextTick } from 'vue' export function useAdminCustomItems({ api, toast, customItems, customItemPage, customItemLimit, customItemPageCount, customItemQuery, customItemFilter, customItemModalOpen, customItemDeleteModalOpen, customItemModalHistoryActive, modalTargetCustomItem, customItemModalDraftLabel, customItemModalDraftTags, customItemModalLabelSaving, customItemModalTargetTemplateId, customItemReplacementQuery, customItemReplacementItems, customItemReplacementLoading, customItemReplacementTargetId, customItemReplacementBusy, templates, selectedTemplateId, refreshCustomItems, loadTemplate, setTab, selectAdminTemplate, resetMessages, success, error, }) { function submitCustomItemSearch() { customItemPage.value = 1 refreshCustomItems() } function changeCustomItemFilter(filter) { customItemFilter.value = filter customItemPage.value = 1 refreshCustomItems() } function changeCustomItemLimit(limit) { customItemLimit.value = limit customItemPage.value = 1 refreshCustomItems() } function moveCustomItemPage(direction) { const nextPage = customItemPage.value + direction if (nextPage < 1 || nextPage > customItemPageCount.value) return customItemPage.value = nextPage refreshCustomItems() } function pushCustomItemModalHistoryState() { if (typeof window === 'undefined') return window.history.pushState({ ...(window.history.state || {}), adminCustomItemModal: true }, '', window.location.href) customItemModalHistoryActive.value = true } async function refreshReplacementCandidates() { const currentItemId = modalTargetCustomItem.value?.id || '' if (!currentItemId) { customItemReplacementItems.value = [] return } try { customItemReplacementLoading.value = true const data = await api.listAdminCustomItems({ q: customItemReplacementQuery.value, page: 1, limit: 50, filter: 'all', }) customItemReplacementItems.value = (data.items || []).filter((item) => item?.id && item.id !== currentItemId) } catch (e) { error.value = '대체할 이미지 목록을 불러오지 못했어요.' customItemReplacementItems.value = [] } finally { customItemReplacementLoading.value = false } } function openCustomItemModal(item) { modalTargetCustomItem.value = item || null customItemModalDraftLabel.value = item?.label || '' customItemModalDraftTags.value = Array.isArray(item?.tags) ? [...item.tags] : [] customItemModalTargetTemplateId.value = '' customItemReplacementQuery.value = '' customItemReplacementItems.value = [] customItemReplacementTargetId.value = '' customItemReplacementBusy.value = false customItemModalOpen.value = true pushCustomItemModalHistoryState() } function closeCustomItemModal({ fromPopState = false } = {}) { customItemModalOpen.value = false customItemDeleteModalOpen.value = false modalTargetCustomItem.value = null customItemModalDraftLabel.value = '' customItemModalDraftTags.value = [] customItemModalLabelSaving.value = false customItemModalTargetTemplateId.value = '' customItemReplacementQuery.value = '' customItemReplacementItems.value = [] customItemReplacementTargetId.value = '' customItemReplacementLoading.value = false customItemReplacementBusy.value = false if (fromPopState) { customItemModalHistoryActive.value = false return } if (customItemModalHistoryActive.value && typeof window !== 'undefined') { customItemModalHistoryActive.value = false window.history.back() } } function openCustomItemDeleteModal(item) { if (!item) return if (item.sourceType === 'user' && !item.replacedAt && (item.usageCount > 0 || item.linkedTemplates.length > 0)) { error.value = '사용 중이거나 템플릿에 연결된 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.' return } modalTargetCustomItem.value = item customItemDeleteModalOpen.value = true } function closeCustomItemDeleteModal() { customItemDeleteModalOpen.value = false } function jumpToTemplateAdmin(templateId) { if (!templateId) return closeCustomItemModal() setTab('template-admin') nextTick(() => { selectAdminTemplate(templateId) }) } async function removeCustomItem(item = modalTargetCustomItem.value) { resetMessages() if (!item) return if (item.sourceType === 'user' && !item.replacedAt && (item.usageCount > 0 || item.linkedTemplates.length > 0)) { error.value = '사용 중이거나 템플릿에 연결된 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.' return } try { await api.deleteAdminCustomItem(item.id) closeCustomItemDeleteModal() closeCustomItemModal() await refreshCustomItems() success.value = item.sourceType === 'template' ? '선택한 템플릿 아이템을 제거했어요.' : item.sourceType === 'asset' ? '선택한 이미지 자산을 삭제했어요.' : '사용자 업로드 이미지를 삭제했어요.' } catch (e) { error.value = item.sourceType === 'template' ? '템플릿 아이템 제거에 실패했어요.' : item.sourceType === 'asset' ? '이미지 자산 삭제에 실패했어요.' : '사용자 업로드 이미지 삭제에 실패했어요.' } } async function removeUnusedCustomItems() { resetMessages() const ok = window.confirm('현재 검색 조건에 맞는 미사용 이미지를 모두 삭제할까요?') if (!ok) return try { const data = await api.deleteAdminUnusedCustomItems({ q: customItemQuery.value }) await refreshCustomItems() success.value = `${data.deletedCount || 0}개의 미사용 이미지를 삭제했어요.` } catch (e) { error.value = '미사용 이미지 일괄 삭제에 실패했어요.' } } function showUnusedCustomItems() { if (customItemFilter.value === 'unused') return resetMessages() customItemFilter.value = 'unused' customItemPage.value = 1 refreshCustomItems() } async function saveCustomItemModalLabel() { const item = modalTargetCustomItem.value const nextLabel = customItemModalDraftLabel.value.trim().slice(0, 60) const nextTags = Array.from( new Set( (Array.isArray(customItemModalDraftTags.value) ? customItemModalDraftTags.value : []) .map((tag) => tag.trim().replace(/^#/, '').slice(0, 40)) .filter(Boolean) ) ).slice(0, 30) if ( !item || !nextLabel || (nextLabel === item.label && JSON.stringify(nextTags) === JSON.stringify(Array.isArray(item.tags) ? item.tags : [])) || customItemModalLabelSaving.value ) { return } try { customItemModalLabelSaving.value = true const data = await api.updateAdminCustomItemLabel(item.id, { label: nextLabel, tags: nextTags, sourceType: item.sourceType }) item.label = data.item?.label || nextLabel item.tags = Array.isArray(data.item?.tags) ? data.item.tags : nextTags customItemModalDraftLabel.value = item.label customItemModalDraftTags.value = [...item.tags] customItems.value = customItems.value.map((entry) => (entry.id === item.id ? { ...entry, label: item.label, tags: item.tags } : entry)) toast.success('아이템 메타를 변경했어요.') } catch (e) { error.value = '아이템 메타 변경에 실패했어요.' } finally { customItemModalLabelSaving.value = false } } async function promoteCustomItem(item) { resetMessages() if (!customItemModalTargetTemplateId.value) { error.value = '추가할 템플릿을 먼저 선택해주세요.' return } try { item.isPromoting = true await api.promoteAdminTemplateItem(item.id, { topicId: customItemModalTargetTemplateId.value }) const targetTemplateName = templates.value.find((template) => template.id === customItemModalTargetTemplateId.value)?.name || customItemModalTargetTemplateId.value if (selectedTemplateId.value === customItemModalTargetTemplateId.value) await loadTemplate() closeCustomItemModal() success.value = `"${item.label}" 이미지를 ${targetTemplateName} 템플릿으로 추가했어요.` } catch (e) { error.value = '선택한 이미지를 템플릿으로 추가하지 못했어요.' } finally { item.isPromoting = false } } async function replaceCustomItem(item = modalTargetCustomItem.value) { resetMessages() const targetItem = customItemReplacementItems.value.find((entry) => entry.id === customItemReplacementTargetId.value) if (!item?.id) { error.value = '대체할 원본 아이템을 찾지 못했어요.' return } if (!targetItem?.id) { error.value = '대체할 대상 이미지를 먼저 선택해주세요.' return } try { customItemReplacementBusy.value = true const data = await api.replaceAdminCustomItem(item.id, { targetItemId: targetItem.id, targetSourceType: targetItem.sourceType || 'user', }) if (selectedTemplateId.value) await loadTemplate() await refreshCustomItems() closeCustomItemModal() success.value = `"${item.label}" 이미지를 "${data.targetItem?.label || targetItem.label}" 기준으로 대체했어요.` } catch (e) { error.value = e?.status === 409 ? '같은 이미지/이름으로는 대체할 수 없어요.' : '이미지 대체에 실패했어요.' } finally { customItemReplacementBusy.value = false } } async function restoreCustomItem(item = modalTargetCustomItem.value) { resetMessages() if (!item?.id || !item.replacedAt) { error.value = '복구할 대체 이력이 없어요.' return } try { customItemReplacementBusy.value = true await api.restoreAdminCustomItem(item.id) if (selectedTemplateId.value) await loadTemplate() await refreshCustomItems() closeCustomItemModal() success.value = `"${item.label}" 아이템을 원래 이미지로 복구했어요.` } catch (e) { error.value = '원래 이미지로 복구하지 못했어요.' } finally { customItemReplacementBusy.value = false } } return { submitCustomItemSearch, changeCustomItemFilter, changeCustomItemLimit, moveCustomItemPage, pushCustomItemModalHistoryState, openCustomItemModal, closeCustomItemModal, openCustomItemDeleteModal, closeCustomItemDeleteModal, jumpToTemplateAdmin, removeCustomItem, removeUnusedCustomItems, showUnusedCustomItems, saveCustomItemModalLabel, promoteCustomItem, refreshReplacementCandidates, replaceCustomItem, restoreCustomItem, } }