% Input:       the pitch dimension from tenkz-core and the declarations below.
% Output:      the named metric registry and the \__tenkz_dim:n accessor.
% Owned state: the global registry of values, kinds, and motivations.
% Invariants:  every entry carries a motivation; a name resolves or errors.
% Next stage:  geometry and rendering measure exclusively through the accessor.
% SPDX-License-Identifier: Apache-2.0
% Copyright the TNLean project; see LICENSE for the full terms.
%
% The registry is the metric table of LANGUAGE-1.0: one pitch, named ratios,
% and print-size floors, each with its reason.  No public path accepts a
% numeric factor; drawing code asks for a NAME.  During the transition the
% legacy \tenkz@r@ macros in tenkz-core remain the old renderer's source;
% the load-time assertions at the end force both tables to agree, so one
% truth wears two spellings until the call-site sweep deletes the old names.
% Dialect-owned ratios migrate here with their stages, not before.

\ExplSyntaxOn

\prop_new:N \g__tenkz_metric_value_prop
\prop_new:N \g__tenkz_metric_kind_prop
\prop_new:N \g__tenkz_metric_why_prop

\msg_new:nnn {tenkz}{metric-unknown}
  { [TKZ-METRIC-UNKNOWN]~no~metric~entry~named~'#1' }
\msg_new:nnn {tenkz}{metric-redeclared}
  { [TKZ-METRIC-REDECLARED]~metric~entry~'#1'~already~exists }
\msg_new:nnn {tenkz}{metric-legacy-kind}
  { [TKZ-METRIC-LEGACY-KIND]~length~metric~'#1'~has~a~ratio-style~legacy~macro }
