const pool = require("../db/pool"); function normalizeItemName(itemName) { return String(itemName || "").trim().toLowerCase(); } async function getHouseholdStoreItemRecord(householdId, storeId, itemId) { const result = await pool.query( `WITH latest_list_items AS ( SELECT DISTINCT ON (hl.household_store_item_id) hl.household_store_item_id, hl.custom_image, hl.custom_image_mime_type, hl.modified_on, hl.id FROM household_lists hl WHERE hl.household_id = $1 AND hl.store_id = $2 ORDER BY hl.household_store_item_id, hl.modified_on DESC NULLS LAST, hl.id DESC ) SELECT hsi.id AS item_id, hsi.name AS item_name, ENCODE(COALESCE(hsi.custom_image, lli.custom_image), 'base64') AS item_image, COALESCE(hsi.custom_image_mime_type, lli.custom_image_mime_type) AS image_mime_type, hic.item_type, hic.item_group, hic.zone FROM household_store_items hsi LEFT JOIN latest_list_items lli ON lli.household_store_item_id = hsi.id LEFT JOIN household_item_classifications hic ON hic.household_id = hsi.household_id AND hic.store_id = hsi.store_id AND hic.household_store_item_id = hsi.id WHERE hsi.household_id = $1 AND hsi.store_id = $2 AND hsi.id = $3`, [householdId, storeId, itemId] ); return result.rows[0] || null; } async function findOrCreateHouseholdStoreItem(householdId, storeId, itemName) { const normalizedName = normalizeItemName(itemName); const existing = await pool.query( `SELECT id, name FROM household_store_items WHERE household_id = $1 AND store_id = $2 AND normalized_name = $3`, [householdId, storeId, normalizedName] ); if (existing.rowCount > 0) { return { itemId: existing.rows[0].id, itemName: existing.rows[0].name, }; } const created = await pool.query( `INSERT INTO household_store_items (household_id, store_id, name, normalized_name, updated_at) VALUES ($1, $2, $3, $4, NOW()) RETURNING id, name`, [householdId, storeId, normalizedName, normalizedName] ); return { itemId: created.rows[0].id, itemName: created.rows[0].name, }; } exports.listAvailableItems = async (householdId, storeId, query = "") => { const trimmedQuery = String(query || "").trim(); const values = [householdId, storeId]; let filterClause = ""; if (trimmedQuery) { values.push(`%${trimmedQuery}%`); filterClause = "AND hsi.name ILIKE $3"; } const result = await pool.query( `WITH latest_list_items AS ( SELECT DISTINCT ON (hl.household_store_item_id) hl.household_store_item_id, hl.custom_image, hl.custom_image_mime_type, hl.modified_on, hl.id FROM household_lists hl WHERE hl.household_id = $1 AND hl.store_id = $2 ORDER BY hl.household_store_item_id, hl.modified_on DESC NULLS LAST, hl.id DESC ) SELECT hsi.id AS item_id, hsi.name AS item_name, ENCODE(COALESCE(hsi.custom_image, lli.custom_image), 'base64') AS item_image, COALESCE(hsi.custom_image_mime_type, lli.custom_image_mime_type) AS image_mime_type, hic.item_type, hic.item_group, hic.zone, ( hsi.custom_image IS NOT NULL OR hic.household_store_item_id IS NOT NULL ) AS has_managed_settings FROM household_store_items hsi LEFT JOIN latest_list_items lli ON lli.household_store_item_id = hsi.id LEFT JOIN household_item_classifications hic ON hic.household_id = hsi.household_id AND hic.store_id = hsi.store_id AND hic.household_store_item_id = hsi.id WHERE hsi.household_id = $1 AND hsi.store_id = $2 ${filterClause} ORDER BY hsi.name ASC LIMIT 100`, values ); return result.rows; }; exports.getAvailableItemById = async (householdId, storeId, itemId) => getHouseholdStoreItemRecord(householdId, storeId, itemId); exports.getAvailableItemImageByName = async (householdId, storeId, itemName) => { const normalizedName = normalizeItemName(itemName); const result = await pool.query( `SELECT id AS item_id, name AS item_name, custom_image, custom_image_mime_type FROM household_store_items WHERE household_id = $1 AND store_id = $2 AND normalized_name = $3`, [householdId, storeId, normalizedName] ); return result.rows[0] || null; }; exports.createAvailableItem = async ( householdId, storeId, itemName, imageBuffer = null, mimeType = null ) => { const { itemId } = await findOrCreateHouseholdStoreItem(householdId, storeId, itemName); if (imageBuffer && mimeType) { await pool.query( `UPDATE household_store_items SET custom_image = $1, custom_image_mime_type = $2, updated_at = NOW() WHERE id = $3 AND household_id = $4 AND store_id = $5`, [imageBuffer, mimeType, itemId, householdId, storeId] ); } return getHouseholdStoreItemRecord(householdId, storeId, itemId); }; exports.updateAvailableItem = async (householdId, storeId, itemId, updates = {}) => { const { itemName, imageBuffer, mimeType, removeImage = false, } = updates; const assignments = ["updated_at = NOW()"]; const values = [householdId, storeId, itemId]; let parameterIndex = values.length; if (itemName !== undefined && String(itemName).trim() !== "") { const normalizedName = normalizeItemName(itemName); parameterIndex += 1; assignments.push(`name = $${parameterIndex}`); values.push(normalizedName); parameterIndex += 1; assignments.push(`normalized_name = $${parameterIndex}`); values.push(normalizedName); } if (removeImage) { assignments.push("custom_image = NULL", "custom_image_mime_type = NULL"); } else if (imageBuffer && mimeType) { parameterIndex += 1; assignments.push(`custom_image = $${parameterIndex}`); values.push(imageBuffer); parameterIndex += 1; assignments.push(`custom_image_mime_type = $${parameterIndex}`); values.push(mimeType); } const result = await pool.query( `UPDATE household_store_items SET ${assignments.join(", ")} WHERE household_id = $1 AND store_id = $2 AND id = $3 RETURNING id`, values ); if (result.rowCount === 0) { return null; } return getHouseholdStoreItemRecord(householdId, storeId, result.rows[0].id); }; exports.deleteAvailableItem = async (householdId, storeId, itemId) => { const result = await pool.query( `DELETE FROM household_store_items WHERE household_id = $1 AND store_id = $2 AND id = $3`, [householdId, storeId, itemId] ); return result.rowCount > 0; }; exports.importCurrentListItems = async (householdId, storeId) => { const result = await pool.query( `INSERT INTO household_store_items (household_id, store_id, name, normalized_name, custom_image, custom_image_mime_type, updated_at) SELECT DISTINCT ON (hl.household_store_item_id) hl.household_id, hl.store_id, hsi.name, hsi.normalized_name, hsi.custom_image, hsi.custom_image_mime_type, NOW() FROM household_lists hl JOIN household_store_items hsi ON hsi.id = hl.household_store_item_id WHERE hl.household_id = $1 AND hl.store_id = $2 ON CONFLICT (household_id, store_id, normalized_name) DO NOTHING RETURNING id`, [householdId, storeId] ); return result.rowCount; }; exports.hasAvailableItems = async (householdId, storeId) => { const result = await pool.query( `SELECT 1 FROM household_store_items WHERE household_id = $1 AND store_id = $2 LIMIT 1`, [householdId, storeId] ); return result.rowCount > 0; };