# Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
# Asset Dock for Terrain3D
@tool
extends PanelContainer

signal confirmation_closed
signal confirmation_confirmed
signal confirmation_canceled

const ES_DOCK_TILE_SIZE: String = "terrain3d/dock/tile_size"
const ES_DOCK_PINNED: String = "terrain3d/dock/always_on_top"
const ES_DOCK_TAB: String = "terrain3d/dock/tab"

var texture_list: ListContainer
var mesh_list: ListContainer
var current_list: ListContainer
var _updating_list: bool

var pinned_btn: Button
var size_slider: HSlider
var box: BoxContainer
var buttons: BoxContainer
var textures_btn: Button
var meshes_btn: Button
var asset_container: ScrollContainer
var confirm_dialog: ConfirmationDialog
var _confirmed: bool = false
var search_box: TextEdit
var search_button: Button

#DEPRECATED 4.5
#class EdDock extends EditorDock:
#func _update_layout(layout: int) -> void:
#layout 1 vertical, 2 horizontal, 4 window
#print("Terrain3DAssetDock: _update_layout called with: ", layout)

var _dock: MarginContainer  #DEPRECATED 4.5 - Use EdDock
var _initialized: bool = false
var plugin: EditorPlugin
var window: Window


func _notification(what: int) -> void:
	if what == NOTIFICATION_ENTER_TREE:
		await get_tree().process_frame
		update_layout()


func initialize(p_plugin: EditorPlugin) -> void:
	if p_plugin:
		plugin = p_plugin

	_dock = ClassDB.instantiate("EditorDock")  #DEPRECATED 4.5 - EdDock.new()
	_dock.title = "Terrain3D"
	_dock.dock_icon = preload("../icons/terrain3d.svg")
	_dock.default_slot = 8  #DEPRECATED 4.5 - EditorDock.DockSlot.DOCK_SLOT_BOTTOM
	_dock.closable = false
	_dock.available_layouts = 0x7  #DEPRECATED 4.5 - EditorDock.DOCK_LAYOUT_ALL
	_dock.add_child(self)
	plugin.add_dock(_dock)
	_dock.open()
	_dock.make_visible()

	pinned_btn = $Box/Buttons/Pinned
	size_slider = $Box/Buttons/SizeSlider
	size_slider.owner = null
	box = $Box
	buttons = $Box/Buttons
	textures_btn = $Box/Buttons/TexturesBtn
	meshes_btn = $Box/Buttons/MeshesBtn
	asset_container = $Box/ScrollContainer
	search_box = $Box/Buttons/SearchBox
	search_box.owner = null
	# Scale left column width to editor scale
	var editor_scale: float = EditorInterface.get_editor_scale()
	search_box.custom_minimum_size = Vector2(100. * editor_scale, 30. * editor_scale)
	search_button = $Box/Buttons/SearchBox/SearchButton

	texture_list = ListContainer.new()
	texture_list.name = "TextureList"
	texture_list.plugin = plugin
	texture_list.type = Terrain3DAssets.TYPE_TEXTURE
	asset_container.add_child(texture_list, true)
	mesh_list = ListContainer.new()
	mesh_list.name = "MeshList"
	mesh_list.plugin = plugin
	mesh_list.type = Terrain3DAssets.TYPE_MESH
	mesh_list.visible = false
	asset_container.add_child(mesh_list, true)
	current_list = texture_list

	load_editor_settings()

	# Connect signals
	resized.connect(update_layout)
	textures_btn.pressed.connect(_on_textures_pressed)
	meshes_btn.pressed.connect(_on_meshes_pressed)
	pinned_btn.toggled.connect(_on_pin_changed)
	pinned_btn.owner = null
	size_slider.value_changed.connect(_on_slider_changed)
	plugin.ui.toolbar.tool_changed.connect(_on_tool_changed)

	meshes_btn.add_theme_font_size_override(
		"font_size", int(16. * EditorInterface.get_editor_scale())
	)
	textures_btn.add_theme_font_size_override(
		"font_size", int(16. * EditorInterface.get_editor_scale())
	)

	search_box.text_changed.connect(_on_search_text_changed)
	search_button.pressed.connect(_on_search_button_pressed)

	confirm_dialog = ConfirmationDialog.new()
	add_child(confirm_dialog, true)
	confirm_dialog.hide()
	confirm_dialog.confirmed.connect(
		func():
			_confirmed = true
			confirmation_closed.emit()
			confirmation_confirmed.emit()
	)
	confirm_dialog.canceled.connect(
		func():
			_confirmed = false
			confirmation_closed.emit()
			confirmation_canceled.emit()
	)

	# Setup styles
	set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel"))
	# Avoid saving icon resources in tscn when editing w/ a tool script
	if EditorInterface.get_edited_scene_root() != self:
		pinned_btn.icon = get_theme_icon("Pin", "EditorIcons")
		pinned_btn.text = ""
		search_button.icon = get_theme_icon("Search", "EditorIcons")

	_initialized = true
	update_dock()
	update_layout()


