#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.Util;
namespace UVC.UIToolkit
{
///
/// 이미지가 포함된 그리드/리스트를 표시하는 UIToolkit 컴포넌트입니다.
/// PrefabGrid의 UIToolkit 버전입니다.
///
/// 개요:
///
/// UTKImageList는 Unity UI Toolkit의 ListView를 래핑하여 이미지+텍스트 형태의
/// 아이템 목록을 2열 그리드로 표시합니다. 검색 필터링, 드래그 앤 드롭 기능을 제공합니다.
///
///
/// 주요 기능:
///
/// - 이미지+텍스트 형태의 아이템을 2열 그리드로 표시
/// - 실시간 검색 필터링 (2글자 이상)
/// - 드래그 앤 드롭 지원
/// - 가상화를 통한 대량 데이터 성능 최적화
///
///
/// UXML 사용 예시:
///
///
///
///
/// ]]>
///
/// C# 사용 예시:
/// ("image-list");
///
/// // 2. 데이터 구성 - 이미지 경로와 이름을 가진 아이템들
/// var data = new List
/// {
/// new UTKImageListItemData
/// {
/// itemName = "의자",
/// imagePath = "Prefabs/Thumbnails/chair",
/// prefabPath = "Prefabs/Furniture/Chair"
/// },
/// new UTKImageListItemData
/// {
/// itemName = "책상",
/// imagePath = "Prefabs/Thumbnails/desk",
/// prefabPath = "Prefabs/Furniture/Desk"
/// },
/// new UTKImageListItemData
/// {
/// itemName = "소파",
/// imagePath = "Prefabs/Thumbnails/sofa",
/// prefabPath = "Prefabs/Furniture/Sofa"
/// }
/// };
///
/// // 3. 데이터 설정
/// imageList.SetData(data);
///
/// // 4. 아이템 클릭 이벤트 구독
/// imageList.OnItemClick += (item) =>
/// {
/// Debug.Log($"클릭된 아이템: {item.itemName}");
/// };
///
/// // 5. 드래그 앤 드롭 이벤트 (씬에 프리팹 배치용)
/// imageList.OnItemBeginDrag += (item, screenPos) =>
/// {
/// Debug.Log($"드래그 시작: {item.itemName}");
/// };
///
/// imageList.OnItemDrag += (item, screenPos) =>
/// {
/// // 드래그 중 - 3D 미리보기 위치 업데이트 등
/// };
///
/// imageList.OnItemEndDrag += (item, screenPos) =>
/// {
/// Debug.Log($"드래그 종료: {item.itemName}");
/// };
///
/// imageList.OnItemDrop += (item) =>
/// {
/// // 드롭 완료 - 프리팹 인스턴스화
/// if (!string.IsNullOrEmpty(item.prefabPath))
/// {
/// var prefab = Resources.Load(item.prefabPath);
/// Instantiate(prefab, dropPosition, Quaternion.identity);
/// }
/// };
///
/// // 6. 리스트 영역 밖으로 드래그 시 3D 미리보기 표시
/// imageList.OnDragExitList += (item, screenPos) =>
/// {
/// // 리스트 밖 = 3D 씬 영역
/// Show3DPreview(item.prefabPath, screenPos);
/// };
///
/// // 7. 리스트 영역 안으로 다시 들어왔을 때 미리보기 숨김
/// imageList.OnDragEnterList += (item, screenPos) =>
/// {
/// Hide3DPreview();
/// };
///
/// // 8. 드래그 영역 설정 (부모 윈도우 영역 기준으로 체크)
/// imageList.DragBoundsElement = parentWindow;
///
/// // 9. 드래그 고스트 이미지 따라다니기 설정
/// imageList.DragImageFollowCursor = true;
///
/// // 10. 검색 실행 (2글자 이상)
/// imageList.ApplySearch("의자");
///
/// // 11. 현재 검색어 확인
/// string currentQuery = imageList.SearchQuery;
///
/// // 12. 아이템 추가/제거
/// var newItem = new UTKImageListItemData { itemName = "새 아이템" };
/// imageList.AddItem(newItem);
/// imageList.RemoveItem(newItem);
///
/// // 13. 전체 삭제
/// imageList.Clear();
///
/// // 14. 아이템 개수 확인
/// int count = imageList.ItemCount;
///
/// // 15. 리스트 표시/숨김
/// imageList.Show();
/// imageList.Hide();
///
/// // 16. 리소스 해제 (OnDestroy에서 호출)
/// imageList.Dispose();
/// ]]>
///
/// 관련 리소스:
///
/// - Resources/UIToolkit/List/UTKImageList.uxml - 메인 레이아웃
/// - Resources/UIToolkit/List/UTKImageListItem.uxml - 행 템플릿 (2열)
/// - Resources/UIToolkit/List/UTKImageListUss.uss - 스타일
///
///
[UxmlElement]
public partial class UTKImageList : VisualElement, IDisposable
{
#region 상수 (Constants)
/// 메인 UXML 파일 경로 (Resources 폴더 기준)
private const string UXML_PATH = "UIToolkit/List/UTKImageList";
private const string USS_PATH = "UIToolkit/List/UTKImageListUss";
/// 아이템 UXML 파일 경로 (Resources 폴더 기준)
private const string ITEM_UXML_PATH = "UIToolkit/List/UTKImageListItem";
/// 최소 검색어 길이
private const int MIN_SEARCH_LENGTH = 2;
/// 이미지 로딩 디바운스 시간 (ms)
private const int IMAGE_LOAD_DEBOUNCE_MS = 50;
/// 한 행에 표시할 아이템 수
private const int ITEMS_PER_ROW = 2;
#endregion
#region 캐싱된 리소스 (Cached Resources)
/// 아이템 UXML 템플릿 (재사용을 위해 캐싱)
private VisualTreeAsset? _itemTemplate;
#endregion
#region UI 컴포넌트 참조 (UI Component References)
/// 검색어 입력 필드
private UTKInputField? _searchField;
/// Unity UI Toolkit의 ListView 컴포넌트
private ListView? _listView;
/// 검색어 지우기 버튼 (UTKButton)
private UTKButton? _clearButton;
/// 검색 결과 건수 라벨
private Label? _searchResultLabel;
#endregion
#region 내부 데이터 (Internal Data)
/// 원본 데이터 (검색 필터 해제 시 복원용)
private List _originalData = new();
/// 필터링된 데이터 (검색 결과)
private List _filteredData = new();
/// 행 단위로 묶인 데이터 (ListView용)
private List _rowData = new();
/// 항목 ID 자동 생성을 위한 시드 값
private int _idSeed = 1;
/// 현재 검색 모드 여부
private bool _isSearchMode;
/// 이미지 로딩 취소 토큰 소스 (메모리 누수 방지)
private CancellationTokenSource? _imageLoadCts;
/// 로드된 이미지 스프라이트 캐시 (경로 → 스프라이트)
private readonly Dictionary _spriteCache = new();
/// 현재 로딩 중인 이미지 경로 추적 (중복 로딩 방지)
private readonly HashSet _loadingImages = new();
/// RowData 객체 풀 (GC 부담 감소)
private readonly Queue _rowDataPool = new();
#endregion
#region 내부 클래스 (Internal Classes)
///
/// 행 데이터를 저장하는 클래스입니다.
/// 한 행에 최대 2개의 아이템을 포함합니다.
///
private class RowData
{
public UTKImageListItemData? LeftItem;
public UTKImageListItemData? RightItem;
}
#endregion
#region IDisposable
private bool _disposed;
#endregion
#region 공개 속성 (Public Properties)
///
/// 드래그 시 이미지가 커서를 따라다니도록 설정합니다.
/// 기본값은 true입니다.
///
public bool DragImageFollowCursor { get; set; } = true;
///
/// 드래그 영역 체크에 사용할 요소를 설정합니다.
/// null이면 자신(UTKImageList)의 worldBound를 사용합니다.
/// 부모 윈도우 등 다른 요소의 영역을 체크하려면 해당 요소를 설정하세요.
///
public VisualElement? DragBoundsElement { get; set; }
///
/// 현재 검색어를 가져오거나 설정합니다.
/// 설정 시 검색 필드의 값만 변경하고 검색은 실행하지 않습니다.
///
public string SearchQuery
{
get => _searchField?.value ?? string.Empty;
set { if (_searchField != null) _searchField.value = value; }
}
///
/// 현재 표시 중인 아이템 수를 반환합니다.
/// 검색 모드에서는 필터링된 결과 수를, 아니면 전체 수를 반환합니다.
///
public int ItemCount => _isSearchMode ? _filteredData.Count : _originalData.Count;
#endregion
#region 외부 이벤트 (Public Events)
///
/// 아이템 클릭 이벤트입니다.
/// 아이템을 클릭하면 해당 데이터와 함께 발생합니다.
///
public Action? OnItemClick;
///
/// 아이템 드롭 이벤트입니다.
/// 드래그 앤 드롭이 완료되면 발생합니다.
///
public Action? OnItemDrop;
///
/// 드래그 시작 이벤트입니다.
/// (아이템 데이터, 화면 좌표)
///
public Action? OnItemBeginDrag;
///
/// 드래그 중 이벤트입니다.
/// (아이템 데이터, 화면 좌표)
///
public Action? OnItemDrag;
///
/// 드래그 종료 이벤트입니다.
/// (아이템 데이터, 화면 좌표)
///
public Action? OnItemEndDrag;
///
/// 드래그 중 리스트 영역을 벗어났을 때 발생하는 이벤트입니다.
/// (아이템 데이터, 화면 좌표)
/// 3D 프리팹 미리보기를 표시하는데 사용합니다.
///
public Action? OnDragExitList;
///
/// 드래그 중 리스트 영역에 다시 진입했을 때 발생하는 이벤트입니다.
/// (아이템 데이터, 화면 좌표)
/// 3D 프리팹 미리보기를 숨기는데 사용합니다.
///
public Action? OnDragEnterList;
#endregion
#region 생성자 (Constructor)
///
/// UTKImageList 컴포넌트를 초기화합니다.
/// UXML 템플릿을 로드하고 내부 컴포넌트를 설정합니다.
///
public UTKImageList()
{
// 1. 메인 UXML 로드 및 복제
var visualTree = Resources.Load(UXML_PATH);
if (visualTree == null)
{
Debug.LogError($"[UTKImageList] UXML not found at: {UXML_PATH}");
return;
}
visualTree.CloneTree(this);
// 2. 아이템 템플릿 로드 (성능: 한 번만 로드하여 재사용)
_itemTemplate = Resources.Load(ITEM_UXML_PATH);
if (_itemTemplate == null)
{
Debug.LogError($"[UTKImageList] Item UXML not found at: {ITEM_UXML_PATH}");
}
// 3. 테마 적용 및 변경 구독
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 4. UI 요소 참조 획득
_searchField = this.Q("search-field");
_listView = this.Q("main-list-view");
_clearButton = this.Q("clear-btn");
_searchResultLabel = this.Q