/* === useMeals(householdId) — third per-table hook, same shape as
   useExpenses / useBrainNotes. Owns two collections in one hook:
     - meal_plan_entries  (one row per planned meal slot)
     - shopping_items     (one row per item-per-week that the user
                            has interacted with OR manually added)

   Recipes and pantry stay in the blob and aren't touched here.

   Returns:
     {
       planEntries,                   // [{id, date, mealType, recipeId, memberId, diningOut, note}]
       shoppingItems,                 // [{id, weekKey, itemKey, displayName, source, checked, cleared}]
       loading, error,

       addPlanEntry(entry),
       updatePlanEntry(id, patch),
       deletePlanEntry(id),

       toggleShoppingItem(weekKey, itemKey, displayName, source, nextChecked),
       addShoppingExtra(weekKey, displayName),
       clearWeekChecked(weekKey),     // moves checked → cleared, soft-deletes extras
     }
*/

function planEntryFromRow(row) {
  return {
    id: row.id,
    date: row.date,
    mealType: row.meal_type,
    recipeId: row.recipe_id || null,
    memberId: row.member_id || null,
    diningOut: !!row.dining_out,
    note: row.note || '',
  };
}

function planEntryToRow(entry, householdId) {
  return {
    id: entry.id,
    household_id: householdId,
    date: entry.date,
    meal_type: entry.mealType,
    recipe_id: entry.recipeId || null,
    member_id: entry.memberId || null,
    dining_out: !!entry.diningOut,
    note: entry.note || null,
  };
}

function shoppingItemFromRow(row) {
  return {
    id: row.id,
    weekKey: row.week_key,
    itemKey: row.item_key,
    displayName: row.display_name,
    source: row.source,
    checked: !!row.checked,
    cleared: !!row.cleared,
  };
}

