import { html, type PropertyValues, type TemplateResult } from 'lit'
import { property, state } from 'lit/decorators.js'
import { LitElement } from 'lit'
import { classMap } from 'lit/directives/class-map.js'
import { DataDomain } from '@api-client/core/modeling/DataDomain.js'
import {
  DataDomainKind,
  DomainAssociationKind,
  DomainEntityKind,
  DomainModelKind,
  DomainNamespaceKind,
  DomainPropertyKind,
} from '@api-client/core/models/kinds.js'
import type { DomainNamespace } from '@api-client/core/modeling/DomainNamespace.js'
import type { DomainModel } from '@api-client/core/modeling/DomainModel.js'
import type { DomainEntity } from '@api-client/core/modeling/DomainEntity.js'
import type { DomainSearchResult } from '@api-client/core/modeling/types.js'
import type { DomainSelectionInfo } from '../types.js'
import { getDomainLabel, getFieldIcon, getDomainIconType } from '../lib/ui_mapping.js'
import type { DomainProperty } from '@api-client/core/modeling/DomainProperty.js'
import type { DomainAssociation } from '@api-client/core/modeling/DomainAssociation.js'
import type { DomainElement } from '@api-client/core/modeling/DomainElement.js'
import type { IconType } from '../../md/icons/Icons.js'

/**
 * Event detail for multi-selection
 */
export interface DomainMultiSelectionEventDetail {
  items: DomainSelectionInfo[]
  selectionType: string
}

/**
 * Event detail for drag and drop operations
 */
export interface DomainDragDropEventDetail {
  items: DomainSelectionInfo[]
  target: DomainSelectionInfo
}

/**
 * A new navigation element for DataDomain hierarchy that supports:
 * - Single click selection with selection events
 * - Ctrl+click multi-selection
 * - Drag and drop operations with proper validation
 * - Mixed selection type prevention
 *
 * @fires items-selected {CustomEvent<DomainMultiSelectionEventDetail>} - when multiple items are selected
 * @fires items-dropped {CustomEvent<DomainDragDropEventDetail>} - when items are dropped (or pasted)
 * @fires item-selected {CustomEvent<DomainSelectionInfo>} - when a single item is selected
 * @fires selection-cleared {CustomEvent<void>} - when selection is cleared
 * @fires items-cut {CustomEvent<{ items: DomainSelectionInfo[], cutItemsType: string }>} - when items are cut
 */
export default class DomainNavigation extends LitElement {
  /**
   * The root DataDomain to render
   */
  @property({ type: Object })
  accessor root: DataDomain | undefined

  #selected: string | undefined

  /**
   * The currently selected item key.
   * To be used by the parent component to control selection.
   * @attribute selected
   */
  @property({ type: String })
  set selected(value: string | undefined) {
    const old = this.#selected
    this.#selected = value
    this.requestUpdate('selected', old)
  }

  get selected(): string | undefined {
    return this.#selected
  }

  /**
   * Highlighted item key for visual indication.
   * Highlighted item only renders with a different style. Has no other meaning.
   */
  @property({ type: String }) accessor highlighted: string | undefined

  /**
   * Currently selected items
   */
  @state()
  accessor selectedItems = new Set<DomainSelectionInfo>()

  /**
   * The type of currently selected items (namespace, model, or entity)
   */
  @state()
  accessor selectionType: string | undefined

  /**
   * Items currently being dragged
   */
  @state()
  accessor draggedItems = new Set<DomainSelectionInfo>()

  /**
   * Current drag target
   */
  @state()
  accessor dragTarget: string | undefined

  /**
   * Set of parent items (by key) that are opened/expanded in the view
   */
  @state()
  accessor openedParents = new Set<string>()

  /**
   * Items that have been cut and are waiting to be pasted
   */
  @state()
  accessor cutItems = new Set<DomainSelectionInfo>()

  /**
   * The type of items that were cut
   */
  @state()
  accessor cutItemsType: string | undefined

  /**
   * Currently focused item key for keyboard navigation
   */
  @state()
  accessor focusedItem: string | undefined

  /**
   * Current search query
   */
  @state()
  accessor searchQuery: string | undefined

  /**
   * Search results from domain search
   */
  @state()
  accessor searchResults: DomainSearchResult[] = []

  /**
   * List of all visible items in navigation order (for keyboard navigation)
   */
  private visibleItems: DomainSelectionInfo[] = []

  // Utility methods for working with selection sets

  /**
   * Check if an item is selected by key
   */
  private isItemSelected(key: string): boolean {
    for (const item of this.selectedItems) {
      if (item.key === key) return true
    }
    return false
  }

  /**
   * Check if an item is dragged by key
   */
  private isItemDragged(key: string): boolean {
    for (const item of this.draggedItems) {
      if (item.key === key) return true
    }
    return false
  }

  /**
   * Check if an item is cut by key
   */
  private isItemCut(key: string): boolean {
    for (const item of this.cutItems) {
      if (item.key === key) return true
    }
    return false
  }

  /**
   * Remove item from selection by key
   */
  private removeFromSelection(key: string): void {
    for (const item of this.selectedItems) {
      if (item.key === key) {
        this.selectedItems.delete(item)
        break
      }
    }
  }

  constructor() {
    super()
    this.handleDomainChange = this.handleDomainChange.bind(this)
    this.handleKeyDown = this.handleKeyDown.bind(this)
    this.addEventListener('focus', this.handleFocus.bind(this))
  }

  override connectedCallback(): void {
    super.connectedCallback()
    this.setAttribute('aria-label', 'Domain hierarchy navigation')
    this.setAttribute('role', 'tree')
    this.setAttribute('tabindex', '0') // Make component focusable for keyboard events

    // Add keyboard event listener
    this.addEventListener('keydown', this.handleKeyDown)
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback()

    // Clean up keyboard event listener
    this.removeEventListener('keydown', this.handleKeyDown)
  }

  protected handleFocus(): void {
    if (!this.focusedItem) {
      this.updateVisibleItemsList()
      this.focusedItem = this.visibleItems[0]?.key
    }
    if (!this.focusedItem) {
      return
    }
    this.removeAttribute('tabindex')
  }

  protected override willUpdate(cp: PropertyValues<this>): void {
    if (cp.has('root')) {
      const old = cp.get('root')
      if (old) {
        old.removeEventListener('change', this.handleDomainChange)
      }
      this.root?.addEventListener('change', this.handleDomainChange)

      // Initialize collapsed state for new root
      if (this.root && this.root !== old) {
        this.initializeOpenedParents()
      }
    }
    if (cp.has('selected')) {
      this.selectedItems.clear()
      if (this.selected) {
        const selectedItem = this.findItemDetails(this.selected)
        if (selectedItem) {
          this.selectedItems.add(selectedItem)
        }
        this.ensureVisibility(this.selected)
      }
    }
    super.willUpdate(cp)
  }

