# Rebuild the kitchen ingredient table from USDA FoodData Central (SR Legacy, 2018-04).
# Every macro and every fat-class fraction is computed from real FDC nutrient rows.
import csv, json, os, urllib.request, zipfile

# ---------------------------------------------------------------- USDA loader
# Downloads and parses the SR Legacy release. No third-party imports, no other
# project files: running `python build-ingredients.py` reproduces the table.
SR_URL = ('https://fdc.nal.usda.gov/fdc-datasets/'
          'FoodData_Central_sr_legacy_food_csv_2018-04.zip')
SR_DIR = 'FoodData_Central_sr_legacy_food_csv_2018-04'

# nutrient_id -> short name. NOTE 1261/1262 are caprylic/capric (C8/C10);
# 1263/1264 are lauric/myristic and must NOT be used for the MCT class.
NUTRIENTS = {
    '1008': 'kcal', '1003': 'protein', '1004': 'fat', '1005': 'carb', '1079': 'fiber',
    '1258': 'sfa', '1292': 'mufa', '1293': 'pufa',
    '1278': 'epa', '1272': 'dha', '1280': 'dpa',
    '1404': 'ala', '1270': 'ala_gen', '1316': 'la', '1269': 'la_gen',
    '1406': 'aa', '1271': 'aa_gen', '1321': 'gla',
    '1261': 'c8', '1262': 'c10',
}

def load_usda():
    if not os.path.isdir(SR_DIR):
        zp = 'sr_legacy.zip'
        if not os.path.exists(zp):
            print('downloading', SR_URL)
            req = urllib.request.Request(SR_URL, headers={'User-Agent': 'Mozilla/5.0'})
            try:
                with urllib.request.urlopen(req, timeout=300) as r, open(zp, 'wb') as f:
                    f.write(r.read())
            except Exception as exc:
                raise SystemExit(
                    'Could not download the USDA release (%s: %s).\n'
                    'Download it manually from:\n  %s\n'
                    'then save it next to this script as "%s" and run again.'
                    % (type(exc).__name__, exc, SR_URL, zp))
        with zipfile.ZipFile(zp) as z:
            z.extractall('.')
    foods, vals, portions = {}, {}, {}
    with open(os.path.join(SR_DIR, 'food.csv'), encoding='utf-8') as fh:
        for row in csv.DictReader(fh):
            foods[row['fdc_id']] = row['description']
    with open(os.path.join(SR_DIR, 'food_nutrient.csv'), encoding='utf-8') as fh:
        for row in csv.DictReader(fh):
            name = NUTRIENTS.get(row['nutrient_id'])
            if name:
                try: vals.setdefault(row['fdc_id'], {})[name] = float(row['amount'])
                except ValueError: pass
    with open(os.path.join(SR_DIR, 'food_portion.csv'), encoding='utf-8') as fh:
        for row in csv.DictReader(fh):
            try: grams = float(row['gram_weight'])
            except ValueError: continue
            portions.setdefault(row['fdc_id'], []).append(
                (row.get('portion_description') or row.get('modifier', ''), row.get('amount', ''), grams))
    return {'foods': foods, 'vals': vals, 'portions': portions}

DB = load_usda()

U = lambda fdc, **kw: dict(fdc=str(fdc), src='usda', **kw)

