<script setup lang="ts">
import { ref, computed, watch, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import PageHeader from '@/components/PageHeader.vue'
import DkuSpinner from '@/components/DkuSpinner.vue'
import { useRouteVersionId } from '@/composables/useRouteVersionId'
import { useVersionLoader } from '@/composables/useVersionLoader'
import { useSaveDiscard } from '@/composables/useSaveDiscard'
import { useSemanticModelsStore } from '@/stores/semantic-models'
import { useAppStore } from '@/stores/app'
import { storeToRefs } from 'pinia'
import { indexingApi, dssResourcesApi } from '@/api'
import { toast } from 'vue-sonner'
import type { DSSLLM, IndexingStatus } from '@/types'
import GroupedSearchSelect from '@/components/GroupedSearchSelect.vue'
import type { GroupedOption } from '@/components/GroupedSearchSelect.vue'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { formatDateTime } from '@/lib/utils'

const route = useRoute()
const store = useSemanticModelsStore()
const appStore = useAppStore()
const { selectedEmbeddingLlmId } = storeToRefs(appStore)

const modelId = computed(() => route.params.id as string)
const versionId = useRouteVersionId(route)

const isIndexing = ref(false)
const lastJobId = ref<string | null>(null)
const indexingStatus = ref<IndexingStatus | null>(null)
const pollingHandle = ref<number | null>(null)
const indicesRefreshed = ref(false)

const availableEmbeddingLlms = ref<DSSLLM[]>([])
const lastIndexedEmbeddingLlmId = computed(() => {
  const raw = store.currentVersion?.privateEditorData?.lastIndexedEmbeddingLlmId
  if (typeof raw !== 'string') return ''
  return raw.trim()
})
const embeddingLlmMismatch = computed(() => {
  if (!lastIndexedEmbeddingLlmId.value) return false
  const current = (selectedEmbeddingLlmId.value || '').trim()
  return current !== lastIndexedEmbeddingLlmId.value
})

const isIndexingRunning = computed(() => {
  if (!lastJobId.value) {
    return false
  }
  if (!indexingStatus.value) {
    return true
  }
  if (indexingStatus.value.aborted) {
    return false
  }
  return !indexingStatus.value.hasResult
})

const statusLabel = computed(() => {
  if (!lastJobId.value) {
    return null
  }
  if (!indexingStatus.value) {
    return 'Indexing started...'
  }
  if (indexingStatus.value.aborted) {
    return 'Indexing aborted'
  }
  if (indexingStatus.value.hasResult) {
    return 'Indexing completed'
  }
  return 'Indexing in progress...'
})

const DEFAULT_INDEXING_SETTINGS = {
  maxDistinctValuesPerAttribute: 1000,
  maxScannedRowsForSQLDatasets: -1,
  maxScannedRowsForNonSQLDatasets: 1000000,
}

const indexingSettings = computed(() => store.draftIndexingSettings as Record<string, number>)

const embeddingLlmOptions = computed((): GroupedOption[] => {
  return availableEmbeddingLlms.value.map(llm => ({
    value: llm.id,
    label: llm.label || llm.id,
    type: llm.type || 'Unknown',
    connection: llm.connection,
  }))
})

const lastIndexedAtLabel = computed(() => {
  const raw = store.currentVersion?.privateEditorData?.lastIndexedAt
  if (typeof raw !== 'string' || !raw) return 'Never'
  const formatted = formatDateTime(raw)
  return formatted || 'Never'
})

const lastSavedAtLabel = computed(() => {
  const raw = store.currentVersion?.privateEditorData?.lastSavedAt
  if (typeof raw !== 'string' || !raw) return ''
  return formatDateTime(raw)
})

function ensureIndexingDefaults() {
  if (!store.draftIndexingSettings) {
    store.draftIndexingSettings = {}
  }
  for (const [key, value] of Object.entries(DEFAULT_INDEXING_SETTINGS)) {
    if (store.draftIndexingSettings[key] === undefined || store.draftIndexingSettings[key] === null) {
      store.draftIndexingSettings[key] = value
    }
  }
}

function syncEmbeddingSelectionFromVersion() {
  const raw = store.currentVersion?.privateEditorData?.embeddingLlmId
  const value = typeof raw === 'string' ? raw.trim() : ''
  selectedEmbeddingLlmId.value = value
}

watch(selectedEmbeddingLlmId, (val) => {
  if (!store.draftPrivateEditorData) {
    store.draftPrivateEditorData = {}
  }
  const trimmed = typeof val === 'string' ? val.trim() : ''
  if (trimmed) {
    store.draftPrivateEditorData.embeddingLlmId = trimmed
  } else {
    delete (store.draftPrivateEditorData as Record<string, unknown>).embeddingLlmId
  }
})

const { isLoading } = useVersionLoader({
  modelId,
  versionId,
  onBeforeLoad: ({ isReload }) => {
    if (!isReload) return
    stopPolling()
    lastJobId.value = null
    indexingStatus.value = null
    indicesRefreshed.value = false
  },
  onAfterLoad: async ({ isReload }) => {
    ensureIndexingDefaults()
    syncEmbeddingSelectionFromVersion()
    if (isReload) return
    try {
      availableEmbeddingLlms.value = await dssResourcesApi.listLlms('TEXT_EMBEDDING_EXTRACTION')
    } catch (e) {
      console.error('Failed to load embedding LLMs:', e)
      toast.error('Failed to load embedding LLMs')
    }
  },
})

onUnmounted(() => {
  stopPolling()
})

const { handleSave, handleDiscard } = useSaveDiscard({
  modelId,
  versionId,
  onDiscard: () => {
    ensureIndexingDefaults()
    syncEmbeddingSelectionFromVersion()
  },
})

function stopPolling() {
  if (pollingHandle.value !== null) {
    window.clearTimeout(pollingHandle.value)
    pollingHandle.value = null
  }
}

async function pollIndexingStatus() {
  if (!lastJobId.value) {
    return
  }
  try {
    indexingStatus.value = await indexingApi.getIndexingStatus(
      modelId.value,
      versionId.value,
      lastJobId.value
    )
  } catch (e: unknown) {
    stopPolling()
    const err = e as { response?: { data?: { error?: string } }; message?: string }
    toast.error('Failed to fetch indexing status', {
      description: err.response?.data?.error || err.message,
    })
    return
  }

  if (indexingStatus.value?.hasResult && !indicesRefreshed.value) {
    indicesRefreshed.value = true
    try {
      const refreshResult = await indexingApi.refreshIndices(modelId.value, versionId.value, {
        embedding_llm_id: selectedEmbeddingLlmId.value || undefined,
      })
      store.markIndexingFresh(refreshResult.lastIndexedAt || null, selectedEmbeddingLlmId.value || null)
      toast.success('Indexing finished successfully')
    } catch (e: unknown) {
      const err = e as { response?: { data?: { error?: string } }; message?: string }
      toast.error('Failed to refresh indices after indexing', {
        description: err.response?.data?.error || err.message,
      })
    }
  }

  if (!indexingStatus.value || indexingStatus.value.hasResult || indexingStatus.value.aborted) {
    stopPolling()
    return
  }
  pollingHandle.value = window.setTimeout(pollIndexingStatus, 2000)
}

async function startIndexing() {
  if (store.hasUnsavedChanges) {
    toast.error('Save your changes before indexing')
    return
  }
  isIndexing.value = true
  lastJobId.value = null
  indexingStatus.value = null
  indicesRefreshed.value = false
  stopPolling()
  try {
    const result = await indexingApi.startIndexing(modelId.value, versionId.value, {
      embedding_llm_id: selectedEmbeddingLlmId.value || undefined,
    })
    lastJobId.value = result.jobId || null
    toast.success('Indexing started')
    if (lastJobId.value) {
      pollIndexingStatus()
    }
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } }; message?: string }
    toast.error('Failed to start indexing', {
      description: err.response?.data?.error || err.message,
    })
  } finally {
    isIndexing.value = false
  }
}
</script>

