import { Plus } from '@phosphor-icons/react'
import { useMemo, useCallback, useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import type { VaultEntry, WorkspaceIdentity } from '../types'
import type { FrontmatterValue } from './Inspector'
import type { ParsedFrontmatter } from '../utils/frontmatter'
import { usePropertyPanelState } from '../hooks/usePropertyPanelState'
import { getEffectiveDisplayMode, detectPropertyType, DISPLAY_MODE_ICONS } from '../utils/propertyTypes'
import { SmartPropertyValueCell, DisplayModeSelector } from './PropertyValueCells'
import { TypeSelector } from './TypeSelector'
import { FormatSelector } from './FormatSelector'
import { WorkspaceSelector } from './WorkspaceSelector'
import { AddPropertyForm } from './AddPropertyForm'
import type { PropertyDisplayMode } from '../utils/propertyTypes'
import { FOCUS_NOTE_ICON_PROPERTY_EVENT } from './noteIconPropertyEvents'
import {
  PROPERTY_PANEL_GRID_STYLE,
  PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME,
  PROPERTY_PANEL_LABEL_CLASS_NAME,
  PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME,
  PROPERTY_PANEL_PLACEHOLDER_LABEL_CLASS_NAME,
  PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME,
  PROPERTY_PANEL_ROW_STYLE,
} from './propertyPanelLayout'
import { humanizePropertyKey } from '../utils/propertyLabels'
import { translate, type AppLocale } from '../lib/i18n'
import { canonicalSystemMetadataKey, hasSystemMetadataKey } from '../utils/systemMetadata'
import { noteFormatFromFrontmatter, type NoteFormat } from '../utils/noteFormat'

// eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component
export function containsWikilinks(value: FrontmatterValue): boolean {
  if (typeof value === 'string') return /^\[\[.*\]\]$/.test(value)
  if (Array.isArray(value)) return value.some(v => typeof v === 'string' && /^\[\[.*\]\]$/.test(v))
  return false
}

const PROPERTY_ROW_CLASS_NAME = 'group/prop grid min-h-7 min-w-0 grid-cols-2 items-center gap-2 rounded px-1.5 outline-none transition-colors hover:bg-muted focus:bg-muted focus:ring-1 focus:ring-primary'

function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultStatuses, vaultTags, locale, onStartEdit, onSave, onSaveList, onUpdate, onDelete, onDisplayModeChange }: {
  propKey: string; value: FrontmatterValue; editingKey: string | null
  displayMode: PropertyDisplayMode; autoMode: PropertyDisplayMode
  vaultStatuses: string[]; vaultTags: string[]
  onStartEdit: (key: string | null) => void; onSave: (key: string, value: string) => void
  onSaveList: (key: string, items: string[]) => void
  onUpdate?: (key: string, value: FrontmatterValue) => void; onDelete?: (key: string) => void
  onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void
  locale: AppLocale
}) {
  return (
    <div className={PROPERTY_ROW_CLASS_NAME} style={PROPERTY_PANEL_ROW_STYLE} data-testid="editable-property">
      <span className={PROPERTY_PANEL_LABEL_CLASS_NAME}>
        <DisplayModeSelector propKey={propKey} currentMode={displayMode} autoMode={autoMode} onSelect={onDisplayModeChange} />
        <span className="min-w-0 flex-1 truncate">{humanizePropertyKey(propKey)}</span>
        {onDelete && (
          <button type="button" className="border-none bg-transparent p-0 text-sm leading-none text-muted-foreground opacity-0 transition-all hover:text-destructive group-hover/prop:opacity-100" onClick={() => onDelete(propKey)} title={translate(locale, 'inspector.properties.deleteProperty')}>&times;</button>
        )}
      </span>
      <div className="min-w-0">
        <SmartPropertyValueCell propKey={propKey} value={value} displayMode={displayMode} isEditing={editingKey === propKey} locale={locale} vaultStatuses={vaultStatuses} vaultTags={vaultTags} onStartEdit={onStartEdit} onSave={onSave} onSaveList={onSaveList} onUpdate={onUpdate} />
      </div>
    </div>
  )
}