# my_id -> mapping.  g_per: grams per piece for unit 'ct' (USDA portion where available).
MAP = {
 # ---------------- seafood ----------------
 'salmon':      U(175167, n='Salmon fillet (farmed Atlantic)', c='sea', u='g'),
 'smokedsalmon':U(173687, n='Smoked salmon (chinook)', c='sea', u='g'),
 'sardines':    U(175139, n='Sardines (92 g tin, in oil, drained)', c='sea', u='ct', g_per=92,
                 pw='USDA portion: 1 can (3.75 oz)',
                 one='tin of sardines', pl='tins of sardines'),
 # SR Legacy lists only a 361 g can, so the retail tin size is an assumption.
 'mackereltin': U(175121, n='Mackerel (125 g tin, jack, drained)', c='sea', u='ct', g_per=125,
                 pw='ASSUMED: typical 125 g retail tin (USDA lists only a 361 g can)',
                 one='tin of mackerel', pl='tins of mackerel'),
 'anchovy':     U(174183, n='Anchovies (45 g tin, in oil, drained)', c='sea', u='ct', g_per=45,
                 pw='USDA portion: 1 can (2 oz)',
                 one='tin of anchovies', pl='tins of anchovies'),
 'shrimp':      U(175179, n='Shrimp, peeled, raw', c='sea', u='g'),
 'cod':         U(171955, n='Cod, Atlantic, raw', c='sea', u='g'),
 'mussels':     U(174216, n='Mussels, blue, raw', c='sea', u='g'),
 'oyster':      U(171978, n='Oyster (eastern, wild)', c='sea', u='ct', g_per=14,
                 pw='USDA portion: 6 medium = 84 g',
                 one='oyster', pl='oysters'),
 # ---------------- meat, poultry & eggs ----------------
 'eggs':        U(171287, n='Eggs', c='meat', u='ct', g_per=50, pw='USDA portion: 1 large',
                 one='egg', pl='eggs'),
 'chickenthigh':U(172385, n='Chicken thighs (skin-on), raw', c='meat', u='g'),
 'chickenbreast':U(171077, n='Chicken breast, skinless, raw', c='meat', u='g'),
 'groundbeef':  U(174036, n='Ground beef (80/20), raw', c='meat', u='g'),
 'steak':       U(168726, n='Beef top sirloin steak, raw', c='meat', u='g'),
 'liver':       U(169451, n='Beef liver, raw', c='meat', u='g'),
 'lamb':        U(172500, n='Lamb shoulder, raw', c='meat', u='g'),
 'porkchop':    U(167839, n='Pork loin chop, raw', c='meat', u='g'),
 'porkbelly':   U(167812, n='Pork belly, raw', c='meat', u='g'),
 'bacon':       U(168277, n='Bacon, cured, unprepared', c='meat', u='g'),
 # ---------------- produce ----------------
 'spinach':     U(168462, n='Spinach', c='prod', u='g'),
 'kale':        U(168421, n='Kale', c='prod', u='g'),
 'arugula':     U(169387, n='Arugula', c='prod', u='g'),
 'romaine':     U(169247, n='Romaine lettuce', c='prod', u='g'),
 'butterlettuce':U(168429, n='Butter lettuce', c='prod', u='g'),
 'endive':      U(168412, n='Endive', c='prod', u='g'),
 'broccoli':    U(170379, n='Broccoli', c='prod', u='g'),
 'cauliflower': U(169986, n='Cauliflower', c='prod', u='g'),
 'cabbage':     U(169975, n='Cabbage', c='prod', u='g'),
 'brussels':    U(170383, n='Brussels sprouts', c='prod', u='g'),
 'zucchini':    U(169291, n='Zucchini', c='prod', u='g'),
 'asparagus':   U(168389, n='Asparagus', c='prod', u='g'),
 'bellpepper':  U(170108, n='Bell pepper, red', c='prod', u='g'),
 'mushrooms':   U(168434, n='Mushrooms (cremini)', c='prod', u='g'),
 'cherrytom':   U(170457, n='Tomatoes, red, ripe', c='prod', u='g'),
 'cucumber':    U(168409, n='Cucumber, with peel', c='prod', u='g'),
 'avocado':     U(171706, n='Avocado (Hass / California)', c='prod', u='ct', g_per=136,
                 pw='USDA portion: 1 fruit, without skin and seed',
                 one='avocado', pl='avocados'),
 'celery':      U(169988, n='Celery', c='prod', u='g'),
 'greenbeans':  U(169961, n='Green beans', c='prod', u='g'),
 'radish':      U(169276, n='Radishes', c='prod', u='g'),
 'scallion':    U(170005, n='Scallion', c='prod', u='ct', g_per=15, pw='USDA portion: 1 medium',
                 one='scallion', pl='scallions'),
 'onion':       U(170000, n='Onion', c='prod', u='ct', g_per=110, pw='USDA portion: 1 medium',
                 one='onion', pl='onions'),
 'garlic':      U(169230, n='Garlic clove', c='prod', u='ct', g_per=3, pw='USDA portion: 1 clove',
                 one='garlic clove', pl='garlic cloves'),
 'ginger':      U(169231, n='Fresh ginger', c='prod', u='g'),
 'lemon':       U(167746, n='Lemon', c='prod', u='ct', g_per=58,
                 pw='USDA portion: 1 fruit (2-1/8 in dia)',
                 one='lemon', pl='lemons'),
 'lime':        U(168155, n='Lime', c='prod', u='ct', g_per=67,
                 pw='USDA portion: 1 fruit (2 in dia)',
                 one='lime', pl='limes'),
 'raspberries': U(167755, n='Raspberries', c='prod', u='g'),
 'blackberries':U(173946, n='Blackberries', c='prod', u='g'),
 'strawberries':U(167762, n='Strawberries', c='prod', u='g'),
 'blueberries': U(171711, n='Blueberries', c='prod', u='g'),
 'parsley':     U(170416, n='Parsley (bunch)', c='prod', u='ct', g_per=60,
                 pw='ASSUMED bunch = 60 g (no USDA bunch; matches its 1 cup chopped)',
                 one='bunch of parsley', pl='bunches of parsley'),
 'cilantro':    U(169997, n='Cilantro (bunch)', c='prod', u='ct', g_per=40,
                 pw='ASSUMED bunch = 40 g (no USDA bunch; its 9 sprigs = 20 g)',
                 one='bunch of cilantro', pl='bunches of cilantro'),
 'basil':       U(172232, n='Fresh basil', c='prod', u='g'),
 'rosemary':    U(173473, n='Rosemary sprig', c='prod', u='ct', g_per=1.7,
                 pw='USDA portion: 1 tbsp, used as a sprig equivalent',
                 one='rosemary sprig', pl='rosemary sprigs'),
 'thyme':       U(173470, n='Thyme sprig', c='prod', u='ct', g_per=0.8,
                 pw='USDA portion: 1 tsp, used as a sprig equivalent',
                 one='thyme sprig', pl='thyme sprigs'),
 'dill':        U(172233, n='Dill (bunch)', c='prod', u='ct', g_per=20,
                 pw='ASSUMED bunch = 20 g (was 9 g = USDA 1 cup of loose sprigs, too light for a retail bunch)',
                 one='bunch of dill', pl='bunches of dill'),
 # ---------------- dairy ----------------
 'butter':      U(173410, n='Butter, salted', c='dairy', u='g'),
 # FDC's ghee entry reports only SFA and PUFA totals (no MUFA, no individual acids),
 # which would render ghee as ~100% saturated. Ghee is clarified butter, so take the
 # fat profile from butter (173410) and keep ghee's own macros.
 'ghee':        dict(fdc='171314', fp_from='173410', src='composite',
                     n='Ghee (clarified butter)', c='dairy', u='g'),
 'cream':       U(170859, n='Heavy cream', c='dairy', u='ml', density=1.008,
                 pw='density from USDA portion 1 fl oz = 29.8 g'),
 'feta':        U(173420, n='Feta', c='dairy', u='g'),
 'parmesan':    U(170848, n='Parmesan, hard', c='dairy', u='g'),
 'cheddar':     U(173414, n='Aged cheddar', c='dairy', u='g'),
 'goatcheese':  U(173433, n='Goat cheese, semisoft', c='dairy', u='g'),
 'yogurt':      U(171304, n='Greek yogurt (whole milk, plain)', c='dairy', u='g'),
 'kefir':       U(170904, n='Kefir (lowfat, plain)', c='dairy', u='ml', density=1.027,
                 pw='density from USDA portion 1 cup = 243 g'),
 'creamcheese': U(173418, n='Cream cheese', c='dairy', u='g'),
 # ---------------- nuts & seeds ----------------
 'macadamia':   U(170178, n='Macadamias, raw', c='nuts', u='g'),
 'walnut':      U(170187, n='Walnuts, English', c='nuts', u='g'),
 'pecan':       U(170182, n='Pecans', c='nuts', u='g'),
 'almond':      U(170567, n='Almonds', c='nuts', u='g'),
 'hazelnut':    U(170581, n='Hazelnuts', c='nuts', u='g'),
 'brazil':      U(170569, n='Brazil nut', c='nuts', u='ct', g_per=5, pw='USDA portion: 1 kernel',
                 one='Brazil nut', pl='Brazil nuts'),
 'pumpkinseeds':U(170557, n='Pumpkin seeds, roasted', c='nuts', u='g'),
 'hemphearts':  U(170148, n='Hemp hearts (hulled)', c='nuts', u='g'),
 'chia':        U(170554, n='Chia seeds', c='nuts', u='g'),
 'flax':        U(169414, n='Ground flaxseed', c='nuts', u='g'),
 'almondflour': U(170568, n='Almond flour (blanched, ground)', c='nuts', u='g'),
 'tahini':      U(170189, n='Tahini', c='nuts', u='g'),
 'pinenuts':    U(170591, n='Pine nuts, dried', c='nuts', u='g'),
 # ---------------- oils & pantry ----------------
 'evoo':        U(171413, n='Extra-virgin olive oil', c='pantry', u='ml', density=0.913, st=1,
                 pw='density from USDA portion 1 cup = 216 g'),
 'avooil':      U(173573, n='Avocado oil', c='pantry', u='ml', density=0.921, st=1,
                 pw='density from USDA portion 1 cup = 218 g'),
 'coconutoil':  U(171412, n='Coconut oil', c='pantry', u='g', st=1),
 'coconutmilk': U(170173, n='Coconut milk (canned, full-fat)', c='pantry', u='ml', density=0.955,
                 pw='density from USDA portion 1 cup = 226 g'),
 'redwinevin':  U(172240, n='Red wine vinegar', c='pantry', u='ml', density=1.010, st=1,
                 pw='density from USDA portion 1 cup = 239 g'),
 # Bones are simmered and discarded, so they carry no "as eaten" macros. They exist
 # only so the shopping list tells you what to buy; the broth's own nutrition comes
 # from the finished-broth values on the bonebroth row (see the base recipe's `out`).
 'beefbones':   dict(src='label', n='Beef marrow / knuckle bones', c='meat', u='g',
                     per100=[0, 0, 0, 0], fp=None, shoponly=1),
 'chickencarcass': dict(src='label', n='Chicken carcass or wings (for broth)', c='meat', u='g',
                     per100=[0, 0, 0, 0], fp=None, shoponly=1),
 'acv':         U(173469, n='Apple cider vinegar', c='pantry', u='ml', density=1.010, st=1,
                 pw='density from USDA portion 1 cup = 239 g'),
 'dijon':       U(172234, n='Dijon mustard', c='pantry', u='g', st=1),
 'darkchoc':    U(170273, n='Dark chocolate (70–85 % cacao)', c='pantry', u='g'),
 # composite: mayonnaise macros from FDC 171009, fat profile from avocado oil FDC 173573
 'mayo':        dict(fdc='171009', fp_from='173573', src='composite',
                     n='Mayonnaise (avocado oil)', c='pantry', u='g'),
 # no SR Legacy equivalent -> typical manufacturer label values, flagged as such
 'mctoil':      dict(src='label', n='MCT oil', c='pantry', u='ml', st=1,
                     per100=[830, 0, 0, 93], fp={'mct': 1.0}),
 'aminos':      dict(src='label', n='Coconut aminos', c='pantry', u='ml', st=1,
                     per100=[100, 20, 0, 0], fp=None),
 'bonebroth':   dict(src='label', n='Bone broth', c='pantry', u='ml',
                     per100=[18, 0, 4.0, 0.2], fp=None),
 # ---------------- fermented & jarred ----------------
 'sauerkraut':  U(169279, n='Sauerkraut', c='ferm', u='g'),
 'kimchi':      U(170392, n='Kimchi', c='ferm', u='g'),
 'olives':      U(169096, n='Olives, green', c='ferm', u='g'),
 'capers':      U(172238, n='Capers', c='ferm', u='g'),
 # ---------------- herbs, spices & salt ----------------
 'salt':        U(173468, n='Sea salt', c='spice', u='g', st=1),
 'pepper':      U(170931, n='Black pepper', c='spice', u='g', st=1),
 'paprika':     U(171329, n='Paprika', c='spice', u='g', st=1),
 'chiliflakes': U(170932, n='Chili flakes', c='spice', u='g', st=1),
 'oregano':     U(171328, n='Dried oregano', c='spice', u='g', st=1),
 'cumin':       U(170923, n='Ground cumin', c='spice', u='g', st=1),
 'turmeric':    U(172231, n='Ground turmeric', c='spice', u='g', st=1),
 'cinnamon':    U(171320, n='Cinnamon', c='spice', u='g', st=1),
}

