export const FREECAD_PROPERTY_STATUS_POSITIONS = {
  Touched: 0,
  Immutable: 1,
  ReadOnly: 2,
  Hidden: 3,
  Transient: 4,
  MaterialEdit: 5,
  NoMaterialListEdit: 6,
  Output: 7,
  LockDynamic: 8,
  NoModify: 9,
  PartialTrigger: 10,
  NoRecompute: 11,
  Single: 12,
  Ordered: 13,
  EvalOnRestore: 14,
  Busy: 15,
  CopyOnChange: 16,
  UserEdit: 17,
  DisableNotify: 18,
  PropDynamic: 21,
  PropNoPersist: 22,
  PropNoRecompute: 23,
  PropReadOnly: 24,
  PropTransient: 25,
  PropHidden: 26,
  PropOutput: 27,
  User1: 28,
  User2: 29,
  User3: 30,
  User4: 31,
} as const

export type FreecadPropertyStatusName = keyof typeof FREECAD_PROPERTY_STATUS_POSITIONS

export type FreecadPropertyStatus = {
  mask: number
  names: FreecadPropertyStatusName[]
  unknownBits: number[]
}

const namesByPosition = new Map<number, FreecadPropertyStatusName>(
  Object.entries(FREECAD_PROPERTY_STATUS_POSITIONS).map(([name, position]) => [position, name as FreecadPropertyStatusName]),
)

export const normalizeFreecadPropertyStatusMask = (value: number): number => {
  if (!Number.isSafeInteger(value) || value < 0 || value > 0xffff_ffff) throw new RangeError('FreeCAD Property status mask must be an unsigned 32-bit integer.')
  return value >>> 0
}

export const decodeFreecadPropertyStatus = (value: number): FreecadPropertyStatus => {
  const mask = normalizeFreecadPropertyStatusMask(value)
  const names: FreecadPropertyStatusName[] = []
  const unknownBits: number[] = []
  for (let position = 0; position < 32; position += 1) {
    if ((mask & (1 << position)) === 0) continue
    const name = namesByPosition.get(position)
    if (name) names.push(name)
    else unknownBits.push(position)
  }
  return { mask, names, unknownBits }
}

export const encodeFreecadPropertyStatus = (names: readonly FreecadPropertyStatusName[]): number => {
  let mask = 0
  for (const name of new Set(names)) mask = (mask | (1 << FREECAD_PROPERTY_STATUS_POSITIONS[name])) >>> 0
  return mask
}

export const hasFreecadPropertyStatus = (mask: number | undefined, ...names: FreecadPropertyStatusName[]): boolean => {
  if (mask === undefined) return false
  const normalized = normalizeFreecadPropertyStatusMask(mask)
  return names.some((name) => (normalized & (1 << FREECAD_PROPERTY_STATUS_POSITIONS[name])) !== 0)
}

export const isFreecadPropertyReadOnly = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'Immutable', 'ReadOnly', 'PropReadOnly')
export const isFreecadPropertyHidden = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'Hidden', 'PropHidden')
export const isFreecadPropertyOutput = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'Output', 'PropOutput')
export const isFreecadPropertyRecomputeSuppressed = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'Output', 'PropOutput', 'PropNoRecompute')
export const isFreecadPropertyNoPersist = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'PropNoPersist')
export const isFreecadPropertyTransient = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'Transient', 'PropTransient')
export const isFreecadPropertyPersistenceSuppressed = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'Transient', 'PropNoPersist', 'PropTransient')
export const isFreecadPropertyDocumentModifiedSuppressed = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'NoModify')
export const isFreecadPropertyDynamicMutationLocked = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'LockDynamic')
export const isFreecadPropertyPartialDocumentTrigger = (mask: number | undefined): boolean => hasFreecadPropertyStatus(mask, 'PartialTrigger')

export const withFreecadPropertyStatus = (mask: number | undefined, ...names: FreecadPropertyStatusName[]): number => {
  const normalized = mask === undefined ? 0 : normalizeFreecadPropertyStatusMask(mask)
  return (normalized | encodeFreecadPropertyStatus(names)) >>> 0
}

export type FreecadPropertyRecomputeEffect = 'none' | 'dependents' | 'owner'

/** Mirrors App::DocumentObject::onChanged for the locked FreeCAD runtime. */
export const freecadPropertyRecomputeEffect = (mask: number | undefined): FreecadPropertyRecomputeEffect => {
  if (isFreecadPropertyOutput(mask)) return 'none'
  if (hasFreecadPropertyStatus(mask, 'PropNoRecompute')) return 'dependents'
  return 'owner'
}