\msg_new:nnnn {tenkz}{metric-drift}
  { [TKZ-METRIC-DRIFT]~registry~'#1'~and~its~legacy~macro~disagree }
  { Registry~value~#2;~legacy~value~#3.~One~truth,~two~spellings:~fix~one. }

\cs_new_protected:Npn \__tenkz_metric_declare_aux:nnnn #1#2#3#4
  {
    \prop_if_in:NnT \g__tenkz_metric_value_prop {#2}
      { \msg_error:nnn {tenkz}{metric-redeclared} {#2} }
    \prop_gput:Nnn \g__tenkz_metric_value_prop {#2} {#3}
    \prop_gput:Nnn \g__tenkz_metric_kind_prop  {#2} {#1}
    \prop_gput:Nnn \g__tenkz_metric_why_prop   {#2} {#4}
    % A registry-first entry mints the kind-correct legacy spelling; a
    % legacy-first entry keeps its \def and the drift assertion holds the
    % two equal.  Ratios use \tenkz@r@<name>; absolute lengths use
    % \tenkz@<name> and must never acquire the ratio spelling.
    \str_if_eq:nnTF {#1} {ratio}
      {
        \cs_if_exist:cF { tenkz@r@#2 }
          { \cs_new:cpn { tenkz@r@#2 } {#3} }
      }
      {
        \cs_if_exist:cF { tenkz@#2 }
          { \cs_new:cpn { tenkz@#2 } {#3} }
        \cs_if_exist:cT { tenkz@r@#2 }
          { \msg_error:nnn {tenkz}{metric-legacy-kind} {#2} }
      }
  }
\cs_new_protected:Npn \__tenkz_metric_declare:nnn #1#2#3
  { \__tenkz_metric_declare_aux:nnnn {ratio} {#1} {#2} {#3} }
\cs_new_protected:Npn \__tenkz_metric_declare_length:nnn #1#2#3
  { \__tenkz_metric_declare_aux:nnnn {length} {#1} {#2} {#3} }

% Expandable accessor: legal inside \dimexpr and pgf coordinates.  A ratio
% scales the pitch; a length stands alone; an unknown name is a coded error
% and resolves to zero so the expansion context survives to report it.
\cs_new:Npn \__tenkz_dim:n #1
  {
    \prop_if_in:NnTF \g__tenkz_metric_value_prop {#1}
      {
        \__tenkz_metric_dim:een
          { \prop_item:Nn \g__tenkz_metric_kind_prop {#1} }
          { \prop_item:Nn \g__tenkz_metric_value_prop {#1} }
          {#1}
      }
      {
        \msg_expandable_error:nnn {tenkz}{metric-unknown} {#1}
        \dimexpr 0pt \relax
      }
  }
\cs_new:Npn \__tenkz_metric_dim:nnn #1#2#3
  {
    \str_if_eq:nnTF {#1} {ratio}
      { \dimexpr #2 \tenkz@pitch \relax }
      {
        \str_if_eq:nnTF {#1} {length}
          { \dimexpr #2 \relax }
          {
            \msg_expandable_error:nnn {tenkz}{metric-unknown} {#3}
            \dimexpr 0pt \relax
          }
      }
  }
\cs_generate_variant:Nn \__tenkz_metric_dim:nnn { een }

% The same value as an fp factor of the pitch, for geometry arithmetic.
\cs_new:Npn \__tenkz_metric_ratio:n #1
  {
    \prop_if_in:NnTF \g__tenkz_metric_value_prop {#1}
      { \prop_item:Nn \g__tenkz_metric_value_prop {#1} }
      {
        \msg_expandable_error:nnn {tenkz}{metric-unknown} {#1}
        0
      }
  }

% Two default beads contribute two page-space half-strokes before daylight
% begins.  Normalize that one full named stroke by the live picture pitch;
% it is deliberately derived, not another registry row.  Affine carriers add
% this page-owned term after asking geometry for the transported bead bodies.
\cs_new:Npn \__tenkz_metric_default_bead_page_gap:
  {
    \fp_eval:n
      {
        \__tenkz_metric_ratio:n {daylight}
        + \dim_to_fp:n { \__tenkz_dim:n {wirewidth} }
          / \dim_to_fp:n { \tenkz@pitch }
      }
  }

% ---------- the core table (motivations abridged from tenkz-core) ----------
\__tenkz_metric_declare:nnn {layersep} {0.80}
  { layers~read~as~layers,~not~as~two~chains }
\__tenkz_metric_declare:nnn {physleg} {0.38}
  { a~labelled~leg~clears~the~bond-label~band }
\__tenkz_metric_declare:nnn {stub} {0.45}
  { open~index:~longer~than~a~leg,~shorter~than~a~bond;~also~the~cup~reach }
\__tenkz_metric_declare:nnn {daylight} {0.15}
  { the~one~gap~between~a~silhouette~and~closure~or~annotation~ink,~
    counted~centre~to~centre~and~calibrated~at~8.5~wire~widths~so~
    wire-weight~ink~never~fuses~with~it:~a~traced~row's~return~stands~
    this~far~past~the~drawn~tips~of~the~row's~open~indices,~a~span~
    brace~this~far~past~the~sites~it~spans }
\__tenkz_metric_declare:nnn {cupmouth} {0.04}
  { horizontal~lead-in~of~a~trace~mouth:~just~enough~bend~to~read~as~a~turn }
\__tenkz_metric_declare:nnn {cupdepth} {0.50}
  { racetrack~half-depth:~the~closure~clears~half~a~cell~beyond~the~word }
\__tenkz_metric_declare:nnn {wireclear} {0.30}
  { lateral~escape~of~an~interrupted~wire~around~an~on-wire~glyph }
\__tenkz_metric_declare:nnn {regioncorner} {0.10}
  { region-loop~corner~radius:~tighter~than~a~glyph~corner~so~a~nested~
    boundary~reads~as~an~outline,~not~a~pill }
\__tenkz_metric_declare:nnn {slantmin} {0.30}
  { plane~slant~floor:~below~it~depth~bonds~steepen~past~61~degrees~and~
    become~confusable~with~vertical~physical~legs }
\__tenkz_metric_declare:nnn {slantmax} {0.65}
  { plane~slant~cap:~above~it~a~receding~row~lands~on~the~next~row's~
    sites~under~rotation }
\__tenkz_metric_declare:nnn {planeslant} {0.45}
  { fixed~plane-frame~depth~run:~separates~depth~from~column~adjacency }
\__tenkz_metric_declare:nnn {planerise} {0.60}
  { fixed~plane-frame~depth~rise:~clears~physical~legs~while~preserving~
    visible~foreshortening }
\__tenkz_metric_declare:nnn {labelclear} {0.12}
  { one~clearance~for~every~label~band }
\__tenkz_metric_declare:nnn {dotlabelband} {0.30}
  { clearance~plus~one~line~of~script~text;~bead~and~leg-tip~labels~alike }
\__tenkz_metric_declare:nnn {inlinephysleg} {0.26}
  { inline:~legs~shrink~faster~than~the~pitch }
\__tenkz_metric_declare:nnn {inlinelabelclear} {0.05}
  { inline:~labels~hug~the~ink~to~spare~line~height }
\__tenkz_metric_declare:nnn {dotdia} {0.16}
  { bead~diameter }
\__tenkz_metric_declare:nnn {tallover} {0.34}
  { a~wires=k~glyph~visibly~covers~the~wires~it~terminates }
\__tenkz_metric_declare:nnn {phtracereach} {0.24}
  { per-site~physical-trace~loop~clears~the~glyph~inside~the~bond~span }
\__tenkz_metric_declare:nnn {pairtracereach} {0.40}
  { pair-trace~racetrack~clears~the~neighbouring~column~at~default~pitch }
\__tenkz_metric_declare:nnn {corner} {0.14}
  { rounded-corner~radius~of~traces~and~hulls }
\__tenkz_metric_declare:nnn {glyphbox} {0.22}
  { flat-skin~height~floor~and~span-decoration~overhang }
\__tenkz_metric_declare:nnn {glyphref} {0.50}
  { size-class~reference~extent:~box,~pill~and~capsule~read~as~one~family,~
    a~box~takes~it~on~both~axes,~and~a~bond~keeps~as~much~ink~as~the~
    glyphs~it~joins }
\__tenkz_metric_declare:nnn {wireglyph} {0.30}
  { on-wire~glyphs~sit~ON~a~wire~and~must~read~over~it }
\__tenkz_metric_declare:nnn {wireglyphcap} {0.15}
  { wireglyph/2~as~a~named~literal:~pgf~detaches~a~leading~\dimexpr~factor }
\__tenkz_metric_declare:nnn {fusionwidth} {0.075}
  { fusion-bar~stroke:~a~wedge,~not~a~wire,~so~it~scales~like~a~glyph }
\__tenkz_metric_declare:nnn {fuseover} {0.10}
  { the~wedge~visibly~clasps~the~wires~it~fuses;~drawing~and~clearance~agree }
\__tenkz_metric_declare:nnn {crossgap} {0.10}
  { quantikz~opens~3pt~at~11mm~pitch;~the~calibrated~crossing~break }
\__tenkz_metric_declare:nnn {stringtilt} {0.55}
  { oblique~torus~projection~shows~longitude~depth~without~flattening~meridians }
\__tenkz_metric_declare:nnn {wrapreach} {0.55}
  { a~traced~rail~runs~past~its~outermost~site~far~enough~to~read~as~
    continuing,~short~enough~not~to~claim~a~new~site }
\__tenkz_metric_declare:nnn {legcrossclear} {0.35}
  { a~leg~declared~crossed~by~a~string~continues~past~the~crossing~by~a~
    visible~margin,~so~the~gap~reads~as~depth,~not~as~an~endpoint }
\__tenkz_metric_declare:nnn {outsidegap} {0.80}
  { an~outside~address~sits~one~clear~step~beyond~the~outermost~cell:~
    farther~than~any~closure~reach,~nearer~than~a~missing~site }
\__tenkz_metric_declare:nnn {clusterstep} {0.25}
  { the~quarter-pitch~sub-frame~of~cluster~sugar:~four~spins~read~as~one~
    site~at~reading~distance }
\__tenkz_metric_declare:nnn {enclosepad} {0.62}
  { an~enclosure~clears~the~dot-label~band~of~the~sites~it~selects~
    without~touching~the~neighbouring~column }
\__tenkz_metric_declare:nnn {enclosetight} {0.12}
  { an~enclosure~of~bare~spins~clears~the~dots~it~names~and~stops~halfway~
    to~the~sub-frame~neighbours~it~does~not:~half~the~cluster~step }
\__tenkz_metric_declare:nnn {clusterdot} {0.11}
  { a~spin~on~a~cluster~sub-frame:~small~enough~that~the~gap~between~two~
    spins~exceeds~their~radius,~so~four~of~them~read~as~four }
\__tenkz_metric_declare:nnn {canonicalwidth} {0.34}
  { canonical-form~triangles~clear~their~inscribed~labels~without~widening~
    enough~to~crowd~the~neighbouring~bond }
\__tenkz_metric_declare:nnn {dirmarkstation} {0.55}
  { direction~barbs~sit~just~past~mid-wire,~clear~of~central~glyphs~while~
    remaining~visually~attached~to~the~bond~they~orient }
\__tenkz_metric_declare:nnn {compactpitch} {0.5}
  { the~compact~metric~profile:~half~pitch~keeps~a~column-wide~word~on~
    one~printed~line,~and~every~named~ratio~follows~the~pitch~while~the~
    print-size~floors~hold }

\__tenkz_metric_declare_length:nnn {wirewidth} {0.55pt}
  { the~wire~stroke~of~the~ink~hierarchy }
\__tenkz_metric_declare_length:nnn {annwidth} {0.40pt}
  { annotation~stroke,~lighter~than~a~wire }
\__tenkz_metric_declare_length:nnn {emphwidth} {0.80pt}
  { emphasis~stroke,~heavier~than~a~wire }
\__tenkz_metric_declare_length:nnn {junctionfloor} {0.9mm}
  { below~this~absolute~floor~a~junction~reads~as~dirt }
\__tenkz_metric_declare_length:nnn {barblen} {2.6pt}
  { directionality~barb:~a~print-size~floor,~not~a~ratio }
\__tenkz_metric_declare_length:nnn {fusedbarblen} {3.8pt}
  { barb~spanning~a~doubled~wire }
\__tenkz_metric_declare_length:nnn {braceamp} {2.6pt}
  { brace~amplitude:~numerically~the~barb~floor,~semantically~independent }
\__tenkz_metric_declare_length:nnn {boxinset} {1.6pt}
  { inscribed-label~padding~that~keeps~a~box~compact~at~the~glyph~floor }
\__tenkz_metric_declare_length:nnn {pillcap} {2.2pt}
  { the~corner~radius~rounds~a~pill's~ends~without~making~it~a~circle }
\__tenkz_metric_declare_length:nnn {pillxinset} {3.2pt}
  { a~pill~leaves~more~horizontal~than~vertical~room~around~its~label }
\__tenkz_metric_declare_length:nnn {pillyinset} {2.2pt}
  { a~pill's~vertical~label~padding~keeps~the~skin~flatter~than~a~box }
\__tenkz_metric_declare_length:nnn {wireglyphxinset} {1.8pt}
  { an~on-wire~capsule~keeps~its~label~off~the~rounded~end~caps }
\__tenkz_metric_declare_length:nnn {wireglyphyinset} {1.4pt}
  { an~on-wire~capsule~stays~flatter~than~an~ordinary~labelled~glyph }
\__tenkz_metric_declare_length:nnn {triangleinset} {0.6pt}
  { a~triangle~circumscribes~its~label,~so~a~small~inset~protects~row~clearance }
\__tenkz_metric_declare_length:nnn {wiredash} {2.2pt}
  { a~dashed~rail~reads~as~dashes~at~print~size~without~closing~into~a~line }
\__tenkz_metric_declare_length:nnn {wiredotgap} {1.8pt}
  { the~paper~between~two~dots~of~a~dotted~rail:~wide~enough~that~the~rail~
    never~reads~as~dashed }
\__tenkz_metric_declare_length:nnn {fusedwiregap} {1.1pt}
  { the~paper~channel~between~a~fused~bond's~two~wire-weight~strokes }
\__tenkz_metric_declare_length:nnn {groupcorner} {2pt}
  { grouped-object~outlines~turn~gently~without~reading~as~pill~glyphs }
\__tenkz_metric_declare_length:nnn {labelinset} {1pt}
  { the~house~label~skin~keeps~script~text~clear~of~its~anchor~ink }

% ---------- one truth, two spellings: assert agreement with the legacy ----
\cs_new_protected:Npn \__tenkz_metric_assert:nn #1#2
  {
    \str_if_eq:eeF { \prop_item:Nn \g__tenkz_metric_value_prop {#1} } {#2}
      {
        \msg_error:nnnee {tenkz}{metric-drift} {#1}
          { \prop_item:Nn \g__tenkz_metric_value_prop {#1} } {#2}
      }
  }
\clist_map_inline:nn
  {
    {layersep}, {physleg}, {stub}, {daylight}, {labelclear},
    {dotlabelband}, {inlinephysleg}, {inlinelabelclear}, {dotdia},
    {tallover}, {phtracereach}, {pairtracereach}, {corner}, {glyphbox},
    {glyphref}, {wireglyph}, {wireglyphcap}, {fusionwidth}, {fuseover},
    {crossgap}
  }
  { \exp_args:Nne \__tenkz_metric_assert:nn {#1} { \use:c { tenkz@r@ #1 } } }
% The registry owns the floors: with core's \def table retired, the
% declare_length entries above mint the legacy spellings themselves.

\ExplSyntaxOff
\endinput
