/**
 * Bitable field render / type-conversion utilities (U2).
 *
 * Pure functions — no Vue, no store, no I/O. Safe to unit-test in isolation.
 *
 * `checkTypeCompatibility` implements the conversion matrix from the U2 spec:
 *   - text <-> select/multiselect: compatible (values double as options)
 *   - number -> text: compatible
 *   - text -> number: compatible only if every non-empty value parses as a number
 *   - date -> text: compatible (ISO string)
 *   - text -> date: compatible only if every non-empty value is ISO-date-shaped
 *   - attachment/image/formula/lookup -> anything: NOT compatible (structured data)
 *   - anything -> attachment/image/formula/lookup: NOT compatible (can't synthesize)
 *   - select <-> multiselect: compatible (shared option list, single value <-> [single])
 *
 * ponytail: unlisted pairs are blocked conservatively. Ceiling: a few safe pairs
 * (e.g. number -> select) are also blocked to keep the matrix small and explicit;
 * upgrade path = extend COMPATIBLE_PAIRS / add a guard below with a test.
 */

import type { FieldType } from '@/api/bitable'

export interface CompatibilityResult {
  compatible: boolean
  reason?: string
}

// ISO-8601 date / date-time. Tolerant: date-only, with/without time + TZ.
const ISO_DATE_RE =
  /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/

const STRUCTURED_OR_CALC: ReadonlySet<FieldType> = new Set([
  'attachment',
  'image',
  'formula',
  'lookup',
])

function isEmpty(v: unknown): boolean {
  return v == null || v === ''
}

function isNumeric(v: unknown): boolean {
  if (typeof v === 'number') return Number.isFinite(v)
  if (typeof v === 'string') {
    const s = v.trim()
    if (s === '') return false
    // ponytail: Number() accepts hex ('0xFF') and ('' ) — guard explicitly.
    if (s.startsWith('0x') || s.startsWith('0X')) return false
    const n = Number(s)
    return !Number.isNaN(n) && Number.isFinite(n)
  }
  return false
}

function isISODate(v: unknown): boolean {
  if (typeof v !== 'string') return false
  const s = v.trim()
  if (!ISO_DATE_RE.test(s)) return false
  const t = Date.parse(s)
  return !Number.isNaN(t)
}

/**
 * Check whether existing field values can be carried over when the field's type
 * changes from `oldType` to `newType`.
 *
 * @param existingValues raw values collected from records for this field
 *   (may include nulls / empty strings; they are ignored).
 */
export function checkTypeCompatibility(
  oldType: FieldType,
  newType: FieldType,
  existingValues: unknown[],
): CompatibilityResult {
  if (oldType === newType) return { compatible: true }

  // Structured / calculated source types cannot be converted away.
  if (STRUCTURED_OR_CALC.has(oldType)) {
    return {
      compatible: false,
      reason: `${oldType} 字段不支持类型转换（数据结构差异较大）`,
    }
  }
  // Cannot synthesize structured / calculated target types from plain values.
  if (STRUCTURED_OR_CALC.has(newType)) {
    return {
      compatible: false,
      reason: `不支持转换到 ${newType} 字段（无法由现有值构造）`,
    }
  }

  const nonEmpty = existingValues.filter((v) => !isEmpty(v))

  // text <-> select / multiselect
  if (
    (oldType === 'text' && (newType === 'select' || newType === 'multiselect')) ||
    ((oldType === 'select' || oldType === 'multiselect') && newType === 'text')
  ) {
    return { compatible: true }
  }

  // select <-> multiselect (shared option list)
  if (
    (oldType === 'select' && newType === 'multiselect') ||
    (oldType === 'multiselect' && newType === 'select')
  ) {
    return { compatible: true }
  }

  // number -> text
  if (oldType === 'number' && newType === 'text') {
    return { compatible: true }
  }

  // text -> number: all non-empty values must parse as numbers
  if (oldType === 'text' && newType === 'number') {
    if (!nonEmpty.every(isNumeric)) {
      return {
        compatible: false,
        reason: '存在非数字文本值，无法转换为数字类型',
      }
    }
    return { compatible: true }
  }

  // date -> text (ISO string)
  if (oldType === 'date' && newType === 'text') {
    return { compatible: true }
  }

  // text -> date: all non-empty values must be ISO dates
  if (oldType === 'text' && newType === 'date') {
    if (!nonEmpty.every(isISODate)) {
      return {
        compatible: false,
        reason: '存在非 ISO 日期格式的文本值，无法转换为日期类型',
      }
    }
    return { compatible: true }
  }

  // number -> date / date -> number: ambiguous (epoch vs calendar) — block.
  // select -> number / number -> select already covered where applicable above;
  // any unlisted pair falls through to the conservative block below.
  return {
    compatible: false,
    reason: `不支持从 ${oldType} 转换到 ${newType}`,
  }
}
