<script setup lang="ts">
import { computed, onScopeDispose, ref, watch } from 'vue'
import {
  DialogClose,
  DialogContent,
  DialogOverlay,
  DialogPortal,
  DialogRoot,
  DialogTitle
} from 'reka-ui'

import type { SceneNode } from '@open-pencil/core/scene-graph'
import { useI18n } from '@open-pencil/vue'

import { nodeIcon } from '@/app/editor/icons'
import { useEditorStore } from '@/app/editor/active-store'
import { openExternalLink } from '@/app/shell/ui'
import AppInput from '@/components/ui/AppInput.vue'
import { useButtonUI } from '@/components/ui/button'
import { useDialogUI } from '@/components/ui/dialog'
import Tip from '@/components/ui/Tip.vue'

type LocalAsset = {
  id: string
  name: string
  node: SceneNode
  componentId: string | null
  variants: Array<{ name: string; values: string[] }>
  variantCount: number
  hasConflicts: boolean
  sourceLibraryKey: string | null
  description: string
  docsUrl: string | null
}

const editor = useEditorStore()
const { panels, commands } = useI18n()
const query = ref('')
const detailsOpen = ref(false)
const selectedAssetId = ref<string | null>(null)
const previewUrl = ref<string | null>(null)
const previewLoading = ref(false)
let previewRequestId = 0
const insertButton = useButtonUI({ tone: 'ghost', size: 'iconSm' })
const primaryButton = useButtonUI({ tone: 'accent', size: 'md' })
const dialog = useDialogUI({ content: 'flex w-[720px] max-w-[92vw] flex-col overflow-hidden' })

function componentSetVariantInfo(componentSetId: string) {
  return [...editor.collectVariantOptions(componentSetId)].map(([name, values]) => ({
    name,
    values: [...values].sort((a, b) => a.localeCompare(b))
  }))
}

const graphNodes = computed(() => ({
  sceneVersion: editor.state.sceneVersion,
  nodes: [...editor.graph.nodes.values()]
}))

const assets = computed<LocalAsset[]>(() => {
  return graphNodes.value.nodes
    .filter((node) => node.type === 'COMPONENT' || node.type === 'COMPONENT_SET')
    .filter((node) => {
      if (node.type === 'COMPONENT_SET') return true
      const parent = node.parentId ? editor.graph.getNode(node.parentId) : null
      return parent?.type !== 'COMPONENT_SET'
    })
    .map((node) => {
      const defaultVariant =
        node.type === 'COMPONENT_SET' ? editor.getDefaultVariantForComponentSet(node.id) : node
      const conflicts =
        node.type === 'COMPONENT_SET' ? editor.getComponentSetVariantConflicts(node.id) : []
      const variants = node.type === 'COMPONENT_SET' ? componentSetVariantInfo(node.id) : []
      return {
        id: node.id,
        name: node.name,
        node,
        componentId: defaultVariant?.id ?? null,
        variants,
        variantCount: node.type === 'COMPONENT_SET' ? node.childIds.length : 0,
        hasConflicts: conflicts.length > 0,
        sourceLibraryKey: node.sourceLibraryKey,
        description: node.symbolDescription,
        docsUrl: node.symbolLinks[0]?.uri ?? null
      }
    })
    .sort((a, b) => a.name.localeCompare(b.name))
})

const filteredAssets = computed(() => {
  const normalized = query.value.trim().toLowerCase()
  if (!normalized) return assets.value
  return assets.value.filter((asset) => asset.name.toLowerCase().includes(normalized))
})

const selectedAsset = computed(
  () => assets.value.find((asset) => asset.id === selectedAssetId.value) ?? null
)
const selectedPreviewNodeId = computed(() => selectedAsset.value?.componentId ?? null)

function revokePreview() {
  if (!previewUrl.value) return
  URL.revokeObjectURL(previewUrl.value)
  previewUrl.value = null
}

async function updatePreview() {
  const requestId = ++previewRequestId
  const nodeId = selectedPreviewNodeId.value
  if (!detailsOpen.value || !nodeId) {
    revokePreview()
    return
  }

  const node = editor.getNode(nodeId)
  if (!node) {
    revokePreview()
    return
  }

  previewLoading.value = true
  try {
    const maxSize = Math.max(node.width, node.height, 1)
    const scale = Math.min(176 / maxSize, 2)
    const data = await editor.renderExportImage([nodeId], scale, 'PNG')
    if (requestId !== previewRequestId) return
    revokePreview()
    if (data) previewUrl.value = URL.createObjectURL(new Blob([data], { type: 'image/png' }))
  } finally {
    if (requestId === previewRequestId) previewLoading.value = false
  }
}

