<template>
  <div class="engineering-analysis-panel">
    <div class="analysis-card analysis-workbench">
      <div class="analysis-card-head">
        <span>工程分析</span>
        <strong v-if="statusText">{{ statusText }}</strong>
      </div>
      <div v-if="!drawing" class="analysis-operation" :class="{ 'has-boundary': boundaryFeature }">
        <label class="analysis-entry is-upload" :class="{ 'is-disabled': drawing }">
          <input type="file" accept=".zip,.json,.geojson" :disabled="drawing" @change="handleFileChange" />
          <span>{{ boundaryFeature ? "更换边界" : "上传边界" }}</span>
          <strong>SHP / GeoJSON</strong>
        </label>
        <button type="button" class="analysis-entry is-draw" :disabled="drawing" @pointerup.stop.prevent="startDrawing">
          <span>{{ boundaryFeature ? "重新绘制" : "在线绘制" }}</span>
          <strong>地图圈定</strong>
        </button>
      </div>

      <div v-if="drawing" class="analysis-drawing-stage">
        <div class="analysis-point-count">
          <span>已落点</span>
          <strong>{{ drawingPointCount }}</strong>
          <em>点</em>
          <small>至少 3 点</small>
        </div>
        <button type="button" class="analysis-control is-primary" :disabled="!canFinishDrawing" @pointerup.stop.prevent="finishDrawing">
          完成分析
        </button>
        <button type="button" class="analysis-control" :disabled="!drawingPointCount" @pointerup.stop.prevent="undoPoint">撤销</button>
        <button type="button" class="analysis-control is-danger" @pointerup.stop.prevent="cancelDrawing">删除图形</button>
      </div>

      <div v-else-if="boundaryFeature" class="analysis-boundary-bar">
        <span>边界已加载</span>
        <strong>{{ resultRows.length ? `命中 ${resultRows.length} 项` : resultTitle }}</strong>
        <button type="button" @pointerup.stop.prevent="clearBoundary">删除图形</button>
      </div>
    </div>

    <div v-if="boundaryFeature" class="analysis-card analysis-result">
      <div class="analysis-card-head">
        <span>建设记录核查</span>
        <strong>{{ resultTitle }}</strong>
      </div>
      <div class="analysis-conclusion" :class="analysisConclusion.levelClass">
        <div class="analysis-conclusion-head">
          <span>分析结论</span>
          <strong>{{ analysisConclusion.verdict }}</strong>
        </div>
        <p>{{ analysisConclusion.summary }}</p>
        <div class="analysis-conclusion-tags">
          <span v-for="tag in analysisConclusion.tags" :key="tag">{{ tag }}</span>
        </div>
        <small>{{ analysisConclusion.advice }}</small>
      </div>
      <div v-if="!resultRows.length" class="analysis-empty is-success">该边界范围内暂未匹配到已建设项目</div>
      <div v-else class="analysis-project-list">
        <button
          v-for="item in resultRows"
          :key="item.key"
          type="button"
          class="analysis-project-row"
          @pointerup.stop.prevent="$emit('focus-project', item)"
        >
          <span class="analysis-project-name">{{ item.projectName }}</span>
          <div class="analysis-project-meta">
            <strong>{{ item.year }}</strong>
            <em>{{ item.category }}</em>
            <small>叠加 {{ formatMu(item.overlapAreaWanMu) }} 亩</small>
          </div>
          <b>{{ item.featureCount }} 个要素</b>
        </button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, onMounted, ref } from "vue"
import shp from "shpjs"

const props = defineProps({
  drawingPointCount: {
    type: Number,
    default: 0,
  },
})

const emit = defineEmits([
  "drawing-change",
  "finish-drawing",
  "undo-point",
  "clear-boundary",
  "analysis-result",
  "focus-project",
])

const drawing = ref(false)
const boundaryFeature = ref(null)
const resultRows = ref([])
const matchedFeatures = ref([])
const fileError = ref("")
const projectLoadState = ref("idle")
const projectFeatures = ref([])
let projectFeaturePromise = null

const summary = computed(() => {
  const featureCount = matchedFeatures.value.length
  const projectCount = resultRows.value.length
  const overlapAreaWanMu = resultRows.value.reduce((sum, item) => sum + Number(item.overlapAreaWanMu || 0), 0)
  return {
    boundaryAreaWanMu: getGeometryAreaWanMu(boundaryFeature.value?.geometry),
    featureCount,
    projectCount,
    overlapAreaWanMu,
  }
})

const statusText = computed(() => {
  if (fileError.value) return fileError.value
  if (projectLoadState.value === "loading") return "项目库加载中"
  if (projectLoadState.value === "error") return "项目库加载失败"
  return ""
})

