/* Data loader — pulls recipes, fetches live prices from Google Sheets */

// Hardcoded prices in gold. These override BOTH sheet and manual prices.
// 1 copper = 0.0001g · 1 silver = 0.01g
window.HARDCODED_PRICES = {
  500: 0.0001,    // Coin — fixed at 1 copper
};

// Manual price storage (localStorage). User-entered prices override sheet, but
// hardcoded values still win.
const MANUAL_KEY = 'craft.manualPrices.v1';
window.loadManualPrices = function() {
  try {
    const raw = localStorage.getItem(MANUAL_KEY);
    return raw ? JSON.parse(raw) : {};
  } catch (e) { return {}; }
};
window.saveManualPrices = function(obj) {
  try { localStorage.setItem(MANUAL_KEY, JSON.stringify(obj)); } catch (e) {}
};

// Parse one row of CSV, handling quoted fields with commas inside.
function parseCSVRow(line) {
  const out = [];
  let cur = '';
  let inQ = false;
  for (let i = 0; i < line.length; i++) {
    const ch = line[i];
    if (inQ) {
      if (ch === '"') {
        if (line[i + 1] === '"') { cur += '"'; i++; }
        else inQ = false;
      } else cur += ch;
    } else {
      if (ch === '"') inQ = true;
      else if (ch === ',') { out.push(cur); cur = ''; }
      else cur += ch;
    }
  }
  out.push(cur);
  return out;
}

const DEFAULT_SHEET = 'https://docs.google.com/spreadsheets/d/1RLmbY7Ly4uUPkEn1VijDJPowb7nr4c6Nn3l4PdgFIKo/export?format=csv&gid=0';

// Fetch live prices. Columns: 0=ID, 1=Name, 3=daily volume, 4=7d avg,
// 5=weekly volume (fallback), 6=30d avg.
// Volume returned as { v, p } where p is 'day' or 'week' — daily preferred,
// weekly used when daily is empty.
async function fetchLivePrices(url = DEFAULT_SHEET) {
  const res = await fetch(url, { cache: 'no-store' });
  if (!res.ok) throw new Error('Sheet fetch failed: HTTP ' + res.status);
  const csv = await res.text();
  const lines = csv.split(/\r?\n/).filter(Boolean);
  const prices = {};
  const volumes = {};
  let lastUpdated = null;
  let used7d = 0, used30d = 0;
  for (let i = 0; i < lines.length; i++) {
    const cells = parseCSVRow(lines[i]);
    if (i === 0) {
      // header row may contain "Last Updated At:" cell
      const lu = cells.findIndex(c => /last updated/i.test(c));
      if (lu >= 0 && cells[lu + 1]) lastUpdated = cells[lu + 1].trim();
      continue;
    }
    const id = parseInt(cells[0]);
    if (!Number.isFinite(id)) continue;
    const daily  = parseFloat((cells[3] || '').replace(/,/g, ''));
    const weekly = parseFloat((cells[5] || '').replace(/,/g, ''));
    if (Number.isFinite(daily) && daily > 0) {
      volumes[id] = { v: daily, p: 'day' };
    } else if (Number.isFinite(weekly) && weekly > 0) {
      volumes[id] = { v: weekly, p: 'week' };
    }
    const p7 = parseFloat(cells[4]);
    const p30 = parseFloat(cells[6]);
    if (Number.isFinite(p7) && p7 > 0) { prices[id] = p7; used7d++; }
    else if (Number.isFinite(p30) && p30 > 0) { prices[id] = p30; used30d++; }
  }
  return { prices, volumes, lastUpdated, used7d, used30d };
}

window.fetchLivePrices = fetchLivePrices;
window.DEFAULT_SHEET = DEFAULT_SHEET;

// Write a price update to the sheet via an Apps Script webhook.
// Apps Script doesn't handle CORS preflight, so we must use a non-preflight
// content type. JSON.stringify is fine in the body — server reads e.postData.contents.
window.writePriceToSheet = async function(webhookUrl, itemId, itemName, price) {
  if (!webhookUrl || !webhookUrl.trim()) throw new Error('No webhook URL configured');
  const res = await fetch(webhookUrl.trim(), {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain;charset=utf-8' },
    body: JSON.stringify({ id: String(itemId), name: itemName, price: Number(price) }),
  });
  if (!res.ok) throw new Error('HTTP ' + res.status);
  const text = await res.text();
  try {
    const json = JSON.parse(text);
    if (json.ok === false) throw new Error(json.error || 'Sheet refused write');
    return json;
  } catch (e) {
    // Non-JSON response — still OK if 200
    return { ok: true, raw: text };
  }
};