  protected override updated(cp: PropertyValues<this>): void {
    super.updated(cp)
    if (cp.has('focusedItem')) {
      this.focusOnCurrentItem()
    }
  }

  protected handleDomainChange(): void {
    this.requestUpdate()
  }

  /**
   * Toggle collapsed state of a parent item
   */
  protected toggleCollapsed(key: string, event?: MouseEvent): void {
    if (event) {
      event.preventDefault()
      event.stopPropagation()
    }

    if (this.openedParents.has(key)) {
      this.openedParents.delete(key)
    } else {
      this.openedParents.add(key)
    }
    this.requestUpdate()
  }

  /**
   * Check if an item is collapsed
   */
  protected isCollapsed(key: string): boolean {
    return !this.openedParents.has(key)
  }

  /**
   * Initialize the opened state.
   *
   * @TODO: When selection change from the outside, we should also update the openedParents
   * to ensure that the parent of the selected item is always expanded.
   */
  protected initializeOpenedParents(): void {
    this.openedParents.clear()
  }

  protected ensureVisibility(key: string): void {
    if (!this.root) return

    const node = this.root.graph.node(key)
    if (!node) {
      return
    }
    let current: DataDomain | DomainNamespace | DomainModel | DomainEntity | undefined = node.getParentInstance()
    while (current) {
      if (current.key === this.root.key) {
        current = undefined
        break // Reached the root, no need to expand further
      }
      this.openedParents.add(current.key)
      if (!(current instanceof DataDomain)) {
        current = current.getParentInstance()
      } else {
        current = undefined // No parent for DataDomain
      }
    }
    this.requestUpdate()
  }

  /**
   * Expand all collapsed items
   */
  expandAll(): void {
    this.openedParents.clear()

    if (!this.root) return

    const collectParentKeys = (container: DataDomain | DomainNamespace): void => {
      // Add namespaces that have children
      for (const namespace of container.listNamespaces()) {
        if (namespace.hasFields()) {
          this.openedParents.add(namespace.key)
          collectParentKeys(namespace)
        }
      }

      // Add models that have entities
      for (const model of container.listModels()) {
        if (model.hasFields()) {
          this.openedParents.add(model.key)
        }
      }
    }

    collectParentKeys(this.root)
    this.requestUpdate()
  }

  /**
   * Collapse all parent items
   */
  collapseAll(): void {
    this.openedParents.clear()
    this.requestUpdate()
  }

  /**
   * Clear current selection
   */
  clearSelection(): void {
    this.selectedItems.clear()
    this.selectionType = undefined
    this.requestUpdate()
    this.dispatchSelectionCleared()
  }

  /**
   * Handle keyboard shortcuts
   */
  protected handleKeyDown(event: KeyboardEvent): void {
    const { key, ctrlKey, metaKey } = event
    const isCtrlPressed = ctrlKey || metaKey

    // Update visible items list for navigation
    this.updateVisibleItemsList()

    if (isCtrlPressed) {
      // Only handle if there are selected items for cut/paste operations
      if (this.selectedItems.size > 0) {
        switch (key.toLowerCase()) {
          case 'x':
            event.preventDefault()
            this.cutSelectedItems()
            break
          case 'v':
            event.preventDefault()
            this.pasteItems()
            break
          case 'c':
            // Future: could implement copy functionality
            break
        }
      }
      return
    }

    // Handle navigation keys
    switch (key) {
      case 'ArrowUp':
        event.preventDefault()
        this.moveFocusUp()
        break
      case 'ArrowDown':
        event.preventDefault()
        this.moveFocusDown()
        break
      case 'ArrowLeft':
        event.preventDefault()
        this.handleArrowLeft()
        break
      case 'ArrowRight':
        event.preventDefault()
        this.handleArrowRight()
        break
      case ' ':
      case 'Space':
        event.preventDefault()
        this.handleSpaceKey()
        break
      case 'Home':
        event.preventDefault()
        this.moveFocusToFirst()
        break
      case 'End':
        event.preventDefault()
        this.moveFocusToLast()
        break
      case 'Escape':
        this.cancelCut()
        break
      case 'Enter':
        event.preventDefault()
        this.handleEnterKey()
        break
    }
  }

  /**
   * Cut the currently selected items
   */
  protected cutSelectedItems(): void {
    if (this.selectedItems.size === 0) return

    // Clear any previous cut items
    this.cutItems.clear()

    // Set current selection as cut items
    for (const item of this.selectedItems) {
      this.cutItems.add(item)
    }
    this.cutItemsType = this.selectionType

    // Clear selection but keep the cut items
    this.selectedItems.clear()
    this.selectionType = undefined

    this.requestUpdate()
    this.dispatchCutItems()
  }

  /**
   * Paste cut items to the current selection target
   */
  protected pasteItems(): void {
    if (this.cutItems.size === 0) return

    // If there's a single selected item, try to paste to it
    if (this.selectedItems.size === 1) {
      const targetItem = Array.from(this.selectedItems)[0]
      if (targetItem && this.isValidPasteTarget(targetItem.key, targetItem.kind)) {
        this.performPaste(targetItem.key, targetItem.kind)
      }
    } else if (this.selectedItems.size === 0) {
      // If no selection, try to paste to root
      if (this.root && this.isValidPasteTarget(this.root.key, this.root.kind)) {
        this.performPaste(this.root.key, this.root.kind)
      }
    }
  }

  /**
   * Perform the actual paste operation
   */
  protected performPaste(targetKey: string, targetKind: string): void {
    const items = Array.from(this.cutItems)

    if (items.length === 0) return

    const dropDetail: DomainDragDropEventDetail = {
      items,
      target: {
        key: targetKey,
        kind: targetKind,
        parent: this.findItemDetails(targetKey)?.parent,
        domain: this.root ? this.root.key : '',
      },
    }

    this.dispatchEvent(new CustomEvent('items-dropped', { detail: dropDetail, bubbles: true }))

    // Clear cut state after successful paste
    this.cutItems.clear()
    this.cutItemsType = undefined
    this.requestUpdate()
  }

  /**
   * Cancel current cut operation
   */
  protected cancelCut(): void {
    this.cutItems.clear()
    this.cutItemsType = undefined
    this.requestUpdate()
  }

  /**
   * Check if target is valid for pasting cut items
   */
  protected isValidPasteTarget(targetKey: string, targetKind: string): boolean {
    if (!this.cutItemsType || this.isItemCut(targetKey)) {
      return false
    }

    // Use the same validation logic as drag and drop
    // Entities can only be pasted on models
    if (this.cutItemsType === DomainEntityKind) {
      return targetKind === DomainModelKind
    }

    // Models can be pasted on namespaces or root
    if (this.cutItemsType === DomainModelKind) {
      return targetKind === DomainNamespaceKind || targetKind === DataDomainKind
    }

    // Namespaces can be pasted on other namespaces or root
    if (this.cutItemsType === DomainNamespaceKind) {
      return targetKind === DomainNamespaceKind || targetKind === DataDomainKind
    }

    return false
  }

