import type { AxiosError } from 'axios'
import http from './http'

/**
 * 与 SimpleLite `MapEditApiController` 对应的前端胶水。
 * 路径前缀：`/sl/projection/map-edit`（YARP → SimpleLite `/projection/map-edit`）。
 *
 * 也覆盖 AI 服务配置 `/sl/projection/ai-config`（GET / POST），由 AiConfigController 提供。
 *
 * 所有方法都通过统一信封 `{ success, code, data, message }` 返回；调用方拿到的是 `data` 部分，
 * 失败时直接抛 Error，让上层 try/catch 或 ElMessage.error 统一处理。
 */

const BASE = '/sl/projection/map-edit'
const AI_BASE = '/sl/projection/ai-config'

export interface MapEditEnvelope<T> {
  success: boolean
  code: number
  data: T | null
  message: string
}

export type MapEditKind =
  | 'site'
  | 'track'
  | 'bezier'
  | 'arc'
  | 'nurbs'
  | 'image'
  | 'text'
  | 'model'
  | 'special'

export interface CreateSitePayload {
  x: number
  y: number
  z?: number
  name?: string
  layer?: string
  [k: string]: unknown
}

export interface CreateTrackPayload {
  siteA: number
  siteB: number
  name?: string
  layer?: string
  [k: string]: unknown
}

export interface CreateImagePayload {
  x: number
  y: number
  imagePath: string
  size?: number
  angle?: number
  opacity?: number
  name?: string
  layer?: string
}

export interface CreateTextPayload {
  x: number
  y: number
  content: string
  color?: string
  size?: number
  name?: string
  layer?: string
}

export interface CreateModelPayload {
  x: number
  y: number
  modelPath: string
  scale?: number
  rotation?: number
  zOffset?: number
  name?: string
  layer?: string
}

export interface CreateCarPayload {
  x: number
  y: number
  /** 朝向，弧度。可省略，默认 0。 */
  theta?: number
  name?: string
  layer?: string
}

export interface BatchOp {
  action: 'create' | 'delete' | 'patch'
  kind?: MapEditKind | string
  id?: number
  data?: Record<string, unknown>
}

export interface PickResult {
  x: number
  y: number
  siteId: number
}

export interface DashboardSummary {
  cars: number
  onlineCars: number
  alarmingCars: number
  sites: number
  tracks: number
  specials: number
  maps: number
  missions: number
  timestamp: string
}

export interface AssetUploadResult {
  fileName: string
  savedPath: string
  /** 相对 url，前端拼上 webVRender host 即可显示。 */
  url: string
}

export type SelectTrackMode = 'all' | 'straight' | 'curve'

export interface ViewFilterSnapshot {
  selectFilter: {
    sites: boolean
    tracks: boolean
    cars: boolean
    decor: boolean
    /** 路径子类型：all=全部；straight=仅 UITrack；curve=贝塞尔/弧/NURBS */
    trackMode?: SelectTrackMode
  }
  alignSnap: { sites: boolean; cars: boolean; tracks: boolean }
  showViewport: { sceneLabels: boolean; scenePrimitives: boolean; cars: boolean }
}

/** 部分更新；只传你要改的字段即可。后端不会覆盖未传字段。 */
export interface ViewFilterPatch {
  selectFilter?: Partial<ViewFilterSnapshot['selectFilter']>
  alignSnap?: Partial<ViewFilterSnapshot['alignSnap']>
  showViewport?: Partial<ViewFilterSnapshot['showViewport']>
}

export interface AiMapGenerateRequest {
  prompt: string
  mode?: 'sites' | 'tracks' | 'sites+tracks' | 'full'
  bounds?: [number, number, number, number]
  referenceImageBase64?: string
  layer?: string
  constraints?: string
}

export interface AiMapGenerateResult {
  created: Array<{ tool: string; kind: string; id: number; typeName?: string; error?: string }>
  assistantText?: string
  usedTools: number
}

