--- ISO week-date calendar conversions.
-- Ported from "Calendrical Calculations" (4th edition)
-- by Nachum Dershowitz and Edward M. Reingold.
-- Original Lisp code (CALENDRICA 4.0) is Apache 2.0 licensed.
-- @module calendrica-iso
-- @release 0.1 2026-07-19

local M = {}

local basic     = require("calendrica-basic")
local gregorian = require("calendrica-gregorian")


-- === Date constructor and accessors ===

-- ISO date constructor.
local function iso_date(year, week, day)
  return {year, week, day}
end

-- ISO year accessor.
local function iso_year(date)
  return date[1]
end

-- ISO week accessor.
local function iso_week(date)
  return date[2]
end

-- ISO day accessor.
local function iso_day(date)
  return date[3]
end


-- === Conversion ===

--- Fixed date equivalent to ISO date `i_date`.
-- @tparam table i_date ISO date {year, week, day}.
-- @treturn number Fixed date.
function M.fixed_from_iso(i_date)
  local week = iso_week(i_date)
  local day  = iso_day(i_date)
  local year = iso_year(i_date)
  -- Add fixed date of Sunday preceding date plus day in week.
  return gregorian.nth_kday(
    week, basic.SUNDAY,
    gregorian.gregorian_date(year - 1, gregorian.DECEMBER, 28)
  ) + day
end
local fixed_from_iso = M.fixed_from_iso

--- ISO date {year, week, day} corresponding to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, week, day}
function M.iso_from_fixed(date)
  -- Year may be one too small.
  local approx = gregorian.gregorian_year_from_fixed(date - 3)
  local year
  if date >= fixed_from_iso(iso_date(approx + 1, 1, 1)) then
    year = approx + 1
  else
    year = approx
  end
  local week = 1 + basic.quotient(
    date - fixed_from_iso(iso_date(year, 1, 1)),
    7
  )
  local day = basic.amod(date - basic.rd(0), 7)
  return iso_date(year, week, day)
end


-- === Long year predicate ===

-- True if i_year is a long (53-week) ISO year.
local function iso_long_year(i_year)
  local jan1  = basic.day_of_week_from_fixed(gregorian.gregorian_new_year(i_year))
  local dec31 = basic.day_of_week_from_fixed(gregorian.gregorian_year_end(i_year))
  return jan1 == basic.THURSDAY or dec31 == basic.THURSDAY
end

--- True if ISO year `i_year` is a long year (53 weeks).
-- @function iso_long_year
-- @tparam number i_year ISO year.
-- @treturn boolean
M.iso_long_year = iso_long_year


return M