  /**
   * Dispatch cut items event
   */
  protected dispatchCutItems(): void {
    const items = Array.from(this.cutItems)

    const detail = {
      items,
      cutItemsType: this.cutItemsType || '',
    }

    this.dispatchEvent(new CustomEvent('items-cut', { detail, bubbles: true }))
  }

  /**
   * Update the list of visible items for keyboard navigation
   */
  protected updateVisibleItemsList(): void {
    this.visibleItems = []
    if (!this.root) return

    const addVisibleItems = (container: DataDomain | DomainNamespace, level = 0): void => {
      // Add namespaces
      for (const namespace of container.listNamespaces()) {
        const item = this.findItemDetails(namespace.key)
        if (item) {
          this.visibleItems.push(item)
        }

        // Add children if not collapsed
        if (namespace.hasFields() && !this.isCollapsed(namespace.key)) {
          addVisibleItems(namespace, level + 1)
        }
      }

      // Add models
      for (const model of container.listModels()) {
        const item = this.findItemDetails(model.key)
        if (item) {
          this.visibleItems.push(item)
        }

        // Add entities if not collapsed
        if (model.hasFields() && !this.isCollapsed(model.key)) {
          for (const entity of model.listEntities()) {
            const entityItem = this.findItemDetails(entity.key)
            if (entityItem) {
              this.visibleItems.push(entityItem)
            }
          }
        }
      }
    }

    addVisibleItems(this.root)
  }

  /**
   * Move focus to the previous item
   */
  protected moveFocusUp(): void {
    if (this.visibleItems.length === 0) return

    const currentIndex = this.focusedItem ? this.visibleItems.findIndex((item) => item.key === this.focusedItem) : -1

    const newIndex = currentIndex <= 0 ? this.visibleItems.length - 1 : currentIndex - 1
    this.focusedItem = this.visibleItems[newIndex].key
    this.requestUpdate()
  }

  /**
   * Move focus to the next item
   */
  protected moveFocusDown(): void {
    if (this.visibleItems.length === 0) return

    const currentIndex = this.focusedItem ? this.visibleItems.findIndex((item) => item.key === this.focusedItem) : -1

    const newIndex = currentIndex >= this.visibleItems.length - 1 ? 0 : currentIndex + 1
    this.focusedItem = this.visibleItems[newIndex].key
    this.requestUpdate()
  }

  /**
   * Handle left arrow key - collapse item or move to parent
   */
  protected handleArrowLeft(): void {
    if (!this.focusedItem) return

    const focusedDetails = this.findItemDetails(this.focusedItem)
    if (!focusedDetails) return

    // If item is expanded, collapse it
    if (
      (focusedDetails.kind === DomainNamespaceKind || focusedDetails.kind === DomainModelKind) &&
      !this.isCollapsed(this.focusedItem)
    ) {
      this.toggleCollapsed(this.focusedItem)
      return
    }

    // If item is already collapsed or is a leaf, move to parent
    if (focusedDetails.parent) {
      this.focusedItem = focusedDetails.parent
      this.requestUpdate()
    }
  }

  /**
   * Handle right arrow key - expand item or move to first child
   */
  protected handleArrowRight(): void {
    if (!this.focusedItem) return

    const focusedDetails = this.findItemDetails(this.focusedItem)
    if (!focusedDetails) return

    // If item can be expanded and is collapsed, expand it
    if (
      (focusedDetails.kind === DomainNamespaceKind || focusedDetails.kind === DomainModelKind) &&
      this.isCollapsed(this.focusedItem)
    ) {
      this.toggleCollapsed(this.focusedItem)
      this.updateVisibleItemsList()
      this.requestUpdate()
      return
    }

    // If item is already expanded, move to first child
    this.updateVisibleItemsList()
    const currentIndex = this.visibleItems.findIndex((item) => item.key === this.focusedItem)
    if (currentIndex >= 0 && currentIndex < this.visibleItems.length - 1) {
      const nextItem = this.visibleItems[currentIndex + 1]
      // Only move to next item if it's actually a child (has current item as parent)
      if (nextItem.parent === this.focusedItem) {
        this.focusedItem = nextItem.key
        this.requestUpdate()
      }
    }
  }

  /**
   * Handle space key - select/deselect focused item
   */
  protected handleSpaceKey(): void {
    if (!this.focusedItem) return

    const item = this.findItemDetails(this.focusedItem)
    if (item) {
      // Toggle selection of focused item
      if (this.isItemSelected(this.focusedItem)) {
        // Deselect if already selected
        this.removeFromSelection(this.focusedItem)
        if (this.selectedItems.size === 0) {
          this.selectionType = undefined
        }
      } else {
        // Select the focused item
        this.handleSingleSelection(item.key, item.kind, item.domain, item.parent)
      }
      this.requestUpdate()
    }
  }

  /**
   * Handle enter key - activate focused item (same as click)
   */
  protected handleEnterKey(): void {
    if (!this.focusedItem) return

    const item = this.findItemDetails(this.focusedItem)
    if (item) {
      this.handleSingleSelection(item.key, item.kind, item.domain, item.parent)
    }
  }

  /**
   * Move focus to first item
   */
  protected moveFocusToFirst(): void {
    if (this.visibleItems.length === 0) return
    this.focusedItem = this.visibleItems[0].key
  }

  /**
   * Move focus to last item
   */
  protected moveFocusToLast(): void {
    if (this.visibleItems.length === 0) return
    this.focusedItem = this.visibleItems[this.visibleItems.length - 1].key
  }

  /**
   * Handle item click for selection
   */
  protected handleItemClick(event: MouseEvent, key: string, kind: string, domain: string, parent?: string): void {
    event.preventDefault()
    event.stopPropagation()

    // Update focus when clicking
    this.focusedItem = key

    const isCtrlClick = event.ctrlKey || event.metaKey

    if (isCtrlClick) {
      this.handleMultiSelection(key, kind, domain, parent)
    } else {
      this.handleSingleSelection(key, kind, domain, parent)
    }
  }

  /**
   * Handle item receiving focus
   */
  protected handleItemFocus(key: string): void {
    this.focusedItem = key
  }

  protected focusOnCurrentItem(): void {
    if (!this.focusedItem) return

    const itemElement = this.shadowRoot?.querySelector(`[data-key="${this.focusedItem}"]`)
    if (itemElement instanceof HTMLElement) {
      itemElement.focus()
      itemElement.scrollIntoView({ block: 'nearest', inline: 'nearest' })
    }
  }

  /**
   * Handle single item selection
   */
  protected handleSingleSelection(key: string, kind: string, domain: string, parent?: string): void {
    // Clear previous selection
    this.selectedItems.clear()
    const selectionInfo: DomainSelectionInfo = { key, kind, domain, parent }
    this.selectedItems.add(selectionInfo)
    this.selectionType = kind
    this.#selected = key

    this.requestUpdate()
    this.dispatchSingleSelection(key, kind, domain, parent)
  }

