import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { semanticModelsApi, versionsApi } from '@/api'
import type {
  SemanticModel,
  SemanticModelVersion,
  SemanticModelSpec,
  VersionCreate,
  GoldenQuery,
  GlossaryTerm,
  GlossaryBinding,
} from '@/types'

export const useSemanticModelsStore = defineStore('semantic-models', () => {
  // ==========================================================================
  // State
  // ==========================================================================

  const currentModel = ref<SemanticModel | null>(null)
  const currentVersion = ref<SemanticModelVersion | null>(null)

  // Draft state (shared across all editor views)
  const draftSpec = ref<SemanticModelSpec | null>(null)
  const draftGoldenQueries = ref<GoldenQuery[]>([])
  const draftGlossaryTerms = ref<GlossaryTerm[]>([])
  const draftGlossaryBindings = ref<GlossaryBinding[]>([])
  const draftVocabularyTermIds = ref<string[]>([])
  const draftIndexingSettings = ref<Record<string, unknown>>({})
  const draftPrivateEditorData = ref<Record<string, unknown>>({})
  const draftSqlGenerationConfig = ref<Record<string, unknown>>({})
  const savedSpecSnapshot = ref<string | null>(null)  // JSON snapshot for comparison
  const savedGoldenQueriesSnapshot = ref<string | null>(null)  // JSON snapshot for comparison
  const savedGlossaryTermsSnapshot = ref<string | null>(null)
  const savedGlossaryBindingsSnapshot = ref<string | null>(null)
  const savedVocabularyTermIdsSnapshot = ref<string | null>(null)
  const savedIndexingSettingsSnapshot = ref<string | null>(null)
  const savedPrivateEditorDataSnapshot = ref<string | null>(null)
  const savedSqlGenerationConfigSnapshot = ref<string | null>(null)
  const isSavingDraft = ref(false)
  const isIndexingStale = computed(() => {
    const draftFlag = draftPrivateEditorData.value?.indexingStale
    if (typeof draftFlag === 'boolean') {
      return draftFlag
    }
    return !!currentVersion.value?.privateEditorData?.indexingStale
  })

  function isSnapshotDirty(draftValue: unknown, savedSnapshot: string | null): boolean {
    if (!savedSnapshot) return false
    return JSON.stringify(draftValue) !== savedSnapshot
  }

  function cloneForDraft<T>(value: T): T {
    if (typeof structuredClone === 'function') {
      try {
        return structuredClone(value)
      } catch {
        // Fallback for non-cloneable payload shapes.
      }
    }
    return JSON.parse(JSON.stringify(value)) as T
  }

  const savedSpecParsed = computed<SemanticModelSpec | null>(() => {
    if (!savedSpecSnapshot.value) return null
    try {
      return JSON.parse(savedSpecSnapshot.value) as SemanticModelSpec
    } catch {
      return null
    }
  })

  const savedSqlGenerationConfigParsed = computed<Record<string, unknown>>(() => {
    if (!savedSqlGenerationConfigSnapshot.value) return {}
    try {
      return JSON.parse(savedSqlGenerationConfigSnapshot.value) as Record<string, unknown>
    } catch {
      return {}
    }
  })

  const isEntitiesDirty = computed(() => {
    if (!draftSpec.value || !savedSpecParsed.value) return false
    const draftEntities = draftSpec.value.entities || []
    const savedEntities = savedSpecParsed.value.entities || []
    return JSON.stringify(draftEntities) !== JSON.stringify(savedEntities)
  })

  const isRelationshipsDirty = computed(() => {
    if (!draftSpec.value || !savedSpecParsed.value) return false
    const draftRelationships = draftSpec.value.relationships || []
    const savedRelationships = savedSpecParsed.value.relationships || []
    return JSON.stringify(draftRelationships) !== JSON.stringify(savedRelationships)
  })

  const isInstructionsDirty = computed(() => {
    const draftInstructionsRaw = (draftSqlGenerationConfig.value || {}).instructions
    const savedInstructionsRaw = (savedSqlGenerationConfigParsed.value || {}).instructions
    const draftInstructions = typeof draftInstructionsRaw === 'string' ? draftInstructionsRaw : ''
    const savedInstructions = typeof savedInstructionsRaw === 'string' ? savedInstructionsRaw : ''
    return draftInstructions !== savedInstructions
  })

  const isGoldenQueriesDirty = computed(() => {
    return isSnapshotDirty(draftGoldenQueries.value, savedGoldenQueriesSnapshot.value)
  })

  const isGlossaryTermsDirty = computed(() => {
    return isSnapshotDirty(draftGlossaryTerms.value, savedGlossaryTermsSnapshot.value)
  })

  const isGlossaryBindingsDirty = computed(() => {
    return isSnapshotDirty(draftGlossaryBindings.value, savedGlossaryBindingsSnapshot.value)
  })

  const isVocabularyDirty = computed(() => {
    return isSnapshotDirty(draftVocabularyTermIds.value, savedVocabularyTermIdsSnapshot.value)
  })

  const isGlossaryDirty = computed(() => {
    return isGlossaryTermsDirty.value || isGlossaryBindingsDirty.value || isVocabularyDirty.value
  })

  const isIndexingDirty = computed(() => {
    return isSnapshotDirty(draftIndexingSettings.value, savedIndexingSettingsSnapshot.value)
  })

  const isPrivateEditorDataDirty = computed(() => {
    return isSnapshotDirty(draftPrivateEditorData.value, savedPrivateEditorDataSnapshot.value)
  })

  const isSqlGenerationConfigDirty = computed(() => {
    return isSnapshotDirty(draftSqlGenerationConfig.value, savedSqlGenerationConfigSnapshot.value)
  })

  const isPlaygroundDirty = computed(() => {
    const draftConfig = (draftSqlGenerationConfig.value || {}) as Record<string, unknown>
    const savedConfig = savedSqlGenerationConfigParsed.value || {}
    const playgroundKeys = [
      'max_rows_per_query',
      'agent_recursion_limit',
      'sources_records_limit',
    ]
    return playgroundKeys.some((key) => (draftConfig[key] ?? null) !== (savedConfig[key] ?? null))
  })

  const isSpecDirty = computed(() => {
    return isEntitiesDirty.value || isRelationshipsDirty.value
  })

  // Computed: check if draft differs from saved snapshot
  const hasUnsavedChanges = computed(() => {
    return (
      isSpecDirty.value ||
      isGoldenQueriesDirty.value ||
      isGlossaryDirty.value ||
      isIndexingDirty.value ||
      isPrivateEditorDataDirty.value ||
      isSqlGenerationConfigDirty.value
    )
  })

  // UI state for entity selection (used by sidebar to track focused entity)
  const selectedEntityId = ref<string | null>(null)

  const isLoading = ref(false)
  const error = ref<string | null>(null)

  // ==========================================================================
  // Getters
  // ==========================================================================

  /**
   * Get the active version of the current model.
   * Returns null if no active version is configured.
   */
  const activeVersion = computed(() => {
    if (!currentModel.value) return null
    const activeId = currentModel.value.activeVersionId
    if (!activeId) return null
    return currentModel.value.versions.find((v) => v.id === activeId) || null
  })

  /**
   * Check if a version is the active version.
   */
  function isActiveVersion(versionId: string): boolean {
    if (!currentModel.value) return false
    const activeId = currentModel.value.activeVersionId
    if (!activeId) return false
    return versionId === activeId
  }

  /**
   * Get the version number to load (for redirects and initial load).
   * Uses activeVersionId only.
   */
  const versionToLoad = computed(() => {
    if (!currentModel.value) return null
    return currentModel.value.activeVersionId ?? null
  })

  // ==========================================================================
  // Actions - Models
  // ==========================================================================

  async function loadModel(id: string) {
    isLoading.value = true
    error.value = null
    try {
      currentModel.value = await semanticModelsApi.get(id)
      return currentModel.value
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Failed to load model'
      throw e
    } finally {
      isLoading.value = false
    }
  }

  // ==========================================================================
  // Actions - Versions
  // ==========================================================================

  async function loadVersion(modelId: string, versionId: string) {
    isLoading.value = true
    error.value = null
    try {
      currentVersion.value = await versionsApi.get(modelId, versionId, { includeSpec: true })
      // Initialize draft from loaded version
      initDraft()
      return currentVersion.value
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Failed to load version'
      throw e
    } finally {
      isLoading.value = false
    }
  }

  async function createVersion(
    modelId: string,
    data?: VersionCreate,
    options?: { refresh?: boolean }
  ) {
    isLoading.value = true
    error.value = null
    try {
      const version = await versionsApi.create(modelId, data)
      const shouldRefresh = options?.refresh !== false
      if (shouldRefresh) {
        // Refresh model to update version list
        if (currentModel.value?.id === modelId) {
          await loadModel(modelId)
        }
      }
      return version
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Failed to create version'
      throw e
    } finally {
      isLoading.value = false
    }
  }

  async function setActiveVersion(modelId: string, versionId: string) {
    isLoading.value = true
    error.value = null
    try {
      const model = await semanticModelsApi.setActiveVersion(modelId, versionId)
      // Refresh model to update activeVersionId
      if (currentModel.value?.id === modelId) {
        currentModel.value = model
      }
      return model
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Failed to set active version'
      throw e
    } finally {
      isLoading.value = false
    }
  }

  async function deleteVersion(modelId: string, versionId: string) {
    isLoading.value = true
    error.value = null
    try {
      await versionsApi.delete(modelId, versionId)
      // Clear current version if it's the one being deleted
      if (currentVersion.value?.id === versionId) {
        currentVersion.value = null
      }
      // Refresh model to update version list
      if (currentModel.value?.id === modelId) {
        await loadModel(modelId)
      }
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Failed to delete version'
      throw e
    } finally {
      isLoading.value = false
    }
  }

  // ==========================================================================
  // Draft Management (shared across editor views)
  // ==========================================================================

  /**
   * Initialize the draft from the current version.
   * Called when loading a version or when draft needs to be reset.
   */
  function initDraft() {
    const specDraft: SemanticModelSpec = currentVersion.value
      ? {
          entities: currentVersion.value.entities || [],
          relationships: currentVersion.value.relationships || [],
        }
      : { entities: [], relationships: [] }
    draftSpec.value = cloneForDraft(specDraft)
    savedSpecSnapshot.value = JSON.stringify(specDraft)

    const goldenQueriesDraft = currentVersion.value?.goldenQueries || []
    draftGoldenQueries.value = cloneForDraft(goldenQueriesDraft)
    savedGoldenQueriesSnapshot.value = JSON.stringify(goldenQueriesDraft)

    const glossaryTermsDraft = currentVersion.value?.glossaryTerms || []
    draftGlossaryTerms.value = cloneForDraft(glossaryTermsDraft)
    savedGlossaryTermsSnapshot.value = JSON.stringify(glossaryTermsDraft)

    const glossaryBindingsDraft = currentVersion.value?.glossaryBindings || []
    draftGlossaryBindings.value = cloneForDraft(glossaryBindingsDraft)
    savedGlossaryBindingsSnapshot.value = JSON.stringify(glossaryBindingsDraft)

    // vocabularyTermIds is stored in sqlGenerationConfig for DSS persistence
    const vocabularyDraft = (currentVersion.value?.sqlGenerationConfig as Record<string, unknown>)?.vocabularyTermIds as string[] || []
    draftVocabularyTermIds.value = cloneForDraft(vocabularyDraft)
    savedVocabularyTermIdsSnapshot.value = JSON.stringify(vocabularyDraft)

    const indexingDraft = currentVersion.value?.indexingSettings || {}
    draftIndexingSettings.value = cloneForDraft(indexingDraft)
    savedIndexingSettingsSnapshot.value = JSON.stringify(indexingDraft)

    const privateDataDraft = currentVersion.value?.privateEditorData || {}
    draftPrivateEditorData.value = cloneForDraft(privateDataDraft)
    savedPrivateEditorDataSnapshot.value = JSON.stringify(privateDataDraft)

    const sqlGenerationConfigDraft = cloneForDraft(currentVersion.value?.sqlGenerationConfig || {})
    draftSqlGenerationConfig.value = sqlGenerationConfigDraft
    savedSqlGenerationConfigSnapshot.value = JSON.stringify(sqlGenerationConfigDraft)
  }

  function buildIndexingSignature(spec: SemanticModelSpec | null): string {
    const entities = (spec?.entities || []).map((entity) => {
      const attrs = (entity.attributes || []).map((attr) => {
        const manual = Array.isArray(attr.manualValues) ? [...attr.manualValues] : []
        manual.sort()
        const autoValuesLimit =
          (attr.sqlGenerationConfig as Record<string, unknown> | undefined)?.autoValuesLimit ?? null
        return {
          name: attr.name,
          resolveInUserRequests: !!attr.resolveInUserRequests,
          distinctValuesHandlingMode: attr.distinctValuesHandlingMode || 'NONE',
          manualValues: manual,
          autoValuesLimit,
        }
      })
      attrs.sort((a, b) => a.name.localeCompare(b.name))
      return {
        name: entity.name,
        type: entity.type,
        datasetRef: entity.datasetRef || null,
        attributes: attrs,
      }
    })
    entities.sort((a, b) => a.name.localeCompare(b.name))

    return JSON.stringify({ entities })
  }

  /**
   * Save the draft (spec + golden queries) to the server.
   */
  async function saveDraft(modelId: string, versionId: string) {
    if (!draftSpec.value) return

    isSavingDraft.value = true
    try {
      const savedSpec = savedSpecSnapshot.value ? JSON.parse(savedSpecSnapshot.value) : null

      const wasIndexingStale = !!draftPrivateEditorData.value?.indexingStale
      const savedSignature = buildIndexingSignature(savedSpec)
      const draftSignature = buildIndexingSignature(draftSpec.value)
      if (!wasIndexingStale && savedSignature !== draftSignature) {
        draftPrivateEditorData.value = {
          ...draftPrivateEditorData.value,
          indexingStale: true,
        }
      }

      // Save both spec and golden queries in one request
      // vocabularyTermIds is stored in sqlGenerationConfig for DSS persistence
      const sqlGenerationConfigWithVocabulary = {
        ...draftSqlGenerationConfig.value,
        vocabularyTermIds: draftVocabularyTermIds.value,
      }
      const version = await versionsApi.update(modelId, versionId, {
        version: {
          entities: draftSpec.value.entities,
          relationships: draftSpec.value.relationships,
          goldenQueries: draftGoldenQueries.value.map(gq => ({
            name: gq.name,
            description: gq.description,
            question: gq.question,
            generatedSql: gq.generatedSql,
            created: gq.created,
          })),
          glossaryTerms: draftGlossaryTerms.value,
          glossaryBindings: draftGlossaryBindings.value,
          indexingSettings: draftIndexingSettings.value,
          privateEditorData: draftPrivateEditorData.value,
          sqlGenerationConfig: sqlGenerationConfigWithVocabulary,
        },
      })

      // Update currentVersion
      if (currentVersion.value?.id === versionId) {
        currentVersion.value = version
      }

      if (version.privateEditorData) {
        draftPrivateEditorData.value = { ...version.privateEditorData }
      }

      // Update snapshots to match saved state
      savedSpecSnapshot.value = JSON.stringify(draftSpec.value)
      savedGoldenQueriesSnapshot.value = JSON.stringify(draftGoldenQueries.value)
      savedGlossaryTermsSnapshot.value = JSON.stringify(draftGlossaryTerms.value)
      savedGlossaryBindingsSnapshot.value = JSON.stringify(draftGlossaryBindings.value)
      savedVocabularyTermIdsSnapshot.value = JSON.stringify(draftVocabularyTermIds.value)
      savedIndexingSettingsSnapshot.value = JSON.stringify(draftIndexingSettings.value)
      savedPrivateEditorDataSnapshot.value = JSON.stringify(draftPrivateEditorData.value)
      savedSqlGenerationConfigSnapshot.value = JSON.stringify(draftSqlGenerationConfig.value)

      // Refresh model to update version list
      if (currentModel.value?.id === modelId) {
        await loadModel(modelId)
      }
    } finally {
      isSavingDraft.value = false
    }
  }

  /**
   * Discard draft changes and reset to the last saved state.
   */
  function discardDraft() {
    if (savedSpecSnapshot.value) {
      draftSpec.value = JSON.parse(savedSpecSnapshot.value)
    }
    if (savedGoldenQueriesSnapshot.value) {
      draftGoldenQueries.value = JSON.parse(savedGoldenQueriesSnapshot.value)
    }
    if (savedGlossaryTermsSnapshot.value) {
      draftGlossaryTerms.value = JSON.parse(savedGlossaryTermsSnapshot.value)
    }
    if (savedGlossaryBindingsSnapshot.value) {
      draftGlossaryBindings.value = JSON.parse(savedGlossaryBindingsSnapshot.value)
    }
    if (savedVocabularyTermIdsSnapshot.value) {
      draftVocabularyTermIds.value = JSON.parse(savedVocabularyTermIdsSnapshot.value)
    }
    if (savedIndexingSettingsSnapshot.value) {
      draftIndexingSettings.value = JSON.parse(savedIndexingSettingsSnapshot.value)
    }
    if (savedPrivateEditorDataSnapshot.value) {
      draftPrivateEditorData.value = JSON.parse(savedPrivateEditorDataSnapshot.value)
    }
    if (savedSqlGenerationConfigSnapshot.value) {
      draftSqlGenerationConfig.value = JSON.parse(savedSqlGenerationConfigSnapshot.value)
    }
  }

  // ==========================================================================
  // Helpers
  // ==========================================================================

  function clearCurrent() {
    currentModel.value = null
    currentVersion.value = null
    draftSpec.value = null
    draftGoldenQueries.value = []
    draftGlossaryTerms.value = []
    draftGlossaryBindings.value = []
    draftVocabularyTermIds.value = []
    draftIndexingSettings.value = {}
    draftPrivateEditorData.value = {}
    draftSqlGenerationConfig.value = {}
    savedSpecSnapshot.value = null
    savedGoldenQueriesSnapshot.value = null
    savedGlossaryTermsSnapshot.value = null
    savedGlossaryBindingsSnapshot.value = null
    savedVocabularyTermIdsSnapshot.value = null
    savedIndexingSettingsSnapshot.value = null
    savedPrivateEditorDataSnapshot.value = null
    savedSqlGenerationConfigSnapshot.value = null
  }

  function buildFreshIndexingPrivateData(
    base: Record<string, unknown> | undefined,
    lastIndexedAt?: string | null,
    lastIndexedEmbeddingLlmId?: string | null
  ): Record<string, unknown> {
    const next: Record<string, unknown> = {
      ...(base || {}),
      indexingStale: false,
      ...(lastIndexedAt ? { lastIndexedAt } : {}),
    }
    if (lastIndexedEmbeddingLlmId !== undefined) {
      if (lastIndexedEmbeddingLlmId) {
        next.lastIndexedEmbeddingLlmId = lastIndexedEmbeddingLlmId
      } else {
        delete (next as Record<string, unknown>).lastIndexedEmbeddingLlmId
      }
    }
    return next
  }

  function markIndexingFresh(lastIndexedAt?: string | null, lastIndexedEmbeddingLlmId?: string | null) {
    const nextPrivate = buildFreshIndexingPrivateData(
      draftPrivateEditorData.value,
      lastIndexedAt,
      lastIndexedEmbeddingLlmId
    )
    draftPrivateEditorData.value = nextPrivate
    savedPrivateEditorDataSnapshot.value = JSON.stringify(draftPrivateEditorData.value)
    if (currentVersion.value) {
      const nextCurrentPrivate = buildFreshIndexingPrivateData(
        currentVersion.value.privateEditorData,
        lastIndexedAt,
        lastIndexedEmbeddingLlmId
      )
      currentVersion.value.privateEditorData = nextCurrentPrivate
    }
  }

  function clearError() {
    error.value = null
  }

  // ==========================================================================
  // Return
  // ==========================================================================

  return {
    // State
    currentModel,
    currentVersion,
    draftSpec,
    draftGoldenQueries,
    draftGlossaryTerms,
    draftGlossaryBindings,
    draftVocabularyTermIds,
    draftIndexingSettings,
    draftPrivateEditorData,
    draftSqlGenerationConfig,
    hasUnsavedChanges,
    isSavingDraft,
    isIndexingStale,
    isEntitiesDirty,
    isRelationshipsDirty,
    isInstructionsDirty,
    isGlossaryDirty,
    isGoldenQueriesDirty,
    isIndexingDirty,
    isPlaygroundDirty,
    selectedEntityId,
    isLoading,
    error,

    // Getters
    activeVersion,
    versionToLoad,
    isActiveVersion,

    // Actions - Models
    loadModel,

    // Actions - Versions
    loadVersion,
    createVersion,
    setActiveVersion,
    deleteVersion,

    // Draft Management
    initDraft,
    saveDraft,
    discardDraft,

    // Helpers
    clearCurrent,
    clearError,
    markIndexingFresh,
  }
})
