#' nlmixr2lib convention standards
#'
#' Internal register of canonical parameter, compartment, covariate, and
#' residual-error names used by [checkModelConventions()]. The canonical
#' covariate, parameter, and compartment / metabolite-suffix lists are parsed
#' at runtime from `inst/references/covariate-columns.md`,
#' `inst/references/parameter-names.md`, and
#' `inst/references/compartment-names.md`
#' (installed as `system.file("references", "<file>.md", package = "nlmixr2lib")`),
#' so the markdown registers remain the single authoritative sources.
#' The static list below retains only structural regex constants and the
#' deprecation lists. Remaining fields mirror the `extract-literature-model`
#' skill's `naming-conventions.md` and `vignettes/create-model-library.Rmd`.
#'
#' @keywords internal
#' @noRd
.nlmixr2libConventionsStatic <- list(
  pkParams = c(
    "lka", "lcl", "lvc", "lvp", "lvp2", "lq", "lq2", "lfdepot",
    "lvmax", "lcl_ss", "lcl_time", "lcl_renal", "lcl_nonren",
    # K-PD / single-rate-constant elimination (primary `ini()` form
    # used when no explicit `vc` is estimated). Canonical lkel adopted
    # 2026-05-28 per the naming audit (replaces `lke`).
    "lkel",
    # Rate-constant-only multi-compartment parameterisation (primary
    # `ini()` form when the dependent variable is an amount rather than
    # a concentration, so volumes of distribution are not identifiable
    # and the inter-compartmental rate constants are the primary
    # quantities). NONMEM ADVAN4 / ADVAN11 papers report these as
    # k_23 / k_32 / k_24 / k_42 under the depot=1, central=2,
    # peripherals=3/4 numbering; the canonical nlmixr2 names treat
    # central as 1 and peripherals as 2/3 after the depot is split out.
    # Founding example: Marier_2002_tobramycin_rat_liposomal /
    # Marier_2002_tobramycin_rat_conventional (intratracheal tobramycin
    # in rats; central state carries amount in ug because the lung-
    # tissue assay output is an amount, not a concentration).
    "lk12", "lk21", "lk13", "lk31",
    # Canonical lag-time name. Replaces the legacy `lalag`, `llag`,
    # `ltz` forms per the 2026-05-28 naming audit.
    "ltlag",
    # Influx (plasma -> tissue ECF/CSF) and efflux (tissue -> plasma)
    # clearances used by physiological CNS-distribution popPK models
    # in which the brain/tumor extracellular fluid is parameterised as
    # a separate compartment connected to plasma central via two
    # asymmetric clearances driven by unbound drug. Used in Campagne
    # 2019 cyclophosphamide mouse CNS penetration popPK.
    "lclin", "lclef"
  ),
  pkBareParams = c(
    "ka", "cl", "vc", "vp", "vp2", "q", "q2", "kel",
    "k12", "k21", "k13", "k31", "fdepot",
    "vmax", "cl_ss", "cl_time", "cl_renal", "cl_nonren"
  ),
  compartments = c(
    "depot", "central", "peripheral1", "peripheral2", "effect",
    "target", "complex", "total_target",
    # Semi-physiological liver compartment used by paper-specific
    # extraction-ratio first-pass models (Xie_2019_agomelatine).
    "liver",
    # Cumulative-hazard state used by time-to-event / dropout sub-models
    # (Girard_2012_pimasertib). The state integrates the instantaneous
    # hazard so that survival = exp(-cumhaz); the source NONMEM idiom is
    # `$MODEL COMP=(CUMHAZ)` with `DADT(<cumhaz>) = HAZARD`.
    "cumhaz",
    # Renal-cortex accumulation compartment used by aminoglycoside
    # nephrotoxicity models (Llanos-Paez_2017_gentamicin). Tracks drug
    # amount sequestered in the renal cortex via saturable uptake from
    # the central compartment plus first-order tubular reabsorption back
    # out (Rougier 2003 / Croes 2011 mechanism).
    "renal_cortex",
    # Cerebrospinal-fluid (CSF) and interstitial-fluid (ISF) physiologic
    # compartments used by mechanistic mAb / target-mediated disposition
    # models with multiple body-fluid distribution volumes
    # (Perez-Ruixo_2025_posdinemab).
    "csf", "isf",
    # Anatomic brain-region compartments used by mAb brain-distribution
    # PK models (Grimm_2023_trontinemab, Grimm_2023_gantenerumab). Each
    # state holds the extracellular drug concentration in the named
    # region; total brain concentration including residual plasma is
    # derived as `Cbrain_<region>` in model().
    "cerebellum", "hippocampus", "striatum", "cortex", "choroid_plexus",
    # Gallbladder / biliary recirculation compartment used by
    # enterohepatic-circulation (EHC) popPK models (Ide_2009_pravastatin
    # and similar). Drug accumulates from the central compartment via
    # biliary excretion (k12) and re-enters central after a delay
    # (k21 gated by gallbladder-emptying time tg), producing the
    # characteristic second-peak phenomenon.
    "gallbladder",
    # Soluble vascular endothelial growth factor receptor 2 (sVEGFR2)
    # plasma compartment used by indirect-response biomarker PD models
    # for angiogenesis inhibitors (Ait-Oudhia 2016, Hansson 2013a).
    "svegfr2",
    # Tumor volume / size compartment used by tumor growth inhibition
    # (TGI) models in oncology (Ait-Oudhia 2016, NA_NA_sunitinib,
    # Schindler 2016, Wilbaux 2015 and similar).
    "tumor",
    # Endogenous plasma metabolic species used by glucose / lactate
    # turnover sub-models with drug-stimulated production (Oualha 2014
    # epinephrine: glucose zero-order production is stimulated by Ep
    # via an Emax function, and lactate is produced at the rate of
    # glucose elimination and itself first-order eliminated). Each
    # state holds a concentration (mmol/L) rather than an amount,
    # mirroring the source paper's mass-balance parameterisation.
    "glucose", "lactate",
    # Plasma non-esterified fatty acids (NEFA / free fatty acids) used
    # by lipid-turnover PD models with feedback control (Ahlstrom 2010:
    # NiAc inhibits hydrolysis of TG to NEFA; NEFA formation is also
    # suppressed by a moderator transit chain (precursor1..precursor8)
    # representing insulin-like delayed feedback, with a NiAc-independent
    # capillary release term setting the lower physiological limit).
    # State holds a concentration (mmol/L) rather than an amount.
    "nefa",
    # Purine metabolism PD compartments used by semi-mechanistic
    # xanthine / uric-acid turnover models (Hill-McManus 2017
    # doi:10.1111/bcp.13427). `xanthine` and `urate` hold serum amounts
    # (mg); `xanthine_urine` and `urate_urine` hold cumulative urinary
    # excretion amounts (mg) integrated from CLX / CLUA renal-clearance
    # outflows for direct comparison with 24-h urinary collection data.
    "xanthine", "urate", "xanthine_urine", "urate_urine",
    # Kinetic-pharmacodynamic (K-PD) body amount compartment used when
    # the source paper drives a PD response from a dose-to-effect K-PD
    # construct rather than from measured plasma concentrations. Drug
    # amount accumulates from each dose event and decays first-order at
    # rate `kel`; the canonical PK-elimination parameter `lkel` is
    # reused (no parallel `lke_kpd`). Suffix `_kpd` disambiguates from
    # the absorption `depot`. Founding example:
    # deVriesSchultink_2018_anthracycline_troponinT (anthracycline
    # K-PD driving the troponin T direct-effect equation).
    "depot_kpd"
  ),
  # Bare numbered chains (transit / effect / precursor / lat / dar /
  # depot) and metabolite-suffixed compartments are validated
  # separately via .matchesCompartment() so that the registered
  # metabolite list can be honored at runtime; this static regex
  # covers only the numbered-chain patterns. `depot[0-9]+` accommodates
  # parallel-absorption models with two or more depots.
  compartmentRegex = "^(transit|effect|precursor|lat|depot)[0-9]+$",
  # Membrane-limited PBPK sub-compartment pattern: paper-prefix +
  # spelled-out organ name. Recognises the recurring `<sub>_<organ>`
  # shape used in Shah 2012 mAb PBPK and Parhiz 2024 mRNA-LNP
  # extractions (bc / eu / eb / fr / is / int / mrna / luc prefixes).
  pbpkSubCompartmentRegex = "^(bc|eu|eb|fr|is|int|mrna|luc)_(liver|lung|kidney|spleen|heart|muscle|skin|adipose|bone|brain|small_intestine|large_intestine|pancreas|thymus|portal|remainder|other|hepatic|fat|rapidly_perfused|slowly_perfused|venous|arterial|urine|gut|tumor)$", # nolint: line_length_linter.
  # DAR-numbered ADC isoform compartments (`dar0_central`,
  # `dar4_peripheral1`, ...).
  darCompartmentRegex = "^dar[0-9]+_(central|peripheral[0-9]?)$",
  # Target species in physiologic body-fluid or named peripheral
  # compartments (e.g., target_csf, target_isf, target_peripheral,
  # target_peripheral1, complex_csf, complex_isf, complex_peripheral).
  targetLocationRegex = "^(target|complex)_(csf|isf|peripheral[0-9]?)$",
  # Bacterial subpopulation states for semi-mechanistic time-kill / HFIM
  # PK-PD models. Subpopulations are named by spelled-out resistance
  # phenotype `bact_<phenotype>` where <phenotype> is one of
  # `susceptible` / `intermediate` / `resistant`. For combination-therapy
  # (two-drug) models the joint per-drug status is a spelled-out compound
  # `bact_<drug1pheno>_<drug2pheno>` (drug order = the model's drug
  # order). An optional trailing digit indexes the Bulitta / Wicha
  # two-state bacterial life cycle (1 = vegetative / resting, 2 =
  # replicating). The phenotype words are required to be spelled out in
  # full (no `s`/`i`/`r` abbreviations) so the state names are
  # self-documenting. Used by Garonzik_2016_daptomycin (single-drug:
  # bact_susceptible1/2, bact_intermediate1/2, bact_resistant1/2),
  # Rees_2018_meropenem_ciprofloxacin and
  # Landersdorfer_2018_imipenem_tobramycin (two-drug, converging to
  # bact_susceptible_susceptible1/2, bact_resistant_intermediate1/2,
  # bact_intermediate_resistant1/2). The scheme is documented in
  # inst/references/compartment-names.md.
  bacterialSubpopRegex = "^bact_(susceptible|intermediate|resistant)(_(susceptible|intermediate|resistant))?[0-9]*$",
  # Intracellular drug / active-metabolite pools inside red blood cells,
  # carried as ODE states in concentration units (e.g. umol/L). Named
  # `rbc_<analyte>` where <analyte> is the measured species inside the
  # erythrocyte: `rbc_mtx` (methotrexate polyglutamates), `rbc_tgn`
  # (6-thioguanine nucleotides). Deliberately NOT routed through
  # registeredMetabolites, because the analyte is frequently the PARENT
  # drug (methotrexate) rather than a metabolite, and recording a parent
  # drug in the metabolite register would mislead later readers of that
  # list. Distinct from the `erythrocytes` canonical, which is a red-cell
  # COUNT pool (cells/L), not an intracellular drug concentration.
  # Founding examples: Gebhard_2023_methotrexate,
  # Gebhard_2023_mercaptopurine, Gebhard_2023_mercaptopurine_anc.
  # Documented in inst/references/compartment-names.md.
  rbcCompartmentRegex = "^rbc_[a-z0-9]+$",
  observationVar = "Cc",
  # propSd and addSd are the canonical proportional and additive
  # residual-error SDs; expSd is the log-scale residual SD used with
  # `~ lnorm(...)`. powExp is the power-error exponent `b` in a power
  # residual model `Cc ~ pow(propSd, powExp)` (residual SD = propSd *
  # f^powExp), used by popPK papers that report a power error model
  # (Tod 1998 amikacin). It is a residual-error-structure parameter,
  # not a deviant SD name.
  residualError = c("propSd", "addSd", "expSd", "powExp"),
  transformPrefixes = c("l", "logit", "probit"),
  # Covariate-effect names match e_<cov>(_<continuation>)+_<param>.
  # The pattern accepts up to 6 underscore-separated tokens after the
  # leading "e_" to accommodate compound covariates (RACE_BLACK,
  # ADA_POSITIVE, FORM_CHO_PHASE2). Semantic interpretation of the
  # trailing tokens is handled by .classifyCovEffect().
  covEffectPattern = "^e_[A-Za-z0-9]+(_[A-Za-z0-9]+){1,5}$",
  # Lowercase paper / payload names allowed as a third-token suffix on
  # parameters and compartments for a non-parent species. The set is
  # extended whenever a new ADC payload, target binding partner, or
  # secondary-analyte appears in a model. Keep mutually disjoint from
  # pkBareParams and clComponents to preserve covariate-effect
  # disambiguation.
  registeredMetabolites = c(
    "mmae", "dxd", "sn38", "dm4", "medm4", "mcmmaf",
    "complex", "ige", "il1b", "tab", "nab",
    "dar0", "dar1", "dar2", "dar3", "dar4", "dar5", "dar6", "dar7", "dar8",
    # Small-molecule metabolites of agomelatine (Xie 2019): 3-hydroxy
    # and 7-desmethyl. Suffixes start with a digit; this is fine
    # because the convention check matches on `endsWith(name, "_<metab>")`
    # rather than treating the metabolite name itself as an R identifier.
    "3oh", "7dm",
    # N-desmethyl-bedaquiline metabolite (M2) of bedaquiline
    # (Svensson 2016 DDMODEL00000219).
    "m2",
    # G-037720 metabolite (M1) of ipatasertib, formed mainly by
    # CYP3A4-mediated metabolism and pharmacologically active (2- to
    # 4-fold less potent than the parent). Used in parent + metabolite
    # joint popPK extractions (Yoshida 2021 doi:10.1002/jcph.1942).
    "m1",
    # Endoxifen (4-hydroxy-N-desmethyltamoxifen), the major active
    # metabolite of tamoxifen -- Ter Heine 2014.
    "endx",
    # Lidocaine sequential metabolites (DDMODEL00000281, NA_NA_lidocaine):
    # MEGX = monoethylglycinexylidide (LID -> MEGX via CYP1A2/3A4),
    # GX   = glycinexylidide (MEGX -> GX), and 2,6-XYL = 2,6-xylidide
    # (LID -> 2,6-XYL minor pathway). Each metabolite is a separate
    # central compartment with its own apparent volume in the source's
    # ADVAN5 parent + 3-metabolite structure.
    "megx", "gx", "xyl",
    # Morphine-3-glucuronide and morphine-6-glucuronide, the two major
    # glucuronide metabolites of morphine -- Knibbe 2009 DDMODEL00000248.
    "m3g", "m6g",
    # Phase-II conjugates: glucuronide (gluc) and sulphate (sulf).
    # Used for paracetamol-glucuronide / paracetamol-sulphate plasma
    # metabolite compartments in Allegaert 2015 (DDMODEL00000267).
    "gluc", "sulf",
    # Paracetamol (APAP) phase-II conjugate metabolites -- APAP-glucuronide
    # ("apapg") and APAP-sulphate ("apaps") used in the Cook 2016 newborn
    # model (DDMODEL00000271).
    "apapg", "apaps",
    # Colistin, the active polymyxin generated in vivo by hydrolysis of
    # the prodrug colistimethate sodium (CMS). Used as a metabolite
    # suffix in parent-prodrug CMS / metabolite-active-drug colistin
    # popPK models (Leuppi-Taegtmeyer 2019 DDMODEL00000295).
    "col",
    # Dihydroartemisinin, the active metabolite of artesunate
    # (Birgersson 2019 DDMODEL00000297).
    "dha",
    # Desethylamodiaquine (DEAQ), the principal CYP2C8-derived
    # antimalarial-active metabolite of amodiaquine. Used as a
    # metabolite suffix in joint parent-plus-metabolite popPK models
    # (Ali 2018 doi:10.1128/AAC.02193-17).
    "deaq",
    # Hydroxy-itraconazole (OH-ITZ), the major active metabolite of
    # itraconazole produced by CYP3A4 hydroxylation. Used as a metabolite
    # suffix in parent + metabolite simultaneous popPK models
    # (Hennig 2006 Clin Pharmacokinet 45(11):1099-1114; Hennig 2007 BJCP
    # 63(4):438-450).
    "ohi",
    # Doxorubicinol, the C-13 alcohol metabolite of doxorubicin
    # (Kunarajah 2017 paediatric oncology popPK/PD model).
    "doxol",
    # Daunorubicinol (DOL), the C-13 alcohol metabolite of
    # daunorubicin formed primarily by carbonyl reductase 1 (CBR1)
    # in adult AML patients (Varatharajan 2016 Cancer Chemother
    # Pharmacol 78(5):1051-1058 doi:10.1007/s00280-016-3166-8).
    "dol",
    # 25-O-desacetyl rifabutin, the primary active metabolite of
    # rifabutin formed by arylacetamide deacetylase (Hennig 2015
    # AAC doi:10.1128/AAC.01195-15).
    "desrbn",
    # AZ5104 (N-desmethyl osimertinib), an active EGFR-inhibitor
    # metabolite of osimertinib formed predominantly via CYP3A4/5
    # (Brown 2017 BJCP 83(6):1216-1226 doi:10.1111/bcp.13223).
    "az5104",
    # N-desmethyl-selumetinib (also reported as the active selumetinib
    # metabolite, ~3-5-fold more potent for MEK1 inhibition than the
    # parent), formed by oxidative N-demethylation of selumetinib
    # (Patel 2017 CPT Pharmacometrics Syst Pharmacol 6(5):305-314
    # doi:10.1002/psp4.12175).
    "ndsel",
    # Capecitabine sequential metabolites (Urien 2005
    # doi:10.1007/s10928-005-0018-2): 5'-DFCR (5'-deoxy-5-
    # fluorocytidine, formed in the liver by carboxylesterase from
    # capecitabine), 5'-DFUR (5'-deoxy-5-fluorouridine, formed from
    # 5'-DFCR by cytidine deaminase in liver and tumour cells), and
    # 5-FU (5-fluorouracil, formed from 5'-DFUR by thymidine
    # phosphorylase preferentially in tumour tissue). Each metabolite
    # has its own central compartment with apparent volume fixed to
    # 1 L (only output rate constants K23, K34, K40 are identifiable
    # in the source NONMEM ADVAN6 fit).
    "dfcr", "dfur", "5fu",
    # AS(N-1)3' truncated antisense strand of GalNAc-conjugated
    # siRNAs (givosiran and other galnac-siRNA conjugates), formed
    # by removal of the 3'-terminal nucleotide from the antisense
    # strand. Treated as the active metabolite that is equipotent
    # with the parent in terms of RISC loading and target mRNA
    # silencing (Ayyar 2024 doi:10.1016/j.xphs.2023.10.026).
    "asn1",
    # 10-monohydroxy derivative (MHD, also "10-hydroxy-carbazepine"),
    # the primary active metabolite of oxcarbazepine produced by
    # cytosolic arylketone reductases (Rodrigues 2017 BJCP
    # doi:10.1111/bcp.13392).
    "mhd",
    # Stereo-isomer (R / S) suffixes for enantiomer-resolved popPK
    # models in which both enantiomers are followed in plasma but no
    # interconversion is modelled (e.g. Valitalo 2017 ketorolac BJCP
    # doi:10.1111/bcp.13311). Treated as "non-parent analyte" suffixes
    # under the same registry as metabolites; neither enantiomer is the
    # parent.
    "r", "s",
    # Roflumilast N-oxide, the active metabolite of roflumilast that
    # contributes about 90% of total PDE4 inhibitory activity (tPDE4i).
    # Used as a metabolite suffix in parent-plus-metabolite popPK models
    # where parent (roflumilast) and metabolite (N-oxide) are fitted
    # jointly but with independent apparent absorption parameters
    # (Lahu 2010 doi:10.2165/11536600-000000000-00000).
    "noxide",
    # Combined acetaminophen cysteine + mercapturate compartment used by
    # CYP2E1-oxidation popPK models that lump the two oxidation
    # metabolites (acetaminophen cysteine and acetaminophen mercapturate)
    # into a single observation compartment because the two species are
    # in rapid equilibrium with overlapping disposition (van Rongen 2016
    # Clin Pharmacokinet doi:10.1007/s40262-015-0357-0).
    "cysmer",
    # Glucarpidase (CPG2), the bacterial carboxypeptidase G2 enzyme
    # given as a rescue therapy after high-dose methotrexate (MTX). Not
    # a metabolite of MTX but a co-administered perpetrator that
    # enzymatically hydrolyzes the substrate; the suffix marks the
    # non-parent species in 2-drug PK/PD models (Kimura 2023
    # doi:10.21873/anticanres.16351).
    "cpg2",
    # 3-N-acetyl-3,4-diaminopyridine (3-Ac DAP), the inactive N-acetyl
    # metabolite of 3,4-diaminopyridine (amifampridine) free base
    # produced by N-acetyltransferases. Used in parent-plus-metabolite
    # popPK models for amifampridine in patients with Lambert-Eaton
    # myasthenia (Thakkar 2017 doi:10.1002/psp4.12218).
    "acdap",
    # H4 (active thiol) metabolite of clopidogrel: the pharmacologically
    # active species responsible for P2Y12 receptor inhibition, formed
    # via sequential CYP-mediated oxidation of clopidogrel (CYP2C19 is
    # the dominant isoform for the second oxidation step). The "H4"
    # designator refers to the H4 stereoisomer specifically, which is
    # the antiplatelet-active diastereomer. Used in parent-plus-
    # metabolite popPK models (Danielak 2017 doi:10.1007/s00228-017-2334-z).
    "h4",
    # Mycophenolic acid glucuronide (MPAG, the 7-O-glucuronide phase II
    # metabolite of mycophenolic acid produced by UGT1A9 and UGT2B7).
    # MPAG is the major plasma metabolite of mycophenolic acid after
    # mycophenolate mofetil (MMF) dosing in renal transplant recipients.
    # Used in parent-plus-metabolite popPK models with explicit competitive
    # protein binding and enterohepatic recirculation
    # (de Winter 2009 doi:10.1007/s10928-009-9136-6).
    "mpag",
    # Asunaprevir (ASV, BMS-650032), the NS3/4A protease-inhibitor direct-
    # acting antiviral (DAA) used as the second drug in the Wang 2018
    # daclatasvir + asunaprevir integrated PK/VD model for genotype-1
    # chronic hepatitis C. Asunaprevir is not a metabolite of daclatasvir
    # but is co-administered alongside it; the suffix marks the non-parent
    # drug under the same multi-drug naming convention used by deKock 2017
    # sulfadoxine + pyrimethamine (where pyrimethamine carries the `pyra`
    # suffix and sulfadoxine is the unsuffixed parent). The Wang 2018
    # model uses daclatasvir as the unsuffixed parent (mentioned first in
    # the paper's title, the lead NS5A inhibitor in the regimen) and
    # asunaprevir with `_asv` everywhere
    # (depot_asv, central_asv, peripheral1_asv, effect_asv, Cc_asv,
    # lka_asv, lcl_asv, etc.).
    "asv",
    # CDB-4453, the active monodemethylated metabolite of telapristone
    # (CDB-4124) formed by N-demethylation on the C-17 side chain. Used
    # in the Morris 2011 parent (telapristone) + metabolite (CDB-4453)
    # popPK model where the metabolite apparent volume V3/F is fixed
    # to 1 L for identifiability (Morris 2011 doi:10.1208/s12248-011-9304-7).
    "cdb4453",
    # Modafinil acid (2-[(diphenylmethyl)sulfonyl]acetic acid), the
    # principal carboxylic-acid metabolite of modafinil formed via
    # esterase / amidase hydrolysis. Used as a metabolite suffix in
    # parent + metabolite simultaneous popPK models
    # (Wu 2012 Acta Pharmacol Sin 33:1401-1408 doi:10.1038/aps.2012.124).
    "mfa",
    # 7-Hydroxy-methotrexate (7-OH-MTX), the principal circulating
    # metabolite of methotrexate formed by aldehyde-oxidase-mediated
    # 7-hydroxylation in the liver. Used as a metabolite suffix in
    # parent + metabolite joint popPK models for high-dose methotrexate
    # therapy (Joerger 2006 Br J Clin Pharmacol 62(1):71-80
    # doi:10.1111/j.1365-2125.2005.02513.x).
    "7ohmtx"
  ),
  # Suffixes allowed for multi-component CL parameters. `_ss` denotes
  # the steady-state arm; `_time` the time-varying decay arm; `_renal`
  # the glomerular-filtration / tubular-secretion arm; `_nonren` the
  # non-renal (hepatic / metabolic / extra-renal) arm (e.g. Jonckheere
  # 2019 cefepime: CL_total = CL_renal + CL_nonren); `_hemodialysis`
  # the intermittent-hemodialysis extracorporeal arm gated by the
  # HEMODIALYSIS covariate (e.g. Veinstein 2013 gentamicin: CL_total =
  # CL + CL_HD); `_dialysis` the broader continuous / general dialysis
  # extracorporeal arm (e.g. Eyler 2014 ertapenem: CL_total = CLS +
  # DIAL * CLdial for CVVHD/CVVHDF).
  clComponents = c("ss", "time", "renal", "nonren", "hemodialysis", "dialysis"),
  requiredUnits = c("time", "dosing", "concentration"),
  requiredMetadata = c("description", "reference", "units"),
  deprecatedResidualError = c(
    "prop.err", "add.err", "propErr", "addErr",
    "err.prop", "err.add"
  ),
  deprecatedIivPrefixes = c("iiv_", "IIV_", "bsv_", "BSV_"),
  # Bare volume names that should be replaced with vc / vp / vp2.
  deprecatedVolumeNames = c("v", "v1", "v2", "v3", "lv", "lv1", "lv2", "lv3"),
  # Deprecated Michaelis-Menten Vmax names.
  deprecatedVmaxNames = c("vm", "lvm"),
  # Deprecated parent-suffix marker. A model that names a parent-side
  # parameter `<base>_adc` should drop the `_adc` suffix; the parent
  # uses the canonical name unsuffixed.
  deprecatedParentSuffix = "_adc",

  # Exact parameter names retired by issues #474-#477, mapped to their
  # replacement. Each was a spelling that defeated name-based discovery:
  # a consumer looking for allometric exponents by `e_wt_*`, for logit-scale
  # bioavailability, or for TMDD constants by case-normalised stems would
  # silently miss these models. Keeping the map here (rather than in prose)
  # means `checkModelConventions()` reports an error-severity issue if one
  # reappears, and `buildModelDb()` aborts on it (see
  # `.conventionErrorsStopIfAny`) rather than merely printing a count.
  renamedParameters = c(
    # #474 allometric exponents -> e_wt_<param>
    allo_cl = "e_wt_cl", allo_q = "e_wt_q", allo_vc = "e_wt_vc",
    allovc = "e_wt_vc", allovp = "e_wt_vp",
    dCLdWT = "e_wt_cl", dVdWT = "e_wt_vc",
    # #475 logit-scale fractions: F vs absorption-pathway split
    logitf1 = "logitfdepot (bioavailability) or logitffo/logitfburst (pathway split)",
    logitfr = "logitffo (first-order arm) or logitfburst (burst/rapid-release)",
    logitf1st = "logitffo",
    # #476 CLL disambiguation
    lcll = "lcl_ligand", cllira = "cl_lira", lcllira_ref = "lcl_lira_ref",
    # #477 TMDD case / separator normalisation
    lKss = "lkss", lkD = "lkd", lBmax = "lbmax", Km = "km",
    kd_LR = "kd_lr", kd_T1 = "kd_t1", kd_T2 = "kd_t2",
    # Same case-normalisation class, found while auditing the extraction
    # skill's own docs against this map: 89 models used `vmax`, one used
    # `Vmax`, and the skill taught the capitalised spelling.
    Vmax = "vmax"
  ),

  # Issue #482: controlled vocabulary for the biological matrix a compartment
  # represents, so "restrict to blood, serum or plasma" is a filter rather
  # than a judgement call made by reading state names.
  #
  # Two entries are deliberately NOT matrices, because forcing every state
  # into a specimen would be worse than saying so plainly:
  #   "administration site" -- depot / transit states, which hold drug that
  #     has not reached a biological matrix yet
  #   "not applicable"      -- latent, PD and bookkeeping states (effect,
  #     precursor*, cumhaz, circ, bacterial subpopulations, tumor-size
  #     states). A database scan found these in 642 of the 1403 models with
  #     ODEs, so the category carries real weight.
  specimenVocabulary = c(
    "plasma", "serum", "whole blood", "blood cell", "CSF", "brain ISF",
    "vitreous", "aqueous humour", "retina", "tissue", "tumor", "lymph",
    "endosome", "urine", "bile", "faeces", "saliva", "milk",
    "synovial fluid", "epithelial lining fluid", "bronchoalveolar lavage",
    "administration site", "not applicable"
  ),

  # Fields every compartmentData entry must carry. `analyte` and `units`
  # answer "what molecule, in what amount"; `specimen` answers "in what
  # matrix". `verified` records whether the entry was checked against the
  # source paper -- a mechanically derived entry is not evidence.
  compartmentDataFields = c("analyte", "units", "specimen", "verified"),

  # Canonical spellings for the `units` block and for unit hints in labels.
  # These are SPELLINGS, not conversions: "hour"/"hr" and "h" are the same
  # unit written three ways, and a library that writes it three ways cannot be
  # parsed by a consumer. Genuinely different units (min, day, s) stay
  # distinct -- normalising those would silently misstate every value.
  #
  # A survey on 2026-08-03 found time = "hour" in 643 models, "h" in 208 and
  # "hr" in 28, plus ~12 notations for second-order association rates.
  canonicalTimeUnit = "h",
  timeUnitSpellings = c(
    hour = "h", hours = "h", hr = "h", hrs = "h",
    minute = "min", minutes = "min", mins = "min",
    days = "day", weeks = "week", months = "month", years = "year",
    second = "s", seconds = "s", sec = "s", secs = "s"
  ),
  doseUnitSpellings = c(
    microgram = "ug", micrograms = "ug", mcg = "ug",
    milligram = "mg", milligrams = "mg",
    nanogram = "ng", nanograms = "ng",
    gram = "g", grams = "g"
  ),

  # Generic structural models (PK_1cmt, PK_2cmt, ...) are dimensionless by
  # design and declare placeholders. These are correct, not unnormalised.
  placeholderUnits = c("time_unit", "dose_unit", "conc_unit", "amount_unit",
                       "vol_unit", "half_life", "none"),

  # Second-order association rate constants. The library wrote these as
  # 1/(nM*h), 1/nM/hour, nM^-1 h^-1, (pmol/L)^-1 h^-1, 1/(nM h) ... all the
  # same shape. Canonical form is 1/(<concentration>*<time>), which keeps the
  # concentration and time bases visible and greppable.
  canonicalRateNotation = "1/(<conc>*<time>)"
)