def g(v, *keys):
    """first present nutrient among keys, else 0"""
    for k in keys:
        if k in v: return v[k]
    return 0.0

def fat_profile(v, total_fat):
    """Fat-class fractions computed from real USDA fatty-acid rows.
       mct = C8+C10 ; s = total SFA - mct ; m = total MUFA ;
       p3m = EPA+DHA+DPA ; p3p = ALA ; p6 = LA + AA + GLA."""
    if total_fat < 0.5: return None
    mct = g(v,'c8') + g(v,'c10')
    s   = max(0.0, g(v,'sfa') - mct)
    m   = g(v,'mufa')
    p3m = g(v,'epa') + g(v,'dha') + g(v,'dpa')
    p3p = g(v,'ala','ala_gen')
    p6  = g(v,'la','la_gen') + g(v,'aa','aa_gen') + g(v,'gla')
    out = {}
    for k, grams in (('m',m),('p3m',p3m),('p3p',p3p),('p6',p6),('s',s),('mct',mct)):
        frac = grams / total_fat
        if frac >= 0.005:
            out[k] = round(frac, 3)
    return out or None

rows, report, missing = [], [], []
for key, mp in MAP.items():
    if mp['src'] == 'label':
        kcal, netc, prot, fat = mp['per100']
        fp = mp.get('fp'); fdc = None; desc = '(manufacturer label, typical)'
    else:
        fdc = mp['fdc']
        v = DB['vals'].get(fdc)
        if not v:
            missing.append((key, fdc)); continue
        desc = DB['foods'][fdc]
        kcal = g(v,'kcal'); prot = g(v,'protein'); fat = g(v,'fat')
        netc = max(0.0, g(v,'carb') - g(v,'fiber'))
        src_v = DB['vals'].get(mp['fp_from'], v) if mp.get('fp_from') else v
        fat_for_profile = g(src_v,'fat') if mp.get('fp_from') else fat
        fp = fat_profile(src_v, fat_for_profile)

    dens = mp.get('density', 1.0)
    scale = (mp['g_per'] if mp['u'] == 'ct' else 100.0 * (dens if mp['u'] == 'ml' else 1.0)) / 100.0
    m4 = [round(kcal*scale,1), round(netc*scale,2), round(prot*scale,2), round(fat*scale,2)]
    rows.append((key, mp, m4, fp, fdc, desc))
    report.append(dict(key=key, fdc=fdc, desc=desc, unit=mp['u'], portion_note=mp.get('pw'),
                       per=('per %g g piece' % mp['g_per']) if mp['u']=='ct' else ('per 100 %s' % mp['u']),
                       macros=m4, fp=fp, src=mp['src'], shoponly=mp.get('shoponly')))

