import http from './http'
import { ElMessageBox } from 'element-plus'
import {
  mockReflectionAssemblies,
  mockReflectionBundle,
  mockReflectionDeleteField,
  mockReflectionExecute,
  mockReflectionFields,
  mockReflectionKinds,
  mockReflectionMethods,
  mockReflectionMethodsByType,
  mockReflectionObjects,
  mockReflectionSetField,
  mockReflectionStatus
} from '@/mock/data/reflection'

// 与 auth.ts / config.ts / ops.ts 统一走 VITE_USE_MOCK 开关，避免 PROJECTION/USE 双命名造成
// .env 改一个忘改另一个的诡异半 mock 状态。env.d.ts 已声明 VITE_USE_MOCK 类型。
const MOCK = import.meta.env.VITE_USE_MOCK === 'true'

/**
 * 与 SimpleLite `ReflectionApiController` 一一对应的前端胶水。
 * 路径前缀：`/sl/projection/reflection`（迷榖平台 YARP 反代下游 → SimpleLite EmbedIO）。
 *
 * 该客户端覆盖了 SimpleLite 主体以及通过 plugins/*.dll 动态加载的 Standard 等
 * 插件中所有标注了 `MethodMember` 的方法与所有继承自 Car/Mission/CarProgram/Site/Track
 * 的子类型，平台前端可据此动态渲染列表、属性、动作按钮。
 */

const BASE = '/sl/projection/reflection'

export type ReflectionKind =
  | 'car'
  | 'vehicle'
  | 'mission'
  | 'process'
  | 'site'
  | 'track'
  | 'map'
  | 'special'
  | 'script'
  // 顶级合成 kind：等于 site + track + special 的并集，由 SimpleLite 后端 /objects/scene 直接返回，
  // 每行带 subKind 字段，前端 bundle / execute / setField 走对应底层 kind。
  | 'scene'

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

export interface ReflectionParam {
  name: string
  typeName: string
  hasDefault: boolean
  defaultValue?: string | null
}

export interface ReflectionMethod {
  methodName: string
  label?: string | null
  description?: string | null
  hint?: string | null
  returnType: string
  hasParams: boolean
  params: ReflectionParam[]
  requiresPlatformConfirm?: boolean
  confirmMessage?: string | null
}

export interface ReflectionObject {
  id: number
  name: string
  typeName: string
  layer?: string | null
  status?: string | null
  summary?: string | null
  /** 当顶级 kind 是合成 kind（如 "scene"）时，此字段标记底层真实 kind。 */
  subKind?: string | null
}

export interface ReflectionKv {
  key: string
  value: string
  locked?: boolean
  /** "typed" = 强类型 [FieldMember]，不可删；"dynamic" = Prop.fields 动态字段，可删。 */
  source?: 'typed' | 'dynamic'
  /** 后端字段的 .NET 类型名（String / Single / Int32 / Boolean 等），用于前端渲染对应控件。 */
  typeName?: string
}

export interface ReflectionTypeMethods {
  typeName: string
  fullTypeName?: string
  typeLabel?: string
  assemblyName: string
  methods: ReflectionMethod[]
}

export interface ReflectionAssembly {
  name: string
  version?: string
  location?: string
}

/** 通过 GET /reflection/types/{kind} 拿到的可创建子类型行（进程 / 脚本 / 车辆管理面板的「新建」下拉用）。 */
export interface ReflectionCreatableType {
  typeName: string
  shortName: string
  label: string
  assemblyName: string
}

/** GET /reflection/plugins 返回的单个插件元信息。 */
export interface PluginEntry {
  name: string
  dllPath: string
  assemblyName: string
  assemblyVersion: string
  collectible: boolean
  loadedTypes: number
  loadedAt: string
  missionTypes: number
  carTypes: number
}

export interface ReflectionKindMeta {
  kind: ReflectionKind
  count: number
  label: string
  /** "primary" 表示顶级 5 分类；undefined / 其他视为底层 kind。 */
  group?: string
}

export interface ReflectionSubKindMeta extends ReflectionKindMeta {
  parent: ReflectionKind
}

export interface ReflectionSelection {
  kind: ReflectionKind | null
  id: number
  name: string
  typeName?: string
}