# The following canonical-name lists are NOT carried on
# .nlmixr2libConventionsStatic above; they are parsed at runtime from
# the markdown registers in inst/references and merged into the
# returned object by .nlmixr2libConventions(): pkParams, pkBareParams,
# and paperNamedParams come from parameter-names.md; compartments and
# registeredMetabolites come from compartment-names.md; and
# canonicalCovariates comes from covariate-columns.md.

.covariateRegisterCache <- new.env(parent = emptyenv())

.covariateColumnsPath <- function() {
  p <- system.file("references", "covariate-columns.md",
                   package = "nlmixr2lib")
  if (nzchar(p)) return(p)
  stop("Could not locate inst/references/covariate-columns.md in the ",
       "nlmixr2lib package. Check the installation.", call. = FALSE)
}

#' Parse the canonical covariate register from covariate-columns.md.
#'
#' Walks the Markdown register and extracts one entry per H3 heading
#' (`### NAME (**...**)`). For each entry, captures the `Units`, `Type`,
#' `Scope`, `Source aliases`, and `Example models` fields. Aliases whose
#' backticked content is not a bare R identifier (e.g. `DVID = "study1"`)
#' are skipped. Example-model tokens are accepted as backticked file names
#' ending in `.R`; the `.R` suffix is stripped so the value matches the
#' bare model function name used throughout the rest of the package.
#'
#' @param path Path to the markdown file.
#' @return A named list keyed by canonical name. Each entry is a list with
#'   `units`, `type`, `scope` (one of `"general"` / `"specific"` / `NA`),
#'   `aliases` (character vector of alias names), and `example_models`
#'   (character vector of model function names).
#' @keywords internal
#' @noRd
.parseCovariateColumns <- function(path) {
  lines <- readLines(path, warn = FALSE)
  # Accumulate into an explicit environment rather than using `<<-` from the
  # inner flush() closure: environments have reference semantics, so a plain
  # `<-` into `acc` mutates the shared accumulator. `<<-` walks the lexical
  # scope chain at run time, which is hard to review and easy to break by
  # moving code between scopes (lintr assignment_linter flags it).
  acc <- new.env(parent = emptyenv())
  acc$entries <- list()
  current <- NULL
  state <- "idle"

  flush <- function() {
    if (is.null(current)) return(invisible())
    for (nm in current$names) {
      acc$entries[[nm]] <- list(
        units = current$units %||% "",
        type = current$type %||% "",
        scope = current$scope %||% NA_character_,
        aliases = current$aliases %||% character(),
        example_models = current$example_models %||% character()
      )
    }
  }

  aliasRegex <- "^\\s*-\\s*`([^`]+)`"
  identRegex <- "^[A-Za-z_][A-Za-z0-9_]*$"
  modelFileRegex <- "^[A-Za-z_][A-Za-z0-9_-]*\\.R$"

  extractBacktickedModels <- function(text) {
    toks <- regmatches(text, gregexpr("`([^`]+)`", text))[[1]]
    models <- character()
    for (tok in toks) {
      inner <- gsub("`", "", tok)
      if (grepl(modelFileRegex, inner)) {
        models <- c(models, sub("\\.R$", "", inner))
      }
    }
    models
  }

  for (line in lines) {
    if (startsWith(line, "## ") && !startsWith(line, "### ")) {
      flush()
      current <- NULL
      state <- "idle"
      next
    }
    if (startsWith(line, "### ")) {
      flush()
      heading <- sub("^###\\s+", "", line)
      heading <- sub("\\s*\\(\\*\\*.*\\*\\*\\)\\s*$", "", heading)
      nms <- trimws(strsplit(heading, ",")[[1]])
      nms <- nms[grepl(identRegex, nms)]
      current <- list(names = nms, aliases = character(),
                      example_models = character())
      state <- "header"
      next
    }
    if (is.null(current)) next

    m <- regmatches(line, regexec("^- \\*\\*Units:\\*\\*\\s*(.*)$", line))[[1]]
    if (length(m) == 2) {
      current$units <- trimws(m[[2]])
      state <- "header"
      next
    }
    m <- regmatches(line, regexec("^- \\*\\*Type:\\*\\*\\s*(.*)$", line))[[1]]
    if (length(m) == 2) {
      current$type <- trimws(m[[2]])
      state <- "header"
      next
    }
    m <- regmatches(line, regexec("^- \\*\\*Scope:\\*\\*\\s*(.*)$", line))[[1]]
    if (length(m) == 2) {
      scope_raw <- tolower(trimws(sub("\\.$", "", m[[2]])))
      if (scope_raw %in% c("general", "specific")) {
        current$scope <- scope_raw
      }
      state <- "header"
      next
    }
    if (grepl("^- \\*\\*Source aliases:\\*\\*", line)) {
      state <- "aliases"
      after <- sub("^- \\*\\*Source aliases:\\*\\*\\s*", "", line)
      # "none", "none known", "none;"-style declarations have no aliases.
      if (grepl("^none\\b", after, ignore.case = TRUE)) next
      # Capture inline aliases up to the first em-dash prose separator.
      after <- strsplit(after, "\\s+\u2014\\s+", perl = TRUE)[[1]][1]
      inline <- regmatches(after, gregexpr("`([^`]+)`", after))[[1]]
      for (tok in inline) {
        inner <- gsub("`", "", tok)
        if (grepl(identRegex, inner)) {
          current$aliases <- c(current$aliases, inner)
        }
      }
      next
    }
    if (grepl("^- \\*\\*Example models:\\*\\*", line)) {
      state <- "example_models"
      after <- sub("^- \\*\\*Example models:\\*\\*\\s*", "", line)
      current$example_models <- c(current$example_models,
                                  extractBacktickedModels(after))
      next
    }

    if (state == "aliases") {
      m <- regmatches(line, regexec(aliasRegex, line))[[1]]
      if (length(m) == 2) {
        inner <- m[[2]]
        if (grepl(identRegex, inner)) {
          current$aliases <- c(current$aliases, inner)
        }
        next
      }
      if (grepl("^- \\*\\*", line)) {
        state <- "header"
      }
    }

    if (state == "example_models") {
      # Continuation bullet lines in a multi-line Example-models list.
      if (grepl("^\\s+-\\s", line)) {
        current$example_models <- c(current$example_models,
                                    extractBacktickedModels(line))
        next
      }
      if (grepl("^- \\*\\*", line)) {
        state <- "header"
      }
    }
  }
  flush()
  acc$entries
}

