--- Modern civil calendars not covered by "Calendrical Calculations".
--
-- This module collects widely used civil calendars that are **not** part of
-- Dershowitz & Reingold's *Calendrical Calculations* (and therefore not ported
-- from CALENDRICA 4.0). Each calendar's algorithm and source (with reference
-- links) is documented with the relevant conversion functions below. All are
-- built on the book's Gregorian or Julian routines.
--
-- Calendars provided:
--
-- * **Indian National calendar** (reformed Śaka) — India's official civil
--   calendar, defined by the Calendar Reform Committee (chaired by Meghnad Saha),
--   adopted 1957-03-22. Matches Unicode CLDR/ICU `indian`.
--   <https://en.wikipedia.org/wiki/Indian_national_calendar>
-- * **Minguo / Republic of China calendar** — Taiwan's official civil calendar;
--   Gregorian months and days with year numbering offset by 1911 (year 1 = 1912 CE).
--   Matches Unicode CLDR/ICU `roc`.
--   <https://en.wikipedia.org/wiki/Republic_of_China_calendar>
-- * **Thai solar calendar** (Suriyakati, Buddhist Era) — Thailand's official civil
--   calendar since 1941; Gregorian months and days with the Buddhist Era offset of
--   543 years. Matches Unicode CLDR/ICU `buddhist`.
--   <https://en.wikipedia.org/wiki/Thai_solar_calendar>
-- * **Revised Bengali calendar** (Bangladesh) — Bangladesh's national calendar as
--   fixed by the Bangla Academy; arithmetic solar calendar locked to the Gregorian
--   year. <https://en.wikipedia.org/wiki/Bengali_calendars>
-- * **Nanakshahi calendar** (Sikh) — the tropical solar calendar designed by Pal
--   Singh Purewal, adopted by the SGPC in 2003.
--   <https://en.wikipedia.org/wiki/Nanakshahi_calendar>
-- * **Amazigh (Berber) calendar** — the agrarian calendar of North Africa; the
--   Julian calendar with years offset by 950. Yennayer (new year) is an official
--   public holiday in Algeria and Morocco.
--   <https://en.wikipedia.org/wiki/Berber_calendar>
-- * **Japanese era calendar** (nengō / wareki) — the modern Japanese civil calendar;
--   Gregorian months and days with years counted within the reigning emperor's era.
--   Matches Unicode CLDR/ICU `japanese`.
--   <https://en.wikipedia.org/wiki/Japanese_era_name>
--
-- For ICU/CLDR calendar equivalences see the ICU calendar guide:
-- <https://unicode-org.github.io/icu/userguide/datetime/calendar/>.
--
-- @module calendrica-modern-civil
-- @release 0.2 2026-10-13

local M = {}

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

local standard_month = basic.standard_month
local standard_day   = basic.standard_day
local standard_year  = basic.standard_year
local quotient       = basic.quotient
local gregorian_leap_year   = gregorian.gregorian_leap_year
local fixed_from_gregorian  = gregorian.fixed_from_gregorian
local gregorian_from_fixed  = gregorian.gregorian_from_fixed


-- === Indian National calendar (reformed Śaka) ===
--
-- Not from "Calendrical Calculations".
-- Source: Report of the Calendar Reform Committee (Council of Scientific and
-- Industrial Research, India, 1955), which defines the reformed Śaka calendar
-- adopted as India's national calendar on 22 March 1957. Same calendar as
-- Unicode CLDR/ICU `indian`.
--
-- Rules:
--   * The Śaka year Y begins on Chaitra 1, which falls on Gregorian March 22 in a
--     common year and March 21 when Gregorian year (Y + 78) is a leap year.
--   * A Śaka year is a leap year exactly when Gregorian year (Y + 78) is a leap
--     year; in that case Chaitra has 31 days instead of 30.
--   * Month lengths: Chaitra 30 (31 in leap years); Vaishakha..Bhadra (months 2–6)
--     31 days each; Ashwin..Phalguna (months 7–12) 30 days each.
--     Year length is therefore 365 (366 in leap years), tracking the Gregorian year.

-- Offset between the Śaka era and the Gregorian year that contains Chaitra 1.
local INDIAN_SAKA_OFFSET = 78

-- Indian National date constructor.
local function indian_national_date(year, month, day)
  return {year, month, day}
end