  protected handleFieldSelection(key: string, kind: string, domain: string, parent: string): void {
    // Clear previous selection
    this.selectedItems.clear()
    const selectionInfo: DomainSelectionInfo = { key: parent, kind: DomainEntityKind, domain }
    this.selectedItems.add(selectionInfo)
    this.selectionType = DomainEntityKind
    this.#selected = parent

    this.requestUpdate()
    this.dispatchSingleSelection(key, kind, domain, parent)
  }

  /**
   * Handle multi-item selection with Ctrl+click
   */
  protected handleMultiSelection(key: string, kind: string, domain: string, parent?: string): void {
    // Check for mixed type selection
    if (this.selectionType && this.selectionType !== kind) {
      // Mixed selection not allowed - clear and start new selection
      this.handleSingleSelection(key, kind, domain, parent)
      return
    }

    const selectionInfo: DomainSelectionInfo = { key, kind, domain, parent }

    // Toggle selection
    if (this.isItemSelected(key)) {
      this.removeFromSelection(key)
      // If no items left, clear selection type
      if (this.selectedItems.size === 0) {
        this.selectionType = undefined
      }
    } else {
      this.selectedItems.add(selectionInfo)
      this.selectionType = kind
    }

    this.requestUpdate()
    this.dispatchMultiSelection()
  }

  /**
   * Dispatch single selection event
   */
  protected dispatchSingleSelection(key: string, kind: string, domain: string, parent?: string): void {
    const detail: DomainSelectionInfo = { key, kind, domain, parent }
    this.dispatchEvent(new CustomEvent('item-selected', { detail, bubbles: true }))
  }

  /**
   * Dispatch multi-selection event
   */
  protected dispatchMultiSelection(): void {
    const items = Array.from(this.selectedItems)

    const detail: DomainMultiSelectionEventDetail = {
      items,
      selectionType: this.selectionType || '',
    }

    this.dispatchEvent(new CustomEvent('items-selected', { detail, bubbles: true }))
  }

  /**
   * Dispatch selection cleared event
   */
  protected dispatchSelectionCleared(): void {
    this.dispatchEvent(new CustomEvent('selection-cleared', { bubbles: true }))
  }

  /**
   * Find item details by key
   */
  protected findItemDetails(key: string): DomainSelectionInfo | undefined {
    if (!this.root) return undefined

    // Check if it's the root domain
    if (key === this.root.key) {
      return { key, kind: DataDomainKind, domain: this.root.key }
    }

    // Search in namespaces
    const namespace = this.root.findNamespace(key)
    if (namespace) {
      const parent = namespace.getParentInstance()
      return {
        key,
        kind: DomainNamespaceKind,
        domain: this.root.key,
        parent: parent?.key !== this.root.key ? parent?.key : undefined,
      }
    }

    // Search in models
    const model = this.root.findModel(key)
    if (model) {
      const parent = model.getParentInstance()
      return {
        key,
        kind: DomainModelKind,
        domain: this.root.key,
        parent: parent?.key !== this.root.key ? parent?.key : undefined,
      }
    }

    // Search in entities
    const entity = this.root.findEntity(key)
    if (entity) {
      const model = entity.getParentInstance()
      return {
        key,
        kind: DomainEntityKind,
        domain: this.root.key,
        parent: model?.key,
      }
    }

    return undefined
  }

  /**
   * Handle drag start
   */
  protected handleDragStart(event: DragEvent, key: string): void {
    if (!event.dataTransfer) return

    let selectedItems = Array.from(this.selectedItems)

    if (!this.isItemSelected(key)) {
      // If the dragged item is not selected, select only this item
      selectedItems = []
      const item = this.getItemDetailsForKey(key)
      if (item) {
        selectedItems.push(item)
      }
    }

    // Set drag data
    this.draggedItems.clear()
    for (const selectedItem of selectedItems) {
      this.draggedItems.add(selectedItem)
    }

    const selectedKeys = selectedItems.map((item) => item.key)

    event.dataTransfer.setData('text/plain', selectedKeys.join(','))
    event.dataTransfer.setData(
      'application/json',
      JSON.stringify({
        items: selectedItems,
      })
    )
    event.dataTransfer.effectAllowed = 'move'

    this.requestUpdate()
  }

  /**
   * Handle drag over
   */
  protected handleDragOver(event: DragEvent, key: string, kind: string): void {
    if (!event.dataTransfer) return

    // Check if this is a valid drop target
    if (this.isValidDropTarget(key, kind)) {
      event.preventDefault()
      this.dragTarget = key
      this.requestUpdate()
    }
  }

  /**
   * Handle drag leave
   */
  protected handleDragLeave(): void {
    this.dragTarget = undefined
    this.requestUpdate()
  }

  /**
   * Handle drop
   */
  protected handleDrop(event: DragEvent, targetKey: string, targetKind: string): void {
    event.preventDefault()

    if (!event.dataTransfer) return

    try {
      const dragData = JSON.parse(event.dataTransfer.getData('application/json'))

      let items: DomainSelectionInfo[] = []

      if (dragData.items) {
        // New format with full item information
        items = dragData.items as DomainSelectionInfo[]
      } else if (Array.isArray(dragData)) {
        // Legacy format - just keys
        items = dragData.map((key: string) => this.findItemDetails(key)).filter(Boolean) as DomainSelectionInfo[]
      } else if (dragData.keys) {
        // Fallback - extract keys and find details
        items = dragData.keys.map((key: string) => this.findItemDetails(key)).filter(Boolean) as DomainSelectionInfo[]
      }

      if (items.length > 0 && this.isValidDropTarget(targetKey, targetKind)) {
        const dropDetail: DomainDragDropEventDetail = {
          items,
          target: {
            key: targetKey,
            kind: targetKind,
            parent: this.findItemDetails(targetKey)?.parent,
            domain: this.root ? this.root.key : '',
          },
        }

        this.dispatchEvent(new CustomEvent('items-dropped', { detail: dropDetail, bubbles: true }))
      }
    } catch {
      // Error handling drop - would normally log to error reporting service
    }

    // Clean up drag state
    this.draggedItems.clear()
    this.dragTarget = undefined
    this.requestUpdate()
  }

  /**
   * Handle drag end
   */
  protected handleDragEnd(): void {
    this.draggedItems.clear()
    this.requestUpdate()
  }

  /**
   * Handle click on external (read-only) items
   */
  protected handleExternalItemClick(e: MouseEvent, key: string, kind: string, domain: string, parent?: string): void {
    e.stopPropagation()

    if (e.ctrlKey || e.metaKey) {
      // Multi-selection
      this.handleExternalMultiSelection(key, kind, domain, parent)
    } else {
      // Single selection
      this.handleExternalSingleSelection(key, kind, domain, parent)
    }
  }