.loadCanonicalCovariates <- function(force = FALSE) {
  if (!force && !is.null(.covariateRegisterCache$canonical)) {
    return(.covariateRegisterCache$canonical)
  }
  entries <- .parseCovariateColumns(.covariateColumnsPath())
  .covariateRegisterCache$canonical <- entries
  entries
}

.parameterNamesPath <- function() {
  p <- system.file("references", "parameter-names.md",
                   package = "nlmixr2lib")
  if (nzchar(p)) return(p)
  stop("Could not locate inst/references/parameter-names.md in the ",
       "nlmixr2lib package. Check the installation.", call. = FALSE)
}

.compartmentNamesPath <- function() {
  p <- system.file("references", "compartment-names.md",
                   package = "nlmixr2lib")
  if (nzchar(p)) return(p)
  stop("Could not locate inst/references/compartment-names.md in the ",
       "nlmixr2lib package. Check the installation.", call. = FALSE)
}

#' Parse a canonical-name markdown register from inst/references/.
#'
#' Walks the markdown file and extracts one entry per H3 heading
#' (`### NAME (**...**)`). For each entry, captures `Type`, `Source aliases`,
#' and `Example models`. A single canonical name may appear under more than
#' one H3 entry (with distinct Type values); each appearance is returned as
#' a separate list element so the caller can route by Type without losing
#' duplicates.
#'
#' @param path Path to the markdown file.
#' @return A list of entries; each entry is a list with `name`, `type`,
#'   `aliases` (character vector), and `example_models` (character vector).
#' @keywords internal
#' @noRd
.parseTypedNamesMd <- function(path) {
  lines <- readLines(path, warn = FALSE)
  # Explicit environment accumulator; see .parseCovariateColumns for why this
  # avoids a superassign from the inner flush() closure.
  acc <- new.env(parent = emptyenv())
  acc$entries <- list()
  current <- NULL
  state <- "idle"

  identRegex <- "^[A-Za-z_0-9][A-Za-z0-9_]*$"
  modelFileRegex <- "^[A-Za-z_][A-Za-z0-9_-]*\\.R$"

  extractBacktickedModels <- function(text) {
    toks <- regmatches(text, gregexpr("`([^`]+)`", text))[[1]]
    models <- character()
    for (tok in toks) {
      inner <- gsub("`", "", tok)
      if (grepl(modelFileRegex, inner)) {
        models <- c(models, sub("\\.R$", "", inner))
      }
    }
    models
  }

  flush <- function() {
    if (is.null(current)) return(invisible())
    if (is.null(current$type) || !nzchar(current$type)) return(invisible())
    for (nm in current$names) {
      acc$entries[[length(acc$entries) + 1]] <- list(
        name = nm,
        type = current$type,
        aliases = current$aliases %||% character(),
        example_models = current$example_models %||% character()
      )
    }
  }

  for (line in lines) {
    if (startsWith(line, "## ") && !startsWith(line, "### ")) {
      flush()
      current <- NULL
      state <- "idle"
      next
    }
    if (startsWith(line, "### ")) {
      flush()
      heading <- sub("^###\\s+", "", line)
      heading <- sub("\\s*\\(\\*\\*.*\\*\\*\\)\\s*$", "", heading)
      nms <- trimws(strsplit(heading, ",")[[1]])
      nms <- nms[grepl(identRegex, nms)]
      current <- list(names = nms, aliases = character(),
                      example_models = character())
      state <- "header"
      next
    }
    if (is.null(current)) next

    m <- regmatches(line, regexec("^- \\*\\*Type:\\*\\*\\s*(.*)$", line))[[1]]
    if (length(m) == 2) {
      current$type <- trimws(m[[2]])
      state <- "header"
      next
    }
    if (grepl("^- \\*\\*Source aliases:\\*\\*", line)) {
      state <- "aliases"
      after <- sub("^- \\*\\*Source aliases:\\*\\*\\s*", "", line)
      if (grepl("^none\\b", after, ignore.case = TRUE)) next
      after <- strsplit(after, "\\s+\u2014\\s+", perl = TRUE)[[1]][1]
      inline <- regmatches(after, gregexpr("`([^`]+)`", after))[[1]]
      for (tok in inline) {
        inner <- gsub("`", "", tok)
        if (grepl(identRegex, inner)) {
          current$aliases <- c(current$aliases, inner)
        }
      }
      next
    }
    if (grepl("^- \\*\\*Example models:\\*\\*", line)) {
      state <- "example_models"
      after <- sub("^- \\*\\*Example models:\\*\\*\\s*", "", line)
      current$example_models <- c(current$example_models,
                                  extractBacktickedModels(after))
      next
    }

    if (state == "aliases") {
      m <- regmatches(line, regexec("^\\s*-\\s*`([^`]+)`", line))[[1]]
      if (length(m) == 2) {
        inner <- m[[2]]
        if (grepl(identRegex, inner)) {
          current$aliases <- c(current$aliases, inner)
        }
        next
      }
      if (grepl("^- \\*\\*", line)) {
        state <- "header"
      }
    }

    if (state == "example_models") {
      if (grepl("^\\s+-\\s", line)) {
        current$example_models <- c(current$example_models,
                                    extractBacktickedModels(line))
        next
      }
      if (grepl("^- \\*\\*", line)) {
        state <- "header"
      }
    }
  }
  flush()
  acc$entries
}