func _gui_input(p_event: InputEvent) -> void:
	if p_event is InputEventMouseButton:
		if search_box.has_focus():
			if plugin.debug:
				print("Terrain3DAssetDock: _on_box_gui_input: search_box releasing focus")
			search_box.release_focus()


## Dock placement


func remove_dock(p_force: bool = false) -> void:
	plugin.remove_dock(_dock)


func update_dock() -> void:
	if not _initialized:
		return
	update_assets()
	_dock.make_visible()


func update_layout() -> void:
	if not _initialized:
		return
	if plugin.debug > 1:
		print("Terrain3DAssetDock: update_layout")

	## Detect if we have a new window from `Make floating` and grab it
	if not window:
		if get_parent().get_parent().get_parent() is Window:
			window = get_parent().get_parent().get_parent()
			_on_pin_changed(pinned_btn.button_pressed)
			plugin.godot_editor_window.mouse_entered.connect(_on_godot_window_entered)
			return  # Displaying will call this function again
		# Check if window was just freed. Freed objects have a different hash than null
		elif hash(window) != hash(null):
			window = null
			plugin.godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
			return  # Will call this function again upon display

	## Vertical layout: buttons on top
	if size.x < 700:
		box.vertical = true
		buttons.vertical = false
		search_box.reparent(box)
		box.move_child(search_box, 1)
		size_slider.reparent(box)
		box.move_child(size_slider, 2)
		pinned_btn.reparent(buttons)
	else:
		# Wide layout: buttons on left
		box.vertical = false
		buttons.vertical = true
		search_box.reparent(buttons)
		buttons.move_child(search_box, 0)
		size_slider.reparent(buttons)
		buttons.move_child(size_slider, 3)
		pinned_btn.reparent(box)

	pinned_btn.visible = is_instance_valid(window)
	save_editor_settings()


# When a floating asset dock has focus and the mouse returns, grab focus so cursor decal works
func _on_godot_window_entered() -> void:
	if plugin.debug > 1:
		print("Terrain3DAssetDock: _on_godot_window_entered")
	if is_instance_valid(window) and window.has_focus():
		plugin.godot_editor_window.grab_focus()


func set_selected_by_asset_id(p_id: int) -> void:
	search_box.text = ""
	_on_search_text_changed()
	current_list.set_selected_id(p_id)


func _on_search_text_changed() -> void:
	if plugin.debug:
		print("Terrain3DAssetDock: _on_search_text_changed: ", search_box.text)
	search_box.text = search_box.text.strip_escapes()
	var len: int = search_box.text.length()
	if len > 0:
		search_box.set_caret_column(len)
		search_button.icon = get_theme_icon("Close", "EditorIcons")
	else:
		search_button.icon = get_theme_icon("Search", "EditorIcons")

	mesh_list.search_text = search_box.text
	texture_list.search_text = search_box.text
	current_list.update_asset_list()
	current_list.set_selected_id(0)