print("mapped:", len(rows), " missing:", missing)
json.dump(report, open('ing_report.json','w'), indent=1)

# ---- emit JS ----
def js_num(x):
    s = ('%g' % x)
    return s
lines = []
CATNAME = {'sea':'seafood','meat':'meat, poultry & eggs','prod':'produce','dairy':'dairy',
           'nuts':'nuts & seeds','pantry':'oils & pantry','ferm':'fermented & jarred','spice':'herbs, spices & salt'}
cur = None
for key, mp, m4, fp, fdc, desc in rows:
    if mp['c'] != cur:
        cur = mp['c']; lines.append('      /* %s */' % CATNAME[cur])
    parts = ["n: %s" % json.dumps(mp['n']), "c: %s" % json.dumps(mp['c']), "u: %s" % json.dumps(mp['u'])]
    parts.append("m: [%s]" % ', '.join(js_num(x) for x in m4))
    if fp:  parts.append("fp: { %s }" % ', '.join('%s: %s' % (k, js_num(v)) for k, v in fp.items()))
    if mp.get('st'): parts.append("st: 1")
    if mp.get('shoponly'): parts.append("shoponly: 1")
    if mp['u'] == 'ct':
        parts.append("one: %s" % json.dumps(mp['one'])); parts.append("pl: %s" % json.dumps(mp['pl']))
    if fdc: parts.append("fdc: %s" % json.dumps(fdc))
    else:   parts.append("fdc: null")
    lines.append("      %-14s { %s }," % (key + ':', ', '.join(parts)))
open('ing_block.js','w',encoding='utf-8').write('\n'.join(lines))
print("wrote ing_block.js (%d lines)" % len(lines))