watch([detailsOpen, selectedPreviewNodeId, () => editor.state.sceneVersion], updatePreview, {
  flush: 'post'
})

onScopeDispose(revokePreview)

function openDetails(asset: LocalAsset) {
  selectedAssetId.value = asset.id
  detailsOpen.value = true
}

function insertionPoint(component: SceneNode, parentId: string) {
  const canvasCenter = editor.viewportCanvasCenter()
  const center = editor.screenToCanvas(canvasCenter.x, canvasCenter.y)
  const parentOffset =
    parentId === editor.state.currentPageId
      ? { x: 0, y: 0 }
      : editor.graph.getAbsolutePosition(parentId)
  return {
    x: center.x - parentOffset.x - component.width / 2,
    y: center.y - parentOffset.y - component.height / 2
  }
}

function insertAsset(asset: LocalAsset) {
  if (!asset.componentId) return
  const component = editor.graph.getNode(asset.componentId)
  if (!component) return
  const parentId = editor.state.enteredContainerId ?? editor.state.currentPageId
  const point = insertionPoint(component, parentId)
  editor.createInstanceFromComponent(asset.componentId, point.x, point.y, parentId)
  editor.requestRender()
}

function insertSelectedAsset() {
  if (!selectedAsset.value) return
  insertAsset(selectedAsset.value)
  detailsOpen.value = false
}
</script>