function AddPropertyButton({ locale, onClick, disabled }: { locale: AppLocale; onClick: () => void; disabled: boolean }) {
  return (
    <Button
      type="button"
      variant="ghost"
      size="sm"
      className={PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME}
      style={PROPERTY_PANEL_ROW_STYLE}
      onClick={onClick}
      disabled={disabled}
      data-testid="add-property-row"
    >
      <span className={PROPERTY_PANEL_PLACEHOLDER_LABEL_CLASS_NAME}>
        <span
          className={PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME}
          data-testid="add-property-icon-slot"
        >
          <Plus className="size-3.5" aria-hidden="true" />
        </span>
        <span className="min-w-0 truncate">{translate(locale, 'inspector.properties.addProperty')}</span>
      </span>
      <span aria-hidden="true" className={PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME} />
    </Button>
  )
}

const SUGGESTED_PROPERTIES = [
  { key: 'Status', label: 'Status' },
  { key: 'date', label: 'Date' },
  { key: 'URL', label: 'URL' },
  { key: 'icon', label: 'Icon' },
] as const

const SUGGESTED_PROPERTY_MODES: Record<string, PropertyDisplayMode> = {
  Status: 'status',
  date: 'date',
  URL: 'url',
  icon: 'text',
}

function getSuggestedDisplayMode(key: string): PropertyDisplayMode {
  return (Reflect.get(SUGGESTED_PROPERTY_MODES, key) as PropertyDisplayMode | undefined) ?? 'text'
}

function resolveMissingTypeName(entryIsA: string | null | undefined, availableTypes: string[]): string | null {
  const trimmed = entryIsA?.trim()
  if (!trimmed) return null
  return availableTypes.includes(trimmed) ? null : trimmed
}

function SuggestedPropertySlot({ label, displayMode, onAdd }: {
  label: string
  displayMode: PropertyDisplayMode
  onAdd: () => void
}) {
  const SuggestedIcon = Reflect.get(DISPLAY_MODE_ICONS, displayMode) as typeof Plus

  return (
    <Button
      type="button"
      variant="ghost"
      size="sm"
      className={PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME}
      style={PROPERTY_PANEL_ROW_STYLE}
      onClick={onAdd}
      data-testid="suggested-property"
    >
      <span className={PROPERTY_PANEL_PLACEHOLDER_LABEL_CLASS_NAME}>
        <span
          className={PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME}
          data-testid="suggested-property-icon-slot"
        >
          <SuggestedIcon
            className="size-3.5 shrink-0 text-muted-foreground/40"
            data-testid={`suggested-property-icon-${displayMode}`}
          />
        </span>
        <span className="min-w-0 truncate">{label}</span>
      </span>
      <span className={PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME}>{'\u2014'}</span>
    </Button>
  )
}

function TypeDerivedPropertySlot({
  propKey,
  editingKey,
  displayMode,
  autoMode,
  vaultStatuses,
  vaultTags,
  onStartEdit,
  onSave,
  onSaveList,
  onUpdate,
  onDisplayModeChange,
  locale,
}: {
  propKey: string
  editingKey: string | null
  displayMode: PropertyDisplayMode
  autoMode: PropertyDisplayMode
  vaultStatuses: string[]
  vaultTags: string[]
  onStartEdit: (key: string | null) => void
  onSave: (key: string, value: string) => void
  onSaveList: (key: string, items: string[]) => void
  onUpdate?: (key: string, value: FrontmatterValue) => void
  onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void
  locale: AppLocale
}) {
  if (editingKey === propKey) {
    return (
      <PropertyRow
        propKey={propKey}
        value=""
        editingKey={editingKey}
        displayMode={displayMode}
        autoMode={autoMode}
        vaultStatuses={vaultStatuses}
        vaultTags={vaultTags}
        onStartEdit={onStartEdit}
        onSave={onSave}
        onSaveList={onSaveList}
        onUpdate={onUpdate}
        onDisplayModeChange={onDisplayModeChange}
        locale={locale}
      />
    )
  }

  const PlaceholderIcon = Reflect.get(DISPLAY_MODE_ICONS, displayMode) as typeof Plus

  return (
    <Button
      type="button"
      variant="ghost"
      size="sm"
      className={PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME}
      style={PROPERTY_PANEL_ROW_STYLE}
      onClick={() => onStartEdit(propKey)}
      disabled={!onUpdate}
      data-testid="type-derived-property"
    >
      <span className={PROPERTY_PANEL_PLACEHOLDER_LABEL_CLASS_NAME}>
        <span
          className={PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME}
          data-testid="type-derived-property-icon-slot"
        >
          <PlaceholderIcon
            className="size-3.5 shrink-0 text-muted-foreground/40"
            data-testid={`type-derived-property-icon-${displayMode}`}
          />
        </span>
        <span className="min-w-0 truncate text-muted-foreground/40">{humanizePropertyKey(propKey)}</span>
      </span>
      <span className={PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME}>{'\u2014'}</span>
    </Button>
  )
}