export interface ScriptSourcePayload {
  id: number
  name: string
  typeName: string
  state?: string | null
  script: string
}

export interface ScriptExceptionStatusPayload {
  id: number
  name: string
  typeName: string
  car: string
  state?: string | null
  exception: string
  notifies: string
  report: string
}

// ──────────────────────────────────────────────────────────────────────────
// 地图监控可见性配置（与后端 MonitorVisibilityConfig / MonitorVisibilityForKind 对应）
//
// `config`    = 管理员勾选的白名单。空数组 = 显示全部；非空 = 仅显示列表中的 key。
// `available` = 当前 SimpleLite 进程里能扫描到的全部可勾选项（基类 + 已加载子类 +
//                运行时实例的 Prop.fields / status 反射键）。是 GET 返回的副产物，
//                POST 写入时不需要、也不该回传。
// ──────────────────────────────────────────────────────────────────────────

export type MonitorVisibilityKind = 'car' | 'site' | 'track'

export interface MonitorVisibilityForKind {
  fields: string[]
  status: string[]
  methods: string[]
}

export interface MonitorVisibilityMap {
  car: MonitorVisibilityForKind
  site: MonitorVisibilityForKind
  track: MonitorVisibilityForKind
}

/** POST /monitor-config 请求体：三组白名单 + 可选按车型动作。 */
export interface MonitorConfigSaveBody extends MonitorVisibilityMap {
  carActionByType?: Record<string, string[]>
}

export interface MonitorConfigPayload {
  config: MonitorVisibilityMap & { carActionByType?: Record<string, string[]> }
  available: MonitorVisibilityMap
}

const MOCK_MONITOR_STORAGE_KEY = 'simple-platform-mock-monitor-config'

function loadMockMonitorConfig(): MonitorConfigSaveBody {
  try {
    const raw = localStorage.getItem(MOCK_MONITOR_STORAGE_KEY)
    if (raw) return JSON.parse(raw) as MonitorConfigSaveBody
  } catch { /* ignore */ }
  return emptyMonitorVisibilityMap()
}

function saveMockMonitorConfig(cfg: MonitorConfigSaveBody) {
  try {
    localStorage.setItem(MOCK_MONITOR_STORAGE_KEY, JSON.stringify(cfg))
  } catch { /* ignore */ }
}

export function emptyMonitorVisibilityForKind(): MonitorVisibilityForKind {
  return { fields: [], status: [], methods: [] }
}

export function emptyMonitorVisibilityMap(): MonitorVisibilityMap {
  return {
    car: emptyMonitorVisibilityForKind(),
    site: emptyMonitorVisibilityForKind(),
    track: emptyMonitorVisibilityForKind()
  }
}

// 语义上 available 与 config 同构（都是按 kind 分组的三组 key 列表），所以共用一个工厂。
// 单独保留命名是为了表达"业务含义不同"——可勾选全集 vs 已勾选白名单。
export const emptyMonitorAvailableMap = emptyMonitorVisibilityMap

export class ReflectionApiError extends Error {
  code: number
  data: unknown

  constructor(message: string, code: number, data?: unknown) {
    super(message)
    this.name = 'ReflectionApiError'
    this.code = code
    this.data = data ?? null
  }
}

export interface ReflectionExecuteResult {
  returnValue?: string | null
  accepted?: boolean
  completed?: boolean
}

/** 根据 execute 返回区分「已完成」与「已受理（后台继续）」文案。 */
export function formatReflectionExecuteMessage(
  label: string,
  result: ReflectionExecuteResult
): string {
  if (result.returnValue) return `已执行：${result.returnValue}`
  if (result.accepted && result.completed === false) {
    return `已受理：${label}（后台继续执行，请稍后在 SimpleLite 查看结果）`
  }
  if (result.accepted) return `已执行 ${label}`
  return `已执行 ${label}`
}

export interface ReflectionExecuteOptions {
  /** 已在平台侧完成二次确认时带上，对应后端 X-Platform-Confirmed: 1 */
  platformConfirmed?: boolean
}

