<template>
  <el-dialog
    v-model="visible"
    :title="isEdit ? t('providers.editWorkflowDialogTitle') : t('providers.workflowDialogTitle')"
    width="960px"
    destroy-on-close
    @closed="onClosed"
  >
    <el-form label-width="100px">
      <el-form-item :label="t('providers.ingressModelId')" required>
        <el-input v-model="form.model_id" placeholder="luminary/flux-sdxl" />
        <p class="form-hint">{{ t('providers.ingressModelIdHint') }}</p>
      </el-form-item>
      <el-form-item :label="t('providers.displayName')">
        <el-input v-model="form.display_name" />
      </el-form-item>
      <el-form-item :label="t('providers.workflowType')">
        <el-select v-model="form.workflow_type" @change="onTypeChange">
          <el-option :label="t('providers.txt2img')" value="txt2img" />
          <el-option :label="t('providers.img2img')" value="img2img" />
        </el-select>
      </el-form-item>
      <el-form-item :label="t('providers.workflowJson')">
        <el-input v-model="form.workflow_json" type="textarea" :rows="8" placeholder="{ ... ComfyUI API Format ... }" />
      </el-form-item>
      <el-form-item>
        <el-button :loading="analyzing" @click="analyze">{{ t('providers.analyzeWorkflow') }}</el-button>
      </el-form-item>
    </el-form>

    <el-table v-if="bindings.length" :data="bindings" size="small" class="u-mt-2">
      <el-table-column prop="param" :label="t('providers.paramName')" width="130" />
      <el-table-column :label="t('providers.bindEnabled')" width="80">
        <template #default="{ row }">
          <el-switch v-model="row.enabled" :disabled="row.required" />
        </template>
      </el-table-column>
      <el-table-column :label="t('providers.targetNode')" min-width="160">
        <template #default="{ row }">
          <el-select v-model="row.node" clearable filterable :disabled="!row.enabled" @change="onNodeChange(row)">
            <el-option
              v-for="n in nodes"
              :key="n.id"
              :label="`${n.id} — ${n.class_type}`"
              :value="n.id"
            />
          </el-select>
        </template>
      </el-table-column>
      <el-table-column :label="t('providers.targetField')" min-width="120">
        <template #default="{ row }">
          <el-select v-model="row.field" clearable :disabled="!row.enabled || !row.node">
            <el-option v-for="f in fieldsFor(row.node)" :key="f" :label="f" :value="f" />
          </el-select>
        </template>
      </el-table-column>
      <el-table-column :label="t('providers.paramDefault')" min-width="130">
        <template #default="{ row }">
          <el-input-number
            v-if="row.paramType !== 'string'"
            v-model="row.defaultValue"
            :disabled="!row.enabled"
            controls-position="right"
            class="w-full"
          />
          <el-input
            v-else
            v-model="row.defaultValue"
            :disabled="!row.enabled"
            clearable
          />
        </template>
      </el-table-column>
    </el-table>
    <p v-if="bindings.length" class="form-hint u-mt-2">{{ t('providers.paramDefaultHint') }}</p>

    <template #footer>
      <el-button @click="visible = false">{{ t('common.cancel') }}</el-button>
      <el-button type="primary" :loading="saving" @click="save">{{ t('common.save') }}</el-button>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import api, { type ModelEntry } from '@/api/client'

const { t } = useI18n()

interface WorkflowNode {
  id: string
  class_type: string
  injectable_fields: string[]
}

interface ParamMeta {
  name: string
  type: 'string' | 'int' | 'float'
  default?: string | number
}

interface BindingRow {
  param: string
  paramType: 'string' | 'int' | 'float'
  required: boolean
  enabled: boolean
  node: string
  field: string
  defaultValue: string | number | null
}

interface NodeFieldBinding {
  node: string
  field: string
}

const props = defineProps<{
  providerId: number
  model?: ModelEntry | null
}>()

const emit = defineEmits<{ saved: [] }>()

const visible = defineModel<boolean>({ required: true })
const analyzing = ref(false)
const saving = ref(false)
const nodes = ref<WorkflowNode[]>([])
const bindings = ref<BindingRow[]>([])
const paramMetaByType = ref<Record<string, ParamMeta[]>>({})

const form = ref({
  model_id: '',
  display_name: '',
  workflow_type: 'txt2img' as 'txt2img' | 'img2img',
  workflow_json: '',
})

const isEdit = computed(() => !!props.model?.id)

const requiredParams = computed(() =>
  form.value.workflow_type === 'img2img' ? ['prompt', 'image'] : ['prompt'],
)

function reset() {
  form.value = { model_id: '', display_name: '', workflow_type: 'txt2img', workflow_json: '' }
  nodes.value = []
  bindings.value = []
}

function onClosed() {
  reset()
}

async function ensureParamMeta() {
  if (Object.keys(paramMetaByType.value).length > 0) return
  const res = await api.get('/workflows/param-meta')
  paramMetaByType.value = {
    txt2img: res.data.txt2img || [],
    img2img: res.data.img2img || [],
  }
}

function systemDefault(param: string): string | number | undefined {
  const defs = paramMetaByType.value[form.value.workflow_type] || []
  return defs.find(d => d.name === param)?.default
}

function parseBindingJSON(raw?: string): Record<string, NodeFieldBinding> {
  if (!raw) return {}
  try {
    const parsed = JSON.parse(raw)
    return parsed && typeof parsed === 'object' ? parsed : {}
  } catch {
    return {}
  }
}