window.CRAFT_LOADER = (async function() {
  const recipesRes = await fetch('data/recipes.json');
  const recipesRaw = await recipesRes.json();

  // Decompact + carry the global index so consumers (getRecipeFor) can check
  // it against a hidden-recipe set without doing an O(n) indexOf each time.
  // amount defaults to 0 — a few recipes (mostly old Coin entries) come
  // through with no `a` field; treating them as 0 keeps cost math finite
  // without crashing the UI.
  const recipes = recipesRaw.map((r, i) => ({
    index: i,
    productItemType: r.i,
    productName: r.n,
    productCount: r.c,
    laborCost: r.l,
    craftId: r.k,
    materials: r.m.map(x => ({ itemType: x.i, name: x.n, amount: x.a ?? 0 })),
  }));

  // Load default-hidden craftIds + productIds. Resolved to recipe indices
  // here so the rest of the app can union them with the user's hidden Set
  // cheaply. craftIds match a specific recipe variant; productIds match
  // every recipe producing that item (useful for legacy recipes lacking a
  // craftId, or items where every variant should be hidden). exceptMatchers
  // carves out specific (productId, firstMaterial) tuples to KEEP visible —
  // used to exclude the real crafting recipe when productIds is hiding
  // event/promo variants of the same item.
  let defaultHidden = new Set();
  try {
    const dhRes = await fetch('data/default-hidden.json');
    if (dhRes.ok) {
      const dh = await dhRes.json();
      const wantedCraftIds = new Set((dh.craftIds || []).map(Number));
      const wantedProductIds = new Set((dh.productIds || []).map(String));
      const exceptKey = (pid, mat) => `${pid}|${mat}`;
      const exceptions = new Set();
      for (const e of (dh.exceptMatchers || [])) {
        if (e && e.productId && e.firstMaterial) {
          exceptions.add(exceptKey(String(e.productId), String(e.firstMaterial)));
        }
      }
      const exacts = new Set();
      for (const e of (dh.exactMatchers || [])) {
        if (e && e.productId && e.firstMaterial) {
          exacts.add(exceptKey(String(e.productId), String(e.firstMaterial)));
        }
      }
      for (const r of recipes) {
        if (wantedCraftIds.has(r.craftId)) { defaultHidden.add(r.index); continue; }
        const firstMat = r.materials[0]?.name;
        const matKey = firstMat ? exceptKey(String(r.productItemType), firstMat) : null;
        if (matKey && exacts.has(matKey)) { defaultHidden.add(r.index); continue; }
        if (wantedProductIds.has(String(r.productItemType))) {
          if (matKey && exceptions.has(matKey)) continue;
          defaultHidden.add(r.index);
        }
      }
    }
  } catch (e) {
    console.warn('default-hidden.json missing or invalid:', e);
  }

  // Index by productItemType
  const recipesByProduct = new Map();
  for (const r of recipes) {
    if (!recipesByProduct.has(r.productItemType)) recipesByProduct.set(r.productItemType, []);
    recipesByProduct.get(r.productItemType).push(r);
  }

  // Item names lookup
  const itemNames = new Map();
  for (const r of recipes) {
    itemNames.set(r.productItemType, r.productName);
    for (const m of r.materials) itemNames.set(m.itemType, m.name);
  }

  // Try live prices first; empty object fallback if it fails.
  let prices = {};
  let volumes = {};
  let priceMeta = { state: 'error', msg: 'No price data', lastUpdated: null };
  try {
    const live = await fetchLivePrices();
    prices = live.prices;
    volumes = live.volumes || {};
    priceMeta = {
      state: 'live',
      msg: `7d avg · ${live.lastUpdated || 'just now'}`,
      lastUpdated: live.lastUpdated,
      used7d: live.used7d,
      used30d: live.used30d,
    };
  } catch (e) {
    priceMeta = { state: 'error', msg: 'Sheet fetch failed', lastUpdated: null };
    console.warn('Live price fetch failed:', e);
  }

  return { recipes, prices, volumes, recipesByProduct, itemNames, priceMeta, defaultHidden };
})();