async function get<T>(path: string): Promise<T> {
  const { data } = await http.get<ReflectionEnvelope<T>>(`${BASE}${path}`)
  if (!data?.success) throw new ReflectionApiError(data?.message ?? `reflection ${path} failed`, data?.code ?? 500, data?.data)
  return data.data as T
}

async function post<T>(
  path: string,
  params?: Record<string, string | number | boolean>,
  headers?: Record<string, string>
): Promise<T> {
  const { data } = await http.post<ReflectionEnvelope<T>>(`${BASE}${path}`, null, { params, headers })
  if (!data?.success) throw new ReflectionApiError(data?.message ?? `reflection ${path} failed`, data?.code ?? 500, data?.data)
  return data.data as T
}

async function del<T>(path: string): Promise<T> {
  const { data } = await http.delete<ReflectionEnvelope<T>>(`${BASE}${path}`)
  if (!data?.success) throw new ReflectionApiError(data?.message ?? `reflection ${path} failed`, data?.code ?? 500, data?.data)
  return data.data as T
}

async function patchJson<T>(path: string, body: unknown): Promise<T> {
  const { data } = await http.patch<ReflectionEnvelope<T>>(`${BASE}${path}`, body)
  if (!data?.success) throw new ReflectionApiError(data?.message ?? `reflection PATCH ${path} failed`, data?.code ?? 500, data?.data)
  return data.data as T
}

async function executeWithPlatformConfirm<T>(
  path: string,
  params?: Record<string, string | number | boolean>,
  opts?: ReflectionExecuteOptions
): Promise<T> {
  const headers = opts?.platformConfirmed ? { 'X-Platform-Confirmed': '1' } : undefined
  try {
    return await post<T>(path, params, headers)
  } catch (e) {
    if (e instanceof ReflectionApiError && e.code === 428 && !opts?.platformConfirmed) {
      const confirmMessage =
        (e.data as { confirmMessage?: string | null } | null)?.confirmMessage?.trim()
        || '此操作需要确认'
      await ElMessageBox.confirm(confirmMessage, '确认', {
        type: 'warning',
        confirmButtonText: '确定',
        cancelButtonText: '取消'
      })
      return await post<T>(path, params, { 'X-Platform-Confirmed': '1' })
    }
    throw e
  }
}