.namesByType <- function(entries, type) {
  out <- character()
  for (e in entries) {
    if (identical(e$type, type)) out <- c(out, e$name)
  }
  out
}

.loadCanonicalParameters <- function(force = FALSE) {
  if (!force && !is.null(.covariateRegisterCache$parameters)) {
    return(.covariateRegisterCache$parameters)
  }
  entries <- .parseTypedNamesMd(.parameterNamesPath())
  .covariateRegisterCache$parameters <- entries
  entries
}

.loadCanonicalCompartments <- function(force = FALSE) {
  if (!force && !is.null(.covariateRegisterCache$compartments)) {
    return(.covariateRegisterCache$compartments)
  }
  entries <- .parseTypedNamesMd(.compartmentNamesPath())
  .covariateRegisterCache$compartments <- entries
  entries
}

.nlmixr2libConventions <- function() {
  out <- .nlmixr2libConventionsStatic
  out$canonicalCovariates <- .loadCanonicalCovariates()
  paramEntries <- .loadCanonicalParameters()
  out$pkParams <- .namesByType(paramEntries, "log-transformed-pk")
  out$pkBareParams <- .namesByType(paramEntries, "bare-pk")
  out$paperNamedParams <- .namesByType(paramEntries, "paper-named-param")
  compEntries <- .loadCanonicalCompartments()
  out$compartments <- .namesByType(compEntries, "compartment")
  out$registeredMetabolites <- .namesByType(compEntries, "metabolite-suffix")
  out
}

