<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'

type SimulationFace = {
  face_id: number
  area?: number
  centroid?: number[]
  tags?: string[]
}

type FaceScanResult = {
  workflow_id?: string
  workflowId?: string
  geometry_file?: string
  geometryFile?: string
  project_path?: string
  projectPath?: string
  face_scan?: { faces?: SimulationFace[] }
  faceScan?: { faces?: SimulationFace[] }
}

type AiPlanResult = { plan?: Record<string, any>; message?: string }
type WorkflowRecord = { workflow_id?: string; workflowId?: string; state?: string; message?: string; error?: string }
type SimulationResult = {
  max_von_mises_stress?: number
  maxVonMisesStress?: number
  max_stress_unit?: string
  maxStressUnit?: string
  max_displacement?: number
  maxDisplacement?: number
  max_displacement_unit?: string
  maxDisplacementUnit?: string
  stress_image_url?: string
  stressImageUrl?: string
  displacement_image_url?: string
  displacementImageUrl?: string
  detailed_report_url?: string
  detailedReportUrl?: string
  report?: string
  mock?: boolean
}

const simulationApiBase = String(import.meta.env.VITE_ANSYS_API_BASE || 'http://127.0.0.1:7200').replace(/\/$/, '')
const modelPath = ref('')
const workflowId = ref('')
const material = ref('structural_steel')
const forceMagnitude = ref(1000)
const forceDirection = ref('[0, -1, 0]')
const meshSize = ref(5)
const fixedFaceId = ref<number | null>(null)
const forceFaceId = ref<number | null>(null)
const faceScan = ref<FaceScanResult | null>(null)
const aiPlan = ref<AiPlanResult | null>(null)
const planConfirmed = ref(false)
const workflow = ref<WorkflowRecord | null>(null)
const result = ref<SimulationResult | null>(null)
const busy = ref(false)
const connectionState = ref<'checking' | 'ready' | 'unavailable'>('checking')
const message = ref('')
const elapsedSeconds = ref(0)
let pollTimer: number | undefined
let elapsedTimer: number | undefined

const faces = computed(() => faceScan.value?.face_scan?.faces ?? faceScan.value?.faceScan?.faces ?? [])
const preparedGeometryPath = computed(() => faceScan.value?.geometry_file || faceScan.value?.geometryFile || modelPath.value.trim())
const canScan = computed(() => !busy.value && !!modelPath.value.trim())
const canPlan = computed(() => !busy.value && faces.value.length > 0)
const canRun = computed(() => !busy.value && faces.value.length > 0 && fixedFaceId.value !== null && forceFaceId.value !== null && planConfirmed.value)
const workflowState = computed(() => workflow.value?.state || 'Idle')
const progressPercent = computed(() => {
  if (result.value) return 100
  if (workflowState.value === 'Failed') return 100
  if (workflowState.value === 'Done') return 95
  if (workflow.value) return 70
  if (planConfirmed.value) return 50
  if (aiPlan.value) return 40
  if (faceScan.value) return 25
  return 0
})
const planSummary = computed(() => {
  const plan = aiPlan.value?.plan
  return plan ? String(plan.objective || plan.summary || plan.model_simplification?.summary || '静力结构仿真方案') : ''
})

async function requestJson<T>(path: string, init?: RequestInit): Promise<T> {
  const response = await fetch(`${simulationApiBase}${path}`, init)
  if (!response.ok) {
    const text = await response.text()
    try {
      const payload = JSON.parse(text) as { detail?: string; message?: string; title?: string }
      throw new Error(payload.detail || payload.message || payload.title || text || `HTTP ${response.status}`)
    } catch (error) {
      if (error instanceof SyntaxError) throw new Error(text || `HTTP ${response.status}`)
      throw error
    }
  }
  return (await response.json()) as T
}

async function checkConnection(showMessage = false) {
  connectionState.value = 'checking'
  try {
    const status = await requestJson<{ message?: string }>('/api/ansys/status')
    connectionState.value = 'ready'
    if (showMessage) message.value = status.message || 'ANSYS 仿真服务已连接。'
  } catch (error) {
    connectionState.value = 'unavailable'
    if (showMessage) message.value = error instanceof Error ? error.message : 'ANSYS 仿真服务连接失败。'
  }
}

function parseDirection() {
  try {
    const value = JSON.parse(forceDirection.value)
    if (!Array.isArray(value) || value.length !== 3 || value.some(item => !Number.isFinite(Number(item)))) throw new Error()
    return value.map(Number)
  } catch {
    throw new Error('载荷方向必须是包含 3 个数字的数组，例如 [0, -1, 0]。')
  }
}

