<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useRoute } from 'vue-router'
import { useAppStore } from '@/stores/app'
import { useSemanticModelsStore } from '@/stores/semantic-models'
import { useRouteVersionId } from '@/composables/useRouteVersionId'
import { playgroundApi, dssResourcesApi, versionsApi } from '@/api'
import type { DSSLLM } from '@/types'
import { toast } from 'vue-sonner'
import GroupedSearchSelect from '@/components/GroupedSearchSelect.vue'
import type { GroupedOption } from '@/components/GroupedSearchSelect.vue'
import PageHeader from '@/components/PageHeader.vue'
import DkuSpinner from '@/components/DkuSpinner.vue'
import type { PlaygroundFullResponse } from '@/types'
import MarkdownIt from 'markdown-it'
import DOMPurify from 'dompurify'

// Markdown renderer with secure defaults
const md = new MarkdownIt({
  html: false,      // Disable HTML tags in source (secure)
  linkify: true,    // Auto-convert URLs to links
  breaks: true,     // Convert \n to <br>
})

const route = useRoute()
const appStore = useAppStore()
const modelsStore = useSemanticModelsStore()
const { selectedLlmId, selectedEmbeddingLlmId } = storeToRefs(appStore)
const { draftSqlGenerationConfig, hasUnsavedChanges, isSavingDraft } = storeToRefs(modelsStore)
const routeVersionId = useRouteVersionId(route)

// Form state
const question = ref('')
const generatedSql = ref('')

// Available LLMs from DSS
const availableLlms = ref<DSSLLM[]>([])

// Execution state
const isGenerating = ref(false)
const isExecuting = ref(false)
const isSaving = ref(false)
const fullResponse = ref<PlaygroundFullResponse | null>(null)
const error = ref<string | null>(null)

// Collapsible panels state
const showCorrections = ref(false)
const showGlossary = ref(false)
const showSources = ref(false)
const showAdvanced = ref(false)
const traceCopied = ref(false)
let traceCopiedTimeout: number | null = null

// Save dialog
const showSaveDialog = ref(false)
const saveQueryName = ref('')

const effectiveModelId = computed(() => {
  const routeModel = route.params.id as string | undefined
  return routeModel || appStore.appConfig?.semantic_model_id || ''
})

const effectiveVersionId = computed(() => {
  return routeVersionId.value || modelsStore.activeVersion?.id || ''
})

// Version ID for header display
const headerVersionId = computed(() => {
  return effectiveVersionId.value
})

// LLM options for GroupedSearchSelect
const llmOptions = computed((): GroupedOption[] => {
  return availableLlms.value.map(llm => ({
    value: llm.id,
    label: llm.label || llm.id,
    type: llm.type || 'Unknown',
    connection: llm.connection,
  }))
})

// Computed: Count logical sources (SQL+RECORDS = 1 source, not 2)
// A source is a (GENERATED_SQL_QUERY, RECORDS) pair
const sourcesCount = computed(() => {
  const items = fullResponse.value?.sources?.items || []
  // Count SQL queries - each SQL query represents one logical source
  const sqlCount = items.filter(item => item.type === 'GENERATED_SQL_QUERY').length
  // If no SQL, count other items individually
  if (sqlCount === 0) {
    return items.length
  }
  // Each SQL query is a logical source (RECORDS follows SQL)
  return sqlCount
})

// Rendered markdown response (sanitized for safety)
const renderedResponse = computed(() => {
  if (!fullResponse.value?.response) return ''
  const html = md.render(fullResponse.value.response)
  return DOMPurify.sanitize(html)
})

function getSqlConfigNumber(key: string, fallback: number): number {
  const raw = (draftSqlGenerationConfig.value as Record<string, any>)[key]
  return typeof raw === 'number' && Number.isFinite(raw) ? raw : fallback
}