  /**
   * Handle single selection for external items
   */
  protected handleExternalSingleSelection(key: string, kind: string, domain: string, parent?: string): void {
    this.selectedItems.clear()
    const selectionInfo: DomainSelectionInfo = { key, kind, domain, parent }
    this.selectedItems.add(selectionInfo)
    this.focusedItem = key
    this.#selected = key

    const event: DomainSelectionInfo = {
      key,
      kind,
      domain,
      parent,
    }

    this.dispatchEvent(new CustomEvent('item-selected', { detail: event }))
    this.requestUpdate()
  }

  /**
   * Handle multi-selection for external items
   */
  protected handleExternalMultiSelection(key: string, kind: string, domain: string, parent?: string): void {
    // Check for mixed selection types
    if (this.selectedItems.size > 0) {
      // Get the kind of already selected items
      const firstSelectedItem = this.getFirstSelectedItemKind()
      if (firstSelectedItem && firstSelectedItem !== kind) {
        // Mixed selection not allowed, replace selection
        this.selectedItems.clear()
        const selectionInfo: DomainSelectionInfo = { key, kind, domain, parent }
        this.selectedItems.add(selectionInfo)
        this.focusedItem = key

        const event: DomainSelectionInfo = {
          key,
          kind,
          domain,
          parent,
        }

        this.dispatchEvent(new CustomEvent('item-selected', { detail: event }))
        this.requestUpdate()
        return
      }
    }

    const selectionInfo: DomainSelectionInfo = { key, kind, domain, parent }

    // Toggle selection
    if (this.isItemSelected(key)) {
      this.removeFromSelection(key)
    } else {
      this.selectedItems.add(selectionInfo)
    }
    this.focusedItem = key

    // Dispatch multi-selection event
    const items = Array.from(this.selectedItems)

    const event: DomainMultiSelectionEventDetail = {
      items,
      selectionType: kind,
    }
    this.dispatchEvent(new CustomEvent('items-selected', { detail: event }))
    this.requestUpdate()
  }

  /**
   * Get the kind of the first selected item
   */
  protected getFirstSelectedItemKind(): string | undefined {
    if (this.selectedItems.size === 0) return undefined

    const firstItem = this.selectedItems.values().next().value
    return firstItem?.kind
  }

  /**
   * Get item details for a given key
   */
  protected getItemDetailsForKey(key: string): DomainSelectionInfo | undefined {
    // First try the main domain
    if (this.root) {
      const result = this.findItemInDomain(key, this.root)
      if (result) return result
    }

    // Then try external domains
    if (this.root?.dependencies) {
      for (const [, externalDomain] of this.root.dependencies) {
        const result = this.findItemInDomain(key, externalDomain)
        if (result) return result
      }
    }

    return undefined
  }

  /**
   * Find item details within a specific domain
   */
  protected findItemInDomain(key: string, domain: DataDomain): DomainSelectionInfo | undefined {
    // Check if it's the domain itself
    if (domain.key === key) {
      return {
        key: domain.key,
        kind: domain.kind,
        domain: domain.key,
      }
    }

    const ns = domain.findNamespace(key)
    if (ns) {
      return {
        key: ns.key,
        kind: DomainNamespaceKind,
        domain: domain.key,
        parent: ns.getParentInstance()?.key,
      }
    }

    const model = domain.findModel(key)
    if (model) {
      return {
        key: model.key,
        kind: DomainModelKind,
        domain: domain.key,
        parent: model.getParentInstance()?.key,
      }
    }

    const entity = domain.findEntity(key)
    if (entity) {
      const modelParent = entity.getParentInstance()
      return {
        key: entity.key,
        kind: DomainEntityKind,
        domain: domain.key,
        parent: modelParent?.key,
      }
    }

    return undefined
  }

  /**
   * Find item in nested namespace structure
   */
  protected findItemInNamespace(
    key: string,
    namespace: DomainNamespace,
    domain: string
  ): DomainSelectionInfo | undefined {
    const field = namespace.fields.find((field) => field.key === key)
    if (!field) {
      return undefined
    }
    if (field.type === 'model') {
      return {
        key: field.key,
        kind: DomainModelKind,
        domain,
        parent: namespace.key,
      }
    }
    if (field.type === 'namespace') {
      return {
        key: field.key,
        kind: DomainNamespaceKind,
        domain,
        parent: namespace.key,
      }
    }
    return undefined
  }

  /**
   * Check if a target is valid for dropping the current selection
   */
  protected isValidDropTarget(targetKey: string, targetKind: string): boolean {
    if (!this.selectionType || this.isItemSelected(targetKey)) {
      return false
    }

    // Entities can only be dropped on models
    if (this.selectionType === DomainEntityKind) {
      return targetKind === DomainModelKind
    }

    // Models can be dropped on namespaces or root
    if (this.selectionType === DomainModelKind) {
      return targetKind === DomainNamespaceKind || targetKind === DataDomainKind
    }

    // Namespaces can be dropped on other namespaces or root
    if (this.selectionType === DomainNamespaceKind) {
      return targetKind === DomainNamespaceKind || targetKind === DataDomainKind
    }

    return false
  }

  protected handleSearch(event: CustomEvent<{ input: string }>): void {
    const query = event.detail.input
    this.searchDomain(query)
  }

  protected handleSearchClear(): void {
    this.clearSearch()
  }

  searchDomain(query: string): void {
    const domain = this.root
    if (!domain) return

    this.searchQuery = query.trim()

    if (!this.searchQuery) {
      this.clearSearch()
      return
    }

    const found = domain.search({
      query: this.searchQuery,
    })
    this.searchResults = found
  }

  protected handleSearchResultClick(event: MouseEvent, result: DomainSearchResult): void {
    event.preventDefault()
    event.stopPropagation()

    const { key, node } = result
    const kind = node.kind
    const domain = node.domain.key

    // For now, just clear search and navigate to the item
    // Selection logic can be implemented later
    this.clearSearch()

    // Find the parent for proper selection info
    let parent: string | undefined
    if (kind === DomainEntityKind) {
      const entity = node as DomainEntity
      const modelParent = entity.getParentInstance()
      parent = modelParent?.key
      this.ensureVisibility(key)
      this.handleSingleSelection(key, kind, domain, parent)
    } else if (kind === DomainModelKind || kind === DomainNamespaceKind) {
      const parentInstance = (node as DomainModel | DomainNamespace).getParentInstance()
      parent = parentInstance?.key !== domain ? parentInstance?.key : undefined
      this.ensureVisibility(key)
      this.handleSingleSelection(key, kind, domain, parent)
    } else if (kind === DomainAssociationKind) {
      const association = node as DomainAssociation
      const modelParent = association.getParentInstance()
      parent = modelParent?.key
      if (parent) {
        this.ensureVisibility(parent)
        this.handleFieldSelection(key, kind, domain, parent)
      }
    } else if (kind === DomainPropertyKind) {
      const property = node as DomainProperty
      const modelParent = property.getParentInstance()
      parent = modelParent?.key
      if (parent) {
        this.ensureVisibility(parent)
        this.handleFieldSelection(key, kind, domain, parent)
      }
    }
  }