const resultTitle = computed(() => {
  if (!boundaryFeature.value) return "未分析"
  return resultRows.value.length ? "已发现建设记录" : "未发现建设记录"
})
const canFinishDrawing = computed(() => drawing.value && props.drawingPointCount >= 3)
const analysisConclusion = computed(() => {
  if (!boundaryFeature.value) {
    return {
      levelClass: "",
      verdict: "",
      summary: "",
      tags: [],
      advice: "",
    }
  }
  if (!resultRows.value.length) {
    return {
      levelClass: "is-clear",
      verdict: "未发现建设记录",
      summary: "该边界范围暂未与项目库中的已建设工程形成空间叠加。",
      tags: [`分析范围 ${formatNumber(summary.value.boundaryAreaWanMu)} 万亩`, "项目库未命中"],
      advice: "可进入新增工程选址论证，仍需结合保护红线、审批台账和现场踏勘复核。",
    }
  }
  const topType = getTopCategory(resultRows.value)
  const yearRange = getYearRangeText(resultRows.value)
  const maxOverlapProject = [...resultRows.value].sort((a, b) => Number(b.overlapAreaWanMu || 0) - Number(a.overlapAreaWanMu || 0))[0]
  const overlapMu = formatMu(summary.value.overlapAreaWanMu)
  const verdict = resultRows.value.length >= 6 || summary.value.overlapAreaWanMu >= 1 ? "存在建设叠加" : "存在局部叠加"
  return {
    levelClass: resultRows.value.length >= 6 ? "is-heavy" : "is-hit",
    verdict,
    summary: `该边界命中 ${resultRows.value.length} 个历史项目，叠加约 ${overlapMu} 亩，项目类型以${topType.name}为主，年份集中在 ${yearRange}。`,
    tags: [
      `主类型 ${topType.name}`,
      `年份 ${yearRange}`,
      `最大叠加 ${formatMu(maxOverlapProject?.overlapAreaWanMu)} 亩`,
    ],
    advice: `重点复核“${truncateText(maxOverlapProject?.projectName)}”（${maxOverlapProject?.year || "未知"}，${maxOverlapProject?.category || "未知类型"}）与拟建范围的重合边界。`,
  }
})

onMounted(() => {
  ensureProjectFeatures().catch(() => {})
})

async function handleFileChange(event) {
  const file = event.target.files?.[0]
  event.target.value = ""
  if (!file) return
  fileError.value = ""
  try {
    const feature = await parseBoundaryFile(file)
    if (drawing.value) {
      drawing.value = false
      emit("drawing-change", false)
    }
    emit("clear-boundary")
    await applyBoundary(feature)
  } catch (error) {
    fileError.value = "边界文件解析失败"
  }
}

async function parseBoundaryFile(file) {
  const name = String(file.name || "").toLowerCase()
  if (name.endsWith(".json") || name.endsWith(".geojson")) {
    return normalizeBoundaryGeojson(JSON.parse(await file.text()))
  }
  if (name.endsWith(".zip")) {
    const parsed = await shp(await file.arrayBuffer())
    return normalizeBoundaryGeojson(parsed)
  }
  throw new Error("unsupported file")
}

function normalizeBoundaryGeojson(value) {
  const polygonFeatures = normalizeGeojsonFeatures(value).filter((feature) => isPolygonGeometry(feature?.geometry))
  if (polygonFeatures.length) return mergePolygonFeatures(polygonFeatures)
  throw new Error("no polygon")
}

function startDrawing() {
  if (drawing.value) return
  fileError.value = ""
  drawing.value = true
  boundaryFeature.value = null
  resultRows.value = []
  matchedFeatures.value = []
  emit("analysis-result", { boundary: null, matches: [], rows: [], summary: summary.value })
  emit("drawing-change", true)
}

function cancelDrawing() {
  if (!drawing.value) return
  drawing.value = false
  emit("clear-boundary")
  emit("analysis-result", { boundary: null, matches: [], rows: [], summary: summary.value })
}

function finishDrawing() {
  if (!canFinishDrawing.value) return
  emit("finish-drawing")
}

function undoPoint() {
  emit("undo-point")
}

function clearBoundary() {
  drawing.value = false
  boundaryFeature.value = null
  resultRows.value = []
  matchedFeatures.value = []
  fileError.value = ""
  emit("clear-boundary")
  emit("analysis-result", { boundary: null, matches: [], rows: [], summary: summary.value })
}

function applyDrawnBoundary(feature) {
  if (!feature?.geometry) return
  drawing.value = false
  applyBoundary(feature)
}

async function applyBoundary(feature) {
  boundaryFeature.value = feature
  matchedFeatures.value = []
  resultRows.value = []
  let matches = []
  try {
    const features = await ensureProjectFeatures()
    matches = findProjectMatches(feature, features)
    matchedFeatures.value = matches
    resultRows.value = buildProjectRows(matches)
  } catch (error) {
    fileError.value = "项目库加载失败"
  }
  emit("analysis-result", {
    boundary: feature,
    matches,
    rows: resultRows.value,
    summary: summary.value,
  })
}

