// Europe/Berlin calendar helpers for SLA / cut-off calculations.
//
// iDempiere REST timestamp semantics (verified on prod 2026-10-27, see
// .claude/rules/idempiere/timestamps-timezone.md):
//   - Created / Updated / DateOrdered-like timestamps are the DB's LOCAL wall-clock time
//     (Europe/Berlin) with a bogus "Z" suffix  → parse with `idempiereLocalToInstant`.
//   - ProcessedOn is a true epoch in milliseconds.
//   - Columns we write ourselves with a real UTC ISO string (m_inout.shipping_date from
//     mark-commissioned / commissioning) come back as the UTC wall-clock with a "Z" → `new Date(str)`.

export const BERLIN_TZ = 'Europe/Berlin'

export interface LocalParts {
  y: number
  m: number   // 1-12
  d: number
  h: number
  mi: number
  s: number
  weekday: number // 0 = Sunday … 6 = Saturday
}

const dtf = new Intl.DateTimeFormat('en-US', {
  timeZone: BERLIN_TZ,
  hourCycle: 'h23',
  year: 'numeric', month: '2-digit', day: '2-digit',
  hour: '2-digit', minute: '2-digit', second: '2-digit',
  weekday: 'short'
})
const WEEKDAYS: Record<string, number> = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 }

/** Calendar parts of an instant as seen in Berlin. */
export const berlinParts = (instant: Date | number): LocalParts => {
  const parts = dtf.formatToParts(typeof instant === 'number' ? new Date(instant) : instant)
  const get = (type: string) => parts.find(p => p.type === type)?.value || ''
  return {
    y: Number(get('year')),
    m: Number(get('month')),
    d: Number(get('day')),
    h: Number(get('hour')) % 24,
    mi: Number(get('minute')),
    s: Number(get('second')),
    weekday: WEEKDAYS[get('weekday')] ?? 0
  }
}

/** Berlin UTC offset (ms) in effect at the given instant. */
export const berlinOffsetMs = (instantMs: number): number => {
  const p = berlinParts(instantMs)
  return Date.UTC(p.y, p.m - 1, p.d, p.h, p.mi, p.s) - Math.floor(instantMs / 1000) * 1000
}

/** Instant (ms) for a Berlin wall-clock time. Handles DST transitions by re-checking the offset. */
export const berlinLocalToInstantMs = (y: number, m: number, d: number, h = 0, mi = 0, s = 0): number => {
  const asUtc = Date.UTC(y, m - 1, d, h, mi, s)
  let instant = asUtc - berlinOffsetMs(asUtc)
  const check = berlinOffsetMs(instant)
  instant = asUtc - check
  return instant
}

/** iDempiere "local time with fake Z" (e.g. "2026-10-27T14:11:08Z") → instant ms, or null. */
export const idempiereLocalToInstantMs = (value: any): number | null => {
  if (!value) return null
  const m = String(value).match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?/)
  if (!m) return null
  return berlinLocalToInstantMs(Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4] || 0), Number(m[5] || 0), Number(m[6] || 0))
}

/** True-UTC ISO string (our own writes, e.g. shipping_date) → instant ms, or null. */
export const utcIsoToInstantMs = (value: any): number | null => {
  if (!value) return null
  let s = String(value).trim().replace(' ', 'T')
  if (!/(Z|[+-]\d{2}:?\d{2})$/.test(s)) s += 'Z'
  const t = new Date(s).getTime()
  return Number.isFinite(t) ? t : null
}

export const pad2 = (n: number) => String(n).padStart(2, '0')
export const dateKey = (p: { y: number; m: number; d: number }) => `${p.y}-${pad2(p.m)}-${pad2(p.d)}`
export const monthKey = (p: { y: number; m: number }) => `${p.y}-${pad2(p.m)}`
export const timeKey = (p: { h: number; mi: number }) => `${pad2(p.h)}:${pad2(p.mi)}`

/** Calendar-day arithmetic on y/m/d without any timezone involvement. */
export const addDays = (p: { y: number; m: number; d: number }, days: number) => {
  const t = Date.UTC(p.y, p.m - 1, p.d + days)
  const dt = new Date(t)
  return { y: dt.getUTCFullYear(), m: dt.getUTCMonth() + 1, d: dt.getUTCDate(), weekday: dt.getUTCDay() }
}
export const weekdayOf = (p: { y: number; m: number; d: number }) => new Date(Date.UTC(p.y, p.m - 1, p.d)).getUTCDay()

/** Format an instant as Berlin "dd.mm.yyyy HH:MM". */
export const formatBerlin = (instantMs: number | null | undefined): string => {
  if (!instantMs && instantMs !== 0) return ''
  const p = berlinParts(instantMs)
  return `${pad2(p.d)}.${pad2(p.m)}.${p.y} ${pad2(p.h)}:${pad2(p.mi)}`
}