func _on_search_button_pressed() -> void:
	if plugin.debug:
		print("Terrain3DAssetDock: _on_search_button_pressed")
	if search_box.text.length() > 0:
		search_box.text = ""
		_on_search_text_changed()
	else:
		if plugin.debug:
			print("Terrain3DAssetDock: _on_search_button_pressed: Search box grabbing focus")
		search_box.grab_focus()


## Dock Button handlers


func _on_pin_changed(toggled: bool) -> void:
	if window:
		window.always_on_top = pinned_btn.button_pressed
	save_editor_settings()


func _on_slider_changed(value: float) -> void:
	# Set both lists so they match
	if texture_list:
		texture_list.set_entry_width(value)
	if mesh_list:
		mesh_list.set_entry_width(value)
	save_editor_settings()
	# Hack to trigger ScrollContainer::_reposition_children() to update size of scroll bar handle
	asset_container.layout_direction = Control.LAYOUT_DIRECTION_LTR
	asset_container.layout_direction = Control.LAYOUT_DIRECTION_INHERITED


func _on_textures_pressed() -> void:
	if plugin.debug:
		print("Terrain3DAssetDock: _on_textures_pressed")
	if _updating_list or current_list == texture_list:
		return
	_updating_list = true
	current_list = texture_list
	texture_list.visible = true
	mesh_list.visible = false
	textures_btn.set_pressed_no_signal(true)
	meshes_btn.set_pressed_no_signal(false)
	texture_list.update_asset_list()
	if plugin.is_terrain_valid():
		EditorInterface.edit_node(plugin.terrain)
	save_editor_settings()
	_updating_list = false


func _on_meshes_pressed() -> void:
	if plugin.debug:
		print("Terrain3DAssetDock: _on_meshes_pressed")
	if _updating_list or current_list == mesh_list:
		return
	_updating_list = true
	current_list = mesh_list
	mesh_list.visible = true
	texture_list.visible = false
	meshes_btn.set_pressed_no_signal(true)
	textures_btn.set_pressed_no_signal(false)
	mesh_list.update_asset_list()
	if plugin.is_terrain_valid():
		EditorInterface.edit_node(plugin.terrain)
	save_editor_settings()
	_updating_list = false


func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
	if plugin.debug:
		print("Terrain3DAssetDock: _on_tool_changed: ", p_tool, ", ", p_operation)
	if p_tool == Terrain3DEditor.INSTANCER:
		_on_meshes_pressed()
	elif p_tool in [Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS]:
		_on_textures_pressed()


## Update Dock Contents


func update_assets() -> void:
	if plugin.debug:
		print(
			"Terrain3DAssetDock: update_assets: ", plugin.terrain.assets if plugin.terrain else ""
		)
	if not _initialized:
		return

	# Verify signals to individual lists
	if plugin.is_terrain_valid() and plugin.terrain.assets:
		if not plugin.terrain.assets.textures_changed.is_connected(texture_list.update_asset_list):
			plugin.terrain.assets.textures_changed.connect(texture_list.update_asset_list)
		if not plugin.terrain.assets.meshes_changed.is_connected(mesh_list.update_asset_list):
			plugin.terrain.assets.meshes_changed.connect(mesh_list.update_asset_list)

	current_list.update_asset_list()


## Manage Editor Settings


func load_editor_settings() -> void:
	# Remove old editor settings
	const ES_DOCK: String = "terrain3d/dock/"
	for setting in ["slot", "floating", "window_position", "window_size"]:
		plugin.erase_setting(ES_DOCK + setting)
	pinned_btn.button_pressed = plugin.get_setting(ES_DOCK_PINNED, true)
	size_slider.value = plugin.get_setting(ES_DOCK_TILE_SIZE, 90)
	_on_slider_changed(size_slider.value)
	# TODO Don't save tab until thumbnail generation more reliable
	#if plugin.get_setting(ES_DOCK_TAB, 0) == 1:
	#	_on_meshes_pressed()