<template>
  <div class="flex h-full flex-col">
    <PageHeader
      :version-id="versionId"
      title="Indexing"
      description="Configure distinct-values indexing and run it for this version."
      show-save-discard
      :has-unsaved-changes="store.hasUnsavedChanges"
      :is-saving="store.isSavingDraft"
      @save="handleSave"
      @discard="handleDiscard"
    />

    <div v-if="isLoading" class="flex flex-1 items-center justify-center">
      <DkuSpinner size="xl" />
    </div>

    <div v-else class="flex-1 overflow-auto p-6">
      <div class="mx-auto max-w-6xl space-y-6">
        <section class="rounded-lg border p-6">
          <h2 class="text-lg font-semibold">Indexing Settings</h2>
          <p class="mt-1 text-sm text-muted-foreground">
            These settings control how DSS computes distinct values for this version.
          </p>

          <div class="mt-6 grid gap-4">
            <div>
              <label class="mb-1 block text-sm font-medium">Embedding LLM</label>
              <GroupedSearchSelect
                :model-value="selectedEmbeddingLlmId"
                :options="embeddingLlmOptions"
                placeholder="Select embedding LLM..."
                search-placeholder="Filter..."
                empty-text="No embedding LLMs found"
                @update:model-value="(val: string) => selectedEmbeddingLlmId = val"
              />
              <p class="mt-1 text-xs text-muted-foreground">
                Used for semantic matching; should match the embedding model used when indexing.
              </p>
              <p v-if="embeddingLlmMismatch" class="mt-1 text-xs text-amber-600">
                Warning: indices were last built with "{{ lastIndexedEmbeddingLlmId }}".
              </p>
            </div>
            <div>
              <label class="mb-1 block text-sm font-medium">Max distinct values per attribute</label>
              <input
                v-model.number="indexingSettings.maxDistinctValuesPerAttribute"
                type="number"
                min="1"
                class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
              />
            </div>

            <div>
              <label class="mb-1 block text-sm font-medium">Max scanned rows for SQL datasets</label>
              <input
                v-model.number="indexingSettings.maxScannedRowsForSQLDatasets"
                type="number"
                class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
              />
              <p class="mt-1 text-xs text-muted-foreground">Use -1 for no limit.</p>
            </div>

            <div>
              <label class="mb-1 block text-sm font-medium">Max scanned rows for non‑SQL datasets</label>
              <input
                v-model.number="indexingSettings.maxScannedRowsForNonSQLDatasets"
                type="number"
                min="1"
                class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
              />
            </div>
          </div>
        </section>

        <section class="rounded-lg border p-6">
          <div class="flex items-center justify-between">
            <div>
              <h2 class="text-lg font-semibold">Run Indexing</h2>
              <p class="mt-1 text-sm text-muted-foreground">
                Triggers DSS to compute distinct values for resolvable attributes in this version.
              </p>
              <div class="mt-2 space-y-1 text-xs text-muted-foreground">
                <p>Last indexed (editor): {{ lastIndexedAtLabel }}</p>
                <p v-if="lastSavedAtLabel">Last saved: {{ lastSavedAtLabel }}</p>
              </div>
            </div>
            <Tooltip v-if="store.hasUnsavedChanges">
              <TooltipTrigger as-child>
                <button
                  class="btn-primary inline-flex items-center gap-2 disabled:opacity-50"
                  :disabled="isIndexing || isIndexingRunning || store.hasUnsavedChanges"
                  @click="startIndexing"
                >
                  <DkuSpinner v-if="isIndexing || isIndexingRunning" size="md" />
                  <i v-else class="dku-icon-play-outline-16" />
                  {{ isIndexing ? 'Starting...' : isIndexingRunning ? 'Indexing...' : 'Start Indexing' }}
                </button>
              </TooltipTrigger>
              <TooltipContent>Save your changes before indexing.</TooltipContent>
            </Tooltip>
            <button
              v-else
              class="btn-primary inline-flex items-center gap-2 disabled:opacity-50"
              :disabled="isIndexing || isIndexingRunning"
              @click="startIndexing"
            >
              <DkuSpinner v-if="isIndexing || isIndexingRunning" size="md" />
              <i v-else class="dku-icon-play-outline-16" />
              {{ isIndexing ? 'Starting...' : isIndexingRunning ? 'Indexing...' : 'Start Indexing' }}
            </button>
          </div>

          <div v-if="lastJobId" class="mt-3 flex items-center gap-2 text-xs text-muted-foreground">
            <DkuSpinner v-if="isIndexingRunning" size="sm" />
            <span>{{ statusLabel }}</span>
          </div>
        </section>
      </div>
    </div>
  </div>
</template>