export const reflectionApi = {
  listKinds: () => MOCK
    ? Promise.resolve(mockReflectionKinds())
    : get<{ kinds: ReflectionKindMeta[]; subKinds?: ReflectionSubKindMeta[] }>('/kinds'),

  listAssemblies: () => MOCK
    ? Promise.resolve(mockReflectionAssemblies())
    : get<ReflectionAssembly[]>('/assemblies'),

  listObjects: (kind: ReflectionKind) => MOCK
    ? Promise.resolve(mockReflectionObjects(kind))
    : get<ReflectionObject[]>(`/objects/${kind}`),

  /** 列出当前可实例化的子类型（用于「新建」下拉）；mock 模式直接给空。 */
  listCreatableTypes: (kind: ReflectionKind): Promise<ReflectionCreatableType[]> => MOCK
    ? Promise.resolve([])
    : get<ReflectionCreatableType[]>(`/types/${kind}`),

  /**
   * 创建一条对象。底层 POST `/objects/{kind}?...`，所有 extras 字段都作为 query 传给后端。
   *
   * 三类典型用法：
   *   - process/script + UiDiscoveryCache 子类：`createObject('process', 'TaskFlow')`
   *   - car（DummyCar 兜底，typeName 可空）：`createObject('car', '', { name: 'AGV-1', x: 0, y: 0 })`
   *   - site/track/image/text/model：`createObject('site', '', { x: 100, y: 200, name: 'A' })`
   *
   * 后端 BuildAndPersist 会做完整的字段类型转换；前端只负责传字符串。
   */
  createObject: (
    kind: ReflectionKind,
    typeName?: string,
    extras?: Record<string, string | number | boolean | undefined>
  ) => {
    if (MOCK) return Promise.resolve({ kind, id: -1, typeName: typeName ?? '' })
    const params: Record<string, string | number | boolean> = {}
    if (typeName) params.typeName = typeName
    if (extras) {
      for (const [k, v] of Object.entries(extras)) {
        if (v === undefined || v === null || v === '') continue
        params[k] = v
      }
    }
    return post<{ kind: string; id: number; typeName: string }>(`/objects/${kind}`, params)
  },

  /** 删除一条对象（process / script / car / site / track / special）。 */
  deleteObject: (kind: ReflectionKind, id: number) => MOCK
    ? Promise.resolve({ kind, id, deleted: true })
    : del<{ kind: string; id: number; deleted: boolean }>(`/objects/${kind}/${id}`),

  /**
   * 触发 SimpleLite 重新扫描 ./plugins 目录、加载新增 dll 并重建 UiDiscoveryCache。
   * 已加载的 dll 不会重复加载。
   * 返回：本次发现的 dll 总数、新加载的 assembly 数、当前可创建的 mission/car 类型计数。
   */
  reloadPlugins: () => MOCK
    ? Promise.resolve({ totalDlls: 0, newlyLoaded: 0, failed: 0, missionTypes: 0, carTypes: 0 })
    : post<{ totalDlls: number; newlyLoaded: number; failed: number; missionTypes: number; carTypes: number }>(
        '/plugins/reload'
      ),

  /** 列出当前已加载的所有 collectible 插件（PluginManager 跟踪范围内）。 */
  listPlugins: () => MOCK
    ? Promise.resolve<PluginEntry[]>([])
    : get<PluginEntry[]>('/plugins'),

  /**
   * 卸载一个 collectible 插件。
   * 失败原因常见：仍有 Mission / Car 实例占用插件类型 → 409。
   */
  unloadPlugin: (name: string) => MOCK
    ? Promise.resolve({ name, message: 'mock', missionTypes: 0, carTypes: 0 })
    : post<{ name: string; message: string; missionTypes: number; carTypes: number }>(
        `/plugins/${encodeURIComponent(name)}/unload`
      ),

  listMethods: (kind: ReflectionKind, id: number) => MOCK
    ? Promise.resolve(mockReflectionMethods(kind))
    : get<ReflectionMethod[]>(`/methods/${kind}/${id}`),

  listMethodsByType: (kind: ReflectionKind) => MOCK
    ? Promise.resolve(mockReflectionMethodsByType(kind))
    : get<ReflectionTypeMethods[]>(`/methods-by-type/${kind}`),

  getStatus: (kind: ReflectionKind, id: number) => MOCK
    ? Promise.resolve(mockReflectionStatus(kind, id))
    : get<ReflectionKv[]>(`/status/${kind}/${id}`),

  getFields: (kind: ReflectionKind, id: number) => MOCK
    ? Promise.resolve(mockReflectionFields(kind, id))
    : get<ReflectionKv[]>(`/fields/${kind}/${id}`),

  setField: (kind: ReflectionKind, id: number, field: string, value: string) => MOCK
    ? Promise.resolve(mockReflectionSetField(kind, id, field, value))
    : post<{ kind: ReflectionKind; id: number; field: string; value: string }>(
        `/fields/${kind}/${id}/${encodeURIComponent(field)}`,
        { value }
      ),

  deleteField: (kind: ReflectionKind, id: number, field: string) => MOCK
    ? Promise.resolve(mockReflectionDeleteField(kind, id, field))
    : del<{ kind: ReflectionKind; id: number; field: string }>(
        `/fields/${kind}/${id}/${encodeURIComponent(field)}`
      ),

  getBundle: (kind: ReflectionKind, id: number) => MOCK
    ? Promise.resolve(mockReflectionBundle(kind, id))
    : get<{
        kind: string
        id: number
        typeName: string
        fullTypeName?: string
        assembly: string
        summary: ReflectionObject
        methods: ReflectionMethod[]
        status: ReflectionKv[]
        /** 扁平 key→value 兼容旧组件。 */
        fields: Record<string, string>
        /** 带 source/locked/typeName 的字段表，新版「对象管理」面板用。 */
        fieldList?: ReflectionKv[]
      }>(`/bundle/${kind}/${id}`),

  execute: (
    kind: ReflectionKind,
    id: number,
    method: string,
    params?: Record<string, string | number | boolean>,
    opts?: ReflectionExecuteOptions
  ) => MOCK
    ? Promise.resolve(mockReflectionExecute(
        kind,
        id,
        method,
        Object.fromEntries(Object.entries(params ?? {}).map(([k, v]) => [k, String(v)]))
      ))
    : executeWithPlatformConfirm<ReflectionExecuteResult>(
        `/execute/${kind}/${id}/${encodeURIComponent(method)}`,
        params,
        opts
      ),

  /** 车辆前往指定站点（Web「去某地」；不依赖 SimpleUI.GetPoint）。 */
  gotoCarSite: (carId: number, siteId: number) => MOCK
    ? Promise.resolve({ carId, siteId, message: `mock goto ${carId} -> ${siteId}` })
    : post<{ carId: number; siteId: number; message: string }>(
        `/car/${carId}/goto-site`,
        { siteId }
      ),

  getScriptSource: (id: number): Promise<ScriptSourcePayload> => MOCK
    ? Promise.resolve({
        id,
        name: `MockScript#${id}`,
        typeName: 'CarProgram',
        state: 'Running',
        script: '// mock script'
      })
    : get<ScriptSourcePayload>(`/scripts/${id}/source`),

  getScriptExceptionStatus: (id: number): Promise<ScriptExceptionStatusPayload> => MOCK
    ? Promise.resolve({
        id,
        name: `MockScript#${id}`,
        typeName: 'CarProgram',
        car: 'AGV-1(#1)',
        state: 'Running',
        exception: '(无)',
        notifies: '(无)',
        report: '=== CarProgram 异常状态报告 ===\nstate          : Running\n\n--- exception ---\n(无)'
      })
    : get<ScriptExceptionStatusPayload>(`/scripts/${id}/exception-status`),

  // ──────────────────────────────────────────────────────────────────────────
  // 地图监控配置（Configuration.conf.monitorVisibility）
  //   /monitor-config   GET   现有配置 + 各 kind 可勾选 fields/status/methods 全集
  //   /monitor-config   POST  body JSON 全量覆盖
  // 空白名单 = 显示全部；非空 = 仅显示列表中的 key。
  // ──────────────────────────────────────────────────────────────────────────

  getMonitorConfig: (): Promise<MonitorConfigPayload> => MOCK
    ? Promise.resolve({
        config: loadMockMonitorConfig(),
        available: emptyMonitorAvailableMap()
      })
    : get<MonitorConfigPayload>('/monitor-config'),

  saveMonitorConfig: async (cfg: MonitorConfigSaveBody): Promise<MonitorConfigSaveBody> => {
    if (MOCK) {
      saveMockMonitorConfig(cfg)
      return cfg
    }
    const { data } = await http.post<ReflectionEnvelope<MonitorConfigSaveBody>>(
      `${BASE}/monitor-config`,
      cfg
    )
    if (!data?.success) throw new Error(data?.message ?? 'saveMonitorConfig failed')
    return data.data as MonitorConfigSaveBody
  },

  // 选中同步：让 SimpleLite 3D 场景同步高亮被点击对象
  getSelection: () => MOCK
    ? Promise.resolve<ReflectionSelection>({ kind: null, id: 0, name: '' })
    : get<ReflectionSelection>('/selection'),

  setSelection: (kind: ReflectionKind, id: number) => MOCK
    ? Promise.resolve({ kind, id, name: `mock(${kind}#${id})` })
    : post<{ kind: ReflectionKind; id: number; name: string }>('/selection', { kind, id }),

  clearSelection: () => MOCK
    ? Promise.resolve({ cleared: true })
    : post<{ cleared: boolean }>('/selection/clear'),

  // ──────────────────────────────────────────────────────────────────────────
  // 项目属性（Scene.conf 单例）
  //   /project/fields            GET  → 列出 Scene.conf 全部 [FieldMember] 字段
  //   /project/fields/{key}      POST → ?value=xxx 写入单字段
  //   /project/save              POST → 把内存项目（含修改后的 conf）写回 JSON
  // ──────────────────────────────────────────────────────────────────────────

  getProjectFields: (): Promise<ProjectPropertiesPayload> => MOCK
    ? Promise.resolve({
        target: 'Scene.conf',
        lastLoadedPath: null,
        autoloadPath: null,
        fields: []
      })
    : get<ProjectPropertiesPayload>('/project/fields'),

  setProjectField: (field: string, value: string) => MOCK
    ? Promise.resolve({ field, value })
    : post<{ field: string; value: string }>(
        `/project/fields/${encodeURIComponent(field)}`,
        { value }
      ),

  /** 保存当前项目到磁盘。path 为空则后端用 LastLoadedPath / Configuration.conf.autoload。 */
  saveProject: (path?: string) => MOCK
    ? Promise.resolve({ path: path ?? '(mock)' })
    : post<{ path: string }>('/project/save', path ? { path } : undefined),

  // ──────────────────────────────────────────────────────────────────────────
  // 核心配置（simple.json / Configuration.conf）
  //   /app-config/fields            GET  → 列出 Configuration.conf 全部 [FieldMember]
  //   /app-config/fields/{key}      POST → ?value=xxx 写入单字段
  //   /app-config/save              POST → 调 Configuration.ToFile("simple.json")
  // ──────────────────────────────────────────────────────────────────────────

  getAppConfigFields: (): Promise<AppConfigPayload> => MOCK
    ? Promise.resolve({ target: 'Configuration.conf', savePath: 'simple.json', fields: [] })
    : get<AppConfigPayload>('/app-config/fields'),

  setAppConfigField: (field: string, value: string) => MOCK
    ? Promise.resolve({ field, value })
    : post<{ field: string; value: string }>(
        `/app-config/fields/${encodeURIComponent(field)}`,
        { value }
      ),

  saveAppConfig: () => MOCK
    ? Promise.resolve({ path: 'simple.json' })
    : post<{ path: string }>('/app-config/save'),

  // ──────────────────────────────────────────────────────────────────────────
  // 车型样式（WorkspaceCarStylesByType / WorkspaceAlarmColorScheme）
  //   /viewport-style                       GET    站点/路径视口 Painter 样式
  //   /viewport-style                       PATCH  部分更新并立即重绘画布

  getViewportStyle: () => MOCK
    ? Promise.resolve({
        site: {
          drawScale: 2.5,
          dotRadiusNormal: 4,
          dotRadiusSelected: 6,
          selectionRingM: 0.14,
          colorNormalArgb: 0xffffffff,
          colorSelectedArgb: 0xffff0000,
          labelColorNormalArgb: 0xffffffff,
          labelColorSelectedArgb: 0xffff0000
        },
        track: {
          drawScale: 1.5,
          widthNormal: 1.2,
          widthSelected: 2.5,
          showDirectionArrows: true,
          colorNormalArgb: 0xff00ffff,
          colorSelectedArgb: 0xffff0000
        }
      } satisfies ViewportStylePayload)
    : get<ViewportStylePayload>('/viewport-style'),

  patchViewportStyle: (patch: ViewportStylePatch) => MOCK
    ? Promise.resolve({
        site: {
          drawScale: 2.5,
          dotRadiusNormal: 4,
          dotRadiusSelected: 6,
          selectionRingM: 0.14,
          colorNormalArgb: 0xffffffff,
          colorSelectedArgb: 0xffff0000,
          labelColorNormalArgb: 0xffffffff,
          labelColorSelectedArgb: 0xffff0000
        },
        track: {
          drawScale: 1.5,
          widthNormal: 1.2,
          widthSelected: 2.5,
          showDirectionArrows: true,
          colorNormalArgb: 0xff00ffff,
          colorSelectedArgb: 0xffff0000
        }
      } satisfies ViewportStylePayload)
    : patchJson<ViewportStylePayload>('/viewport-style', sanitizeViewportPatch(patch)),

  //   /car-style/types                      GET    列出所有 Car 子类型样式
  //   /car-style/{typeFullName}             GET    单个车型当前样式
  //   /car-style/{typeFullName}             POST   body JSON 全量覆盖
  //   /car-style/{typeFullName}             DELETE 恢复为默认
  //   /car-style/alarm-colors               GET    7 种报警键 → 颜色
  //   /car-style/alarm-colors               POST   body JSON 全量覆盖映射
  //   /car-style/save                       POST   写回 simple.json
  // ──────────────────────────────────────────────────────────────────────────

  getCarStyleTypes: () => MOCK
    ? Promise.resolve<CarStyleTypesPayload>({ globalDefault: defaultCarStyleDto(), types: [] })
    : get<CarStyleTypesPayload>('/car-style/types'),

  /** 车型编码字段元数据（site/track/plan/car 四类字段及默认值）。 */
  getCarTypeCoderFields: () => MOCK
    ? Promise.resolve<CarTypeCoderFieldsRow[]>([])
    : get<CarTypeCoderFieldsRow[]>('/car-types/coder-fields'),

  getCarStyle: (typeFullName: string) => MOCK
    ? Promise.resolve(defaultCarStyleDto())
    : get<CarStyleDto>(`/car-style/${encodeURIComponent(typeFullName)}`),

  /** 用 axios 直接以 JSON body 提交，避免拼 query。 */
  putCarStyle: async (typeFullName: string, body: CarStyleDto) => {
    if (MOCK) return body
    const { data } = await http.post<ReflectionEnvelope<CarStyleDto>>(
      `${BASE}/car-style/${encodeURIComponent(typeFullName)}`,
      body
    )
    if (!data?.success) throw new Error(data?.message ?? 'putCarStyle failed')
    return data.data as CarStyleDto
  },

  deleteCarStyle: (typeFullName: string) => MOCK
    ? Promise.resolve({ typeFullName, removed: true })
    : del<{ typeFullName: string; removed: boolean }>(`/car-style/${encodeURIComponent(typeFullName)}`),

  getAlarmColors: () => MOCK
    ? Promise.resolve<AlarmColorsPayload>({ defaultKeys: [], entries: [] })
    : get<AlarmColorsPayload>('/car-style/alarm-colors'),

  putAlarmColors: async (palette: Record<string, number>) => {
    if (MOCK) return { count: Object.keys(palette).length }
    const { data } = await http.post<ReflectionEnvelope<{ count: number }>>(
      `${BASE}/car-style/alarm-colors`,
      palette
    )
    if (!data?.success) throw new Error(data?.message ?? 'putAlarmColors failed')
    return data.data as { count: number }
  },

  saveCarStyle: () => MOCK
    ? Promise.resolve({ path: 'simple.json' })
    : post<{ path: string }>('/car-style/save')
}