export interface AiConfig {
  endpoint: string
  apiKey: string
  model: string
  systemPrompt: string
  temperature: number
  maxTokens: number
  timeoutSec: number
  /** 仅 GET 返回时携带：apiKey 非空 → true。 */
  configured?: boolean
}

async function unwrap<T>(promise: Promise<{ data: MapEditEnvelope<T> }>): Promise<T> {
  const { data } = await promise
  if (!data?.success) throw new Error(data?.message ?? 'request failed')
  return data.data as T
}

export const mapEditApi = {
  // 对象创建/删除
  createSite: (p: CreateSitePayload) =>
    unwrap<{ kind: 'site'; id: number; name?: string }>(http.post(`${BASE}/objects/site`, p)),

  createTrack: (kind: 'track' | 'bezier' | 'arc' | 'nurbs', p: CreateTrackPayload) =>
    unwrap<{ kind: 'track'; id: number; typeName: string }>(http.post(`${BASE}/objects/${kind}`, p)),

  createImage: (p: CreateImagePayload) =>
    unwrap<{ kind: 'image'; id: number }>(http.post(`${BASE}/objects/image`, p)),

  createText: (p: CreateTextPayload) =>
    unwrap<{ kind: 'text'; id: number }>(http.post(`${BASE}/objects/text`, p)),

  createModel: (p: CreateModelPayload) =>
    unwrap<{ kind: 'model'; id: number }>(http.post(`${BASE}/objects/model`, p)),

  /** 直接创建一辆模拟车 (DummyCar)，不经过画布 pick。 */
  createCar: (p: CreateCarPayload) =>
    unwrap<{ kind: 'car'; id: number; typeName: string }>(http.post(`${BASE}/objects/car`, p)),

  deleteObject: (kind: MapEditKind | string, id: number) =>
    unwrap<{ kind: string; id: number; deleted: boolean }>(http.delete(`${BASE}/objects/${kind}/${id}`)),

  batch: (ops: BatchOp[]) =>
    unwrap<{ count: number; results: unknown[] }>(http.post(`${BASE}/objects/batch`, { ops })),

  /**
   * CAD 相对包围盒对齐（SimpleLite CadAlignService）。
   * mode: left|right|top|bottom|centerH|centerV|center|distributeH|distributeV
   */
  cadAlign: (mode: string, targets: Array<{ kind: string; id: number }>) =>
    unwrap<{
      mode: string
      count: number
      previous: Array<{ kind: string; id: number; x: number; y: number }>
      updated: Array<{ kind: string; id: number; x: number; y: number }>
    }>(http.post(`${BASE}/cad/align`, { mode, targets })),

  /** 横向/纵向等距建站（CadBatchGenerateService.PlanLinear）。 */
  cadBatchLinear: (p: {
    horizontal: boolean
    x1: number
    y1: number
    x2: number
    y2: number
    count: number
    layer?: string
    namePrefix?: string
  }) =>
    unwrap<{ count: number; created: Array<{ kind: string; id: number }> }>(
      http.post(`${BASE}/cad/batch-linear`, p)
    ),

  /** 矩阵建站（CadBatchGenerateService.PlanMatrix）。 */
  cadBatchMatrix: (p: {
    x1: number
    y1: number
    x2: number
    y2: number
    rows: number
    cols: number
    layer?: string
    namePrefix?: string
  }) =>
    unwrap<{ count: number; created: Array<{ kind: string; id: number }> }>(
      http.post(`${BASE}/cad/batch-matrix`, p)
    ),

  copyFieldsTo: (kind: MapEditKind | string, id: number, fieldNames: string[], targets: Array<{ kind: string; id: number }>) =>
    unwrap<{ copied: number }>(http.post(`${BASE}/objects/${kind}/${id}/fields/copy-to`, { fieldNames, targets })),

  /** 场景已有自定义字段键（Ctrl+I /「复制字段」弹窗候选）。 */
  sceneFieldKeys: () =>
    unwrap<{ keys: string[] }>(http.get(`${BASE}/scene-field-keys`)),

  /** 对多个目标批量写入同一字段值（对齐桌面 Ctrl+I）。 */
  batchSetField: (key: string, value: string, targets: Array<{ kind: string; id: number }>) =>
    unwrap<{ affected: number }>(http.post(`${BASE}/batch-set-field`, { key, value, targets })),

  /** 端点落在给定站点上的路径（删站点会级联删除；撤销前需一并快照）。 */
  tracksTouchingSites: (siteIds: number[]) =>
    unwrap<{ tracks: Array<{ id: number; typeName: string; siteA: number; siteB: number }> }>(
      http.post(`${BASE}/tracks-touching-sites`, { siteIds })
    ),

  // 拾取 / 仪表盘。snap=false 时落点用原始鼠标坐标（文本/图片/站点）；布线取端点默认 true。
  pick: (opts?: { snap?: boolean }) =>
    unwrap<PickResult>(http.post(`${BASE}/pick`, { snap: opts?.snap ?? true })),

  dashboardSummary: () =>
    unwrap<DashboardSummary>(http.get(`${BASE}/dashboard/summary`)),

  // 资产上传：JSON body 传 base64（后端按 JSON 解析；勿手动设 multipart 头 —— body 并非 multipart）。
  uploadAsset: (filename: string, dataBase64: string) =>
    unwrap<AssetUploadResult>(http.post(`${BASE}/assets/upload`, { filename, data: dataBase64 })),

  // AI 生图
  aiMapGenerate: (req: AiMapGenerateRequest) =>
    unwrap<AiMapGenerateResult>(http.post(`${BASE}/ai/map-generate`, req)),

  projectSave: (path?: string) =>
    unwrap<{ path: string; savedAt: string }>(http.post(`${BASE}/project/save`, { path: path ?? '' })),

  projectLoad: (path: string) =>
    unwrap<{ path: string; sites: number; tracks: number; specials: number; missions: number }>(
      http.post(`${BASE}/project/load`, { path })
    ),

  projectCurrent: () =>
    unwrap<{ sites: number; tracks: number; specials: number; maps: number; missions: number; timestamp: string; cwd: string }>(
      http.get(`${BASE}/project/current`)
    ),

  projectBrowse: (dir?: string) =>
    unwrap<{
      dir: string
      parent: string | null
      subdirs: Array<{ name: string; fullPath: string }>
      jsons: Array<{ name: string; fullPath: string; sizeBytes: number; modified: string }>
    }>(http.get(`${BASE}/project/browse`, { params: dir ? { dir } : {} })),

  /**
   * 在 SimpleLite 桌面进程上弹 Windows 原生「打开文件」对话框，
   * 让用户挑一个项目 JSON。返回 { path, cancelled }；cancelled=true 时 path 为 null。
   * initialDir 默认 SimpleLite 程序工作目录。
   */
  projectNativePickOpen: (initialDir?: string) =>
    unwrap<{ path: string | null; cancelled: boolean }>(
      http.post(`${BASE}/project/native-pick-open`, { initialDir: initialDir ?? '' })
    ),

  /**
   * 弹 Windows 原生「另存为」对话框。返回 { path, cancelled }。
   * suggestedFileName 默认 project.current.json。
   */
  projectNativePickSave: (initialDir?: string, suggestedFileName?: string) =>
    unwrap<{ path: string | null; cancelled: boolean }>(
      http.post(`${BASE}/project/native-pick-save`, {
        initialDir: initialDir ?? '',
        suggestedFileName: suggestedFileName ?? 'project.current.json'
      })
    ),

  sampleSitesAlongTrack: (trackId: number, opts: { spacingMm: number; includeEndpoints?: boolean; layer?: string }) =>
    unwrap<{ created: number; ids: number[] }>(
      http.post(`${BASE}/objects/track/${trackId}/sample-sites`, opts)
    ),

  /**
   * 读取后端「过滤」菜单当前的三组开关。编辑器载入时拉一次，让前端 reactive 状态
   * 与 SimpleLite 真实开关一致（避免「前端勾着、后端没改」的偏差）。
   */
  getViewFilter: () => unwrap<ViewFilterSnapshot>(http.get(`${BASE}/view-filter`)),

  /**
   * 部分更新「过滤」菜单。只传要改的字段即可，未传字段保持原状。
   * 后端写入对应 SimpleUI 静态字段 + 触发 SimpleSceneRenderer.NotifyStateChanged()
   * 立即重画工作区，对 SelectFilter 变化还会同步 CycleGUI 引擎层 SelectObject 模式。
   */
  setViewFilter: (patch: ViewFilterPatch) =>
    unwrap<ViewFilterSnapshot>(http.post(`${BASE}/view-filter`, patch))
}