-- Fixed date of Chaitra 1 for Śaka year `year`.
local function indian_national_new_year(year)
  local greg_year = year + INDIAN_SAKA_OFFSET
  local first_day = gregorian_leap_year(greg_year) and 21 or 22
  return fixed_from_gregorian(gregorian.gregorian_date(greg_year, gregorian.MARCH, first_day))
end

-- Days from Chaitra 1 up to (but not including) the first of month `month`,
-- given whether the year is a leap year.
local function indian_national_days_before_month(month, leap)
  local chaitra_length = leap and 31 or 30
  if month == 1 then
    return 0
  elseif month <= 6 then
    -- Chaitra, then (month - 2) full 31-day months (Vaishakha..).
    return chaitra_length + 31 * (month - 2)
  else
    -- Chaitra + five 31-day months + (month - 7) full 30-day months.
    return chaitra_length + 31 * 5 + 30 * (month - 7)
  end
end

--- Fixed date equivalent to the Indian National (reformed Śaka) date `i_date`.
-- @tparam table i_date Indian National date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_indian_national(i_date)
  local year  = standard_year(i_date)
  local month = standard_month(i_date)
  local day   = standard_day(i_date)
  local leap  = gregorian_leap_year(year + INDIAN_SAKA_OFFSET)
  return indian_national_new_year(year)
    + indian_national_days_before_month(month, leap)
    + day - 1
end

--- Indian National (reformed Śaka) date {year, month, day} for fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.indian_national_from_fixed(date)
  -- Chaitra 1 lies in the Gregorian year of `date` unless `date` precedes it,
  -- in which case the Śaka year began the previous Gregorian year.
  local year   = standard_year(gregorian_from_fixed(date)) - INDIAN_SAKA_OFFSET
  if date < indian_national_new_year(year) then
    year = year - 1
  end
  local leap      = gregorian_leap_year(year + INDIAN_SAKA_OFFSET)
  local day_count = date - indian_national_new_year(year)   -- 0-based day of year.
  local chaitra_length = leap and 31 or 30
  local month, day
  if day_count < chaitra_length then
    month = 1
    day   = day_count + 1
  elseif day_count < chaitra_length + 31 * 5 then
    local rem = day_count - chaitra_length
    month = 2 + quotient(rem, 31)
    day   = rem % 31 + 1
  else
    local rem = day_count - chaitra_length - 31 * 5
    month = 7 + quotient(rem, 30)
    day   = rem % 30 + 1
  end
  return indian_national_date(year, month, day)
end


-- === Minguo / Republic of China calendar ===
--
-- Not from "Calendrical Calculations".
-- Source: Republic of China civil calendar (in official use in Taiwan). The
-- calendar is the Gregorian calendar with years counted from the founding of the
-- Republic in 1912, so ROC year = Gregorian year - 1911. Same calendar as Unicode
-- CLDR/ICU `roc`.

local ROC_OFFSET = 1911

-- Minguo date constructor.
local function roc_date(year, month, day)
  return {year, month, day}
end

--- Fixed date equivalent to the Minguo (ROC) date `r_date`.
-- @tparam table r_date Minguo date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_roc(r_date)
  return fixed_from_gregorian(gregorian.gregorian_date(
    standard_year(r_date) + ROC_OFFSET,
    standard_month(r_date),
    standard_day(r_date)
  ))
end

--- Minguo (ROC) date {year, month, day} corresponding to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.roc_from_fixed(date)
  local g = gregorian_from_fixed(date)
  return roc_date(standard_year(g) - ROC_OFFSET, standard_month(g), standard_day(g))
end


-- === Thai solar calendar (Suriyakati / Buddhist Era) ===
--
-- Not from "Calendrical Calculations".
-- Source: Thai solar civil calendar as reformed in 1941, when the year was aligned
-- to begin on 1 January. It is the Gregorian calendar with years counted in the
-- Buddhist Era, so Thai year = Gregorian year + 543. Same calendar as Unicode
-- CLDR/ICU `buddhist` (applied proleptically here; the pre-1941 April new year is
-- not modelled).

local THAI_SOLAR_OFFSET = 543

-- Thai solar date constructor.
local function thai_solar_date(year, month, day)
  return {year, month, day}
end

--- Fixed date equivalent to the Thai solar (Buddhist Era) date `t_date`.
-- @tparam table t_date Thai solar date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_thai_solar(t_date)
  return fixed_from_gregorian(gregorian.gregorian_date(
    standard_year(t_date) - THAI_SOLAR_OFFSET,
    standard_month(t_date),
    standard_day(t_date)
  ))
end