function getExistingPropertyKeys(propertyEntries: [string, FrontmatterValue][], frontmatter: ParsedFrontmatter): Set<string> {
  const keys = new Set(propertyEntries.map(([key]) => key.toLowerCase()))
  for (const key of Object.keys(frontmatter)) keys.add(key.toLowerCase())
  if (hasSystemMetadataKey(keys, '_icon')) keys.add('icon')
  return keys
}

function getMissingSuggestedProperties(canAddProperty: boolean, existingKeys: Set<string>, pendingSuggestedKey: string | null) {
  if (!canAddProperty) return []

  return SUGGESTED_PROPERTIES.filter(
    ({ key }) => !existingKeys.has(key.toLowerCase()) && key !== pendingSuggestedKey,
  )
}

function useFocusNoteIconProperty({
  onAddProperty,
  setEditingKey,
  setPendingSuggestedKey,
}: {
  onAddProperty?: (key: string, value: FrontmatterValue) => void
  setEditingKey: (key: string | null) => void
  setPendingSuggestedKey: (key: string | null) => void
}) {
  useEffect(() => {
    const handleFocusNoteIcon = () => {
      if (!onAddProperty) return
      setPendingSuggestedKey('icon')
      setEditingKey('icon')
    }

    window.addEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon)
    return () => window.removeEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon)
  }, [onAddProperty, setEditingKey, setPendingSuggestedKey])
}

function useSuggestedPropertyActions({
  onAddProperty,
  setEditingKey,
  setPendingSuggestedKey,
}: {
  onAddProperty?: (key: string, value: FrontmatterValue) => void
  setEditingKey: (key: string | null) => void
  setPendingSuggestedKey: (key: string | null) => void
}) {
  const handleSuggestedAdd = useCallback((key: string) => {
    if (!onAddProperty) return
    setPendingSuggestedKey(key)
    setEditingKey(key)
  }, [onAddProperty, setEditingKey, setPendingSuggestedKey])

  const handlePendingSuggestedEdit = useCallback((key: string | null) => {
    setEditingKey(key)
    if (key === null) setPendingSuggestedKey(null)
  }, [setEditingKey, setPendingSuggestedKey])

  const handleSaveSuggestedValue = useCallback((key: string, newValue: string) => {
    setEditingKey(null)
    setPendingSuggestedKey(null)
    if (!onAddProperty) {
      return
    }
    const trimmed = newValue.trim()
    if (!trimmed) {
      return
    }
    onAddProperty(key === 'icon' ? canonicalSystemMetadataKey(key) : key, trimmed)
  }, [onAddProperty, setEditingKey, setPendingSuggestedKey])

  return {
    handlePendingSuggestedEdit,
    handleSaveSuggestedValue,
    handleSuggestedAdd,
  }
}