export const aiConfigApi = {
  get: () => unwrap<AiConfig>(http.get(`${AI_BASE}/`)),
  save: (cfg: AiConfig) => unwrap<{ saved: boolean }>(http.post(`${AI_BASE}/`, cfg))
}

// ──────────────────────────────────────────────────────────────────────────
// 地图管理（固定文件夹 + 按名称列表 / 保存 / 删除 / 使用 / 编辑打开）
// 对应 SimpleLite MapEditApiController 的 /map-edit/maps* 接口。
// ──────────────────────────────────────────────────────────────────────────

export interface MapListItem {
  /** 地图名称（文件名去掉 .json 后缀），列表展示与各操作都以它为标识。 */
  name: string
  fileName: string
  sizeBytes: number
  modified: string
  /** 是否为当前项目默认使用的地图（列表高亮）。 */
  isCurrent: boolean
}

export interface MapListResult {
  directory: string
  currentFileName: string | null
  maps: MapListItem[]
}

export interface MapLoadSummary {
  name: string
  fileName: string
  path: string
  sites: number
  tracks: number
  specials: number
  missions: number
}

export interface SceneTaskBusyCar {
  id: number
  name: string
  reasons: string[]
}

export interface SceneTaskStatus {
  hasTask: boolean
  busyCount: number
  busyCars: SceneTaskBusyCar[]
}

