<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import EntityEditor from '@/components/editor/EntityEditor.vue'
import AddEntityModal from '@/components/AddEntityModal.vue'
import SampleValuesModal from '@/components/editor/SampleValuesModal.vue'
import PageHeader from '@/components/PageHeader.vue'
import DkuSpinner from '@/components/DkuSpinner.vue'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useRouteVersionId } from '@/composables/useRouteVersionId'
import { useVersionLoader } from '@/composables/useVersionLoader'
import { useSaveDiscard } from '@/composables/useSaveDiscard'
import { useSemanticModelsStore } from '@/stores/semantic-models'
import { dssResourcesApi, type DSSProject } from '@/api'
import { toast } from 'vue-sonner'
import type {
  EntityClass,
  Relationship,
  DSSDataset,
  DSSColumn,
  Attribute,
} from '@/types'

const route = useRoute()
const router = useRouter()
const store = useSemanticModelsStore()

const modelId = computed(() => route.params.id as string)
const versionId = useRouteVersionId(route)
const entityIdParam = computed(() => {
  const id = route.params.entityId as string | undefined
  return id ? decodeURIComponent(id) : undefined
})

// UI state
const showAddEntityModal = ref(false)
const showDeleteEntityDialog = ref(false)
const entityToDelete = ref<{ index: number; name: string } | null>(null)

// Sample values modal state
const showValuesModal = ref(false)
const valuesModalEntity = ref<EntityClass | null>(null)
const valuesModalAttrIndex = ref<number>(-1)

// DSS Resources state
const projects = ref<DSSProject[]>([])
const datasets = ref<Record<string, DSSDataset[]>>({})
const columns = ref<Record<string, DSSColumn[]>>({})

// Loading states
const loadingProjects = ref(false)
const loadingDatasets = ref<Record<string, boolean>>({})
const loadingColumns = ref<Record<string, boolean>>({})

function splitDatasetRef(ref?: string): { projectKey: string; datasetName: string } {
  if (!ref || !ref.includes('.')) {
    return { projectKey: '', datasetName: '' }
  }
  const [projectKey, datasetName] = ref.split('.', 2)
  return { projectKey, datasetName }
}

// Current entity from URL (uses store.draftSpec)
const currentEntity = computed(() => {
  if (!entityIdParam.value || !store.draftSpec) return null
  return store.draftSpec.entities.find(e => e.name === entityIdParam.value) || null
})

const currentEntitySafe = computed(() => currentEntity.value as EntityClass)

const currentEntityIndex = computed(() => {
  if (!entityIdParam.value || !store.draftSpec) return -1
  return store.draftSpec.entities.findIndex(e => e.name === entityIdParam.value)
})

// Breadcrumb items for PageHeader
const breadcrumbItems = computed(() => {
  if (currentEntity.value) {
    return [
      { label: 'Entities', to: `/semantic-models/${modelId.value}/v/${versionId.value}/entities` },
      { label: currentEntity.value.name },
    ]
  }
  return [{ label: 'Entities' }]
})

async function loadEntityResources() {
  await loadProjects()
  if (!store.draftSpec) return

  for (const entity of store.draftSpec.entities) {
    if (entity.type === 'DATASET') {
      const { projectKey } = splitDatasetRef(entity.datasetRef)
      if (projectKey) {
        await loadDatasetsForProject(projectKey)
      }
    }
    await loadColumnsForEntity(entity)
  }
}

const { isLoading } = useVersionLoader({
  modelId,
  versionId,
  onAfterLoad: loadEntityResources,
})

watch(
  () => [currentEntity.value?.name, currentEntity.value?.datasetRef] as const,
  async () => {
    if (!currentEntity.value) return
    await loadColumnsForEntity(currentEntity.value)
  },
  { immediate: true }
)

// =============================================================================
// DSS Resource Loading
// =============================================================================

async function loadProjects() {
  loadingProjects.value = true
  try {
    projects.value = await dssResourcesApi.listProjects()
  } catch (e) {
    toast.error('Failed to load projects')
  } finally {
    loadingProjects.value = false
  }
}