func save_editor_settings() -> void:
	if not _initialized:
		return
	plugin.set_setting(ES_DOCK_TILE_SIZE, size_slider.value)
	plugin.set_setting(ES_DOCK_PINNED, pinned_btn.button_pressed)
	# TODO Don't save tab until thumbnail generation more reliable
	# plugin.set_setting(ES_DOCK_TAB, 0 if current_list == texture_list else 1)


##############################################################
## class ListContainer
##############################################################


class ListContainer:
	extends Container
	var plugin: EditorPlugin
	var type := Terrain3DAssets.TYPE_TEXTURE
	var entries: Array[ListEntry]
	var selected_id: int = 0
	var height: float = 0.
	var width: float = 90.
	var focus_style: StyleBox
	var _clearing_resource: bool = false
	var search_text: String = ""

	func _ready() -> void:
		set_v_size_flags(SIZE_EXPAND_FILL)
		set_h_size_flags(SIZE_EXPAND_FILL)
		add_theme_color_override("font_color", Color.WHITE)
		add_theme_color_override("font_shadow_color", Color.BLACK)
		add_theme_constant_override("shadow_offset_x", 1)
		add_theme_constant_override("shadow_offset_y", 1)

	func clear() -> void:
		for e in entries:
			e.get_parent().remove_child(e)
			e.queue_free()
		entries.clear()

	func update_asset_list() -> void:
		if plugin.debug:
			print("Terrain3DListContainer ", name, ": update_asset_list")
		clear()

		# Grab terrain
		var t: Terrain3D = plugin.get_terrain()
		if not (t and t.assets):
			return

		if type == Terrain3DAssets.TYPE_TEXTURE:
			var texture_count: int = t.assets.get_texture_count()
			for i in texture_count:
				var texture: Terrain3DTextureAsset = t.assets.get_texture(i)
				add_item(texture)
			if texture_count < Terrain3DAssets.MAX_TEXTURES:
				add_item()
		else:
			if plugin.terrain:
				plugin.terrain.assets.create_mesh_thumbnails()
			var mesh_count: int = t.assets.get_mesh_count()
			for i in mesh_count:
				var mesh: Terrain3DMeshAsset = t.assets.get_mesh_asset(i)
				add_item(mesh)
			if mesh_count < Terrain3DAssets.MAX_MESHES:
				add_item()
		set_selected_id(selected_id)

	func add_item(p_resource: Resource = null) -> void:
		var entry: ListEntry = ListEntry.new()
		entry.focus_style = focus_style
		entry.set_edited_resource(p_resource)
		if not entry.get_resource_name().containsn(search_text) and not search_text == "":
			return

		var res_id: int = p_resource.id if p_resource else entries.size()
		entry.hovered.connect(_on_resource_hovered.bind(res_id))
		entry.clicked.connect(clicked_id.bind(entries.size()))
		entry.inspected.connect(_on_resource_inspected)
		entry.changed.connect(_on_resource_changed.bind(res_id))
		entry.type = type
		add_child(entry, true)
		entries.push_back(entry)

		if p_resource:
			if not p_resource.id_changed.is_connected(set_selected_after_swap):
				p_resource.id_changed.connect(set_selected_after_swap)

	func _on_resource_hovered(p_id: int):
		if type == Terrain3DAssets.TYPE_MESH:
			if plugin.terrain:
				plugin.terrain.assets.create_mesh_thumbnails(p_id)

	func set_selected_after_swap(
		p_type: Terrain3DAssets.AssetType, p_old_id: int, p_new_id: int
	) -> void:
		EditorInterface.mark_scene_as_unsaved()
		set_selected_id(clamp(p_new_id, 0, entries.size() - 2))

	func clicked_id(p_id: int) -> void:
		# Select Tool if clicking an asset
		plugin.select_terrain()
		if (
			type == Terrain3DAssets.TYPE_TEXTURE
			and not (
				plugin.editor.get_tool()
				in [Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS]
			)
		):
			var paint_btn: Button = plugin.ui.toolbar.get_node_or_null("PaintTexture")
			if paint_btn:
				paint_btn.set_pressed(true)
				plugin.ui._on_tool_changed(Terrain3DEditor.TEXTURE, Terrain3DEditor.REPLACE)
		elif (
			type == Terrain3DAssets.TYPE_MESH
			and plugin.editor.get_tool() != Terrain3DEditor.INSTANCER
		):
			var instancer_btn: Button = plugin.ui.toolbar.get_node_or_null("InstanceMeshes")
			if instancer_btn:
				instancer_btn.set_pressed(true)
				plugin.ui._on_tool_changed(Terrain3DEditor.INSTANCER, Terrain3DEditor.ADD)
		set_selected_id(p_id)

	func set_selected_id(p_id: int) -> void:
		# "Add new" is the final entry only when search box is blank
		var max_id: int = max(0, entries.size() - (1 if search_text else 2))
		if plugin.debug:
			print(
				"Terrain3DListContainer ",
				name,
				": set_selected_id: ",
				selected_id,
				" to ",
				clamp(p_id, 0, max_id)
			)
		selected_id = clamp(p_id, 0, max_id)
		for i in entries.size():
			var entry: ListEntry = entries[i]
			entry.set_selected(i == selected_id)
		plugin.ui._on_setting_changed()

	func get_selected_asset_id() -> int:
		# "Add new" is the final entry only when search box is blank
		var max_id: int = max(0, entries.size() - (1 if search_text else 2))
		var id: int = clamp(selected_id, 0, max_id)
		if plugin.debug:
			print(
				"Terrain3DListContainer ",
				name,
				": get_selected_asset_id: selected_id: ",
				selected_id,
				", clamped: ",
				id,
				", entries: ",
				entries.size()
			)
		if id >= entries.size():
			return 0
		var res: Resource = entries[id].resource
		if not res:
			return 0
		if type == Terrain3DAssets.AssetType.TYPE_MESH:
			return (res as Terrain3DMeshAsset).id
		else:
			return (res as Terrain3DTextureAsset).id

	func _on_resource_inspected(p_resource: Resource) -> void:
		await get_tree().process_frame
		EditorInterface.edit_resource(p_resource)

	func _on_resource_changed(p_resource: Resource, p_id: int) -> void:
		if not p_resource and _clearing_resource:
			return
		if not p_resource:
			if plugin.debug:
				print(
					"Terrain3DListContainer ",
					name,
					": _on_resource_changed: removing asset ID: ",
					p_id
				)
			_clearing_resource = true
			var asset_dock: Control = get_parent().get_parent().get_parent()
			if type == Terrain3DAssets.TYPE_TEXTURE:
				asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this texture?"
			else:
				asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this mesh and delete all instances?"
			asset_dock.confirm_dialog.popup_centered()
			await asset_dock.confirmation_closed
			if not asset_dock._confirmed:
				update_asset_list()
				_clearing_resource = false
				return

		if not plugin.is_terrain_valid():
			plugin.select_terrain()
			await get_tree().process_frame

		if plugin.is_terrain_valid():
			if type == Terrain3DAssets.TYPE_TEXTURE:
				plugin.terrain.assets.set_texture(p_id, p_resource)
			else:
				plugin.terrain.assets.set_mesh_asset(p_id, p_resource)

			# If removing an entry, clear inspector
			if not p_resource:
				EditorInterface.inspect_object(null)
		_clearing_resource = false

	func set_entry_width(value: float) -> void:
		var min_width: float = 90.0 * max(1.0, EditorInterface.get_editor_scale())
		width = clamp(value, min_width, 512.0)
		redraw()

	func get_entry_width() -> float:
		return width

	func redraw() -> void:
		height = 0
		var id: int = 0
		var separation: float = 2.
		var columns: int = 3
		columns = clamp(size.x / width, 1, 100)
		var tile_size: Vector2 = Vector2(width, width) - Vector2(separation, separation)
		var name_font_size := int(
			clamp(tile_size.x / 12., 12., 16.) * EditorInterface.get_editor_scale()
		)
		for c in get_children():
			if is_instance_valid(c):
				c.size = tile_size
				c.position = (
					Vector2(id % columns, id / columns) * width
					+ Vector2(separation / columns, separation / columns)
				)
				height = max(height, c.position.y + width)
				id += 1
				c.name_label.add_theme_font_size_override("font_size", name_font_size)

	# Needed to enable ScrollContainer scroll bar
	func _get_minimum_size() -> Vector2:
		return Vector2(0, height)

	func _notification(p_what) -> void:
		if p_what == NOTIFICATION_SORT_CHILDREN:
			redraw()