function PropertyEntryRows({
  source,
  entries,
  editingKey,
  displayOverrides,
  vaultStatuses,
  vaultTagsByKey,
  locale,
  onStartEdit,
  onSave,
  onSaveList,
  onUpdate,
  onDelete,
  onDisplayModeChange,
}: {
  source: 'frontmatter' | 'type-derived'
  entries: [string, FrontmatterValue][]
  editingKey: string | null
  displayOverrides: Record<string, PropertyDisplayMode>
  vaultStatuses: string[]
  vaultTagsByKey: Record<string, string[]>
  locale: AppLocale
  onStartEdit: (key: string | null) => void
  onSave: (key: string, value: string) => void
  onSaveList: (key: string, items: string[]) => void
  onUpdate?: (key: string, value: FrontmatterValue) => void
  onDelete?: (key: string) => void
  onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void
}) {
  return (
    <>
      {entries.map(([key, value]) => (
        source === 'type-derived' ? (
          <TypeDerivedPropertySlot
            key={`type-derived:${key}`}
            propKey={key}
            editingKey={editingKey}
            displayMode={getEffectiveDisplayMode(key, value, displayOverrides)}
            autoMode={detectPropertyType(key, value)}
            vaultStatuses={vaultStatuses}
            vaultTags={(Reflect.get(vaultTagsByKey, key) as string[] | undefined) ?? []}
            onStartEdit={onStartEdit}
            onSave={onSave}
            onSaveList={onSaveList}
            onUpdate={onUpdate}
            onDisplayModeChange={onDisplayModeChange}
            locale={locale}
          />
        ) : (
          <PropertyRow
            key={key} propKey={key} value={value}
            editingKey={editingKey} displayMode={getEffectiveDisplayMode(key, value, displayOverrides)} autoMode={detectPropertyType(key, value)}
            vaultStatuses={vaultStatuses}
            vaultTags={(Reflect.get(vaultTagsByKey, key) as string[] | undefined) ?? []}
            onStartEdit={onStartEdit} onSave={onSave}
            onSaveList={onSaveList} onUpdate={onUpdate}
            onDelete={onDelete}
            onDisplayModeChange={onDisplayModeChange}
            locale={locale}
          />
        )
      ))}
    </>
  )
}

function PendingSuggestedPropertyRow({
  pendingSuggestedKey,
  editingKey,
  vaultStatuses,
  vaultTagsByKey,
  locale,
  onStartEdit,
  onSave,
  onSaveList,
  onDisplayModeChange,
}: {
  pendingSuggestedKey: string | null
  editingKey: string | null
  vaultStatuses: string[]
  vaultTagsByKey: Record<string, string[]>
  locale: AppLocale
  onStartEdit: (key: string | null) => void
  onSave: (key: string, value: string) => void
  onSaveList: (key: string, items: string[]) => void
  onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void
}) {
  if (!pendingSuggestedKey || editingKey !== pendingSuggestedKey) {
    return null
  }

  return (
    <PropertyRow
      key={`pending:${pendingSuggestedKey}`}
      propKey={pendingSuggestedKey}
      value=""
      editingKey={editingKey}
      displayMode={getSuggestedDisplayMode(pendingSuggestedKey)}
      autoMode={getSuggestedDisplayMode(pendingSuggestedKey)}
      vaultStatuses={vaultStatuses}
      vaultTags={(Reflect.get(vaultTagsByKey, pendingSuggestedKey) as string[] | undefined) ?? []}
      onStartEdit={onStartEdit}
      onSave={onSave}
      onSaveList={onSaveList}
      onUpdate={undefined}
      onDelete={undefined}
      onDisplayModeChange={onDisplayModeChange}
      locale={locale}
    />
  )
}

function SuggestedPropertyRows({
  properties,
  onAdd,
}: {
  properties: Array<{ key: string; label: string }>
  onAdd: (key: string) => void
}) {
  return (
    <>
      {properties.map(({ key, label }) => (
        <SuggestedPropertySlot
          key={key}
          label={label}
          displayMode={getSuggestedDisplayMode(key)}
          onAdd={() => onAdd(key)}
        />
      ))}
    </>
  )
}

type PropertyPanelState = ReturnType<typeof usePropertyPanelState>

function MetadataSelectors({
  availableTypes,
  customColorKey,
  entry,
  format,
  locale,
  missingTypeName,
  onChangeWorkspace,
  onCreateMissingType,
  onDeleteProperty,
  onNavigate,
  onUpdateProperty,
  typeColorKeys,
  typeIconKeys,
  workspaces,
}: {
  availableTypes: string[]
  customColorKey?: string | null
  entry: VaultEntry
  format: NoteFormat
  locale: AppLocale
  missingTypeName?: string | null
  onChangeWorkspace?: (workspace: WorkspaceIdentity) => void | Promise<void>
  onCreateMissingType?: (typeName: string) => boolean | void | Promise<boolean | void>
  onDeleteProperty?: (key: string) => void
  onNavigate?: (target: string) => void
  onUpdateProperty?: (key: string, value: FrontmatterValue) => void
  typeColorKeys: Record<string, string | null>
  typeIconKeys: Record<string, string | null>
  workspaces?: WorkspaceIdentity[]
}) {
  return (
    <>
      <WorkspaceSelector
        currentWorkspace={entry.workspace}
        workspaces={workspaces ?? []}
        onChangeWorkspace={onChangeWorkspace}
        locale={locale}
      />
      <TypeSelector
        isA={entry.isA}
        customColorKey={customColorKey}
        availableTypes={availableTypes}
        typeColorKeys={typeColorKeys}
        typeIconKeys={typeIconKeys}
        onUpdateProperty={onUpdateProperty}
        onNavigate={onNavigate}
        missingTypeName={missingTypeName}
        onCreateMissingType={onCreateMissingType}
        locale={locale}
      />
      <FormatSelector
        format={format}
        locale={locale}
        onDeleteProperty={onDeleteProperty}
        onUpdateProperty={onUpdateProperty}
      />
    </>
  )
}