<template>
  <section data-test-id="assets-panel" class="flex min-h-0 flex-1 flex-col overflow-hidden">
    <header class="shrink-0 px-3 py-2 text-[11px] tracking-wider text-muted uppercase">
      {{ panels.assets }}
    </header>
    <div class="shrink-0 px-2 pb-2">
      <AppInput
        v-model="query"
        type="search"
        test-id="assets-search"
        size="sm"
        :placeholder="panels.searchLocalComponents"
      />
    </div>

    <div class="scrollbar-thin flex-1 overflow-y-auto px-1 pb-2">
      <button
        v-for="asset in filteredAssets"
        :key="asset.id"
        data-test-id="asset-item"
        :data-asset-id="asset.id"
        class="group/asset flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-surface hover:bg-hover"
        @click="openDetails(asset)"
        @dblclick="insertAsset(asset)"
      >
        <component
          :is="nodeIcon(asset.node)"
          class="size-3.5 shrink-0 text-component"
          aria-hidden="true"
        />
        <span class="min-w-0 flex-1">
          <span class="flex min-w-0 items-center gap-1.5">
            <span data-test-id="asset-name" class="truncate">{{ asset.name }}</span>
            <span
              v-if="asset.sourceLibraryKey"
              data-test-id="asset-library-badge"
              class="shrink-0 rounded bg-component/15 px-1 py-px text-[9px] font-medium text-component uppercase"
            >
              {{ panels.assetLibraryBadge }}
            </span>
          </span>
          <span
            v-if="asset.variants.length > 0"
            data-test-id="asset-variant-summary"
            class="mt-0.5 block truncate text-[10px] text-muted"
          >
            {{
              panels.assetVariantSummary({
                count: asset.variantCount,
                names: asset.variants.map((variant) => variant.name).join(', ')
              })
            }}
          </span>
          <span
            v-if="asset.description"
            data-test-id="asset-description"
            class="mt-0.5 block truncate text-[10px] text-muted"
          >
            {{ asset.description }}
          </span>
          <span
            v-if="asset.hasConflicts"
            data-test-id="asset-variant-conflict"
            class="mt-0.5 block truncate text-[10px] text-[var(--color-warning-text)]"
          >
            {{ panels.duplicateVariantValues }}
          </span>
        </span>
        <Tip v-if="asset.docsUrl" :label="panels.openDocumentation">
          <span
            :class="insertButton.base"
            data-test-id="asset-docs"
            @pointerdown.stop
            @dblclick.stop
            @click.stop="asset.docsUrl ? openExternalLink(asset.docsUrl) : undefined"
          >
            <icon-lucide-book-open class="size-3" />
          </span>
        </Tip>
        <Tip :label="commands.createInstance">
          <span
            :class="insertButton.base"
            data-test-id="asset-insert"
            @pointerdown.stop
            @click.stop="insertAsset(asset)"
          >
            <icon-lucide-plus class="size-3" />
          </span>
        </Tip>
      </button>

      <div
        v-if="filteredAssets.length === 0"
        data-test-id="assets-empty"
        class="px-3 py-6 text-center text-xs text-muted"
      >
        {{ panels.noLocalComponents }}
      </div>
    </div>

    <DialogRoot v-model:open="detailsOpen">
      <DialogPortal>
        <DialogOverlay :class="dialog.overlay" />
        <DialogContent
          v-if="selectedAsset"
          data-test-id="asset-details-dialog"
          :class="dialog.content"
        >
          <div class="flex items-center justify-between border-b border-border px-4 py-3">
            <div class="flex min-w-0 items-center gap-2">
              <component
                :is="nodeIcon(selectedAsset.node)"
                class="size-4 shrink-0 text-component"
              />
              <div class="min-w-0">
                <DialogTitle :class="dialog.title" class="truncate">{{
                  selectedAsset.name
                }}</DialogTitle>
                <p class="mt-0.5 text-[11px] text-muted">
                  {{
                    selectedAsset.node.type === 'COMPONENT_SET'
                      ? panels.componentSet
                      : panels.component
                  }}
                  <span v-if="selectedAsset.variantCount > 0">
                    · {{ selectedAsset.variantCount }} variants</span
                  >
                </p>
              </div>
            </div>
            <DialogClose
              data-test-id="asset-details-close"
              class="flex size-7 cursor-pointer items-center justify-center rounded border-none bg-transparent text-muted hover:bg-hover hover:text-surface"
            >
              <icon-lucide-x class="size-4" />
            </DialogClose>
          </div>

          <div class="grid min-h-0 grid-cols-[260px_1fr] gap-0">
            <div class="border-r border-border p-4">
              <div
                data-test-id="asset-details-preview"
                class="flex h-36 items-center justify-center overflow-hidden rounded-lg border border-border bg-canvas/60"
              >
                <img
                  v-if="previewUrl"
                  data-test-id="asset-details-preview-image"
                  :src="previewUrl"
                  :alt="`${selectedAsset.name} preview`"
                  class="max-h-[120px] max-w-[210px] object-contain"
                />
                <div v-else class="text-center">
                  <icon-lucide-loader-2
                    v-if="previewLoading"
                    class="mx-auto size-5 animate-spin text-muted"
                  />
                  <component
                    v-else
                    :is="nodeIcon(selectedAsset.node)"
                    class="mx-auto size-8 text-component"
                  />
                  <p class="mt-2 max-w-44 truncate text-xs font-medium text-surface">
                    {{ selectedAsset.name }}
                  </p>
                </div>
              </div>
              <button
                data-test-id="asset-details-insert"
                :class="primaryButton.base"
                class="mt-3 w-full"
                @click="insertSelectedAsset"
              >
                {{ panels.insertInstance }}
              </button>
            </div>

            <div class="min-w-0 p-4">
              <section v-if="selectedAsset.description" class="mb-4">
                <h3 class="text-[11px] font-medium tracking-wider text-muted uppercase">
                  {{ panels.description }}
                </h3>
                <p
                  data-test-id="asset-details-description"
                  class="mt-1 text-xs leading-5 text-surface"
                >
                  {{ selectedAsset.description }}
                </p>
              </section>

              <section v-if="selectedAsset.sourceLibraryKey" class="mb-4">
                <h3 class="text-[11px] font-medium tracking-wider text-muted uppercase">
                  {{ panels.assetLibraryBadge }}
                </h3>
                <p data-test-id="asset-details-library" class="mt-1 break-all text-xs text-muted">
                  {{ selectedAsset.sourceLibraryKey }}
                </p>
              </section>

              <section v-if="selectedAsset.docsUrl" class="mb-4">
                <h3 class="text-[11px] font-medium tracking-wider text-muted uppercase">
                  {{ panels.documentation }}
                </h3>
                <button
                  data-test-id="asset-details-docs"
                  class="mt-1 inline-flex items-center gap-1 rounded px-1 py-0.5 text-xs text-component hover:bg-component/10"
                  @click="
                    selectedAsset.docsUrl ? openExternalLink(selectedAsset.docsUrl) : undefined
                  "
                >
                  <icon-lucide-book-open class="size-3" />
                  {{ panels.openDocs }}
                </button>
              </section>

              <section v-if="selectedAsset.variants.length > 0">
                <h3 class="text-[11px] font-medium tracking-wider text-muted uppercase">
                  {{ panels.properties }}
                </h3>
                <div class="mt-2 flex flex-col gap-2">
                  <div
                    v-for="variant in selectedAsset.variants"
                    :key="variant.name"
                    data-test-id="asset-details-property"
                    class="rounded border border-border bg-input/40 px-2 py-1.5"
                  >
                    <div class="text-xs font-medium text-surface">{{ variant.name }}</div>
                    <div class="mt-1 text-[11px] text-muted">{{ variant.values.join(', ') }}</div>
                  </div>
                </div>
              </section>
            </div>
          </div>
        </DialogContent>
      </DialogPortal>
    </DialogRoot>
  </section>
</template>