function setSqlConfigNumber(key: string, value: number | null) {
  if (typeof value === 'number' && Number.isFinite(value)) {
    ;(draftSqlGenerationConfig.value as Record<string, any>)[key] = value
  } else {
    delete (draftSqlGenerationConfig.value as Record<string, any>)[key]
  }
}


const maxRowsPerQuery = computed<number>({
  get: () => getSqlConfigNumber('max_rows_per_query', appStore.appConfig?.max_rows_per_query ?? 1000),
  set: (val) => setSqlConfigNumber('max_rows_per_query', val),
})

const agentRecursionLimit = computed<number>({
  get: () => getSqlConfigNumber('agent_recursion_limit', appStore.appConfig?.agent_recursion_limit ?? 100),
  set: (val) => setSqlConfigNumber('agent_recursion_limit', val),
})

const sourcesRecordsLimit = computed<number>({
  get: () => getSqlConfigNumber('sources_records_limit', appStore.appConfig?.sources_records_limit ?? 50),
  set: (val) => setSqlConfigNumber('sources_records_limit', val),
})

onMounted(async () => {
  // Config is loaded by App.vue - loadConfig is idempotent so safe to call
  await appStore.loadConfig()

  // Fetch available LLMs from DSS
  try {
    availableLlms.value = await dssResourcesApi.listLlms()
    appStore.ensureDefaultLlm(availableLlms.value)
  } catch (e) {
    console.error('Failed to load LLMs:', e)
    toast.error('Failed to load available LLMs')
  }

  // Question can come from query params
  if (route.query.question) {
    question.value = route.query.question as string
  }
})

// Reset execution state when changing model or version
function resetExecutionState() {
  generatedSql.value = ''
  fullResponse.value = null
  error.value = null
  showCorrections.value = false
  showGlossary.value = false
  showSources.value = false
}

// Load model details when route/app context changes
watch(effectiveModelId, async (newId) => {
  resetExecutionState()
  if (newId) {
    await modelsStore.loadModel(newId)
  }
}, { immediate: true })

// Load version details when model/version changes
watch(
  [effectiveModelId, effectiveVersionId],
  async ([modelId, versionId]) => {
    if (modelId && versionId) {
      await modelsStore.loadVersion(modelId, versionId)
      syncEmbeddingSelectionFromVersion()
    }
  },
  { immediate: true }
)

// Reset execution state when version changes
watch(effectiveVersionId, () => {
  resetExecutionState()
})

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

async function handleRunQuery() {
  if (!effectiveModelId.value || !question.value.trim()) {
    toast.error('Please enter a question in a model context')
    return
  }

  isGenerating.value = true
  isExecuting.value = true
  error.value = null
  fullResponse.value = null
  generatedSql.value = ''  // Reset to prevent showing old SQL

  try {
    const response = await playgroundApi.execute({
      semantic_model_id: effectiveModelId.value,
      version_id: effectiveVersionId.value || undefined,
      llm_id: selectedLlmId.value || undefined,
      embedding_llm_id: selectedEmbeddingLlmId.value || undefined,
      max_rows_per_query: maxRowsPerQuery.value,
      agent_recursion_limit: agentRecursionLimit.value,
      sources_records_limit: sourcesRecordsLimit.value,
      question: question.value,
    })

    fullResponse.value = response
    generatedSql.value = response.sql || ''

    // Expand panels only if they have data
    showCorrections.value = (response.corrections?.length ?? 0) > 0
    showGlossary.value = (response.glossary_terms_used?.length ?? 0) > 0
    showSources.value = (response.sources?.items?.length ?? 0) > 0

    const duration = response.duration_ms ? `${(response.duration_ms / 1000).toFixed(1)}s` : ''

    // Show appropriate toast based on whether SQL was executed
    if (response.success) {
      toast.success(`Query completed${duration ? ` in ${duration}` : ''}`)
    } else if (response.error) {
      error.value = response.error
      toast.error('Query failed')
    } else if (response.response) {
      // Agent provided an answer without SQL (e.g., general knowledge question)
      toast.info(`Answered${duration ? ` in ${duration}` : ''}`)
    } else {
      // True failure - set error for display
      error.value = 'Query could not be answered'
      toast.error('Query failed')
    }
  } catch (e: any) {
    // Extract error message from agent response
    const errorMessage = e.message || 'Query failed'
    error.value = errorMessage

    // Try to capture partial data from the error response
    const originalError = e.originalError
    if (originalError?.response?.data) {
      const data = originalError.response.data
      if (data.sql) {
        generatedSql.value = data.sql
      }
      // Store full response even on error (for sources/artifacts inspection)
      if (data.sources) {
        fullResponse.value = data as PlaygroundFullResponse
      }
    }

    toast.error('Query failed')
  } finally {
    isGenerating.value = false
    isExecuting.value = false
  }
}