async function loadDatasetsForProject(projectKey: string) {
  if (datasets.value[projectKey]) return
  loadingDatasets.value[projectKey] = true
  try {
    datasets.value[projectKey] = await dssResourcesApi.listDatasets(projectKey)
  } catch (e) {
    toast.error(`Failed to load datasets for project ${projectKey}`)
  } finally {
    loadingDatasets.value[projectKey] = false
  }
}

async function loadColumnsForEntity(entity: EntityClass) {
  const cacheKey = entity.name
  if (loadingColumns.value[cacheKey]) return
  const cached = columns.value[cacheKey]
  if (Array.isArray(cached) && cached.length > 0) return

  if (!entity.datasetRef) {
    columns.value[cacheKey] = []
    return
  }

  loadingColumns.value[cacheKey] = true
  try {
    const { projectKey, datasetName } = splitDatasetRef(entity.datasetRef)
    if (!projectKey || !datasetName) {
      columns.value[cacheKey] = []
      return
    }
    const schema = await dssResourcesApi.getDatasetSchema(projectKey, datasetName)
    columns.value[cacheKey] = schema.columns.map(c => ({
      name: c.name,
      type: c.type,
      nullable: true,
      comment: c.comment || '',
    }))
  } catch (e) {
    columns.value[cacheKey] = []
  } finally {
    loadingColumns.value[cacheKey] = false
  }
}

// =============================================================================
// Entity Management
// =============================================================================

function getEntityColumns(entityId: string): DSSColumn[] {
  return columns.value[entityId] || []
}

async function handleProjectChange(entity: EntityClass, projectKey: string) {
  // Clear dataset and data when project changes
  entity.datasetRef = undefined
  entity.attributes = []
  entity.metrics = []
  entity.filters = []
  entity.primaryKey = { attributes: [] }
  delete columns.value[entity.name]
  if (projectKey) {
    await loadDatasetsForProject(projectKey)
  }
}

async function handleDatasetChange(entity: EntityClass) {
  // Clear old data when dataset changes
  entity.attributes = []
  entity.metrics = []
  entity.filters = []
  entity.primaryKey = { attributes: [] }
  delete columns.value[entity.name]
  await loadColumnsForEntity(entity)
}

function openDeleteEntityDialog(index: number) {
  if (!store.draftSpec) return
  const entity = store.draftSpec.entities[index]
  entityToDelete.value = { index, name: entity.name }
  showDeleteEntityDialog.value = true
}

async function handleConfirmDeleteEntity() {
  if (entityToDelete.value !== null && store.draftSpec) {
    store.draftSpec.entities.splice(entityToDelete.value.index, 1)
    entityToDelete.value = null

    // Navigate to entities list - changes are tracked by store.hasUnsavedChanges
    router.replace(`/semantic-models/${modelId.value}/v/${versionId.value}/entities`)
  }
}

function hasAutoDistinctValues(entity: EntityClass): boolean {
  return (entity.attributes || []).some((attr) => {
    if (attr.type === 'EXPRESSION') {
      return false
    }
    return attr.distinctValuesHandlingMode === 'AUTO_INDEX' || attr.indexDistinctValues === true
  })
}