function newPlanEntryId()    { return 'mp' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
function newShoppingItemId() { return 'si' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }

function useMeals(householdId) {
  const [planEntries, setPlanEntries]     = React.useState([]);
  const [shoppingItems, setShoppingItems] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [error, setError]     = React.useState(null);

  const pendingPlanIds = React.useRef(new Set());
  const pendingItemIds = React.useRef(new Set());

  // Initial load
  React.useEffect(() => {
    if (!householdId) return;
    let cancelled = false;
    setLoading(true);
    (async () => {
      const [planRes, itemsRes] = await Promise.all([
        supabaseClient.from('meal_plan_entries')
          .select('*')
          .eq('household_id', householdId)
          .is('deleted_at', null)
          .order('date', { ascending: true }),
        supabaseClient.from('shopping_items')
          .select('*')
          .eq('household_id', householdId)
          .is('deleted_at', null),
      ]);
      if (cancelled) return;
      if (planRes.error)  { setError(planRes.error.message);  setLoading(false); return; }
      if (itemsRes.error) { setError(itemsRes.error.message); setLoading(false); return; }
      setPlanEntries((planRes.data || []).map(planEntryFromRow));
      setShoppingItems((itemsRes.data || []).map(shoppingItemFromRow));
      setError(null);
      setLoading(false);
    })();
    return () => { cancelled = true; };
  }, [householdId]);

  // Realtime subscriptions for both tables.
  React.useEffect(() => {
    if (!householdId) return;
    const channel = supabaseClient
      .channel(`meals:${householdId}`)
      .on('postgres_changes',
          { event: 'INSERT', schema: 'public', table: 'meal_plan_entries', filter: `household_id=eq.${householdId}` },
          (payload) => {
            const row = payload.new;
            if (pendingPlanIds.current.has(row.id)) { pendingPlanIds.current.delete(row.id); return; }
            setPlanEntries(prev => prev.some(e => e.id === row.id) ? prev : [...prev, planEntryFromRow(row)]);
          })
      .on('postgres_changes',
          { event: 'UPDATE', schema: 'public', table: 'meal_plan_entries', filter: `household_id=eq.${householdId}` },
          (payload) => {
            const row = payload.new;
            if (pendingPlanIds.current.has(row.id)) { pendingPlanIds.current.delete(row.id); return; }
            setPlanEntries(prev => {
              if (row.deleted_at) return prev.filter(e => e.id !== row.id);
              return prev.map(e => e.id === row.id ? planEntryFromRow(row) : e);
            });
          })
      .on('postgres_changes',
          { event: 'INSERT', schema: 'public', table: 'shopping_items', filter: `household_id=eq.${householdId}` },
          (payload) => {
            const row = payload.new;
            if (pendingItemIds.current.has(row.id)) { pendingItemIds.current.delete(row.id); return; }
            setShoppingItems(prev => prev.some(i => i.id === row.id) ? prev : [...prev, shoppingItemFromRow(row)]);
          })
      .on('postgres_changes',
          { event: 'UPDATE', schema: 'public', table: 'shopping_items', filter: `household_id=eq.${householdId}` },
          (payload) => {
            const row = payload.new;
            if (pendingItemIds.current.has(row.id)) { pendingItemIds.current.delete(row.id); return; }
            setShoppingItems(prev => {
              if (row.deleted_at) return prev.filter(i => i.id !== row.id);
              return prev.map(i => i.id === row.id ? shoppingItemFromRow(row) : i);
            });
          })
      .subscribe();
    return () => { supabaseClient.removeChannel(channel); };
  }, [householdId]);

  // ----- Plan entry CRUD -----
  const addPlanEntry = React.useCallback(async (entry) => {
    if (!householdId) throw new Error('No household');
    const id = entry.id || newPlanEntryId();
    const full = { ...entry, id };
    pendingPlanIds.current.add(id);
    setPlanEntries(prev => [...prev, full]);
    const { error } = await supabaseClient.from('meal_plan_entries').insert(planEntryToRow(full, householdId));
    if (error) {
      pendingPlanIds.current.delete(id);
      setPlanEntries(prev => prev.filter(e => e.id !== id));
      setError(error.message);
      throw error;
    }
    setError(null);
    return full;
  }, [householdId]);

  const updatePlanEntry = React.useCallback(async (id, patch) => {
    if (!householdId) throw new Error('No household');
    const prev = planEntries.find(e => e.id === id) || null;
    if (prev) {
      pendingPlanIds.current.add(id);
      setPlanEntries(p => p.map(e => e.id === id ? { ...e, ...patch } : e));
    }
    const columnPatch = {};
    if ('date'      in patch) columnPatch.date       = patch.date;
    if ('mealType'  in patch) columnPatch.meal_type  = patch.mealType;
    if ('recipeId'  in patch) columnPatch.recipe_id  = patch.recipeId || null;
    if ('memberId'  in patch) columnPatch.member_id  = patch.memberId || null;
    if ('diningOut' in patch) columnPatch.dining_out = !!patch.diningOut;
    if ('note'      in patch) columnPatch.note       = patch.note || null;
    const { error } = await supabaseClient.from('meal_plan_entries').update(columnPatch).eq('id', id);
    if (error) {
      if (prev) {
        pendingPlanIds.current.delete(id);
        setPlanEntries(p => p.map(e => e.id === id ? prev : e));
      }
      setError(error.message);
      throw error;
    }
    setError(null);
  }, [householdId, planEntries]);

  const deletePlanEntry = React.useCallback(async (id) => {
    if (!householdId) throw new Error('No household');
    const prev = planEntries.find(e => e.id === id) || null;
    if (prev) {
      pendingPlanIds.current.add(id);
      setPlanEntries(p => p.filter(e => e.id !== id));
    }
    const { error } = await supabaseClient.from('meal_plan_entries')
      .update({ deleted_at: new Date().toISOString() })
      .eq('id', id);
    if (error) {
      if (prev) {
        pendingPlanIds.current.delete(id);
        setPlanEntries(p => [...p, prev]);
      }
      setError(error.message);
      throw error;
    }
    setError(null);
  }, [householdId, planEntries]);

  // ----- Shopping item ops -----
  // toggleShoppingItem is UPSERT-shaped: if no row exists for
  // (week, item_key), INSERT one; if a row exists, UPDATE its
  // `checked` flag. The unique partial index on the table
  // backs this. Display name + source are captured on first INSERT
  // and never overwritten — this matters because derived items
  // could change source (if the underlying recipe is removed) but
  // we want the user's interaction history preserved.
  const toggleShoppingItem = React.useCallback(async (weekKey, itemKey, displayName, source, nextChecked) => {
    if (!householdId) throw new Error('No household');
    const existing = shoppingItems.find(i => i.weekKey === weekKey && i.itemKey === itemKey);
    if (existing) {
      pendingItemIds.current.add(existing.id);
      setShoppingItems(prev => prev.map(i =>
        i.id === existing.id ? { ...i, checked: nextChecked } : i
      ));
      const { error } = await supabaseClient.from('shopping_items')
        .update({ checked: nextChecked })
        .eq('id', existing.id);
      if (error) {
        pendingItemIds.current.delete(existing.id);
        setShoppingItems(prev => prev.map(i =>
          i.id === existing.id ? existing : i
        ));
        setError(error.message);
        throw error;
      }
    } else {
      const id = newShoppingItemId();
      const row = {
        id,
        household_id: householdId,
        week_key: weekKey,
        item_key: itemKey,
        display_name: displayName,
        source: source || 'derived',
        checked: nextChecked,
        cleared: false,
      };
      pendingItemIds.current.add(id);
      setShoppingItems(prev => [...prev, shoppingItemFromRow(row)]);
      const { error } = await supabaseClient.from('shopping_items').insert(row);
      if (error) {
        pendingItemIds.current.delete(id);
        setShoppingItems(prev => prev.filter(i => i.id !== id));
        setError(error.message);
        throw error;
      }
    }
    setError(null);
  }, [householdId, shoppingItems]);

  // addShoppingExtra: ensure an 'extra' row exists for this week+key
  // and is not cleared. Used by both ShoppingTab.addExtra and the
  // DashboardFab grocery quick-add.
  const addShoppingExtra = React.useCallback(async (weekKey, displayName) => {
    if (!householdId) throw new Error('No household');
    const name = (displayName || '').trim();
    if (!name) return;
    const itemKey = name.toLowerCase();
    const existing = shoppingItems.find(i => i.weekKey === weekKey && i.itemKey === itemKey);
    if (existing) {
      // Already there — un-clear it if cleared, otherwise no-op.
      if (!existing.cleared && existing.source === 'extra') return;
      pendingItemIds.current.add(existing.id);
      setShoppingItems(prev => prev.map(i =>
        i.id === existing.id ? { ...i, cleared: false, source: 'extra', displayName: name } : i
      ));
      const { error } = await supabaseClient.from('shopping_items')
        .update({ cleared: false, source: 'extra', display_name: name })
        .eq('id', existing.id);
      if (error) {
        pendingItemIds.current.delete(existing.id);
        setShoppingItems(prev => prev.map(i => i.id === existing.id ? existing : i));
        setError(error.message);
        throw error;
      }
    } else {
      const id = newShoppingItemId();
      const row = {
        id,
        household_id: householdId,
        week_key: weekKey,
        item_key: itemKey,
        display_name: name,
        source: 'extra',
        checked: false,
        cleared: false,
      };
      pendingItemIds.current.add(id);
      setShoppingItems(prev => [...prev, shoppingItemFromRow(row)]);
      const { error } = await supabaseClient.from('shopping_items').insert(row);
      if (error) {
        pendingItemIds.current.delete(id);
        setShoppingItems(prev => prev.filter(i => i.id !== id));
        setError(error.message);
        throw error;
      }
    }
    setError(null);
  }, [householdId, shoppingItems]);

  // clearWeekChecked: moves every checked item this week to cleared
  // (and unchecks them); soft-deletes any checked 'extra' rows so
  // they disappear from the wall, matching today's clearChecked
  // semantics. Pantry updates are handled by the caller setState
  // (pantry stays in the blob).
  const clearWeekChecked = React.useCallback(async (weekKey) => {
    if (!householdId) return null;
    const checkedItems = shoppingItems.filter(i => i.weekKey === weekKey && i.checked);
    if (!checkedItems.length) return 0;
    // Optimistic local update.
    setShoppingItems(prev => prev
      .map(i => {
        if (i.weekKey !== weekKey || !i.checked) return i;
        if (i.source === 'extra') return null;  // marked for removal
        return { ...i, checked: false, cleared: true };
      })
      .filter(Boolean)
    );
    const now = new Date().toISOString();
    const extrasIds = checkedItems.filter(i => i.source === 'extra').map(i => i.id);
    const derivedIds = checkedItems.filter(i => i.source !== 'extra').map(i => i.id);
    [...extrasIds, ...derivedIds].forEach(id => pendingItemIds.current.add(id));
    const ops = [];
    if (derivedIds.length) {
      ops.push(supabaseClient.from('shopping_items')
        .update({ checked: false, cleared: true })
        .in('id', derivedIds));
    }
    if (extrasIds.length) {
      ops.push(supabaseClient.from('shopping_items')
        .update({ deleted_at: now })
        .in('id', extrasIds));
    }
    const results = await Promise.all(ops);
    const errs = results.filter(r => r.error);
    if (errs.length) {
      setError(errs.map(e => e.error.message).join('; '));
      throw new Error(errs.map(e => e.error.message).join('; '));
    }
    setError(null);
    return checkedItems.length;
  }, [householdId, shoppingItems]);

  return {
    planEntries, shoppingItems, loading, error,
    addPlanEntry, updatePlanEntry, deletePlanEntry,
    toggleShoppingItem, addShoppingExtra, clearWeekChecked,
  };
}

Object.assign(window, { useMeals });