function makeWorkflowId() {
  return workflowId.value.trim() || `sim-${Date.now()}`
}

async function scanFaces() {
  if (!canScan.value) return
  stopPolling()
  busy.value = true
  faceScan.value = null
  aiPlan.value = null
  workflow.value = null
  result.value = null
  planConfirmed.value = false
  message.value = '正在读取模型并扫描 ANSYS 几何面...'
  try {
    const id = makeWorkflowId()
    workflowId.value = id
    const scan = await requestJson<FaceScanResult>('/api/ansys/simulation/scan-faces', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        workflow_id: id,
        source_agent: 'agent4',
        model_artifacts: { geometry_path: modelPath.value.trim() },
        execution_options: { timeout: 7200, use_tools_functional_faces: true },
        design_context: {},
      }),
    })
    faceScan.value = scan
    const availableFaces = scan.face_scan?.faces ?? scan.faceScan?.faces ?? []
    fixedFaceId.value = availableFaces.find(face => face.tags?.includes('y_min'))?.face_id ?? availableFaces[0]?.face_id ?? null
    forceFaceId.value = availableFaces.find(face => face.tags?.includes('y_max'))?.face_id ?? availableFaces.at(-1)?.face_id ?? null
    message.value = `模型扫描完成，共识别 ${availableFaces.length} 个面。`
  } catch (error) {
    message.value = error instanceof Error ? error.message : '模型扫描失败。'
  } finally {
    busy.value = false
  }
}

async function buildAiPlan() {
  if (!canPlan.value) return
  busy.value = true
  message.value = '正在生成仿真方案...'
  try {
    const plan = await requestJson<AiPlanResult>('/api/ansys/simulation/ai-plan', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        workflow_id: makeWorkflowId(),
        source_agent: 'agent4',
        model_artifacts: { geometry_path: preparedGeometryPath.value, original_geometry_path: modelPath.value.trim(), faces: faces.value },
        execution_options: {
          material: material.value,
          force_magnitude: Number(forceMagnitude.value),
          force_direction: parseDirection(),
          mesh_size: Number(meshSize.value),
        },
        design_context: { faces: faces.value },
      }),
    })
    aiPlan.value = plan
    planConfirmed.value = false
    message.value = plan.message || 'AI 仿真方案已生成，请确认后提交求解。'
  } catch (error) {
    message.value = error instanceof Error ? error.message : '生成仿真方案失败。'
  } finally {
    busy.value = false
  }
}

function confirmPlan() {
  if (!aiPlan.value) return
  planConfirmed.value = true
  message.value = '方案已确认，可以提交 ANSYS 求解。'
}

async function runSimulation() {
  if (!canRun.value) return
  busy.value = true
  result.value = null
  message.value = '正在提交 ANSYS 静力结构求解...'
  startElapsedTimer()
  try {
    const id = makeWorkflowId()
    workflowId.value = id
    const plan = aiPlan.value?.plan ?? null
    const response = await requestJson<{ current_workflow?: WorkflowRecord; currentWorkflow?: WorkflowRecord }>('/api/ansys/simulation/plans', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        workflow_id: id,
        source_agent: 'agent4',
        approved_plan_text: planSummary.value || 'Agent4 前端已确认的 ANSYS 静力结构仿真计划。',
        execution_options: { timeout: 7200, ai_plan_confirmed: true },
        model_artifacts: {
          geometry_path: preparedGeometryPath.value,
          original_geometry_path: modelPath.value.trim(),
          ansys_project_path: faceScan.value?.project_path || faceScan.value?.projectPath || '',
          ai_simulation_plan: plan,
        },
        design_context: { faces: faces.value, ai_simulation_plan: plan },
        simulation_plan: {
          enabled: true,
          target_agent: 'ansys',
          units: 'mm',
          analysis_type: 'static_structural',
          summary: '对用户指定三维模型执行静力结构仿真。',
          steps: [{
            id: 'static-structural',
            target_agent: 'ansys',
            skill_name: 'run_static_structural_simulation',
            name: '执行静力结构仿真',
            arguments: {
              geometry_path: preparedGeometryPath.value,
              material: material.value,
              fixed_face_id: fixedFaceId.value,
              force_face_id: forceFaceId.value,
              force_magnitude: Number(forceMagnitude.value),
              force_direction: parseDirection(),
              mesh_size: Number(meshSize.value),
              ai_simulation_plan: plan,
            },
          }],
        },
      }),
    })
    workflow.value = response.current_workflow ?? response.currentWorkflow ?? { workflow_id: id, state: 'Pending' }
    message.value = workflow.value.message || '仿真任务已提交。'
    startPolling(id)
  } catch (error) {
    message.value = error instanceof Error ? error.message : '提交仿真失败。'
    stopElapsedTimer()
  } finally {
    busy.value = false
  }
}