--- Thai solar (Buddhist Era) date {year, month, day} corresponding to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.thai_solar_from_fixed(date)
  local g = gregorian_from_fixed(date)
  return thai_solar_date(standard_year(g) + THAI_SOLAR_OFFSET, standard_month(g), standard_day(g))
end


-- === Shared machinery for Gregorian-locked solar civil calendars ===
--
-- The revised Bengali and Nanakshahi calendars share the same arithmetic shape: a run
-- of leading 31-day months, then 30-day months, with one designated month that gains a
-- day in a leap year. They differ in the details (see the specs below), which are
-- captured by the `spec` fields. The new year is pinned to a fixed Gregorian month and
-- day, so a calendar year is a leap year exactly when the Gregorian February that falls
-- inside it — that of Gregorian year (year + offset + 1) — is a leap February. That also
-- keeps every new year on its fixed Gregorian date and makes the conversions exact
-- inverses.
--
-- A `spec` is {offset, new_year_month, new_year_day, long_months, leap_month,
-- leap_month_base}: `long_months` leading months of 31 days; every other month has 30
-- days except `leap_month`, which has `leap_month_base` days (`leap_month_base + 1` in a
-- leap year).

-- Fixed date of the first day of the year for `year` under `spec`.
local function solar_civil_new_year(spec, year)
  return fixed_from_gregorian(gregorian.gregorian_date(
    year + spec.offset, spec.new_year_month, spec.new_year_day))
end

-- True if `year` is a leap year under `spec`.
local function solar_civil_leap(spec, year)
  return gregorian_leap_year(year + spec.offset + 1)
end

-- Length of month `month` under `spec`, given whether the year is a leap year.
local function solar_civil_month_length(spec, month, leap)
  if month <= spec.long_months then
    return 31
  elseif month == spec.leap_month then
    return spec.leap_month_base + (leap and 1 or 0)
  else
    return 30
  end
end

-- Fixed date from a {year, month, day} date under `spec`.
local function fixed_from_solar_civil(spec, date)
  local year  = standard_year(date)
  local month = standard_month(date)
  local leap  = solar_civil_leap(spec, year)
  local days_before = 0
  for m = 1, month - 1 do
    days_before = days_before + solar_civil_month_length(spec, m, leap)
  end
  return solar_civil_new_year(spec, year) + days_before + standard_day(date) - 1
end

-- {year, month, day} under `spec` from a fixed `date`.
local function solar_civil_from_fixed(spec, date)
  local year = standard_year(gregorian_from_fixed(date)) - spec.offset
  if date < solar_civil_new_year(spec, year) then
    year = year - 1
  end
  local leap      = solar_civil_leap(spec, year)
  local day_count = date - solar_civil_new_year(spec, year)   -- 0-based day of year.
  local month     = 1
  local len       = solar_civil_month_length(spec, month, leap)
  while day_count >= len do
    day_count = day_count - len
    month = month + 1
    len = solar_civil_month_length(spec, month, leap)
  end
  return {year, month, day_count + 1}
end


-- === Revised Bengali calendar (Bangladesh) ===
--
-- Not from "Calendrical Calculations".
-- Source: the Bangladesh national calendar as revised by the Bangla Academy (2019).
-- Boishakh 1 (Pohela Boishakh) is fixed to 14 April; months 1–6 (Boishakh–Ashwin)
-- have 31 days and months 7–12 have 30 days, except Falgun (month 11) which has 29 days
-- (30 in a leap year). This keeps 21 February on 8 Falgun (Ekushey) and 16 December on
-- 1 Poush. The Bengali year (Bangabda) is the Gregorian year − 593 from Boishakh 1
-- onward. See <https://en.wikipedia.org/wiki/Bengali_calendars> (National calendar
-- of Bangladesh).

local BENGALI_REVISED_SPEC = {
  offset          = 593,
  new_year_month  = gregorian.APRIL,
  new_year_day    = 14,
  long_months     = 6,    -- Boishakh–Ashwin have 31 days.
  leap_month      = 11,   -- Falgun.
  leap_month_base = 29,   -- Falgun has 29 days (30 in a leap year).
}

--- Fixed date equivalent to the revised Bengali date `b_date`.
-- @tparam table b_date Revised Bengali date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_bengali_revised(b_date)
  return fixed_from_solar_civil(BENGALI_REVISED_SPEC, b_date)
end

--- Revised Bengali date {year, month, day} corresponding to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.bengali_revised_from_fixed(date)
  return solar_civil_from_fixed(BENGALI_REVISED_SPEC, date)