function parseDefaultsJSON(raw?: string): Record<string, string | number> {
  if (!raw) return {}
  try {
    const parsed = JSON.parse(raw)
    if (!parsed || typeof parsed !== 'object') return {}
    return parsed as Record<string, string | number>
  } catch {
    return {}
  }
}

function initialDefaultValue(
  param: string,
  paramType: 'string' | 'int' | 'float',
  saved: Record<string, string | number>,
  workflowDefault: unknown,
): string | number | null {
  if (saved[param] !== undefined && saved[param] !== null) {
    return saved[param]
  }
  if (workflowDefault !== undefined && workflowDefault !== null && workflowDefault !== '') {
    if (paramType === 'string') return String(workflowDefault)
    if (paramType === 'int') return Number(workflowDefault)
    return Number(workflowDefault)
  }
  const builtin = systemDefault(param)
  if (builtin !== undefined) return builtin
  return paramType === 'string' ? '' : null
}

function applySavedBindings(savedBindings: Record<string, NodeFieldBinding>) {
  for (const row of bindings.value) {
    const hit = savedBindings[row.param]
    if (hit?.node && hit?.field) {
      row.enabled = true
      row.node = hit.node
      row.field = hit.field
    }
  }
}

function fieldsFor(nodeId: string) {
  return nodes.value.find(n => n.id === nodeId)?.injectable_fields || []
}

function onNodeChange(row: BindingRow) {
  const fields = fieldsFor(row.node)
  if (!fields.includes(row.field)) row.field = fields[0] || ''
}

async function onTypeChange() {
  if (form.value.workflow_json) await analyze()
}

function snapshotState() {
  return {
    bindings: buildBindingMap(),
    defaults: buildDefaultsMap(),
  }
}

async function analyze() {
  if (!form.value.workflow_json.trim()) {
    ElMessage.warning(t('providers.workflowJsonRequired'))
    return
  }
  const saved = snapshotState()
  analyzing.value = true
  try {
    await ensureParamMeta()
    const res = await api.post('/workflows/analyze', {
      workflow_json: form.value.workflow_json,
      workflow_type: form.value.workflow_type,
    })
    nodes.value = res.data.nodes || []
    bindings.value = (res.data.suggestions || []).map((s: any) => {
      const paramType = (s.type || 'string') as 'string' | 'int' | 'float'
      return {
        param: s.param,
        paramType,
        required: requiredParams.value.includes(s.param),
        enabled: requiredParams.value.includes(s.param) || !!(s.node && s.field),
        node: s.node || '',
        field: s.field || '',
        defaultValue: initialDefaultValue(s.param, paramType, saved.defaults, s.default_from_workflow),
      }
    })
    applySavedBindings(saved.bindings)
    for (const row of bindings.value) {
      if (saved.defaults[row.param] !== undefined) {
        row.defaultValue = saved.defaults[row.param]
      }
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || t('providers.analyzeFailed'))
  } finally {
    analyzing.value = false
  }
}

function buildBindingMap() {
  const out: Record<string, NodeFieldBinding> = {}
  for (const row of bindings.value) {
    if (!row.enabled || !row.node || !row.field) continue
    out[row.param] = { node: row.node, field: row.field }
  }
  return out
}

function buildDefaultsMap() {
  const out: Record<string, string | number> = {}
  for (const row of bindings.value) {
    if (!row.enabled) continue
    if (row.defaultValue === null || row.defaultValue === '') continue
    if (row.paramType === 'int') {
      out[row.param] = Number(row.defaultValue)
    } else if (row.paramType === 'float') {
      out[row.param] = Number(row.defaultValue)
    } else {
      out[row.param] = String(row.defaultValue)
    }
  }
  return out
}

async function loadModel(m: ModelEntry) {
  await ensureParamMeta()
  form.value = {
    model_id: m.model_id,
    display_name: m.display_name || m.model_id,
    workflow_type: (m.workflow_type as 'txt2img' | 'img2img') || 'txt2img',
    workflow_json: m.workflow_json || '',
  }
  nodes.value = []
  bindings.value = []
  const savedBindings = parseBindingJSON(m.input_binding_json)
  const savedDefaults = parseDefaultsJSON(m.input_defaults_json)
  if (form.value.workflow_json.trim()) {
    await analyze()
    applySavedBindings(savedBindings)
    for (const row of bindings.value) {
      if (savedDefaults[row.param] !== undefined) {
        row.defaultValue = savedDefaults[row.param]
      }
    }
  }
}

async function save() {
  if (!form.value.model_id.trim()) {
    ElMessage.warning(t('providers.ingressModelIdRequired'))
    return
  }
  if (!form.value.workflow_json.trim()) {
    ElMessage.warning(t('providers.workflowJsonRequired'))
    return
  }
  saving.value = true
  try {
    const payload = {
      model_id: form.value.model_id.trim(),
      display_name: form.value.display_name || form.value.model_id,
      workflow_type: form.value.workflow_type,
      workflow_json: form.value.workflow_json,
      input_binding: buildBindingMap(),
      input_defaults: buildDefaultsMap(),
    }
    if (isEdit.value && props.model?.id) {
      await api.put(`/providers/${props.providerId}/models/${props.model.id}`, payload)
    } else {
      await api.post(`/providers/${props.providerId}/models`, payload)
    }
    ElMessage.success(t('providers.workflowSaved'))
    visible.value = false
    emit('saved')
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || t('providers.updateFailed'))
  } finally {
    saving.value = false
  }
}

watch(visible, async (open) => {
  if (!open) return
  if (props.model?.id) {
    await loadModel(props.model)
  } else {
    reset()
    await ensureParamMeta()
  }
})
</script>