async function pollWorkflow(id = workflowId.value.trim()) {
  if (!id) return
  try {
    const current = await requestJson<WorkflowRecord>(`/api/ansys/simulation/workflows/${encodeURIComponent(id)}`)
    workflow.value = current
    message.value = current.error || current.message || message.value
    if (current.state === 'Done') await loadResult(id)
    if (current.state === 'Failed') {
      stopPolling()
      stopElapsedTimer()
    }
  } catch (error) {
    message.value = error instanceof Error ? error.message : '读取仿真状态失败。'
  }
}

async function loadResult(id = workflowId.value.trim()) {
  if (!id) return
  try {
    result.value = await requestJson<SimulationResult>(`/api/ansys/simulation/workflows/${encodeURIComponent(id)}/results`)
    message.value = 'ANSYS 仿真完成。'
    stopPolling()
    stopElapsedTimer()
  } catch (error) {
    message.value = error instanceof Error ? error.message : '读取仿真结果失败。'
  }
}

function startPolling(id: string) {
  stopPolling()
  void pollWorkflow(id)
  pollTimer = window.setInterval(() => void pollWorkflow(id), 5000)
}

function stopPolling() {
  if (pollTimer !== undefined) window.clearInterval(pollTimer)
  pollTimer = undefined
}

function startElapsedTimer() {
  stopElapsedTimer()
  elapsedSeconds.value = 0
  elapsedTimer = window.setInterval(() => { elapsedSeconds.value += 1 }, 1000)
}

function stopElapsedTimer() {
  if (elapsedTimer !== undefined) window.clearInterval(elapsedTimer)
  elapsedTimer = undefined
}

