// ── model.jsx ────────────────────────────────────────────────────────────────
// GEOMETRIC PRIMITIVES + shared tables.
//
// This module holds the per-shape AREA functions (every knitted object is summed
// from primitive shapes — rectangle, tube, cone, dome, triangle, ribband, heel,
// toe), the row-gauge default, the gauge↔m/100g autofill, held-double combination,
// and the neckline / construction label tables. The actual fabric DENSITY and the
// construction-aware sweater geometry live in geometry.jsx.
//
// ── LAYER 1 · DENSITY ────────────────────────────────────────────────────────
// A yarn/fabric contributes exactly two independent numbers:
//   • m_per_100g     — linear density (ball band) → converts metres ↔ grams
//   • metres_per_cm² — fabric density from gauge   → multiply by area → metres
// metres_per_cm² is a property of the FABRIC (loop geometry), not the fibre, so
// it is ~fibre-independent: cotton-vs-wool lives entirely in m_per_100g. No
// fibre modifier (Build Plan §1, audit A4).
//
// DENSITY now lives entirely in geometry.jsx (metresPerCm2Flat, the FLAT-SWATCH
// curve K=0.01245, P=0.8098). The old metresPerCm2New power law (K=0.00253,
// P=1.2469) was secretly fitted to reproduce an obsolete whole-garment regression
// and under-read coarse gauge — it has been retired. This module keeps only the
// geometry primitives, the row-gauge default, and gauge↔m/100g autofill.

// Default row gauge from stitch gauge (Constants sheet fit; rows = 1.195·sts+2.052).
// Rows CANNOT be derived from m/100g — they are a fabric/tension property — so this
// is an honest, visibly-estimated default the user can override (audit A11).
function stdRowsFor(sts) { return Math.max(6, 1.195 * (sts || 22) + 2.052) }

// Continuous stitch-gauge autofill from m/100g (Build Plan §1.2). A power curve
// across the whole lace→bulky range — NOT a snap to the nearest weight bucket.
function stsFromMPer100g(m) {
  if (!m || m <= 0) return 22
  return Math.round(1.04652 * Math.pow(m, 0.5429))
}

// Held-double / du laikomi kartu (Build Plan §1.4): two strands held together are
// ONE fabric with a combined linear density. No ×2 multiplier — it is simply a
// different yarn (lower m/100g) knit at a coarser gauge, flowing through the
// identical pipeline. combined m/100g = harmonic-style mA·mB/(mA+mB).
function combineHeldDouble(mA, mB) {
  if (!mA || !mB) return mA || mB || 0
  return Math.round((mA * mB) / (mA + mB))
}

// ── LAYER 2 · GEOMETRY / PRIMITIVES ──────────────────────────────────────────
// Area (cm²) for each primitive shape. Pure functions of resolved dimensions.
const AREA = {
  // scarf / panel / sweater body (flattened front+back: width here is the full
  // circumference for a body tube, or the flat width for a scarf)
  rectangle: d => (d.width || 0) * (d.length || 0),
  // hat body · sock leg/foot · cowl — circumference × height (full tube wall)
  tube: d => (d.circumference || 0) * (d.depth || 0),
  // sleeve / sock-foot taper — tapered tube: average of the two ends × length
  cone: d => ((((d.circumference || 0) + (d.cuff != null ? d.cuff : d.circumference || 0)) / 2) * (d.depth || 0)),
  // hat crown — the yarn is the sum of the decreasing rounds (full circ → a point).
  // Unrolled that is a cone/triangle, so crown yarn ≈ 0.5·circ·depth, floored at the
  // flat-disc minimum πr² (you can't close the tube with less). depth = crown height.
  // (Was π(r²+d²), a spherical-cap SURFACE area — it over-counts crown yarn ~2×;
  // stitch counts give the triangle.)
  dome: d => {
    const circ = d.circumference || 0
    const r = circ / (2 * Math.PI)
    const depth = d.depth != null ? d.depth : r
    return 0.5 * circ * Math.max(depth, r)
  },
  // triangular shawl — measured AFTER blocking, so the blocked area is exact
  triangle: d => 0.5 * (d.base || 0) * (d.height || 0),
  // brim · cuff · hem · waistband — same wall as a tube
  ribband: d => (d.circumference || 0) * (d.depth || 0),
  // sock heel — flap (≈½ the round) + heel-turn + gusset wedges. Coefficient on
  // circ² derived from AnyFoot stitch counts (gauge-independent): heel-system
  // stitches / circ² is constant ≈0.30 across all sizes. (Was 0.50 — ~1.7× high.)
  heel: d => 0.30 * Math.pow(d.circumference || 0, 2),
  // sock toe — paired decrease wedge. Tiny: the foot tube already runs heel→toe,
  // so only the tip taper is extra. AnyFoot stitch counts give ≈0.03·circ².
  // (Was 0.17 — ~6× high; it was double-counting the foot tube's end.)
  toe: d => 0.03 * Math.pow(d.circumference || 0, 2),
  overhead: () => 0,
}

// Negative neckline area subtracted from a sweater body (Build Plan §2.1, §3.1).
// A manipulable negative block; the selector swaps which shape is subtracted.
//   crew/scoop  — half-ellipse  0.5·π·(w/2)·d
//   v           — triangle      0.5·w·d
//   boat        — shallow slot  0.7·w·d (wide, very shallow)
const NECK_TYPES = {
  crew:  { label: "Crew",  hint: "Apvali", wK: 0.20, dDef: 4,  area: (w, d) => 0.5 * Math.PI * (w / 2) * d },
  scoop: { label: "Scoop", hint: "Gili apvali", wK: 0.24, dDef: 9, area: (w, d) => 0.5 * Math.PI * (w / 2) * d },
  v:     { label: "V-neck", hint: "V formos", wK: 0.20, dDef: 14, area: (w, d) => 0.5 * w * d },
  boat:  { label: "Boat",  hint: "Valties", wK: 0.34, dDef: 2,  area: (w, d) => 0.7 * w * d },
}
function neckArea(neckType, chest, neckW, neckD) {
  const nt = NECK_TYPES[neckType]
  if (!nt) return 0
  const w = neckW != null ? neckW : Math.round(chest * nt.wK)
  const d = neckD != null ? neckD : nt.dDef
  return Math.max(0, nt.area(w, d))
}

// Per-construction labels for the construction selector. The actual junction
// allowance + geometry now live in geometry.jsx (SWEATER_GEO); this table is
// labels/hints only. (The old per-construction `mod` multipliers were dead and
// disagreed with SWEATER_GEO, so they have been removed.)
const JUNCTION = {
  set_in:        { label: "Set-in",        hint: "Įstatyta" },
  raglan:        { label: "Raglan",        hint: "Reglanas" },
  yoke:          { label: "Circular yoke", hint: "Apvali koketė" },
  drop:          { label: "Drop shoulder", hint: "Nuleistas petys" },
  modified_drop: { label: "Modified drop", hint: "Pusiau nuleistas" },
}

Object.assign(window, {
  stdRowsFor,
  stsFromMPer100g, combineHeldDouble, AREA, NECK_TYPES, neckArea, JUNCTION,
})