async function handleSave() {
  if (!effectiveModelId.value || !effectiveVersionId.value) {
    toast.error('No active version available to save')
    return
  }

  try {
    await modelsStore.saveDraft(effectiveModelId.value, effectiveVersionId.value)
    toast.success('Version saved')
  } catch (e) {
    toast.error('Failed to save', { description: e instanceof Error ? e.message : 'Unknown error' })
  }
}

function handleDiscard() {
  modelsStore.discardDraft()
}

async function handleSaveAsGoldenQuery() {
  if (!saveQueryName.value.trim()) {
    toast.error('Please enter a name')
    return
  }

  if (!effectiveModelId.value || !effectiveVersionId.value) {
    toast.error('No active version available to save the golden query')
    return
  }

  isSaving.value = true
  try {
    const version = await versionsApi.get(
      effectiveModelId.value,
      effectiveVersionId.value,
      { includeSpec: false }
    )
    const existing = version.goldenQueries || []
    const nextQueries = [
      ...existing.map(gq => ({
        name: gq.name,
        description: gq.description,
        question: gq.question,
        generatedSql: gq.generatedSql,
        created: gq.created,
      })),
      {
        name: saveQueryName.value.trim(),
        question: question.value,
        generatedSql: generatedSql.value,
        created: { on: new Date().toISOString() },
      },
    ]

    await versionsApi.update(effectiveModelId.value, effectiveVersionId.value, {
      version: { goldenQueries: nextQueries },
    })
    toast.success('Saved as golden query')
    showSaveDialog.value = false
    saveQueryName.value = ''
  } catch (e) {
    toast.error('Failed to save golden query')
  } finally {
    isSaving.value = false
  }
}

// Helper to format source item type label
function formatSourceType(type: string): string {
  switch (type) {
    case 'GENERATED_SQL_QUERY':
      return 'Generated SQL Query'
    case 'RECORDS':
      return 'Records'
    case 'INFO':
      return 'Info'
    case 'ERROR':
      return 'Error'
    default:
      return type.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
  }
}

// Helper to get header background class based on type
function getHeaderBgClass(type: string): string {
  switch (type) {
    case 'GENERATED_SQL_QUERY':
      return 'bg-purple-100 dark:bg-purple-900/30'
    case 'RECORDS':
      return 'bg-green-100 dark:bg-green-900/30'
    case 'ERROR':
      return 'bg-red-100 dark:bg-red-900/30'
    default:
      return 'bg-muted/50'
  }
}

// Copy text to clipboard
async function copyToClipboard(text: string) {
  try {
    await navigator.clipboard.writeText(text)
    toast.success('Copied to clipboard')
  } catch {
    toast.error('Failed to copy')
  }
}

async function copyTrace() {
  const tracePayload = fullResponse.value?.trace
  const span = (tracePayload as any)?.span
  if (!span) {
    toast.error('No trace available')
    return
  }
  const text = JSON.stringify(span, null, 2)
  await copyToClipboard(text)
  traceCopied.value = true
  if (traceCopiedTimeout) {
    window.clearTimeout(traceCopiedTimeout)
  }
  traceCopiedTimeout = window.setTimeout(() => {
    traceCopied.value = false
  }, 2000)
}
</script>