end


-- === Nanakshahi calendar (Sikh) ===
--
-- Not from "Calendrical Calculations".
-- Source: the Nanakshahi calendar designed by Pal Singh Purewal and adopted by the
-- Shiromani Gurdwara Parbandhak Committee in 2003 (original tropical-solar version).
-- Chet 1 is fixed to 14 March; months 1–5 (Chet–Sawan) have 31 days, months 6–12
-- have 30 days, and Phagun (month 12) gains a day in a leap year. The Nanakshahi
-- year is the Gregorian year − 1468 from Chet 1 onward (era from Guru Nanak's birth,
-- 1469 CE). See <https://en.wikipedia.org/wiki/Nanakshahi_calendar>.

local NANAKSHAHI_SPEC = {
  offset          = 1468,
  new_year_month  = gregorian.MARCH,
  new_year_day    = 14,
  long_months     = 5,    -- Chet–Sawan have 31 days.
  leap_month      = 12,   -- Phagun.
  leap_month_base = 30,   -- Phagun has 30 days (31 in a leap year).
}

--- Fixed date equivalent to the Nanakshahi date `n_date`.
-- @tparam table n_date Nanakshahi date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_nanakshahi(n_date)
  return fixed_from_solar_civil(NANAKSHAHI_SPEC, n_date)
end

--- Nanakshahi date {year, month, day} corresponding to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.nanakshahi_from_fixed(date)
  return solar_civil_from_fixed(NANAKSHAHI_SPEC, date)
end


-- === Amazigh (Berber) calendar ===
--
-- Not from "Calendrical Calculations".
-- Source: the North African agrarian calendar, which is the Julian calendar with
-- year numbering offset by 950 (e.g. Gregorian 1950 → Berber year 2900). Month and
-- day are exactly those of the Julian calendar; only the year label differs, so this
-- is a thin wrapper over `calendrica-julian`. See
-- <https://en.wikipedia.org/wiki/Berber_calendar>.

local AMAZIGH_OFFSET = 950

--- Fixed date equivalent to the Amazigh (Berber) date `a_date`.
-- @tparam table a_date Amazigh date {year, month, day}.
-- @treturn number Fixed date.
function M.fixed_from_amazigh(a_date)
  return julian.fixed_from_julian(julian.julian_date(
    standard_year(a_date) - AMAZIGH_OFFSET,
    standard_month(a_date),
    standard_day(a_date)))
end

--- Amazigh (Berber) date {year, month, day} corresponding to fixed `date`.
-- @tparam number date Fixed date.
-- @treturn table {year, month, day}
function M.amazigh_from_fixed(date)
  local j = julian.julian_from_fixed(date)
  return {standard_year(j) + AMAZIGH_OFFSET, standard_month(j), standard_day(j)}
end


-- === Japanese era (nengō / wareki) calendar ===
--
-- Not from "Calendrical Calculations". The modern Japanese civil calendar: the
-- Gregorian calendar with years counted within the reign era (nengō) of the current
-- emperor. Months and days are Gregorian; only the year is expressed as an era name
-- plus the year within that era (the first year of an era is year 1, *gannen*).
--
-- The model mirrors current Unicode CLDR/ICU: only the modern gengō eras (Meiji onward)
-- are defined, and earlier dates inherit the Gregorian eras (`inheritEras` → gregorian).
-- The historical pre-Meiji eras were deliberately removed from CLDR/ICU: applied to a
-- proleptic Gregorian timeline they are anachronistic (Japan used a lunisolar calendar
-- before 1873) and of dubious accuracy (see CLDR-11400 and ICU-23341). For the historical
-- Japanese calendar, which was lunisolar, use the Japanese calendar in
-- `calendrica-chinese` instead.
--
-- Accordingly the `era` field is, on or after 1868-10-23, one of `"meiji"`, `"taisho"`,
-- `"showa"`, `"heisei"`, `"reiwa"`; before that it is the inherited Gregorian era `"ce"`
-- (with the Gregorian year) or `"bce"` (year counted backwards, 1 BCE = Gregorian year 0).
--
-- References: Japanese era names <https://en.wikipedia.org/wiki/Japanese_era_name>;
-- ICU calendar guide <https://unicode-org.github.io/icu/userguide/datetime/calendar/>;
-- era simplification <https://unicode-org.atlassian.net/browse/CLDR-11400> and
-- <https://unicode-org.atlassian.net/browse/ICU-23341>.