async function ensureProjectFeatures() {
  if (projectFeatures.value.length) return projectFeatures.value
  if (projectFeaturePromise) return projectFeaturePromise
  projectLoadState.value = "loading"
  const dataPath = "datas/ecological-protection/ecological_restoration_projects.geojson"
  projectFeaturePromise = fetch(buildPublicUrl(dataPath), { credentials: "include" })
    .then((response) => {
      if (!response.ok) throw new Error(`HTTP ${response.status}`)
      return response.json()
    })
    .then((geojson) => {
      const features = normalizeGeojsonFeatures(geojson)
        .filter((feature) => feature?.geometry?.coordinates)
        .map((feature, index) => ({
          ...feature,
          properties: {
            ...(feature.properties || {}),
            source_index: index,
          },
        }))
      projectFeatures.value = features
      projectLoadState.value = "success"
      return features
    })
    .catch((error) => {
      projectLoadState.value = "error"
      projectFeaturePromise = null
      throw error
    })
  return projectFeaturePromise
}

function buildPublicUrl(path) {
  if (!path) return ""
  if (/^https?:\/\//.test(path) || path.startsWith("/")) return path
  return `${import.meta.env.BASE_URL || "./"}${path}`
}

function normalizeGeojsonFeatures(value) {
  if (!value) return []
  if (Array.isArray(value)) return value.flatMap((item) => normalizeGeojsonFeatures(item))
  if (value.type === "FeatureCollection") return (value.features || []).filter(Boolean)
  if (value.type === "Feature") return value.geometry ? [value] : []
  if (isSupportedGeometry(value)) return [{ type: "Feature", properties: {}, geometry: value }]
  if (typeof value === "object") return Object.values(value).flatMap((item) => normalizeGeojsonFeatures(item))
  return []
}

function mergePolygonFeatures(features) {
  const polygons = features.flatMap((feature) =>
    feature.geometry.type === "Polygon" ? [feature.geometry.coordinates] : feature.geometry.coordinates
  )
  if (polygons.length === 1) {
    return { type: "Feature", properties: features[0].properties || {}, geometry: { type: "Polygon", coordinates: polygons[0] } }
  }
  return {
    type: "Feature",
    properties: {
      sourceFeatureCount: features.length,
    },
    geometry: {
      type: "MultiPolygon",
      coordinates: polygons,
    },
  }
}

function findProjectMatches(boundary, features = projectFeatures.value) {
  const boundaryBounds = getGeometryBounds(boundary.geometry)
  return features
    .filter((feature) => geometryIntersectsBoundary(feature.geometry, boundary.geometry, boundaryBounds))
    .map((feature) => ({
      feature,
      properties: feature.properties || {},
      overlapAreaWanMu: estimateOverlapAreaWanMu(feature, boundary.geometry),
    }))
}

function buildProjectRows(matches) {
  const groupMap = new Map()
  matches.forEach((match) => {
    const props = match.properties || {}
    const name = props.project_name || props.projectName || "未命名项目"
    const current = groupMap.get(name) || {
      key: name,
      projectName: name,
      category: props.project_category || "未知类型",
      year: props.year || "未知",
      featureCount: 0,
      overlapAreaWanMu: 0,
      features: [],
      sourceIndex: Number(props.source_index || 0),
    }
    current.featureCount += 1
    current.overlapAreaWanMu += Number(match.overlapAreaWanMu || 0)
    current.features.push(match.feature)
    groupMap.set(name, current)
  })
  return [...groupMap.values()].map((item) => ({ ...item, overlapAreaWanMu: Number(item.overlapAreaWanMu.toFixed(4)) }))
}

function geometryIntersectsBoundary(geometry, boundaryGeometry, boundaryBounds) {
  if (!boundsIntersects(getGeometryBounds(geometry), boundaryBounds)) return false
  const boundaryRings = getPolygonRings(boundaryGeometry)
  if (!boundaryRings.length) return false
  if (isPolygonGeometry(geometry)) return polygonIntersectsBoundary(geometry, boundaryRings)
  if (geometry?.type === "Point") return pointInAnyPolygon(geometry.coordinates, boundaryRings)
  if (geometry?.type === "MultiPoint") return geometry.coordinates.some((coordinate) => pointInAnyPolygon(coordinate, boundaryRings))
  if (geometry?.type === "LineString") return lineIntersectsBoundary(geometry.coordinates, boundaryRings)
  if (geometry?.type === "MultiLineString") return geometry.coordinates.some((line) => lineIntersectsBoundary(line, boundaryRings))
  return false
}

function polygonIntersectsBoundary(geometry, boundaryRings) {
  const geometryRings = getPolygonRings(geometry)
  if (!geometryRings.length) return false
  return geometryRings.some((ring) =>
    ring.some((coordinate) => pointInAnyPolygon(coordinate, boundaryRings))
  ) || boundaryRings.some((ring) =>
    ring.some((coordinate) => pointInAnyPolygon(coordinate, geometryRings))
  ) || geometryRings.some((ring) =>
    boundaryRings.some((boundaryRing) => ringsHaveCrossing(ring, boundaryRing))
  )
}

function lineIntersectsBoundary(line = [], boundaryRings) {
  if (!Array.isArray(line) || !line.length) return false
  if (line.some((coordinate) => pointInAnyPolygon(coordinate, boundaryRings))) return true
  return boundaryRings.some((ring) => lineHaveCrossing(line, ring))
}

function lineHaveCrossing(line, ring) {
  for (let i = 1; i < line.length; i += 1) {
    for (let j = 1; j < ring.length; j += 1) {
      if (segmentsIntersect(line[i - 1], line[i], ring[j - 1], ring[j])) return true
    }
  }
  return false
}

function estimateOverlapAreaWanMu(feature, boundaryGeometry) {
  const geometry = feature?.geometry
  const geometryRings = getPolygonRings(geometry)
  const boundaryRings = getPolygonRings(boundaryGeometry)
  const totalArea = getFeatureAreaWanMu(feature)
  const boundaryArea = getGeometryAreaWanMu(boundaryGeometry)
  if (!geometryRings.length || !boundaryRings.length) return 0
  const insideCount = geometryRings.reduce((sum, ring) =>
    sum + ring.filter((coordinate) => pointInAnyPolygon(coordinate, boundaryRings)).length,
  0)
  const totalCount = geometryRings.reduce((sum, ring) => sum + ring.length, 0)
  if (insideCount) return Number((totalArea * Math.max(0.08, Math.min(1, insideCount / Math.max(totalCount, 1)))).toFixed(4))
  const boundaryInsideCount = boundaryRings.reduce((sum, ring) =>
    sum + ring.filter((coordinate) => pointInAnyPolygon(coordinate, geometryRings)).length,
  0)
  if (boundaryInsideCount) return Number(Math.min(totalArea, boundaryArea).toFixed(4))
  if (geometryRings.some((ring) => boundaryRings.some((boundaryRing) => ringsHaveCrossing(ring, boundaryRing)))) {
    return Number((Math.min(totalArea, boundaryArea) * 0.12).toFixed(4))
  }
  return 0
}

function getPolygonRings(geometry) {
  if (geometry?.type === "Polygon") return [geometry.coordinates?.[0] || []]
  if (geometry?.type === "MultiPolygon") return geometry.coordinates.map((polygon) => polygon?.[0] || []).filter((ring) => ring.length)
  return []
}

function isPolygonGeometry(geometry) {
  return geometry?.type === "Polygon" || geometry?.type === "MultiPolygon"
}

function isSupportedGeometry(geometry) {
  return ["Polygon", "MultiPolygon", "LineString", "MultiLineString", "Point", "MultiPoint"].includes(geometry?.type)
}

function pointInAnyPolygon(coordinate, rings) {
  return rings.some((ring) => pointInRing(coordinate, ring))
}

function pointInRing(coordinate, ring) {
  let inside = false
  const x = Number(coordinate?.[0])
  const y = Number(coordinate?.[1])
  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const xi = Number(ring[i]?.[0])
    const yi = Number(ring[i]?.[1])
    const xj = Number(ring[j]?.[0])
    const yj = Number(ring[j]?.[1])
    const intersects = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
    if (intersects) inside = !inside
  }
  return inside
}

