// Tapestry UTC bucket and 30-minute page math. Mirror of the server's
// `app/Halflight/Support/TapestryWindow.php`. The page size is a frozen
// mirrored constant — changing it would be a v2 endpoint, not a config
// flip (companion plan §2).
//
// Bucket strings are exactly 12 ASCII digits, formatted YYYYMMDDHHMM in
// UTC. Parsing is strict: the parsed value's reformat must match the
// input byte-for-byte, which guards against silent rollover behaviour
// for inputs like `202613320000` (month=13, day=32 → next valid date
// instead of an error).

export const PAGE_MINUTES = 30

const MS_PER_MINUTE = 60_000
const PAGE_MS = PAGE_MINUTES * MS_PER_MINUTE

function pad2(n: number): string {
  return n < 10 ? `0${n}` : `${n}`
}

function pad4(n: number): string {
  if (n < 10) return `000${n}`
  if (n < 100) return `00${n}`
  if (n < 1000) return `0${n}`
  return `${n}`
}

export function formatBucket(d: Date): string {
  return (
    pad4(d.getUTCFullYear()) +
    pad2(d.getUTCMonth() + 1) +
    pad2(d.getUTCDate()) +
    pad2(d.getUTCHours()) +
    pad2(d.getUTCMinutes())
  )
}

export function parseBucket(value: string): Date | null {
  if (!/^\d{12}$/.test(value)) return null
  const year = Number(value.slice(0, 4))
  const month = Number(value.slice(4, 6))
  const day = Number(value.slice(6, 8))
  const hour = Number(value.slice(8, 10))
  const minute = Number(value.slice(10, 12))
  const d = new Date(Date.UTC(year, month - 1, day, hour, minute, 0, 0))
  if (Number.isNaN(d.getTime())) return null
  if (formatBucket(d) !== value) return null
  return d
}

export function parsePage(value: string): Date | null {
  const d = parseBucket(value)
  if (!d) return null
  if (d.getUTCMinutes() % PAGE_MINUTES !== 0) return null
  return d
}

export function currentBucket(now: Date = new Date()): string {
  const floored = new Date(
    Date.UTC(
      now.getUTCFullYear(),
      now.getUTCMonth(),
      now.getUTCDate(),
      now.getUTCHours(),
      now.getUTCMinutes(),
      0,
      0
    )
  )
  return formatBucket(floored)
}

export function currentPage(now: Date = new Date()): string {
  const minute = now.getUTCMinutes()
  const flooredMinute = minute - (minute % PAGE_MINUTES)
  const floored = new Date(
    Date.UTC(
      now.getUTCFullYear(),
      now.getUTCMonth(),
      now.getUTCDate(),
      now.getUTCHours(),
      flooredMinute,
      0,
      0
    )
  )
  return formatBucket(floored)
}

export function previousPage(page: string): string {
  const d = parsePage(page)
  if (!d) throw new Error(`previousPage: invalid page "${page}"`)
  return formatBucket(new Date(d.getTime() - PAGE_MS))
}

export function nextPage(page: string): string {
  const d = parsePage(page)
  if (!d) throw new Error(`nextPage: invalid page "${page}"`)
  return formatBucket(new Date(d.getTime() + PAGE_MS))
}

export function isPastBucket(bucket: string, now: Date = new Date()): boolean {
  const d = parseBucket(bucket)
  if (!d) return false
  return d.getTime() + MS_PER_MINUTE <= now.getTime()
}

export function isPastPage(page: string, now: Date = new Date()): boolean {
  const d = parsePage(page)
  if (!d) return false
  return d.getTime() + PAGE_MS <= now.getTime()
}

// Weekday short glyph for a bucket whose HHMM is exactly `0000` (the day's
// first minute), null otherwise. Drives the very-low-opacity gutter mark
// behind the first row of each day — companion §7.5's "calendrical warp
// behind the weft". Single ASCII letter per locale, M/T/W/T/F/S/S, no
// numbers or dates so the gutter reads as texture rather than chrome.
const WEEKDAY_GLYPHS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as const
export function dayStartWeekdayGlyph(bucket: string): string | null {
  if (bucket.length !== 12) return null
  if (bucket.slice(8, 12) !== '0000') return null
  const d = parseBucket(bucket)
  if (!d) return null
  return WEEKDAY_GLYPHS[d.getUTCDay()] ?? null
}