async function handleEntityAccept(entities: EntityClass[], relationships: Relationship[] = []) {
  if (!store.draftSpec) return

  // Upsert entities by name to avoid duplicates when regenerating.
  for (const entity of entities) {
    const existingIndex = store.draftSpec.entities.findIndex(e => e.name === entity.name)
    if (existingIndex >= 0) {
      store.draftSpec.entities[existingIndex] = entity
    } else {
      store.draftSpec.entities.push(entity)
    }
  }

  if (relationships.length > 0) {
    const existing = new Set(
      store.draftSpec.relationships.map(
        (rel) => `${rel.firstEntity}:${rel.secondEntity}:${rel.pseudoSQLExpression || ''}`
      )
    )
    for (const rel of relationships) {
      const key = `${rel.firstEntity}:${rel.secondEntity}:${rel.pseudoSQLExpression || ''}`
      if (!existing.has(key)) {
        store.draftSpec.relationships.push(rel)
        existing.add(key)
      }
    }
  }

  if (entities.some(hasAutoDistinctValues) && !store.isIndexingStale) {
    store.draftPrivateEditorData = {
      ...(store.draftPrivateEditorData || {}),
      indexingStale: true,
    }
    toast.info('Auto sample values enabled. Save, then run Indexing.')
  }

  showAddEntityModal.value = false

  // Navigate to the first new entity - hasUnsavedChanges will be set by the watcher
  if (entities.length > 0) {
    router.push(
      `/semantic-models/${modelId.value}/v/${versionId.value}/entities/${encodeURIComponent(entities[0].name)}`
    )
  }
}

function navigateToEntity(entityId: string) {
  router.push(`/semantic-models/${modelId.value}/v/${versionId.value}/entities/${encodeURIComponent(entityId)}`)
}

function handleEntityNameChange(newName: string) {
  if (!newName || !store.draftSpec) return
  const oldName = entityIdParam.value
  if (oldName && newName !== oldName) {
    for (const rel of store.draftSpec.relationships) {
      if (rel.firstEntity === oldName) {
        rel.firstEntity = newName
      }
      if (rel.secondEntity === oldName) {
        rel.secondEntity = newName
      }
    }
    for (const entity of store.draftSpec.entities) {
      for (const fk of entity.foreignKeys || []) {
        if (fk.referencedEntity === oldName) {
          fk.referencedEntity = newName
        }
      }
    }
    if (columns.value[oldName]) {
      columns.value[newName] = columns.value[oldName]
      delete columns.value[oldName]
    }
    if (loadingColumns.value[oldName] !== undefined) {
      loadingColumns.value[newName] = loadingColumns.value[oldName]
      delete loadingColumns.value[oldName]
    }
    router.replace(
      `/semantic-models/${modelId.value}/v/${versionId.value}/entities/${encodeURIComponent(newName)}`
    )
  }
}

// =============================================================================
// Sample Values Modal
// =============================================================================

function openValuesModal(entity: EntityClass, attrIndex: number) {
  valuesModalEntity.value = entity
  valuesModalAttrIndex.value = attrIndex
  showValuesModal.value = true
}

const DEFAULT_SAMPLE_LIMIT = 5

function getSampleLimitFor(attr: Attribute): number {
  const limit = (attr.sqlGenerationConfig as Record<string, any> | undefined)?.autoValuesLimit
  if (typeof limit === 'number' && limit > 0) {
    return limit
  }
  return DEFAULT_SAMPLE_LIMIT
}

function hasSampleLimitConfigured(attr: Attribute): boolean {
  const limit = (attr.sqlGenerationConfig as Record<string, any> | undefined)?.autoValuesLimit
  return typeof limit === 'number'
}

function getCurrentSampleLimit(): number {
  if (!valuesModalEntity.value || valuesModalAttrIndex.value < 0) return DEFAULT_SAMPLE_LIMIT
  const attr = valuesModalEntity.value.attributes[valuesModalAttrIndex.value]
  if (!attr) return DEFAULT_SAMPLE_LIMIT
  return getSampleLimitFor(attr)
}

function getCurrentSampleLimitConfigured(): boolean {
  if (!valuesModalEntity.value || valuesModalAttrIndex.value < 0) return false
  const attr = valuesModalEntity.value.attributes[valuesModalAttrIndex.value]
  if (!attr) return false
  return hasSampleLimitConfigured(attr)
}