.nlmixr2libCovariateAliasMap <- function() {
  conv <- .loadCanonicalCovariates()
  out <- character()
  for (canon in names(conv)) {
    for (a in conv[[canon]]$aliases) {
      out[a] <- canon
    }
  }
  out
}

# Return TRUE when `name` is a canonical log-transformed PK parameter or
# a metabolite-suffixed PK parameter (`l<base>_<metab>`).
.isPkParam <- function(name, conv) {
  if (name %in% conv$pkParams) return(TRUE)
  for (metab in conv$registeredMetabolites) {
    suf <- paste0("_", metab)
    if (endsWith(name, suf)) {
      base <- substr(name, 1, nchar(name) - nchar(suf))
      if (base %in% conv$pkParams) return(TRUE)
    }
  }
  FALSE
}

# Return TRUE when `name` is a canonical bare PK parameter or a
# metabolite-suffixed bare PK parameter (`<base>_<metab>`).
.isPkBareParam <- function(name, conv) {
  if (name %in% conv$pkBareParams) return(TRUE)
  for (metab in conv$registeredMetabolites) {
    suf <- paste0("_", metab)
    if (endsWith(name, suf)) {
      base <- substr(name, 1, nchar(name) - nchar(suf))
      if (base %in% conv$pkBareParams) return(TRUE)
    }
  }
  FALSE
}