function DynamicPropertiesPanelContent({
  entry,
  propertyState,
  pendingSuggestedKey,
  missingSuggested,
  missingTypeName,
  format,
  locale,
  workspaces,
  onUpdateProperty,
  onDeleteProperty,
  onAddProperty,
  onNavigate,
  onCreateMissingType,
  onChangeWorkspace,
  onStartPendingSuggestedEdit,
  onSaveSuggestedValue,
  onAddSuggestedProperty,
}: {
  entry: VaultEntry
  propertyState: PropertyPanelState
  pendingSuggestedKey: string | null
  missingSuggested: Array<{ key: string; label: string }>
  missingTypeName: string | null
  format: NoteFormat
  locale: AppLocale
  workspaces?: WorkspaceIdentity[]
  onUpdateProperty?: (key: string, value: FrontmatterValue) => void
  onDeleteProperty?: (key: string) => void
  onAddProperty?: (key: string, value: FrontmatterValue) => void
  onNavigate?: (target: string) => void
  onCreateMissingType?: (typeName: string) => boolean | void | Promise<boolean | void>
  onChangeWorkspace?: (workspace: WorkspaceIdentity) => void | Promise<void>
  onStartPendingSuggestedEdit: (key: string | null) => void
  onSaveSuggestedValue: (key: string, newValue: string) => void
  onAddSuggestedProperty: (key: string) => void
}) {
  const {
    editingKey, setEditingKey, showAddDialog, setShowAddDialog, displayOverrides,
    availableTypes, customColorKey, typeColorKeys, typeIconKeys, vaultStatuses, vaultTagsByKey, propertyEntries,
    typeDerivedPropertyEntries, handleSaveValue, handleSaveTypeDerivedValue, handleSaveList, handleAdd, handleDisplayModeChange,
  } = propertyState

  return (
    <div className="flex flex-col gap-3">
      <div className="grid min-w-0 gap-x-2 gap-y-1.5" style={PROPERTY_PANEL_GRID_STYLE}>
        <MetadataSelectors
          availableTypes={availableTypes}
          customColorKey={customColorKey}
          entry={entry}
          format={format}
          locale={locale}
          missingTypeName={missingTypeName}
          onChangeWorkspace={onChangeWorkspace}
          onCreateMissingType={onCreateMissingType}
          onDeleteProperty={onDeleteProperty}
          onNavigate={onNavigate}
          onUpdateProperty={onUpdateProperty}
          typeColorKeys={typeColorKeys}
          typeIconKeys={typeIconKeys}
          workspaces={workspaces}
        />
        <PropertyEntryRows
          source="frontmatter"
          entries={propertyEntries}
          editingKey={editingKey}
          displayOverrides={displayOverrides}
          vaultStatuses={vaultStatuses}
          vaultTagsByKey={vaultTagsByKey}
          locale={locale}
          onStartEdit={setEditingKey}
          onSave={handleSaveValue}
          onSaveList={handleSaveList}
          onUpdate={onUpdateProperty}
          onDelete={onDeleteProperty}
          onDisplayModeChange={handleDisplayModeChange}
        />
        <PropertyEntryRows
          source="type-derived"
          entries={typeDerivedPropertyEntries}
          editingKey={editingKey}
          displayOverrides={displayOverrides}
          vaultStatuses={vaultStatuses}
          vaultTagsByKey={vaultTagsByKey}
          locale={locale}
          onStartEdit={setEditingKey}
          onSave={handleSaveTypeDerivedValue}
          onSaveList={handleSaveList}
          onUpdate={onUpdateProperty}
          onDisplayModeChange={handleDisplayModeChange}
        />
        <PendingSuggestedPropertyRow
          pendingSuggestedKey={pendingSuggestedKey}
          editingKey={editingKey}
          vaultStatuses={vaultStatuses}
          vaultTagsByKey={vaultTagsByKey}
          locale={locale}
          onStartEdit={onStartPendingSuggestedEdit}
          onSave={onSaveSuggestedValue}
          onSaveList={handleSaveList}
          onDisplayModeChange={handleDisplayModeChange}
        />
        <SuggestedPropertyRows properties={missingSuggested} onAdd={onAddSuggestedProperty} />
        {!showAddDialog && (
          <AddPropertyButton
            locale={locale}
            onClick={() => setShowAddDialog(true)}
            disabled={!onAddProperty}
          />
        )}
      </div>
      {showAddDialog && (
        <AddPropertyForm
          onAdd={handleAdd}
          onCancel={() => setShowAddDialog(false)}
          vaultStatuses={vaultStatuses}
          locale={locale}
        />
      )}
    </div>
  )
}