function handleSampleLimitUpdate(attrIndex: number, limit: number) {
  if (!valuesModalEntity.value || attrIndex < 0) return
  const entity = valuesModalEntity.value
  const attr = entity.attributes[attrIndex]
  if (!attr) return

  const normalized = Math.max(1, Math.min(10000, Math.floor(limit)))
  if (!attr.sqlGenerationConfig) {
    attr.sqlGenerationConfig = {}
  }
  ;(attr.sqlGenerationConfig as Record<string, any>).autoValuesLimit = normalized

  // Enforce auto sample values (clear manual values)
  if (attr.manualValues?.length) {
    attr.manualValues = []
  }
  attr.distinctValuesHandlingMode = 'AUTO_INDEX'
  attr.indexDistinctValues = true
}

type ValuesMode = 'AUTO' | 'MANUAL' | 'NONE'

function handleValuesModeUpdate(attrIndex: number, mode: ValuesMode) {
  if (!valuesModalEntity.value || attrIndex < 0) return
  const entity = valuesModalEntity.value
  const attr = entity.attributes[attrIndex]
  if (!attr) return

  if (mode === 'AUTO') {
    const limit = getSampleLimitFor(attr)
    handleSampleLimitUpdate(attrIndex, limit)
    return
  }

  // Clear auto sample config
  if (attr.sqlGenerationConfig) {
    delete (attr.sqlGenerationConfig as Record<string, any>).autoValuesLimit
    if (Object.keys(attr.sqlGenerationConfig).length === 0) {
      delete (attr as Record<string, any>).sqlGenerationConfig
    }
  }

  if (mode === 'MANUAL') {
    attr.distinctValuesHandlingMode = 'MANUAL'
    attr.indexDistinctValues = false
    if (!attr.manualValues) {
      attr.manualValues = []
    }
    return
  }

  // NONE
  attr.manualValues = []
  attr.distinctValuesHandlingMode = 'NONE'
  attr.indexDistinctValues = false
}

function handleManualValuesUpdate(attrIndex: number, manualValues: string[]) {
  if (!valuesModalEntity.value || attrIndex < 0) return
  const entity = valuesModalEntity.value
  const attr = entity.attributes[attrIndex]
  if (!attr) return

  attr.manualValues = manualValues

  if (manualValues.length > 0) {
    // Clear auto sample config to enforce manual-only mode
    if (attr.sqlGenerationConfig) {
      delete (attr.sqlGenerationConfig as Record<string, any>).autoValuesLimit
      if (Object.keys(attr.sqlGenerationConfig).length === 0) {
        delete (attr as Record<string, any>).sqlGenerationConfig
      }
    }

    attr.distinctValuesHandlingMode = 'MANUAL'
    attr.indexDistinctValues = false
    return
  }

  if (attr.distinctValuesHandlingMode === 'MANUAL') {
    attr.indexDistinctValues = false
    return
  }
}

const { handleSave, handleDiscard } = useSaveDiscard({
  modelId,
  versionId,
  onDiscard: async () => {
    const currentName = entityIdParam.value

    // After discard, check if the entity still exists (name might have changed locally)
    if (currentName && store.draftSpec) {
      const entityExists = store.draftSpec.entities.some(e => e.name === currentName)
      if (!entityExists) {
        // Entity name was changed locally - navigate to entities list
        await router.replace(`/semantic-models/${modelId.value}/v/${versionId.value}/entities`)
      }
    }
  },
})
</script>