function assetUrl(path?: string) {
  if (!path) return ''
  if (/^https?:\/\//i.test(path)) return path
  if (path.startsWith('/results/')) return `${simulationApiBase}/api/ansys${path}`
  return `${simulationApiBase}${path.startsWith('/') ? '' : '/'}${path}`
}

function numberText(value?: number) {
  return Number.isFinite(Number(value)) ? Number(value).toPrecision(5) : '--'
}

function vectorText(value?: number[]) {
  return Array.isArray(value) ? `[${value.map(item => Number(item).toFixed(2)).join(', ')}]` : '--'
}

onMounted(() => {
  const params = new URLSearchParams(window.location.search)
  modelPath.value = params.get('modelPath') || params.get('model_path') || ''
  workflowId.value = params.get('workflowId') || params.get('workflow_id') || ''
  void checkConnection(false)
  if (workflowId.value) startPolling(workflowId.value)
})

onBeforeUnmount(() => {
  stopPolling()
  stopElapsedTimer()
})
</script>

<template>
  <section class="simulation-workspace">
    <aside class="simulation-controls">
      <div class="simulation-heading">
        <div><p>Static Structural</p><h2>ANSYS 仿真</h2></div>
        <button class="connection-button" type="button" :title="simulationApiBase" @click="checkConnection(true)">
          <span :class="connectionState"></span>
          {{ connectionState === 'ready' ? '已连接' : connectionState === 'checking' ? '检测中' : '未连接' }}
        </button>
      </div>

      <label><span>模型路径</span><input v-model="modelPath" placeholder="D:\path\model.SLDASM" /></label>
      <label><span>工作流 ID</span><input v-model="workflowId" placeholder="自动生成，也可填写已有 ID" /></label>

      <div class="parameter-grid">
        <label><span>材料</span><select v-model="material"><option value="structural_steel">结构钢</option><option value="aluminum_alloy">铝合金</option><option value="stainless_steel">不锈钢</option><option value="cast_iron">铸铁</option></select></label>
        <label><span>载荷 N</span><input v-model.number="forceMagnitude" type="number" min="0" step="10" /></label>
        <label><span>载荷方向</span><input v-model="forceDirection" placeholder="[0, -1, 0]" /></label>
        <label><span>网格 mm</span><input v-model.number="meshSize" type="number" min="0.1" step="0.5" /></label>
      </div>

      <div class="simulation-actions">
        <button type="button" :disabled="!canScan" @click="scanFaces">1. 扫描模型</button>
        <button type="button" class="secondary" :disabled="!canPlan" @click="buildAiPlan">2. 生成方案</button>
        <button type="button" class="secondary" :disabled="!aiPlan || busy" @click="confirmPlan">3. 确认方案</button>
        <button type="button" :disabled="!canRun" @click="runSimulation">4. 开始求解</button>
        <button type="button" class="secondary" :disabled="!workflowId || busy" @click="pollWorkflow()">刷新状态</button>
      </div>
      <p class="simulation-message">{{ message || `仿真 API：${simulationApiBase}` }}</p>
    </aside>

    <section class="simulation-main">
      <header class="simulation-status">
        <div><p>Workflow</p><h2>{{ workflowState }}</h2></div>
        <div class="status-metrics"><span>{{ elapsedSeconds }} s</span><span>{{ progressPercent }}%</span></div>
      </header>
      <div class="progress-track"><div :style="{ width: `${progressPercent}%` }"></div></div>

      <section v-if="faceScan" class="simulation-section">
        <div class="section-title"><div><p>Geometry</p><h3>几何面选择</h3></div><span>{{ faces.length }} faces</span></div>
        <div class="selected-faces">
          <article><span>固定面</span><strong>{{ fixedFaceId ?? '--' }}</strong></article>
          <article><span>载荷面</span><strong>{{ forceFaceId ?? '--' }}</strong></article>
          <article><span>中间几何</span><strong>{{ preparedGeometryPath }}</strong></article>
        </div>
        <div class="face-table-wrap">
          <table>
            <thead><tr><th>ID</th><th>面积</th><th>中心</th><th>标签</th><th>边界条件</th></tr></thead>
            <tbody><tr v-for="face in faces.slice(0, 100)" :key="face.face_id">
              <td>{{ face.face_id }}</td><td>{{ numberText(face.area) }}</td><td>{{ vectorText(face.centroid) }}</td><td>{{ face.tags?.join(', ') || '--' }}</td>
              <td class="face-actions"><button type="button" :class="{ active: fixedFaceId === face.face_id }" @click="fixedFaceId = face.face_id">固定</button><button type="button" :class="{ active: forceFaceId === face.face_id }" @click="forceFaceId = face.face_id">载荷</button></td>
            </tr></tbody>
          </table>
        </div>
      </section>

      <section v-if="aiPlan" class="simulation-section plan-section" :class="{ confirmed: planConfirmed }">
        <div class="section-title"><div><p>Engineering Plan</p><h3>AI 仿真方案</h3></div><span>{{ planConfirmed ? '已确认' : '待确认' }}</span></div>
        <p>{{ planSummary }}</p><pre>{{ JSON.stringify(aiPlan.plan, null, 2) }}</pre>
      </section>

      <section v-if="result" class="simulation-section result-section">
        <div class="section-title"><div><p>Solution</p><h3>{{ result.mock ? '演示结果' : '求解结果' }}</h3></div><a v-if="result.detailed_report_url || result.detailedReportUrl" :href="assetUrl(result.detailed_report_url || result.detailedReportUrl)" target="_blank" rel="noreferrer">详细报告</a></div>
        <div class="result-metrics">
          <article><span>最大等效应力</span><strong>{{ numberText(result.max_von_mises_stress ?? result.maxVonMisesStress) }}</strong><small>{{ result.max_stress_unit || result.maxStressUnit || 'MPa' }}</small></article>
          <article><span>最大位移</span><strong>{{ numberText(result.max_displacement ?? result.maxDisplacement) }}</strong><small>{{ result.max_displacement_unit || result.maxDisplacementUnit || 'mm' }}</small></article>
        </div>
        <div class="result-images">
          <figure v-if="result.stress_image_url || result.stressImageUrl"><img :src="assetUrl(result.stress_image_url || result.stressImageUrl)" alt="等效应力云图" /><figcaption>等效应力</figcaption></figure>
          <figure v-if="result.displacement_image_url || result.displacementImageUrl"><img :src="assetUrl(result.displacement_image_url || result.displacementImageUrl)" alt="总变形云图" /><figcaption>总变形</figcaption></figure>
        </div>
        <pre v-if="result.report">{{ result.report }}</pre>
      </section>

      <section v-if="!faceScan && !result" class="simulation-empty"><strong>等待模型</strong><p>填写模型路径后开始扫描。</p></section>
    </section>
  </section>
</template>

<style scoped>
.simulation-workspace{display:grid;grid-template-columns:360px minmax(0,1fr);gap:16px;padding:16px}.simulation-controls,.simulation-main{border:1px solid #d9e2df;border-radius:8px;background:#fff;padding:16px}.simulation-controls{align-self:start;display:grid;gap:14px}.simulation-heading,.simulation-status,.section-title{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.simulation-heading p,.simulation-status p,.section-title p{margin:0 0 4px;color:#64736f;font-size:12px;font-weight:800;letter-spacing:0;text-transform:uppercase}h2,h3{margin:0}label{display:grid;gap:6px;color:#25343a;font-weight:700}input,select{width:100%;box-sizing:border-box;border:1px solid #cbd7d3;border-radius:6px;background:#fff;padding:10px;color:#172026}.connection-button{display:inline-flex;align-items:center;gap:7px;min-height:34px;border:1px solid #d9e2df;background:#f7faf9;color:#3e514b;padding:6px 9px}.connection-button span{width:8px;height:8px;border-radius:50%;background:#bd6c42}.connection-button span.ready{background:#2f806f}.connection-button span.checking{background:#d39a28}.parameter-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.simulation-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}button{min-height:40px;border:1px solid #1e675f;border-radius:6px;background:#1e675f;color:#fff;padding:8px 10px;font-weight:800;cursor:pointer}button.secondary{background:#fff;color:#1e675f}button:disabled{cursor:not-allowed;opacity:.45}.simulation-message{min-height:42px;margin:0;color:#8a3b12;font-weight:700;line-height:1.5;overflow-wrap:anywhere}.simulation-main{min-width:0;display:grid;align-content:start;gap:14px}.status-metrics{display:flex;gap:8px}.status-metrics span,.section-title>span{border:1px solid #d9e2df;border-radius:999px;background:#f7faf9;padding:5px 9px;color:#3e514b;font-size:13px;font-weight:800}.progress-track{height:8px;border-radius:4px;background:#e8efed;overflow:hidden}.progress-track div{height:100%;background:#1e675f;transition:width .25s ease}.simulation-section{border-top:1px solid #e3ebe8;padding-top:16px}.selected-faces,.result-metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:12px}.selected-faces article,.result-metrics article{min-width:0;border:1px solid #d9e2df;border-radius:6px;background:#f7faf9;padding:10px}.selected-faces span,.result-metrics span{display:block;color:#64736f;font-size:13px}.selected-faces strong{display:block;margin-top:5px;overflow-wrap:anywhere}.face-table-wrap{margin-top:12px;overflow:auto;max-height:420px;border:1px solid #d9e2df}table{width:100%;border-collapse:collapse;font-size:13px}th,td{border-bottom:1px solid #e3ebe8;padding:8px;text-align:left;white-space:nowrap}th{position:sticky;top:0;background:#f1f6f4;z-index:1}.face-actions{display:flex;gap:6px}.face-actions button{min-height:30px;background:#fff;color:#1e675f;padding:4px 8px}.face-actions button.active{background:#1e675f;color:#fff}.plan-section>p{line-height:1.6;color:#3e514b}.plan-section.confirmed{border-top-color:#2f806f}pre{max-height:320px;overflow:auto;border:1px solid #d9e2df;border-radius:6px;background:#172026;color:#eaf2ef;padding:12px;white-space:pre-wrap;overflow-wrap:anywhere}.result-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.result-metrics strong{display:inline-block;margin:8px 6px 0 0;font-size:26px}.result-images{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:12px}.result-images figure{margin:0;border:1px solid #d9e2df;border-radius:6px;overflow:hidden}.result-images img{display:block;width:100%;aspect-ratio:4/3;object-fit:contain;background:#eef3f1}.result-images figcaption{padding:8px 10px;color:#64736f}.simulation-empty{min-height:360px;display:grid;place-content:center;text-align:center;color:#64736f}.simulation-empty strong{color:#25343a;font-size:20px}.simulation-empty p{margin-bottom:0}a{color:#1e675f;font-weight:800}@media(max-width:900px){.simulation-workspace{grid-template-columns:1fr}.parameter-grid,.selected-faces,.result-metrics,.result-images{grid-template-columns:1fr}.simulation-heading,.simulation-status,.section-title{flex-direction:column}}
</style>