export interface MapSaveResult {
  name: string
  fileName: string
  path: string
  savedAt: string
}

export interface MapContentResult {
  name: string
  fileName: string
  content: string
}

/** 保存结果：success 正常返回 data；conflict=true 表示同名地图已存在，调用方应弹「替换」确认。 */
export type MapSaveOutcome =
  | { ok: true; data: MapSaveResult }
  | { ok: false; conflict: boolean; message: string }

export interface MapMergeResult {
  name: string
  fileName: string
  path: string
  /** 底图 = 合并时的「当前使用地图」名。 */
  baseMap: string
  /** 合并进来的源地图名（去重、且不含当前地图自身，按叠加顺序）。 */
  sources: string[]
  sourceCount: number
  /** target 与当前地图同名 → 本次合并覆盖了当前地图。 */
  overwroteCurrent: boolean
  /** 合并后统计（用于结果提示）。 */
  sites: number
  tracks: number
  specials: number
  missions: number
  savedAt: string
}

/** 合并结果：conflict=true 仅表示目标地图同名已存在，调用方应弹「替换」确认后重试。 */
export type MapMergeOutcome =
  | { ok: true; data: MapMergeResult }
  | { ok: false; conflict: boolean; message: string }

export const mapsApi = {
  list: () => unwrap<MapListResult>(http.get(`${BASE}/maps`)),

  /** 读取固定地图文件夹内某张地图的 JSON 原文（MiGu.Server MapsContentController）。 */
  readContent: (name: string) =>
    http.get<MapContentResult>(
      `/maps/${encodeURIComponent(name)}/content`
    ).then((r) => r.data),

  sceneTaskStatus: () => unwrap<SceneTaskStatus>(http.get(`${BASE}/maps/scene-task-status`)),

  /**
   * 保存当前场景为固定文件夹内的地图。overwrite=false 且同名已存在时后端回 409，
   * 这里翻译为 { ok:false, conflict:true }，让调用方弹「是否替换原地图」确认框。
   */
  async save(name: string, overwrite = false): Promise<MapSaveOutcome> {
    try {
      const { data } = await http.post<MapEditEnvelope<MapSaveResult>>(`${BASE}/maps/save`, { name, overwrite })
      if (data?.success) return { ok: true, data: data.data as MapSaveResult }
      return { ok: false, conflict: data?.code === 409, message: data?.message ?? '保存失败' }
    } catch (err) {
      const ax = err as AxiosError<MapEditEnvelope<MapSaveResult>>
      const body = ax.response?.data
      if (ax.response?.status === 409 || body?.code === 409) {
        return { ok: false, conflict: true, message: body?.message ?? '地图已存在' }
      }
      throw err
    }
  },

  /** 加载指定地图到场景以供编辑（不校验任务、不改当前使用地图）。 */
  open: (name: string) => unwrap<MapLoadSummary>(http.post(`${BASE}/maps/open`, { name })),

  /**
   * 设为当前使用地图并加载。后端切换前会校验场景无车辆任务；存在任务回 409，
   * unwrap 会抛出携带后端友好文案的 Error，调用方 try/catch 提示即可。
   */
  use: (name: string) => unwrap<MapLoadSummary>(http.post(`${BASE}/maps/use`, { name })),

  delete: (name: string) =>
    unwrap<{ name: string; fileName: string; deleted: boolean }>(
      http.delete(`${BASE}/maps/${encodeURIComponent(name)}`)
    ),

  /** 重命名 maps 目录下的地图文件（from / to 均为不含扩展名的地图名）。 */
  rename: (from: string, to: string) =>
    unwrap<{ from: string; to: string; fileName: string; path: string }>(
      http.post(`${BASE}/maps/rename`, { from, to })
    ),

  /**
   * 把选中的地图合并进「当前使用地图」后另存为新地图（语义同桌面端「合并」= SimpleProject.ImportFile）。
   * sources 为要合并进来的源地图名数组（≥1，当前地图自身会被后端忽略），以「当前使用地图」为底图依次叠加。
   * target 与当前地图同名 + overwrite 即覆盖当前地图。
   * 目标同名且 overwrite=false 时后端回 409 → { ok:false, conflict:true }，调用方弹「替换」确认；
   * 当前未设置使用地图（400）/ 场景内有车辆任务（409）时 message 不含「已存在」，conflict=false，调用方直接提示。
   */
  async merge(sources: string[], target: string, overwrite = false): Promise<MapMergeOutcome> {
    try {
      const { data } = await http.post<MapEditEnvelope<MapMergeResult>>(`${BASE}/maps/merge`, {
        sources,
        target,
        overwrite
      })
      if (data?.success) return { ok: true, data: data.data as MapMergeResult }
      const conflict = data?.code === 409 && (data?.message ?? '').includes('已存在')
      return { ok: false, conflict, message: data?.message ?? '合并失败' }
    } catch (err) {
      // 与 save() 对齐：后端以 HTTP 409 状态码返回时同样翻译为 conflict，
      // 让调用方能弹「是否替换」确认而非直接报错。
      const ax = err as AxiosError<MapEditEnvelope<MapMergeResult>>
      const body = ax.response?.data
      if (ax.response?.status === 409 || body?.code === 409) {
        const message = body?.message ?? '目标地图已存在'
        return { ok: false, conflict: message.includes('已存在'), message }
      }
      throw err
    }
  }
}