function ringsHaveCrossing(first, second) {
  for (let i = 1; i < first.length; i += 1) {
    for (let j = 1; j < second.length; j += 1) {
      if (segmentsIntersect(first[i - 1], first[i], second[j - 1], second[j])) return true
    }
  }
  return false
}

function segmentsIntersect(a, b, c, d) {
  if (!segmentBoundsIntersect(a, b, c, d)) return false
  const det = (p, q, r) => (Number(q[0]) - Number(p[0])) * (Number(r[1]) - Number(p[1])) - (Number(q[1]) - Number(p[1])) * (Number(r[0]) - Number(p[0]))
  const d1 = det(a, b, c)
  const d2 = det(a, b, d)
  const d3 = det(c, d, a)
  const d4 = det(c, d, b)
  if (d1 === 0 && pointOnSegment(c, a, b)) return true
  if (d2 === 0 && pointOnSegment(d, a, b)) return true
  if (d3 === 0 && pointOnSegment(a, c, d)) return true
  if (d4 === 0 && pointOnSegment(b, c, d)) return true
  return ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))
}

function segmentBoundsIntersect(a, b, c, d) {
  const first = getBoundsFromCoordinates([a, b])
  const second = getBoundsFromCoordinates([c, d])
  return boundsIntersects(first, second)
}