  clearSearch(): void {
    this.searchQuery = undefined
    this.searchResults = []
  }

  handleSearchKeyDown(e: KeyboardEvent): void {
    e.stopPropagation()
    if (e.defaultPrevented) {
      return
    }
  }

  override render(): TemplateResult {
    const { root } = this
    if (!root) {
      return html`<div class="empty-state">No data domain available</div>`
    }
    const hasFields = root.hasFields()
    const hasDependencies = root.dependencies?.size > 0
    if (!hasFields && !hasDependencies) {
      return html`<div class="empty-state">No schemas to display</div>`
    }

    return html`
      <div class="hierarchy-tree">
        ${this.cutItems.size > 0
          ? this.renderCutItemsInfo()
          : this.selectedItems.size > 1
            ? this.renderMultiSelectionInfo()
            : this.renderDomainHeader(root)}
        ${this.searchQuery ? this.renderSearchResults() : ''}
        ${hasFields ? this.renderDomainTree(root) : this.renderEmptyDomain()} ${this.renderExternalDomains()}
      </div>
    `
  }

  protected renderEmptyDomain(): TemplateResult {
    return html`<div class="empty-state">No data to render.</div>`
  }

  protected renderCutItemsInfo(): TemplateResult {
    const count = this.cutItems.size
    const type = getDomainLabel(this.cutItemsType || 'items')

    return html`
      <div class="multi-selection-info cut-items">
        <span class="body-small"
          >${count} ${type}${count > 1 ? 's' : ''} cut - Select target and press Ctrl+V to paste</span
        >
        <ui-button size="xs" color="tonal" @click="${this.cancelCut}">Cancel</ui-button>
      </div>
    `
  }

  protected renderDomainTitle(root: DataDomain): TemplateResult {
    return html`<h2 class="domain-title title-small">${root.info.getLabel()}</h2>`
  }

  protected renderDomainHeader(root: DataDomain): TemplateResult {
    const hasCollapsibleItems =
      [...root.listNamespaces()].some((ns) => ns.hasFields()) || [...root.listModels()].some((m) => m.hasFields())

    return html`
      <div class="domain-header">
        <search-input
          class="search-input"
          placeholder="Search domain..."
          @search="${this.handleSearch}"
          @clear="${this.handleSearchClear}"
          @keydown="${this.handleSearchKeyDown}"
          value="${this.searchQuery || ''}"
        ></search-input>
        ${hasCollapsibleItems
          ? html`
              <div class="expand-controls">
                <ui-icon-button @click="${this.expandAll}" color="standard" shape="round" size="xs" title="Expand all"
                  ><ui-icon icon="add"></ui-icon
                ></ui-icon-button>
                <ui-icon-button
                  @click="${this.collapseAll}"
                  color="standard"
                  shape="round"
                  size="xs"
                  title="Collapse all"
                  ><ui-icon icon="remove"></ui-icon
                ></ui-icon-button>
              </div>
            `
          : ''}
      </div>
    `
  }

  protected renderSearchResults(): TemplateResult {
    if (!this.searchQuery || this.searchResults.length === 0) {
      return html`
        <div class="search-results-overlay">
          <div class="search-no-results">
            <span class="body-medium">No results found for "${this.searchQuery}"</span>
          </div>
        </div>
      `
    }

    return html`
      <div class="search-results-overlay">
        <div class="search-results-list">
          ${this.searchResults.map((result) => this.renderSearchResultItem(result))}
        </div>
      </div>
    `
  }

  protected getSearchIcon(item: DomainElement): IconType {
    switch (item.kind) {
      case DomainAssociationKind:
      case DomainPropertyKind:
        return getFieldIcon(item as DomainProperty | DomainAssociation)
      default:
        return getDomainIconType(item.kind)
    }
  }

  protected renderSearchResultItem(result: DomainSearchResult): TemplateResult {
    const { node, key, isForeign, matchedFields } = result
    const nodeInfo = node.info
    const icon = this.getSearchIcon(node)

    return html`
      <div
        class="search-result-item"
        data-key="${key}"
        data-kind="${node.kind}"
        data-domain="${node.domain.key}"
        @click="${(e: MouseEvent) => this.handleSearchResultClick(e, result)}"
      >
        <div class="search-result-content">
          <div class="search-result-main">
            <ui-icon class="search-result-icon" icon="${icon}"></ui-icon>
            <div class="search-result-info">
              <span class="search-result-name body-medium">${nodeInfo.getLabel()}</span>
              ${isForeign ? html`<span class="search-result-badge label-small">External</span>` : ''}
            </div>
          </div>
          ${nodeInfo.description
            ? html`<div class="search-result-description body-small">${nodeInfo.description}</div>`
            : ''}
          ${matchedFields.length > 0
            ? html`
                <div class="search-result-matches">
                  <span class="label-small">Matched: ${matchedFields.join(', ')}</span>
                </div>
              `
            : ''}
        </div>
      </div>
    `
  }

  protected renderMultiSelectionInfo(): TemplateResult {
    const count = this.selectedItems.size
    const type = getDomainLabel(this.selectionType || 'items')
    return html`
      <div class="multi-selection-info">
        <span class="body-small">${count} ${type}${count > 1 ? 's' : ''} selected</span>
        <ui-button size="xs" color="tonal" @click="${this.clearSelection}">Clear Selection</ui-button>
      </div>
    `
  }

  protected renderDomainTree(root: DataDomain | DomainNamespace): TemplateResult {
    const items: TemplateResult[] = []

    // Render namespaces
    for (const namespace of root.listNamespaces()) {
      items.push(this.renderNamespaceItem(namespace, root))
    }

    // Render models
    for (const model of root.listModels()) {
      items.push(this.renderModelItem(model, root))
    }

    return html`<div class="tree-items">${items}</div>`
  }

  /**
   * Render external data domains from dependencies
   */
  protected renderExternalDomains(): TemplateResult {
    if (!this.root?.dependencies?.size) {
      return html``
    }

    const externalDomains: TemplateResult[] = []

    for (const [, externalDomain] of this.root.dependencies) {
      externalDomains.push(this.renderExternalDomain(externalDomain))
    }

    return html`
      <div class="external-domains">
        <ui-divider></ui-divider>
        <div class="external-domains-header">
          <h3 class="external-domains-title title-medium">External Dependencies</h3>
        </div>
        ${externalDomains}
      </div>
    `
  }

  /**
   * Render a single external domain
   */
  protected renderExternalDomain(domain: DataDomain): TemplateResult {
    return html`
      <div class="external-domain">
        <div class="external-domain-header">
          <h4 class="external-domain-title title-small">${domain.info.getLabel()}</h4>
          <span class="external-domain-badge label-medium">Read-only</span>
        </div>
        ${this.renderExternalDomainTree(domain)}
      </div>
    `
  }