# Compartment name validator. Recognizes:
#   - canonical names from conv$compartments
#   - numbered chains via conv$compartmentRegex (transit/effect/precursor/lat/depot)
#   - DAR-numbered ADC isoforms via conv$darCompartmentRegex
#   - target species in physiologic compartments via conv$targetLocationRegex
#   - intracellular red-cell analyte pools via conv$rbcCompartmentRegex
#   - metabolite-suffixed compartments: <canonical>_<metab>
.matchesCompartment <- function(name, conv) {
  if (name %in% conv$compartments) return(TRUE)
  if (grepl(conv$compartmentRegex, name)) return(TRUE)
  if (grepl(conv$darCompartmentRegex, name)) return(TRUE)
  if (grepl(conv$targetLocationRegex, name)) return(TRUE)
  if (!is.null(conv$pbpkSubCompartmentRegex) &&
      grepl(conv$pbpkSubCompartmentRegex, name)) return(TRUE)
  if (!is.null(conv$bacterialSubpopRegex) &&
      grepl(conv$bacterialSubpopRegex, name)) return(TRUE)
  if (!is.null(conv$rbcCompartmentRegex) &&
      grepl(conv$rbcCompartmentRegex, name)) return(TRUE)
  for (metab in conv$registeredMetabolites) {
    suf <- paste0("_", metab)
    if (endsWith(name, suf)) {
      base <- substr(name, 1, nchar(name) - nchar(suf))
      if (base %in% conv$compartments) return(TRUE)
      # Compositions of a numbered-chain prefix with a metabolite
      # suffix are canonical: e.g., `transit1_m3g`, `precursor2_dxd`,
      # `lat1_complex`. Used by formation-delay transit chains feeding
      # a metabolite central compartment (deHoogd 2017 morphine model:
      # transit1_m3g..transit5_m3g and transit1_m6g..transit2_m6g).
      if (grepl(conv$compartmentRegex, base)) return(TRUE)
      # Recursive: strip one metabolite suffix and re-check whether
      # the base matches another canonical pattern (compartment list,
      # chain regex, DAR regex, target-location regex, PBPK sub-
      # compartment regex, or another metabolite-suffixed canonical).
      # Lets multi-suffix compartments pass, e.g.
      # `liver_endo_asn1` -> strip `_asn1` -> `liver_endo` -> strip
      # `_endo` -> `liver` (canonical). Used by Ayyar 2024 givosiran
      # parent + metabolite PBPK extraction.
      if (.matchesCompartment(base, conv)) return(TRUE)
    }
  }
  FALSE
}