/** 与 SimpleLite 视口样式对话框 / Configuration.conf.viewport 同步。 */
export interface ViewportStyleSite {
  drawScale: number
  dotRadiusNormal: number
  dotRadiusSelected: number
  selectionRingM: number
  colorNormalArgb: number
  colorSelectedArgb: number
  labelColorNormalArgb: number
  labelColorSelectedArgb: number
}

export interface ViewportStyleTrack {
  drawScale: number
  widthNormal: number
  widthSelected: number
  showDirectionArrows: boolean
  colorNormalArgb: number
  colorSelectedArgb: number
}

export interface ViewportStylePayload {
  site: ViewportStyleSite
  track: ViewportStyleTrack
}

export type ViewportStylePatch = {
  site?: Partial<ViewportStyleSite>
  track?: Partial<ViewportStyleTrack>
}

/** 将 ARGB 规范为无符号 32 位，避免 JSON 序列化成负数导致后端 UInt32 反序列化失败。 */
export function normalizeArgb(argb: number): number {
  if (!Number.isFinite(argb)) return 0
  return argb >>> 0
}

export function argbToHex(argb: number): string {
  const u = normalizeArgb(argb)
  const a = (u >>> 24) & 0xff
  const r = (u >>> 16) & 0xff
  const g = (u >>> 8) & 0xff
  const b = u & 0xff
  if (a >= 255) return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
  return `#${a.toString(16).padStart(2, '0')}${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
}

export function hexToArgb(hex: string): number {
  const h = hex.replace('#', '').trim()
  if (h.length === 6) {
    const r = parseInt(h.slice(0, 2), 16)
    const g = parseInt(h.slice(2, 4), 16)
    const b = parseInt(h.slice(4, 6), 16)
    return normalizeArgb((0xff << 24) | (r << 16) | (g << 8) | b)
  }
  if (h.length === 8) {
    const a = parseInt(h.slice(0, 2), 16)
    const r = parseInt(h.slice(2, 4), 16)
    const g = parseInt(h.slice(4, 6), 16)
    const b = parseInt(h.slice(6, 8), 16)
    return normalizeArgb((a << 24) | (r << 16) | (g << 8) | b)
  }
  return 0xffffffff
}

const VIEWPORT_SITE_ARGB_KEYS = [
  'colorNormalArgb',
  'colorSelectedArgb',
  'labelColorNormalArgb',
  'labelColorSelectedArgb'
] as const

const VIEWPORT_TRACK_ARGB_KEYS = ['colorNormalArgb', 'colorSelectedArgb'] as const

/** PATCH 前把视口样式中的 ARGB 字段转为 UInt32 可接受的非负整数。 */
export function sanitizeViewportPatch(patch: ViewportStylePatch): ViewportStylePatch {
  const out: ViewportStylePatch = {}
  if (patch.site) {
    const site = { ...patch.site }
    for (const k of VIEWPORT_SITE_ARGB_KEYS) {
      if (site[k] != null) site[k] = normalizeArgb(site[k]!)
    }
    out.site = site
  }
  if (patch.track) {
    const track = { ...patch.track }
    for (const k of VIEWPORT_TRACK_ARGB_KEYS) {
      if (track[k] != null) track[k] = normalizeArgb(track[k]!)
    }
    out.track = track
  }
  return out
}

export function normalizeViewportPayload(raw: ViewportStylePayload): ViewportStylePayload {
  return {
    site: {
      ...raw.site,
      colorNormalArgb: normalizeArgb(raw.site.colorNormalArgb),
      colorSelectedArgb: normalizeArgb(raw.site.colorSelectedArgb),
      labelColorNormalArgb: normalizeArgb(raw.site.labelColorNormalArgb),
      labelColorSelectedArgb: normalizeArgb(raw.site.labelColorSelectedArgb)
    },
    track: {
      ...raw.track,
      colorNormalArgb: normalizeArgb(raw.track.colorNormalArgb),
      colorSelectedArgb: normalizeArgb(raw.track.colorSelectedArgb)
    }
  }
}

export interface CarStyleDto {
  bodyLengthM: number
  bodyWidthM: number
  bodyColorArgb: number
  outlineColorArgb: number
  labelColorArgb: number
  showLabel: boolean
  modelPath: string
}

export interface CarStyleTypeRow {
  typeName: string
  shortName: string
  label: string
  assemblyName: string
  hasOverride: boolean
  style: CarStyleDto
}

export interface CarStyleTypesPayload {
  globalDefault: CarStyleDto
  types: CarStyleTypeRow[]
}

export interface CoderFieldDef {
  name: string
  typeName: string
  defaultValue: unknown
}

export interface CoderFieldGroup {
  typeName: string
  shortName: string
  assemblyName: string
  baseTypeName?: string
  fields: CoderFieldDef[]
}

export interface CarTypeCoderFieldsRow {
  typeName: string
  shortName: string
  label: string
  assemblyName: string
  siteFields: CoderFieldGroup
  trackFields: CoderFieldGroup
  planFields: CoderFieldGroup
  carFields: CoderFieldGroup
}

export interface AlarmColorEntry {
  key: string
  colorArgb: number
  isDefault: boolean
}

export interface AlarmColorsPayload {
  defaultKeys: string[]
  entries: AlarmColorEntry[]
}

function defaultCarStyleDto(): CarStyleDto {
  return {
    bodyLengthM: 0.64,
    bodyWidthM: 0.42,
    bodyColorArgb: 0xffffffff,
    outlineColorArgb: 0xff2a2a2a,
    labelColorArgb: 0xffffffff,
    showLabel: true,
    modelPath: ''
  }
}

export interface ProjectPropertyRow {
  key: string
  label: string
  value: string
  typeName: string
  locked: boolean
}

export interface ProjectPropertiesPayload {
  target: string
  lastLoadedPath: string | null
  autoloadPath: string | null
  fields: ProjectPropertyRow[]
}

export interface AppConfigPayload {
  target: string
  savePath: string
  fields: ProjectPropertyRow[]
}