  /**
   * Render external domain tree (read-only)
   */
  protected renderExternalDomainTree(domain: DataDomain | DomainNamespace): TemplateResult {
    const items: TemplateResult[] = []

    // Render namespaces
    for (const namespace of domain.listNamespaces()) {
      items.push(this.renderExternalNamespaceItem(namespace, domain))
    }

    // Render models
    for (const model of domain.listModels()) {
      items.push(this.renderExternalModelItem(model, domain))
    }

    return html`<div class="external-tree-items">${items}</div>`
  }

  protected renderNamespaceItem(namespace: DomainNamespace, parent: DataDomain | DomainNamespace): TemplateResult {
    const hasChildren = namespace.hasFields()
    const isSelected = this.isItemSelected(namespace.key)
    const isMultiSelected = isSelected && this.selectedItems.size > 1
    const isDragSource = this.isItemDragged(namespace.key)
    const isDragTarget = this.dragTarget === namespace.key
    const isCollapsed = this.isCollapsed(namespace.key)
    const isCut = this.isItemCut(namespace.key)
    const isFocused = this.focusedItem === namespace.key

    return html`
      <div class="tree-item" role="treeitem">
        <div
          class="item-content ${classMap({
            'selected': isSelected && !isMultiSelected,
            'multi-selected': isMultiSelected,
            'drag-source': isDragSource,
            'drag-over': isDragTarget,
            'cut': isCut,
            'focused': isFocused,
            'highlighted': this.highlighted === namespace.key,
          })}"
          tabindex="${isFocused ? '0' : '-1'}"
          draggable="true"
          data-key="${namespace.key}"
          data-kind="${namespace.kind}"
          data-domain="${namespace.domain.key}"
          data-parent="${parent.key !== namespace.domain.key ? parent.key : ''}"
          @click="${(e: MouseEvent) =>
            this.handleItemClick(
              e,
              namespace.key,
              namespace.kind,
              namespace.domain.key,
              parent.key !== namespace.domain.key ? parent.key : undefined
            )}"
          @focus="${() => this.handleItemFocus(namespace.key)}"
          @dragstart="${(e: DragEvent) => this.handleDragStart(e, namespace.key)}"
          @dragover="${(e: DragEvent) => this.handleDragOver(e, namespace.key, namespace.kind)}"
          @dragleave="${() => this.handleDragLeave()}"
          @drop="${(e: DragEvent) => this.handleDrop(e, namespace.key, namespace.kind)}"
          @dragend="${() => this.handleDragEnd()}"
        >
          ${hasChildren
            ? html`
                <ui-icon
                  class="expand-icon ${isCollapsed ? '' : 'expanded'}"
                  icon="chevronRight"
                  @click="${(e: MouseEvent) => this.toggleCollapsed(namespace.key, e)}"
                ></ui-icon>
              `
            : html`<span class="expand-placeholder"></span>`}
          <ui-icon class="item-icon" icon="${getDomainIconType(namespace.kind)}"></ui-icon>
          <span class="item-label">${namespace.info.getLabel()}</span>
        </div>
        ${hasChildren && !isCollapsed ? html` <div class="children">${this.renderDomainTree(namespace)}</div>` : ''}
      </div>
    `
  }

  protected renderModelItem(model: DomainModel, parent: DataDomain | DomainNamespace): TemplateResult {
    const hasEntities = model.hasFields()
    const isSelected = this.isItemSelected(model.key)
    const isMultiSelected = this.selectedItems.size > 1 && isSelected
    const isDragSource = this.isItemDragged(model.key)
    const isDragTarget = this.dragTarget === model.key
    const isCollapsed = this.isCollapsed(model.key)
    const isCut = this.isItemCut(model.key)
    const isFocused = this.focusedItem === model.key

    return html`
      <div class="tree-item" role="treeitem">
        <div
          class="item-content ${classMap({
            'selected': isSelected && !isMultiSelected,
            'multi-selected': isMultiSelected,
            'drag-source': isDragSource,
            'drag-over': isDragTarget,
            'cut': isCut,
            'focused': isFocused,
            'highlighted': this.highlighted === model.key,
          })}"
          tabindex="${isFocused ? '0' : '-1'}"
          draggable="true"
          data-key="${model.key}"
          data-kind="${model.kind}"
          data-domain="${model.domain.key}"
          data-parent="${parent.key !== model.domain.key ? parent.key : ''}"
          @click="${(e: MouseEvent) =>
            this.handleItemClick(
              e,
              model.key,
              model.kind,
              model.domain.key,
              parent.key !== model.domain.key ? parent.key : undefined
            )}"
          @focus="${() => this.handleItemFocus(model.key)}"
          @dragstart="${(e: DragEvent) => this.handleDragStart(e, model.key)}"
          @dragover="${(e: DragEvent) => this.handleDragOver(e, model.key, model.kind)}"
          @dragleave="${() => this.handleDragLeave()}"
          @drop="${(e: DragEvent) => this.handleDrop(e, model.key, model.kind)}"
          @dragend="${() => this.handleDragEnd()}"
        >
          ${hasEntities
            ? html`
                <ui-icon
                  class="expand-icon ${isCollapsed ? '' : 'expanded'}"
                  icon="chevronRight"
                  @click="${(e: MouseEvent) => this.toggleCollapsed(model.key, e)}"
                ></ui-icon>
              `
            : html`<span class="expand-placeholder"></span>`}
          <ui-icon class="item-icon" icon="${getDomainIconType(model.kind)}"></ui-icon>
          <span class="item-label">${model.info.getLabel()}</span>
        </div>
        ${hasEntities && !isCollapsed ? html` <div class="children">${this.renderEntityList(model)}</div>` : ''}
      </div>
    `
  }

  protected renderEntityList(model: DomainModel): TemplateResult {
    const entities: TemplateResult[] = []

    for (const entity of model.listEntities()) {
      entities.push(this.renderEntityItem(entity, model))
    }

    return html`<div class="entity-items">${entities}</div>`
  }

  protected renderEntityItem(entity: DomainEntity, parent: DomainModel): TemplateResult {
    const isSelected = this.isItemSelected(entity.key)
    const isMultiSelected = isSelected && this.selectedItems.size > 1
    const isDragSource = this.isItemDragged(entity.key)
    const isCut = this.isItemCut(entity.key)
    const isFocused = this.focusedItem === entity.key

    return html`
      <div class="tree-item" role="treeitem">
        <div
          class="item-content ${classMap({
            'selected': isSelected && !isMultiSelected,
            'multi-selected': isMultiSelected,
            'drag-source': isDragSource,
            'cut': isCut,
            'focused': isFocused,
            'highlighted': this.highlighted === entity.key,
          })}"
          tabindex="${isFocused ? '0' : '-1'}"
          draggable="true"
          data-key="${entity.key}"
          data-kind="${entity.kind}"
          data-domain="${entity.domain.key}"
          data-parent="${parent.key !== entity.domain.key ? parent.key : ''}"
          @click="${(e: MouseEvent) => this.handleItemClick(e, entity.key, entity.kind, entity.domain.key, parent.key)}"
          @focus="${() => this.handleItemFocus(entity.key)}"
          @dragstart="${(e: DragEvent) => this.handleDragStart(e, entity.key)}"
          @dragend="${() => this.handleDragEnd()}"
        >
          <span class="expand-placeholder"></span>
          <ui-icon class="item-icon" icon="${getDomainIconType(entity.kind)}"></ui-icon>
          <span class="item-label">${entity.info.getLabel()}</span>
        </div>
      </div>
    `
  }