function pointOnSegment(point, start, end) {
  const px = Number(point?.[0])
  const py = Number(point?.[1])
  const x1 = Number(start?.[0])
  const y1 = Number(start?.[1])
  const x2 = Number(end?.[0])
  const y2 = Number(end?.[1])
  const cross = (px - x1) * (y2 - y1) - (py - y1) * (x2 - x1)
  if (Math.abs(cross) > 1e-10) return false
  return px >= Math.min(x1, x2) - 1e-10 && px <= Math.max(x1, x2) + 1e-10 && py >= Math.min(y1, y2) - 1e-10 && py <= Math.max(y1, y2) + 1e-10
}

function getGeometryBounds(geometry) {
  const coordinates = []
  visitCoordinates(geometry?.coordinates, coordinates)
  if (!coordinates.length) return null
  return coordinates.reduce((bounds, coordinate) => ({
    minLng: Math.min(bounds.minLng, Number(coordinate[0])),
    minLat: Math.min(bounds.minLat, Number(coordinate[1])),
    maxLng: Math.max(bounds.maxLng, Number(coordinate[0])),
    maxLat: Math.max(bounds.maxLat, Number(coordinate[1])),
  }), {
    minLng: Number.POSITIVE_INFINITY,
    minLat: Number.POSITIVE_INFINITY,
    maxLng: Number.NEGATIVE_INFINITY,
    maxLat: Number.NEGATIVE_INFINITY,
  })
}

function boundsIntersects(first, second) {
  if (!first || !second) return false
  return !(first.maxLng < second.minLng || first.minLng > second.maxLng || first.maxLat < second.minLat || first.minLat > second.maxLat)
}

function getFeatureAreaWanMu(feature) {
  return getGeometryAreaWanMu(feature?.geometry)
}

function getGeometryAreaWanMu(geometry) {
  if (geometry?.type === "Polygon") return polygonAreaWanMu(geometry.coordinates)
  if (geometry?.type === "MultiPolygon") {
    return geometry.coordinates.reduce((sum, polygon) => sum + polygonAreaWanMu(polygon), 0)
  }
  return 0
}

function polygonAreaWanMu(rings = []) {
  const ring = rings[0] || []
  if (ring.length < 3) return 0
  const centerLat = ring.reduce((sum, coordinate) => sum + Number(coordinate[1] || 0), 0) / ring.length
  const metersPerLng = 111320 * Math.cos((centerLat * Math.PI) / 180)
  const metersPerLat = 110540
  let area = 0
  for (let index = 0; index < ring.length; index += 1) {
    const current = ring[index]
    const next = ring[(index + 1) % ring.length]
    const x1 = Number(current[0] || 0) * metersPerLng
    const y1 = Number(current[1] || 0) * metersPerLat
    const x2 = Number(next[0] || 0) * metersPerLng
    const y2 = Number(next[1] || 0) * metersPerLat
    area += x1 * y2 - x2 * y1
  }
  return Math.abs(area) / 2 / 666.6666667 / 10000
}

function visitCoordinates(value, coordinates) {
  if (!Array.isArray(value)) return
  if (typeof value[0] === "number" && typeof value[1] === "number") {
    coordinates.push(value)
    return
  }
  value.forEach((item) => visitCoordinates(item, coordinates))
}

function getBoundsFromCoordinates(coordinates) {
  return coordinates.reduce((bounds, coordinate) => ({
    minLng: Math.min(bounds.minLng, Number(coordinate?.[0])),
    minLat: Math.min(bounds.minLat, Number(coordinate?.[1])),
    maxLng: Math.max(bounds.maxLng, Number(coordinate?.[0])),
    maxLat: Math.max(bounds.maxLat, Number(coordinate?.[1])),
  }), {
    minLng: Number.POSITIVE_INFINITY,
    minLat: Number.POSITIVE_INFINITY,
    maxLng: Number.NEGATIVE_INFINITY,
    maxLat: Number.NEGATIVE_INFINITY,
  })
}

function formatNumber(value) {
  const number = Number(value || 0)
  if (!Number.isFinite(number)) return "0"
  return Number(number.toFixed(number >= 10 ? 2 : 4)).toString()
}

function formatMu(value) {
  const number = Number(value || 0) * 10000
  if (!Number.isFinite(number)) return "0"
  if (number >= 1000) return Number(number.toFixed(0)).toLocaleString("zh-CN")
  if (number >= 10) return Number(number.toFixed(1)).toLocaleString("zh-CN")
  return Number(number.toFixed(2)).toString()
}

function getTopCategory(rows = []) {
  const categoryMap = new Map()
  rows.forEach((row) => {
    const name = row.category || "未知类型"
    const current = categoryMap.get(name) || { name, count: 0, area: 0 }
    current.count += 1
    current.area += Number(row.overlapAreaWanMu || 0)
    categoryMap.set(name, current)
  })
  return [...categoryMap.values()].sort((a, b) => b.area - a.area || b.count - a.count)[0] || { name: "未知类型", count: 0, area: 0 }
}