<template>
  <div class="flex h-full flex-col">
    <PageHeader
      :version-id="versionId"
      title="Entities"
      description="Manage entities, define the core concepts of your semantic model."
      :breadcrumb-items="breadcrumbItems"
      show-save-discard
      :has-unsaved-changes="store.hasUnsavedChanges"
      :is-saving="store.isSavingDraft"
      @save="handleSave"
      @discard="handleDiscard"
    />

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

    <!-- Content -->
    <div v-else-if="store.draftSpec" class="flex-1 overflow-auto p-6">
      <div class="mx-auto max-w-6xl">
      <!-- Entity list header (only shown when no specific entity is selected) -->
      <div v-if="!currentEntity" class="mb-4 flex items-center justify-end">
        <button
          class="btn-tertiary"
          @click="showAddEntityModal = true"
        >
          + Add Entity
        </button>
      </div>

      <!-- No entities state -->
      <div v-if="store.draftSpec.entities.length === 0" class="rounded-lg border-2 border-dashed p-8 text-center">
        <p class="text-muted-foreground">No entities defined. Click "Add Entity" to create one.</p>
      </div>

      <!-- Entity editor (single entity) -->
      <div v-else-if="currentEntity">
        <EntityEditor
          :id="`entity-${currentEntitySafe.name}`"
          :entity="currentEntitySafe"
          :entity-index="currentEntityIndex"
          :columns="getEntityColumns(currentEntitySafe.name)"
          :loading-columns="loadingColumns[currentEntitySafe.name] || false"
          :projects="projects"
          :datasets="datasets"
          :loading-projects="loadingProjects"
          :loading-datasets="loadingDatasets"
          @name-change="handleEntityNameChange"
          @project-change="(projectKey) => handleProjectChange(currentEntitySafe, projectKey)"
          @dataset-change="handleDatasetChange(currentEntitySafe)"
          @open-values-modal="openValuesModal(currentEntitySafe, $event)"
          @delete="openDeleteEntityDialog(currentEntityIndex)"
        />
      </div>

      <!-- Entity list (when no entity selected but entities exist) -->
      <div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
        <div
          v-for="(entity, index) in store.draftSpec.entities"
          :key="entity.name"
          class="group relative flex flex-col items-start rounded-lg border bg-card p-4 text-left transition-colors hover:border-active cursor-pointer"
          @click="navigateToEntity(entity.name)"
        >
          <!-- Delete button (shows on hover) -->
          <Tooltip>
            <TooltipTrigger as-child>
              <button
                class="absolute right-2 top-2 rounded p-1.5 text-destructive opacity-0 transition-opacity hover:bg-destructive/10 group-hover:opacity-100"
                @click.stop="openDeleteEntityDialog(index)"
              >
                <i class="dku-icon-trash-16" />
              </button>
            </TooltipTrigger>
            <TooltipContent>Delete entity</TooltipContent>
          </Tooltip>

          <span class="font-medium">{{ entity.name }}</span>
          <p v-if="entity.description" class="mt-1 text-sm text-muted-foreground line-clamp-2">
            {{ entity.description }}
          </p>
          <div class="mt-2 flex flex-wrap gap-2 text-xs text-muted-foreground">
            <span>{{ entity.attributes.length }} attributes</span>
            <span v-if="entity.metrics.length">{{ entity.metrics.length }} metrics</span>
            <span v-if="entity.filters.length">{{ entity.filters.length }} filters</span>
            <span v-if="entity.primaryKey?.attributes?.length" class="text-primary">
              PK: {{ entity.primaryKey.attributes.join(', ') }}
            </span>
          </div>
        </div>
      </div>
      </div>
    </div>

    <!-- Dialogs -->
    <ConfirmDialog
      v-model:open="showDeleteEntityDialog"
      title="Delete Entity"
      :description="`Are you sure you want to delete '${entityToDelete?.name}'? This action cannot be undone.`"
      confirm-text="Delete"
      cancel-text="Cancel"
      variant="destructive"
      @confirm="handleConfirmDeleteEntity"
    />

    <!-- Add Entity Modal (Manual or Generate) -->
    <AddEntityModal
      :show="showAddEntityModal"
      @close="showAddEntityModal = false"
      @accept="handleEntityAccept"
    />

    <!-- Sample Values Modal -->
    <SampleValuesModal
      v-if="valuesModalEntity"
      :show="showValuesModal"
      :entity="valuesModalEntity"
      :model-id="modelId"
      :version-id="versionId"
      :sample-limit="getCurrentSampleLimit()"
      :sample-limit-configured="getCurrentSampleLimitConfigured()"
      :attr-index="valuesModalAttrIndex"
      @close="showValuesModal = false"
      @update:values-mode="handleValuesModeUpdate"
      @update:sample-limit="handleSampleLimitUpdate"
      @update:manual-values="handleManualValuesUpdate"
    />
  </div>
</template>