  /**
   * Render external namespace item (read-only)
   */
  protected renderExternalNamespaceItem(
    namespace: DomainNamespace,
    parent: DataDomain | DomainNamespace
  ): TemplateResult {
    const hasChildren = namespace.hasFields()
    const isSelected = this.isItemSelected(namespace.key)
    const isMultiSelected = this.selectedItems.size > 1 && isSelected
    const isCollapsed = this.isCollapsed(namespace.key)
    const isFocused = this.focusedItem === namespace.key

    return html`
      <div class="tree-item external-item" role="treeitem">
        <div
          class="item-content external-item-content ${classMap({
            'selected': isSelected && !isMultiSelected,
            'multi-selected': isMultiSelected,
            'focused': isFocused,
            'highlighted': this.highlighted === namespace.key,
          })}"
          tabindex="${isFocused ? '0' : '-1'}"
          draggable="true"
          data-key="${namespace.key}"
          data-kind="${namespace.kind}"
          data-domain="${namespace.domain.key}"
          data-parent="${parent.key !== namespace.domain.key ? parent.key : ''}"
          @click="${(e: MouseEvent) =>
            this.handleExternalItemClick(
              e,
              namespace.key,
              namespace.kind,
              namespace.domain.key,
              parent.key !== namespace.domain.key ? parent.key : undefined
            )}"
          @focus="${() => this.handleItemFocus(namespace.key)}"
          @dragstart="${(e: DragEvent) => this.handleDragStart(e, namespace.key)}"
          @dragend="${() => this.handleDragEnd()}"
        >
          ${hasChildren
            ? html`
                <ui-icon
                  class="expand-icon ${isCollapsed ? '' : 'expanded'}"
                  icon="chevronRight"
                  @click="${(e: MouseEvent) => this.toggleCollapsed(namespace.key, e)}"
                ></ui-icon>
              `
            : html`<span class="expand-placeholder"></span>`}
          <ui-icon class="item-icon" icon="${getDomainIconType(namespace.kind)}"></ui-icon>
          <span class="item-label">${namespace.info.getLabel()}</span>
        </div>
        ${hasChildren && !isCollapsed
          ? html` <div class="children">${this.renderExternalDomainTree(namespace)}</div>`
          : ''}
      </div>
    `
  }

  /**
   * Render external model item (read-only)
   */
  protected renderExternalModelItem(model: DomainModel, parent: DataDomain | DomainNamespace): TemplateResult {
    const hasEntities = model.hasFields()
    const isSelected = this.isItemSelected(model.key)
    const isMultiSelected = this.selectedItems.size > 1 && isSelected
    const isCollapsed = this.isCollapsed(model.key)
    const isFocused = this.focusedItem === model.key

    return html`
      <div class="tree-item external-item" role="treeitem">
        <div
          class="item-content external-item-content ${classMap({
            'selected': isSelected && !isMultiSelected,
            'multi-selected': isMultiSelected,
            'focused': isFocused,
            'highlighted': this.highlighted === model.key,
          })}"
          tabindex="${isFocused ? '0' : '-1'}"
          draggable="true"
          data-key="${model.key}"
          data-kind="${model.kind}"
          data-domain="${model.domain.key}"
          data-parent="${parent.key !== model.domain.key ? parent.key : ''}"
          @click="${(e: MouseEvent) =>
            this.handleExternalItemClick(
              e,
              model.key,
              model.kind,
              model.domain.key,
              parent.key !== model.domain.key ? parent.key : undefined
            )}"
          @focus="${() => this.handleItemFocus(model.key)}"
          @dragstart="${(e: DragEvent) => this.handleDragStart(e, model.key)}"
          @dragend="${() => this.handleDragEnd()}"
        >
          ${hasEntities
            ? html`
                <ui-icon
                  class="expand-icon ${isCollapsed ? '' : 'expanded'}"
                  icon="chevronRight"
                  @click="${(e: MouseEvent) => this.toggleCollapsed(model.key, e)}"
                ></ui-icon>
              `
            : html`<span class="expand-placeholder"></span>`}
          <ui-icon class="item-icon" icon="${getDomainIconType(model.kind)}"></ui-icon>
          <span class="item-label">${model.info.getLabel()}</span>
        </div>
        ${hasEntities && !isCollapsed ? html` <div class="children">${this.renderExternalEntityList(model)}</div>` : ''}
      </div>
    `
  }

  /**
   * Render external entity list
   */
  protected renderExternalEntityList(model: DomainModel): TemplateResult {
    const entities: TemplateResult[] = []

    for (const entity of model.listEntities()) {
      entities.push(this.renderExternalEntityItem(entity, model))
    }

    return html`<div class="external-entity-items">${entities}</div>`
  }

  /**
   * Render external entity item (read-only)
   */
  protected renderExternalEntityItem(entity: DomainEntity, parent: DomainModel): TemplateResult {
    const isSelected = this.isItemSelected(entity.key)
    const isMultiSelected = this.selectedItems.size > 1 && isSelected
    const isFocused = this.focusedItem === entity.key

    return html`
      <div class="tree-item external-item" role="treeitem">
        <div
          class="item-content external-item-content ${classMap({
            'selected': isSelected && !isMultiSelected,
            'multi-selected': isMultiSelected,
            'focused': isFocused,
            'highlighted': this.highlighted === entity.key,
          })}"
          tabindex="${isFocused ? '0' : '-1'}"
          draggable="true"
          data-key="${entity.key}"
          data-kind="${entity.kind}"
          data-domain="${entity.domain.key}"
          data-parent="${parent.key !== entity.domain.key ? parent.key : ''}"
          @click="${(e: MouseEvent) =>
            this.handleExternalItemClick(e, entity.key, entity.kind, entity.domain.key, parent.key)}"
          @focus="${() => this.handleItemFocus(entity.key)}"
          @dragstart="${(e: DragEvent) => this.handleDragStart(e, entity.key)}"
          @dragend="${() => this.handleDragEnd()}"
        >
          <span class="expand-placeholder"></span>
          <ui-icon class="item-icon" icon="${getDomainIconType(entity.kind)}"></ui-icon>
          <span class="item-label">${entity.info.getLabel()}</span>
        </div>
      </div>
    `
  }
}