##############################################################
## class ListEntry
##############################################################


class ListEntry:
	extends MarginContainer
	signal hovered
	signal clicked
	signal changed(resource: Resource)
	signal inspected(resource: Resource)

	var resource: Resource
	var type := Terrain3DAssets.TYPE_TEXTURE
	var _thumbnail: Texture2D
	var drop_data: bool = false
	var is_hovered: bool = false
	var is_selected: bool = false

	var name_label: Label
	var button_row: FlowContainer
	var button_enabled: TextureButton
	var button_edit: TextureButton
	var spacer: Control
	var button_clear: TextureButton

	@onready var focus_style: StyleBox = get_theme_stylebox("focus", "Button").duplicate()
	@onready var background: StyleBox = get_theme_stylebox("pressed", "Button")
	@onready var clear_icon: Texture2D = get_theme_icon("Close", "EditorIcons")
	@onready var edit_icon: Texture2D = get_theme_icon("Edit", "EditorIcons")
	@onready var enabled_icon: Texture2D = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
	@onready var disabled_icon: Texture2D = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
	@onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons")

	func _ready() -> void:
		name = "ListEntry"
		custom_minimum_size = Vector2i(86., 86.)
		mouse_filter = Control.MOUSE_FILTER_PASS
		add_theme_constant_override("margin_top", 5)
		add_theme_constant_override("margin_left", 5)
		add_theme_constant_override("margin_right", 5)

		setup_buttons()
		setup_label()
		focus_style.set_border_width_all(2)
		focus_style.set_border_color(Color(1, 1, 1, .67))

	func setup_buttons() -> void:
		destroy_buttons()

		button_row = FlowContainer.new()
		button_enabled = TextureButton.new()
		button_edit = TextureButton.new()
		spacer = Control.new()
		button_clear = TextureButton.new()

		var icon_size: Vector2 = Vector2(12, 12)

		button_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
		button_row.alignment = FlowContainer.ALIGNMENT_CENTER
		button_row.mouse_filter = Control.MOUSE_FILTER_PASS
		add_child(button_row, true)

		if type == Terrain3DAssets.TYPE_MESH:
			button_enabled.set_texture_normal(enabled_icon)
			button_enabled.set_texture_pressed(disabled_icon)
			button_enabled.set_custom_minimum_size(icon_size)
			button_enabled.set_h_size_flags(Control.SIZE_SHRINK_END)
			button_enabled.set_visible(resource != null)
			button_enabled.tooltip_text = "Enable Instances"
			button_enabled.toggle_mode = true
			button_enabled.mouse_filter = Control.MOUSE_FILTER_PASS
			button_enabled.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
			button_enabled.pressed.connect(_on_enable)
			button_row.add_child(button_enabled, true)

		button_edit.set_texture_normal(edit_icon)
		button_edit.set_custom_minimum_size(icon_size)
		button_edit.set_h_size_flags(Control.SIZE_SHRINK_END)
		button_edit.set_visible(resource != null)
		button_edit.tooltip_text = "Edit Asset"
		button_edit.mouse_filter = Control.MOUSE_FILTER_PASS
		button_edit.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
		button_edit.pressed.connect(_on_edit)
		button_row.add_child(button_edit, true)

		spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
		spacer.mouse_filter = Control.MOUSE_FILTER_PASS
		button_row.add_child(spacer, true)

		button_clear.set_texture_normal(clear_icon)
		button_clear.set_custom_minimum_size(icon_size)
		button_clear.set_h_size_flags(Control.SIZE_SHRINK_END)
		button_clear.set_visible(resource != null)
		button_clear.tooltip_text = "Clear Asset"
		button_clear.mouse_filter = Control.MOUSE_FILTER_PASS
		button_clear.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
		button_clear.pressed.connect(_on_clear)
		button_row.add_child(button_clear, true)

	func destroy_buttons() -> void:
		if button_row:
			button_row.free()
			button_row = null
		if button_enabled:
			button_enabled.free()
			button_enabled = null
		if button_edit:
			button_edit.free()
			button_edit = null
		if spacer:
			spacer.free()
			spacer = null
		if button_clear:
			button_clear.free()
			button_clear = null

	func get_resource_name() -> StringName:
		if resource:
			if resource is Terrain3DMeshAsset:
				return (resource as Terrain3DMeshAsset).get_name()
			elif resource is Terrain3DTextureAsset:
				return (resource as Terrain3DTextureAsset).get_name()
		return ""

	func setup_label() -> void:
		name_label = Label.new()
		name_label.name = "MeshLabel"
		name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
		name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
		name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
		name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
		name_label.add_theme_font_size_override(
			"font_size", int(14. * EditorInterface.get_editor_scale())
		)
		name_label.add_theme_color_override("font_color", Color.WHITE)
		name_label.add_theme_color_override("font_shadow_color", Color.BLACK)
		name_label.add_theme_constant_override("shadow_offset_x", 1)
		name_label.add_theme_constant_override("shadow_offset_y", 1)
		name_label.visible = false
		name_label.autowrap_mode = TextServer.AUTOWRAP_OFF
		name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
		add_child(name_label, true)

	func _notification(p_what) -> void:
		match p_what:
			NOTIFICATION_PREDELETE:
				destroy_buttons()
			NOTIFICATION_DRAW:
				# Hide spacer if icons are crowding small textures
				spacer.visible = size.x > 94. or type == Terrain3DAssets.TYPE_TEXTURE
				var rect: Rect2 = Rect2(Vector2.ZERO, get_size())
				if !resource:
					draw_style_box(background, rect)
					draw_texture(add_icon, (get_size() / 2) - (add_icon.get_size() / 2))
				else:
					if type == Terrain3DAssets.TYPE_TEXTURE:
						_thumbnail = resource.get_albedo_texture()
					else:
						_thumbnail = resource.get_thumbnail()
					if _thumbnail:
						draw_texture_rect(_thumbnail, rect, false)
						texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST_WITH_MIPMAPS
					else:
						draw_rect(rect, Color(.15, .15, .15, 1.))
					if type == Terrain3DAssets.TYPE_TEXTURE:
						self_modulate = resource.get_albedo_color()
					else:
						button_enabled.set_pressed_no_signal(!resource.is_enabled())
				if drop_data:
					draw_style_box(focus_style, rect)
				if is_hovered:
					draw_rect(rect, Color(1, 1, 1, 0.2))
				if is_selected:
					draw_style_box(focus_style, rect)
			NOTIFICATION_MOUSE_ENTER:
				if not resource:
					name_label.visible = false
				else:
					name_label.visible = true
				is_hovered = true
				name_label.text = get_resource_name()
				tooltip_text = get_resource_name()
				emit_signal("hovered")
				queue_redraw()
			NOTIFICATION_MOUSE_EXIT:
				name_label.visible = false
				is_hovered = false
				drop_data = false
				queue_redraw()

	func _gui_input(p_event: InputEvent) -> void:
		if p_event is InputEventMouseButton:
			if p_event.is_pressed():
				match p_event.get_button_index():
					MOUSE_BUTTON_LEFT:
						# If `Add new` is clicked
						if !resource:
							if type == Terrain3DAssets.TYPE_TEXTURE:
								set_edited_resource(Terrain3DTextureAsset.new(), false)
							else:
								set_edited_resource(Terrain3DMeshAsset.new(), false)
							_on_edit()
						else:
							emit_signal("clicked")
					MOUSE_BUTTON_RIGHT:
						if resource:
							_on_edit()
					MOUSE_BUTTON_MIDDLE:
						if resource:
							_on_clear()

	func _can_drop_data(p_at_position: Vector2, p_data: Variant) -> bool:
		drop_data = false
		if typeof(p_data) == TYPE_DICTIONARY:
			if p_data.files.size() == 1:
				queue_redraw()
				drop_data = true
		return drop_data

	func _drop_data(p_at_position: Vector2, p_data: Variant) -> void:
		if typeof(p_data) == TYPE_DICTIONARY:
			var res: Resource = load(p_data.files[0])
			if res is Texture2D and type == Terrain3DAssets.TYPE_TEXTURE:
				var ta := Terrain3DTextureAsset.new()
				if resource is Terrain3DTextureAsset:
					ta.id = resource.id
				ta.set_albedo_texture(res)
				set_edited_resource(ta, false)
				resource = ta
			elif res is Terrain3DTextureAsset and type == Terrain3DAssets.TYPE_TEXTURE:
				if resource is Terrain3DTextureAsset:
					res.id = resource.id
				set_edited_resource(res, false)
			elif res is PackedScene and type == Terrain3DAssets.TYPE_MESH:
				if not resource:
					resource = Terrain3DMeshAsset.new()
				set_edited_resource(resource, false)
				resource.set_scene_file(res)
			elif res is Terrain3DMeshAsset and type == Terrain3DAssets.TYPE_MESH:
				if resource is Terrain3DMeshAsset:
					res.id = resource.id
				set_edited_resource(res, false)
			emit_signal("clicked")
			emit_signal("inspected", resource)

	func set_edited_resource(p_res: Resource, p_no_signal: bool = true) -> void:
		resource = p_res
		if resource:
			if not resource.setting_changed.is_connected(_on_resource_changed):
				resource.setting_changed.connect(_on_resource_changed)
			if resource is Terrain3DTextureAsset:
				if not resource.file_changed.is_connected(_on_resource_changed):
					resource.file_changed.connect(_on_resource_changed)
			elif resource is Terrain3DMeshAsset:
				if not resource.instancer_setting_changed.is_connected(_on_resource_changed):
					resource.instancer_setting_changed.connect(_on_resource_changed)

		if button_clear:
			button_clear.set_visible(resource != null)

		queue_redraw()
		if not p_no_signal:
			emit_signal("changed", resource)

	func _on_resource_changed(_value: int = 0) -> void:
		queue_redraw()
		emit_signal("changed", resource)

	func set_selected(value: bool) -> void:
		if not is_inside_tree():
			#push_error("not in tree")
			return
		is_selected = value
		if is_selected:
			# Handle scrolling to show the selected item
			await get_tree().process_frame
			if is_inside_tree():
				get_parent().get_parent().get_v_scroll_bar().ratio = (
					position.y / get_parent().size.y
				)
		queue_redraw()

	func _on_clear() -> void:
		if resource:
			name_label.hide()
			set_edited_resource(null, false)

	func _on_edit() -> void:
		emit_signal("clicked")
		emit_signal("inspected", resource)

	func _on_enable() -> void:
		if resource is Terrain3DMeshAsset:
			resource.set_enabled(!resource.is_enabled())

	func _format_number(num: int) -> String:
		var is_negative: bool = num < 0
		var str_num: String = str(abs(num))
		var result: String = ""
		var length: int = str_num.length()
		for i in length:
			result = str_num[length - 1 - i] + result
			if i < length - 1 and (i + 1) % 3 == 0:
				result = "," + result
		return "-" + result if is_negative else result