interface DynamicPropertiesPanelProps {
  entry: VaultEntry
  content?: string | null
  frontmatter: ParsedFrontmatter
  entries?: VaultEntry[]
  workspaces?: WorkspaceIdentity[]
  onUpdateProperty?: (key: string, value: FrontmatterValue) => void
  onDeleteProperty?: (key: string) => void
  onAddProperty?: (key: string, value: FrontmatterValue) => void
  onNavigate?: (target: string) => void
  onCreateMissingType?: (typeName: string) => boolean | void | Promise<boolean | void>
  onChangeWorkspace?: (workspace: WorkspaceIdentity) => void | Promise<void>
  locale?: AppLocale
}

export function DynamicPropertiesPanel({
  entry, frontmatter, entries,
  onUpdateProperty, onDeleteProperty, onAddProperty, onNavigate, onCreateMissingType,
  onChangeWorkspace, workspaces,
  locale = 'en',
}: DynamicPropertiesPanelProps) {
  const propertyState = usePropertyPanelState({ entries, entryIsA: entry.isA, frontmatter, onUpdateProperty, onDeleteProperty, onAddProperty })
  const [pendingSuggestedKey, setPendingSuggestedKey] = useState<string | null>(null)
  const missingTypeName = useMemo(() => resolveMissingTypeName(entry.isA, propertyState.availableTypes), [entry.isA, propertyState.availableTypes])
  const format = useMemo(() => noteFormatFromFrontmatter(frontmatter), [frontmatter])

  const existingKeys = useMemo(
    () => getExistingPropertyKeys([...propertyState.propertyEntries, ...propertyState.typeDerivedPropertyEntries], frontmatter),
    [frontmatter, propertyState.propertyEntries, propertyState.typeDerivedPropertyEntries],
  )
  const missingSuggested = useMemo(
    () => getMissingSuggestedProperties(Boolean(onAddProperty), existingKeys, pendingSuggestedKey),
    [existingKeys, onAddProperty, pendingSuggestedKey],
  )
  const {
    handlePendingSuggestedEdit,
    handleSaveSuggestedValue,
    handleSuggestedAdd,
  } = useSuggestedPropertyActions({
    onAddProperty,
    setEditingKey: propertyState.setEditingKey,
    setPendingSuggestedKey,
  })

  useFocusNoteIconProperty({ onAddProperty, setEditingKey: propertyState.setEditingKey, setPendingSuggestedKey })

  return (
    <DynamicPropertiesPanelContent
      entry={entry}
      propertyState={propertyState}
      pendingSuggestedKey={pendingSuggestedKey}
      missingSuggested={missingSuggested}
      missingTypeName={missingTypeName}
      format={format}
      locale={locale}
      workspaces={workspaces}
      onUpdateProperty={onUpdateProperty}
      onDeleteProperty={onDeleteProperty}
      onAddProperty={onAddProperty}
      onNavigate={onNavigate}
      onCreateMissingType={onCreateMissingType}
      onChangeWorkspace={onChangeWorkspace}
      onStartPendingSuggestedEdit={handlePendingSuggestedEdit}
      onSaveSuggestedValue={handleSaveSuggestedValue}
      onAddSuggestedProperty={handleSuggestedAdd}
    />
  )
}