function getYearRangeText(rows = []) {
  const years = rows
    .map((row) => Number(row.year))
    .filter((year) => Number.isFinite(year))
  if (!years.length) return "未知年份"
  const minYear = Math.min(...years)
  const maxYear = Math.max(...years)
  return minYear === maxYear ? `${minYear} 年` : `${minYear}-${maxYear} 年`
}

function truncateText(value, maxLength = 18) {
  const text = String(value || "未命名项目")
  return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text
}

defineExpose({
  applyDrawnBoundary,
})
</script>

<style lang="scss" scoped>
.engineering-analysis-panel {
  position: absolute;
  z-index: 8;
  left: 32px;
  top: 188px;
  width: 430px;
  display: grid;
  grid-template-rows: auto auto;
  gap: 12px;
  max-height: calc(100vh - 190px);
  pointer-events: auto;
}

.analysis-card {
  position: relative;
  overflow: hidden;
  min-height: 0;
  padding: 14px 16px 16px;
  border: 1px solid rgba(55, 228, 255, 0.28);
  background:
    linear-gradient(135deg, rgba(7, 36, 52, 0.88), rgba(4, 18, 29, 0.74)),
    radial-gradient(circle at 16% 4%, rgba(36, 246, 196, 0.18), transparent 38%),
    radial-gradient(circle at 90% 0, rgba(48, 220, 255, 0.12), transparent 34%);
  box-shadow:
    inset 0 1px 0 rgba(180, 252, 255, 0.16),
    inset 0 0 26px rgba(48, 220, 255, 0.12),
    0 16px 30px rgba(0, 0, 0, 0.32);
  box-sizing: border-box;

  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 2px;
    background: linear-gradient(90deg, #24f6c4, rgba(48, 220, 255, 0.18), transparent);
    opacity: 0.8;
  }

  &::after {
    content: "";
    position: absolute;
    right: 12px;
    top: 10px;
    width: 78px;
    height: 18px;
    border-top: 1px solid rgba(48, 220, 255, 0.22);
    background: repeating-linear-gradient(90deg, rgba(48, 220, 255, 0.18) 0 7px, transparent 7px 14px);
    opacity: 0.42;
    pointer-events: none;
  }
}

.analysis-card-head {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: 26px;
  color: #ffffff;

  span {
    padding-left: 10px;
    font-size: 16px;
    font-weight: 800;
    letter-spacing: 0;

    &::before {
      content: "";
      position: absolute;
      left: 0;
      top: 7px;
      width: 3px;
      height: 14px;
      background: #24f6c4;
      box-shadow: 0 0 10px rgba(36, 246, 196, 0.8);
    }
  }

  strong {
    color: #24f6c4;
    font-size: 13px;
    font-weight: 700;
  }
}

.analysis-operation {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 14px;

  &.has-boundary {
    margin-top: 10px;

    .analysis-entry {
      height: 40px;

      span {
        font-size: 13px;
      }
    }
  }
}