# TRUE when `name` ends with `_<metab>` for any registered metabolite.
.endsWithMetabolite <- function(name, conv) {
  for (metab in conv$registeredMetabolites) {
    if (endsWith(name, paste0("_", metab))) return(TRUE)
  }
  FALSE
}

# TRUE when `name` ends with `_<component>` for any registered CL component.
.endsWithClComponent <- function(name, conv) {
  for (comp in conv$clComponents) {
    if (endsWith(name, paste0("_", comp))) return(TRUE)
  }
  FALSE
}

# TRUE when `name` ends with `_<param>` for any bare PK parameter.
# Used to detect shared-exponent covariate effects like e_wt_cl_q.
.endsWithBarePkParam <- function(name, conv) {
  for (p in conv$pkBareParams) {
    if (endsWith(name, paste0("_", p))) return(TRUE)
  }
  FALSE
}

# Classify a covariate-effect name by its trailing suffix. Returns one of:
#   "two_token"  - matches e_<cov>_<param> with no third-token suffix
#   "metabolite" - matches e_<cov>_<param>_<metab>
#   "shared"     - matches e_<cov>_<param>_<param2> (shared exponent)
#   "component"  - matches e_<cov>_<param>_<component> (multi-CL arm)
#   "unknown"    - has a third-token suffix that doesn't match any
#                  registered category
.classifyCovEffect <- function(name, conv) {
  if (!startsWith(name, "e_")) return(NA_character_)
  if (!grepl(conv$covEffectPattern, name)) return(NA_character_)
  if (.endsWithMetabolite(name, conv)) return("metabolite")
  if (.endsWithClComponent(name, conv)) return("component")
  if (.endsWithBarePkParam(name, conv)) return("shared")
  # Strip the leading `e_` and check whether the rest is a single
  # `<cov>_<param>` pair (no third-token suffix). If yes, two_token;
  # otherwise the name has an unrecognized trailing suffix.
  rest <- substr(name, 3, nchar(name))
  parts <- strsplit(rest, "_", fixed = TRUE)[[1]]
  if (length(parts) == 2) return("two_token")
  "unknown"
}