<template>
  <div class="flex h-full flex-col">
    <PageHeader
      :version-id="headerVersionId"
      title="Playground"
      description="Test your semantic models by generating and executing SQL queries."
      show-save-discard
      :has-unsaved-changes="hasUnsavedChanges"
      :is-saving="isSavingDraft"
      @save="handleSave"
      @discard="handleDiscard"
    />

    <!-- Main content -->
    <div class="flex-1 overflow-auto p-6">
      <div class="mx-auto max-w-6xl space-y-6">
        <!-- Query input -->
        <div class="space-y-4 p-4">

          <!-- LLM selector -->
          <div class="max-w-sm">
            <label class="body-444 mb-1 block">LLM</label>
            <GroupedSearchSelect
              :model-value="selectedLlmId"
              :options="llmOptions"
              placeholder="Select LLM..."
              search-placeholder="Filter..."
              empty-text="No LLMs found"
              @update:model-value="(val: string) => selectedLlmId = val"
            />
          </div>

          <!-- Question input -->
          <div>
            <label class="body-444 mb-1 block">Question</label>
            <textarea
              v-model="question"
              class="min-h-[100px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground"
              placeholder="Enter your natural language question..."
            ></textarea>
          </div>

          <!-- Run Query button -->
          <div>
            <button
              class="btn-primary disabled:opacity-50"
              :disabled="isGenerating || isExecuting || !effectiveModelId || !question.trim()"
              @click="handleRunQuery"
            >
              Run Query
            </button>
          </div>

          <!-- SQL generation settings -->
          <div>
            <button
              class="inline-flex items-center gap-2 text-sm font-medium"
              @click="showAdvanced = !showAdvanced"
            >
              <i v-if="!showAdvanced" class="dku-icon-chevron-right-16" />
              <i v-else class="dku-icon-chevron-down-16" />
              <span class="body-444">SQL Generation Settings</span>
            </button>
            <div v-if="showAdvanced" class="mt-3 space-y-4">
              <div class="grid gap-4 md:grid-cols-2">
                <div>
                  <label class="body-444 mb-1 block">Max Rows Per Query</label>
                  <input
                    v-model.number="maxRowsPerQuery"
                    type="number"
                    min="1"
                    class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
                  />
                </div>
                <div>
                  <label class="body-444 mb-1 block">Agent Recursion Limit</label>
                  <input
                    v-model.number="agentRecursionLimit"
                    type="number"
                    min="1"
                    class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
                  />
                </div>
                <div>
                  <label class="body-444 mb-1 block">Max Rows in Sources</label>
                  <input
                    v-model.number="sourcesRecordsLimit"
                    type="number"
                    min="1"
                    class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
                  />
                </div>
              </div>
            </div>
          </div>
        </div>

        <!-- Results -->
        <div class="space-y-4">
          <!-- Answer section -->
          <div v-if="fullResponse?.response && !error">
            <p class="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">Answer</p>
            <div
              class="rounded-lg bg-muted/50 p-3 prose prose-sm max-w-none"
              v-html="renderedResponse"
            ></div>
          </div>

          <!-- Save as Golden Query button -->
          <div v-if="generatedSql && effectiveVersionId && fullResponse?.response && !error" class="flex justify-end">
            <button
              class="btn-secondary inline-flex items-center gap-2"
              @click="showSaveDialog = true"
            >
              <i class="dku-icon-save-16" />
              Save as Golden Query
            </button>
          </div>

        <!-- Error -->
        <div
          v-if="error"
          class="flex items-start gap-2 rounded-lg border border-destructive/50 bg-destructive/10 p-3"
        >
          <i class="dku-icon-error-circle-outline-20 text-destructive" />
          <p class="text-sm text-destructive">{{ error }}</p>
        </div>

        <!-- Empty state -->
        <div
          v-if="!fullResponse && !error"
          class="flex h-48 flex-col items-center justify-center rounded-lg"
        >
          <DkuSpinner v-if="isGenerating || isExecuting" size="xl" />
          <i v-else class="dku-icon-database-sql-32 text-muted-foreground" />
          <p class="mt-2 text-sm text-muted-foreground">
            {{ isGenerating || isExecuting ? 'Processing query' : 'Run a query to see results here' }}
          </p>
        </div>

        <!-- Agent Tool Panels (Sources, Artifacts) -->
        <div v-if="fullResponse" class="space-y-3 border-t pt-4">
          <div class="flex items-center justify-between">
            <p class="text-xs font-medium text-muted-foreground uppercase tracking-wide">Details</p>
            <button
              v-if="fullResponse?.trace"
              class="inline-flex items-center p-1 text-primary hover:text-primary/80"
              title="Copy trace to clipboard"
              aria-label="Copy trace to clipboard"
              @click="copyTrace"
            >
              <i :class="traceCopied ? 'dku-icon-checkmark-16' : 'dku-icon-trace-16'" />
            </button>
          </div>

          <!-- Value Corrections Panel (always show) -->
          <div>
            <button
              class="inline-flex items-center gap-2 text-sm font-medium"
              @click="showCorrections = !showCorrections"
            >
              <span class="body-444">Value Corrections ({{ fullResponse.corrections?.length || 0 }})</span>
              <i v-if="!showCorrections" class="dku-icon-chevron-right-16" />
              <i v-else class="dku-icon-chevron-down-16" />
            </button>
            <div v-if="showCorrections" class="mt-3 space-y-2">
              <p v-if="!fullResponse.corrections?.length" class="text-xs text-muted-foreground">
                No value corrections needed
              </p>
              <div
                v-for="(correction, idx) in fullResponse.corrections"
                :key="idx"
                class="flex items-center gap-2 text-sm bg-muted/50 rounded px-2 py-1.5"
              >
                <span class="font-medium">"{{ correction.original }}"</span>
                <span class="text-muted-foreground">→</span>
                <span class="font-medium">"{{ correction.resolved }}"</span>
                <span class="text-xs text-muted-foreground ml-auto">
                  <template v-if="correction.llm_inferred">
                    verified LLM suggestion from {{ correction.entity_id }}.{{ correction.attribute }}
                  </template>
                  <template v-else>
                    {{ correction.match_type }} {{ Math.round(correction.score) }}% from {{ correction.entity_id }}.{{ correction.attribute }}
                  </template>
                </span>
              </div>
            </div>
          </div>

          <!-- Glossary Terms Panel (always show) -->
          <div>
            <button
              class="inline-flex items-center gap-2 text-sm font-medium"
              @click="showGlossary = !showGlossary"
            >
              <span class="body-444">Glossary Terms ({{ fullResponse.glossary_terms_used?.length || 0 }})</span>
              <i v-if="!showGlossary" class="dku-icon-chevron-right-16" />
              <i v-else class="dku-icon-chevron-down-16" />
            </button>
            <div v-if="showGlossary" class="mt-3 space-y-2">
              <p v-if="!fullResponse.glossary_terms_used?.length" class="text-xs text-muted-foreground">
                No glossary terms matched
              </p>
              <div
                v-for="(term, idx) in fullResponse.glossary_terms_used"
                :key="idx"
                class="bg-indigo-50 dark:bg-indigo-950 rounded px-2 py-1.5"
              >
                <div class="flex items-center gap-2">
                  <span class="font-medium text-indigo-700 dark:text-indigo-400">{{ term.term }}</span>
                  <span v-if="term.synonyms?.length" class="text-xs text-muted-foreground">
                    ({{ term.synonyms.join(', ') }})
                  </span>
                  <span class="text-xs text-muted-foreground ml-auto">
                    <template v-if="term.entity_name">
                      {{ term.entity_name }}<template v-if="term.target_name">.{{ term.target_name }}</template>
                    </template>
                  </span>
                </div>
                <p v-if="term.description" class="text-xs text-muted-foreground mt-1">{{ term.description }}</p>
              </div>
            </div>
          </div>

          <!-- Sources Panel -->
          <div v-if="fullResponse.sources">
            <button
              class="inline-flex items-center gap-2 text-sm font-medium"
              @click="showSources = !showSources"
            >
              <span class="body-444">Sources ({{ sourcesCount }})</span>
              <i v-if="!showSources" class="dku-icon-chevron-right-16" />
              <i v-else class="dku-icon-chevron-down-16" />
            </button>
            <div v-if="showSources" class="mt-3 space-y-2">
              <div
                v-for="(item, idx) in fullResponse.sources.items"
                :key="idx"
                class="rounded border border-border"
              >
                <div :class="[getHeaderBgClass(item.type), 'flex items-center justify-between rounded-t px-2 py-1']">
                  <span class="text-xs font-medium">
                    {{ formatSourceType(item.type) }}
                  </span>
                  <button
                    v-if="item.performedQuery"
                    class="p-1 text-muted-foreground hover:text-foreground"
                    @click="copyToClipboard(item.performedQuery)"
                  >
                    <i class="dku-icon-copy-16" />
                  </button>
                </div>
                <div class="px-2 py-1">
                  <p v-if="item.textSnippet" class="text-xs">{{ item.textSnippet }}</p>
                  <pre v-if="item.performedQuery" class="text-xs bg-muted/30 p-1 rounded mt-1 overflow-x-auto">{{ item.performedQuery }}</pre>
                  <!-- RECORDS table in Sources (limited rows for LLM context preview) -->
                  <div v-if="item.records">
                    <div class="text-xs text-muted-foreground mb-1">
                      {{ item.records.columns?.length }} columns, {{ item.records.data?.length }} rows
                      <span v-if="item.truncated" class="text-amber-600 dark:text-amber-400"> (truncated from {{ item.total_rows }})</span>
                    </div>
                    <div class="max-h-[150px] overflow-auto rounded border border-border">
                      <table class="w-full text-xs">
                        <thead class="sticky top-0 bg-muted">
                          <tr>
                            <th
                              v-for="col in item.records.columns"
                              :key="col"
                              class="border-b border-border px-2 py-1 text-left font-medium"
                            >
                              {{ col }}
                            </th>
                          </tr>
                        </thead>
                        <tbody>
                          <tr v-for="(row, rIdx) in item.records.data" :key="rIdx" class="hover:bg-muted/50">
                            <td v-for="(cell, cIdx) in row" :key="cIdx" class="border-b border-border px-2 py-1">
                              {{ cell }}
                            </td>
                          </tr>
                        </tbody>
                      </table>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>

        </div>
        </div>
      </div>
    </div>

    <!-- Save Dialog -->
    <Teleport to="body">
      <div
        v-if="showSaveDialog"
        class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
              >
        <div class="w-full max-w-md rounded-lg bg-background p-6 shadow-lg">
          <h2 class="text-lg font-semibold">Save as Golden Query</h2>
          <p class="mt-1 text-sm text-muted-foreground">
            Save this question and SQL as a golden query for {{ effectiveVersionId }}.
          </p>

          <form class="mt-4 space-y-4" @submit.prevent="handleSaveAsGoldenQuery">
            <div>
              <label class="mb-1 block text-sm font-medium">Name</label>
              <input
                v-model="saveQueryName"
                type="text"
                class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
                placeholder="e.g., Total sales by country"
                required
              />
            </div>

            <div class="flex justify-end gap-2">
              <button
                type="button"
                class="btn-secondary"
                @click="showSaveDialog = false; saveQueryName = ''"
              >
                Cancel
              </button>
              <button
                type="submit"
                class="btn-primary inline-flex items-center gap-2 disabled:opacity-50"
                :disabled="isSaving"
              >
                <DkuSpinner v-if="isSaving" size="md" />
                Save
              </button>
            </div>
          </form>
        </div>
      </div>
    </Teleport>
  </div>
</template>