.analysis-entry {
  position: relative;
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr);
  column-gap: 12px;
  align-content: center;
  align-items: center;
  min-width: 0;
  height: 64px;
  padding: 0 16px;
  border: 1px solid rgba(48, 220, 255, 0.34);
  background:
    linear-gradient(90deg, rgba(48, 220, 255, 0.18), rgba(48, 220, 255, 0.04)),
    rgba(4, 23, 34, 0.62);
  color: #ffffff;
  text-align: left;
  cursor: pointer;
  box-sizing: border-box;
  box-shadow: inset 0 0 18px rgba(48, 220, 255, 0.08);

  &::before {
    content: "";
    grid-row: 1 / 3;
    width: 34px;
    height: 34px;
    border: 1px solid rgba(36, 246, 196, 0.42);
    background:
      linear-gradient(135deg, rgba(36, 246, 196, 0.22), rgba(48, 220, 255, 0.08)),
      rgba(4, 23, 34, 0.7);
    box-shadow:
      inset 0 0 12px rgba(48, 220, 255, 0.12),
      0 0 12px rgba(36, 246, 196, 0.16);
  }

  &.is-upload::before {
    background:
      linear-gradient(#24f6c4 0 0) 50% 9px / 2px 15px no-repeat,
      linear-gradient(45deg, transparent 42%, #24f6c4 43% 56%, transparent 57%) 10px 8px / 8px 8px no-repeat,
      linear-gradient(-45deg, transparent 42%, #24f6c4 43% 56%, transparent 57%) 16px 8px / 8px 8px no-repeat,
      linear-gradient(#24f6c4 0 0) 50% 25px / 16px 2px no-repeat,
      linear-gradient(135deg, rgba(36, 246, 196, 0.22), rgba(48, 220, 255, 0.08)),
      rgba(4, 23, 34, 0.7);
  }

  &.is-draw::before {
    background:
      radial-gradient(circle, #24f6c4 0 2px, transparent 2.5px) 21px 7px / 8px 8px no-repeat,
      linear-gradient(135deg, transparent 42%, #24f6c4 43% 56%, transparent 57%) 9px 11px / 18px 18px no-repeat,
      linear-gradient(#24f6c4 0 0) 9px 25px / 14px 2px no-repeat,
      linear-gradient(135deg, rgba(36, 246, 196, 0.22), rgba(48, 220, 255, 0.08)),
      rgba(4, 23, 34, 0.7);
  }

  &::after {
    content: "";
    position: absolute;
    right: 9px;
    top: 9px;
    width: 18px;
    height: 18px;
    border-top: 1px solid rgba(36, 246, 196, 0.45);
    border-right: 1px solid rgba(36, 246, 196, 0.45);
  }

  span {
    min-width: 0;
    font-size: 16px;
    font-weight: 700;
    line-height: 20px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  strong {
    min-width: 0;
    margin-top: 2px;
    color: rgba(232, 252, 255, 0.72);
    font-size: 11px;
    font-weight: 500;
    line-height: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  input {
    position: absolute;
    left: -9999px;
    top: -9999px;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
  }

  &:disabled,
  &.is-disabled {
    opacity: 0.42;
    cursor: not-allowed;
  }

  &:not(:disabled):not(.is-disabled):hover {
    border-color: rgba(36, 246, 196, 0.72);
    background:
      linear-gradient(90deg, rgba(36, 246, 196, 0.2), rgba(48, 220, 255, 0.06)),
      rgba(4, 23, 34, 0.56);
  }
}

.analysis-drawing-bar,
.analysis-drawing-stage,
.analysis-boundary-bar {
  display: grid;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding: 0 12px;
  border: 1px solid rgba(36, 246, 196, 0.34);
  background:
    linear-gradient(90deg, rgba(36, 246, 196, 0.13), rgba(48, 220, 255, 0.06)),
    rgba(4, 23, 34, 0.58);
  box-sizing: border-box;
}

.analysis-drawing-stage {
  grid-template-columns: 78px 74px 54px minmax(68px, 1fr);
  height: 70px;
  padding: 0 12px;
  border-color: rgba(126, 251, 246, 0.46);
  background:
    linear-gradient(90deg, rgba(36, 246, 196, 0.15), rgba(48, 220, 255, 0.08)),
    rgba(4, 23, 34, 0.54);
}

.analysis-drawing-bar {
  grid-template-columns: 92px minmax(0, 1fr) 66px 74px;
}

.analysis-boundary-bar {
  grid-template-columns: 92px minmax(0, 1fr) 86px;
  height: 42px;

  span {
    color: #24f6c4;
    font-size: 13px;
    font-weight: 700;
  }

  strong {
    min-width: 0;
    overflow: hidden;
    color: rgba(232, 252, 255, 0.72);
    font-size: 12px;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  button {
    height: 30px;
    border: 1px solid rgba(255, 225, 90, 0.5);
    background: rgba(255, 225, 90, 0.1);
    color: #ffe15a;
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
  }
}

.analysis-point-count {
  display: grid;
  grid-template-columns: auto auto;
  align-items: baseline;
  column-gap: 3px;
  color: rgba(232, 252, 255, 0.82);
  font-size: 12px;

  span {
    grid-column: 1 / -1;
    color: rgba(232, 252, 255, 0.7);
    line-height: 18px;
  }

  strong {
    color: #ffffff;
    font-family: D-DIN, Arial, sans-serif;
    font-size: 30px;
    line-height: 32px;
  }

  em {
    color: rgba(232, 252, 255, 0.72);
    font-style: normal;
  }

  small {
    grid-column: 1 / -1;
    margin-top: 2px;
    color: rgba(255, 225, 90, 0.78);
    font-size: 11px;
    line-height: 14px;
  }
}

.analysis-control {
  height: 34px;
  min-width: 0;
  padding: 0 4px;
  border: 1px solid rgba(48, 220, 255, 0.26);
  background: rgba(48, 220, 255, 0.08);
  color: rgba(255, 255, 255, 0.9);
  font-size: 12px;
  cursor: pointer;

  &.is-primary {
    border-color: rgba(36, 246, 196, 0.78);
    background: rgba(36, 246, 196, 0.22);
    color: #ffffff;
    font-weight: 700;
  }

  &.is-danger {
    border-color: rgba(255, 128, 96, 0.52);
    background: rgba(255, 128, 96, 0.12);
    color: #ffb39a;
  }

  &:disabled {
    opacity: 0.42;
    cursor: not-allowed;
  }
}

.analysis-result {
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: clamp(500px, 56vh, 620px);
  min-height: 0;
  max-height: calc(100vh - 350px);
}

.analysis-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 126px;
  color: rgba(232, 252, 255, 0.68);
  font-size: 13px;
  text-align: center;

  &.is-success {
    min-height: 64px;
    border: 1px solid rgba(36, 246, 196, 0.18);
    background: rgba(36, 246, 196, 0.06);
    color: #24f6c4;
  }
}

.analysis-conclusion {
  display: grid;
  gap: 8px;
  padding: 12px;
  border: 1px solid rgba(48, 220, 255, 0.22);
  background:
    linear-gradient(135deg, rgba(48, 220, 255, 0.09), rgba(4, 23, 34, 0.48)),
    rgba(4, 23, 34, 0.5);
  box-sizing: border-box;

  &.is-hit {
    border-color: rgba(255, 225, 90, 0.24);
    background: linear-gradient(135deg, rgba(255, 225, 90, 0.08), rgba(4, 23, 34, 0.36));
  }

  &.is-heavy {
    border-color: rgba(255, 128, 96, 0.28);
    background: linear-gradient(135deg, rgba(255, 128, 96, 0.1), rgba(4, 23, 34, 0.38));
  }

  &.is-clear {
    border-color: rgba(36, 246, 196, 0.24);
    background: linear-gradient(135deg, rgba(36, 246, 196, 0.08), rgba(4, 23, 34, 0.36));
  }

  p,
  small {
    margin: 0;
    color: rgba(232, 252, 255, 0.76);
    font-size: 12px;
    line-height: 20px;
  }

  small {
    color: rgba(255, 225, 90, 0.86);
    font-weight: 600;
  }
}

.analysis-conclusion-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;

  span {
    color: rgba(232, 252, 255, 0.72);
    font-size: 11px;
  }

  strong {
    color: #24f6c4;
    font-size: 13px;
    font-weight: 700;
  }
}

.analysis-conclusion-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;

  span {
    max-width: 100%;
    height: 24px;
    padding: 0 9px;
    overflow: hidden;
    border: 1px solid rgba(48, 220, 255, 0.2);
    background: rgba(48, 220, 255, 0.08);
    color: rgba(232, 252, 255, 0.8);
    font-size: 11px;
    line-height: 22px;
    text-overflow: ellipsis;
    white-space: nowrap;
    box-sizing: border-box;
  }
}

.analysis-project-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding-right: 6px;
  scrollbar-color: rgba(48, 220, 255, 0.58) rgba(48, 220, 255, 0.1);
  scrollbar-width: thin;

  &::-webkit-scrollbar {
    width: 4px;
  }

  &::-webkit-scrollbar-track {
    background: rgba(48, 220, 255, 0.08);
  }

  &::-webkit-scrollbar-thumb {
    border-radius: 0;
    background: linear-gradient(180deg, rgba(36, 246, 196, 0.9), rgba(48, 220, 255, 0.42));
  }
}

.analysis-project-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 7px;
  width: 100%;
  min-height: 86px;
  margin-bottom: 8px;
  padding: 10px 12px;
  border: 1px solid rgba(48, 220, 255, 0.18);
  border-left-color: rgba(36, 246, 196, 0.56);
  background:
    linear-gradient(90deg, rgba(36, 246, 196, 0.1), rgba(48, 220, 255, 0.04)),
    rgba(4, 23, 34, 0.54);
  color: #ffffff;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
  box-sizing: border-box;
  box-shadow: inset 0 0 16px rgba(48, 220, 255, 0.05);

  .analysis-project-name,
  b {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .analysis-project-name {
    display: block;
    color: #ffffff;
    font-size: 13px;
    font-weight: 700;
    line-height: 18px;
  }

  .analysis-project-meta {
    display: grid;
    grid-template-columns: 52px 72px minmax(0, 1fr);
    align-items: center;
    gap: 6px;
    min-width: 0;
  }

  strong,
  em,
  small {
    height: 26px;
    min-width: 0;
    overflow: hidden;
    border: 1px solid rgba(48, 220, 255, 0.18);
    background: rgba(4, 23, 34, 0.46);
    font-size: 12px;
    line-height: 24px;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
    box-sizing: border-box;
  }

  strong {
    color: #24f6c4;
    font-weight: 700;
  }

  em {
    color: rgba(255, 255, 255, 0.82);
    font-style: normal;
  }

  small {
    padding: 0 5px;
    color: rgba(255, 225, 90, 0.9);
    text-align: right;
  }

  b {
    color: rgba(232, 252, 255, 0.66);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
  }

  &:hover {
    border-color: rgba(36, 246, 196, 0.48);
    background:
      linear-gradient(90deg, rgba(36, 246, 196, 0.14), rgba(48, 220, 255, 0.06)),
      rgba(4, 23, 34, 0.62);
  }
}
</style>