local JAPANESE_CE  = "ce"
local JAPANESE_BCE = "bce"

-- Modern gengō eras and their Gregorian start dates, matching current CLDR/ICU.
local JAPANESE_ERA_DEFS = {
  { id = "meiji",  name = "Meiji",  start = { 1868, 10, 23 } },
  { id = "taisho", name = "Taishō", start = { 1912,  7, 30 } },
  { id = "showa",  name = "Shōwa",  start = { 1926, 12, 25 } },
  { id = "heisei", name = "Heisei", start = { 1989,  1,  8 } },
  { id = "reiwa",  name = "Reiwa",  start = { 2019,  5,  1 } },
}

-- Build derived fields (start fixed date and start Gregorian year) and a lookup.
local JAPANESE_ERAS = {}
local JAPANESE_ERA_BY_ID = {}
for i, def in ipairs(JAPANESE_ERA_DEFS) do
  local era = {
    id         = def.id,
    name       = def.name,
    start_year = def.start[1],
    start_fixed = fixed_from_gregorian(
      gregorian.gregorian_date(def.start[1], def.start[2], def.start[3])),
  }
  JAPANESE_ERAS[i] = era
  JAPANESE_ERA_BY_ID[def.id] = era
end

local JAPANESE_ERA_NAMES = { [JAPANESE_CE] = "CE", [JAPANESE_BCE] = "BCE" }
for _, era in ipairs(JAPANESE_ERAS) do JAPANESE_ERA_NAMES[era.id] = era.name end

-- Construct a Japanese era date {era, year, month, day}.
local function japanese_era_date(era, year, month, day)
  return { era, year, month, day }
end

-- Gregorian year for era `era_id` at era-year `year`.
local function japanese_gregorian_year_of(era_id, year)
  if era_id == JAPANESE_CE then
    return year
  elseif era_id == JAPANESE_BCE then
    return 1 - year
  else
    local era = JAPANESE_ERA_BY_ID[era_id]
      or error("unknown Japanese era: " .. tostring(era_id), 3)
    return era.start_year + year - 1
  end
end

--- Fixed date equivalent to the Japanese era date `j_date`.
-- `j_date` is `{era, year, month, day}`, where `era` is a gengō era id (`"meiji"`,
-- `"taisho"`, `"showa"`, `"heisei"`, `"reiwa"`) or an inherited Gregorian era (`"ce"`,
-- `"bce"`), `year` is the year within the era (the first year is 1), and `month`/`day`
-- are Gregorian.
-- @tparam table j_date Japanese era date {era, year, month, day}.
-- @treturn number Fixed date.
-- @raise If the era identifier is unknown.
function M.fixed_from_japanese_era(j_date)
  return fixed_from_gregorian(gregorian.gregorian_date(
    japanese_gregorian_year_of(j_date[1], j_date[2]), j_date[3], j_date[4]))
end

--- Japanese era date {era, year, month, day} corresponding to fixed `date`.
-- Dates before the Meiji era use the inherited Gregorian eras `"ce"`/`"bce"`.
-- @tparam number date Fixed date.
-- @treturn table {era, year, month, day}
function M.japanese_era_from_fixed(date)
  local g     = gregorian_from_fixed(date)
  local month = standard_month(g)
  local day   = standard_day(g)
  for i = #JAPANESE_ERAS, 1, -1 do
    if date >= JAPANESE_ERAS[i].start_fixed then
      return japanese_era_date(
        JAPANESE_ERAS[i].id, standard_year(g) - JAPANESE_ERAS[i].start_year + 1, month, day)
    end
  end
  -- Before Meiji: inherit the Gregorian era.
  local greg_year = standard_year(g)
  if greg_year >= 1 then
    return japanese_era_date(JAPANESE_CE, greg_year, month, day)
  else
    return japanese_era_date(JAPANESE_BCE, 1 - greg_year, month, day)
  end
end

--- Ordered list of modern gengō eras.
-- Each entry is `{id, name, start_year, start_fixed}`.
M.japanese_eras = JAPANESE_ERAS

--- Display name for Japanese era identifier `era_id`, or `nil` if unknown.
-- Covers the gengō eras plus the inherited `"ce"` and `"bce"`.
-- @tparam string era_id Era identifier.
-- @treturn string|nil Display name, e.g. `"Reiwa"`, `"CE"`.
function M.japanese_era_name(era_id)
  return JAPANESE_ERA_NAMES[era_id]
end


return M
