/**
 * Tabs use full page navigation (?tab=) from index.php; active state is set server-side.
 * Do not programmatically .click() <a class="tab"> links on load — that re-follows href and reloads in a tight loop.
 */

(function ($) {
  'use strict';

  var apiBase = typeof window.familyHubApiBase === 'string' ? window.familyHubApiBase : '/api';

  /**
   * Avoids JSON.parse on empty bodies (Firefox: "unexpected end of data at line 1 column 1")
   * and surfaces clearer errors when PHP returns HTML or nothing.
   */
  function parseJsonResponseText(res, text) {
    var trimmed = (text || '').trim();
    if (trimmed === '') {
      var emptyErr = new Error(
        !res.ok ? (res.statusText || 'Request failed') : 'Empty response from server.'
      );
      emptyErr.payload = {};
      emptyErr.status = res.status;
      throw emptyErr;
    }
    try {
      return JSON.parse(trimmed);
    } catch (e) {
      var parseErr = new Error('Server did not return JSON. Check server logs or network tab.');
      parseErr.payload = trimmed.slice(0, 500);
      parseErr.status = res.status;
      throw parseErr;
    }
  }

  function postJson(path, body) {
    return fetch(apiBase + path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(body || {})
    }).then(function (res) {
      return res.text().then(function (text) {
        var data = parseJsonResponseText(res, text);
        if (!res.ok) {
          var err = new Error(data.error || res.statusText || 'Request failed');
          err.payload = data;
          err.status = res.status;
          throw err;
        }
        return data;
      });
    });
  }

  function showAlert($el, kind, message) {
    if (!$el || !$el.length) {
      return;
    }
    $el.removeClass('d-none alert-success alert-danger alert-warning');
    $el.addClass('alert-' + kind);
    $el.text(message);
  }

  function escapeHtml(value) {
    return String(value || '')
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  var pendingPersonId = null;
  var pinModal = null;

  function switchPerson(personId, pin) {
    return postJson('/switch_person.php', { personId: personId, pin: pin || '' });
  }

  function bindPersonaSwitcher() {
    $(document).on('click', '.persona-chip', function () {
      var $btn = $(this);
      var id = $btn.data('person-id');
      var needsPin = String($btn.data('needs-pin')) === '1';
      var name = $btn.data('person-name') || 'this profile';

      if (!id) {
        return;
      }

      if (needsPin) {
        pendingPersonId = id;
        $('#hohPinModalPrompt').text('Enter PIN for ' + name + '.');
        $('#hohPinInput').val('');
        $('#hohPinError').addClass('d-none').text('');
        if (!pinModal && typeof bootstrap !== 'undefined') {
          pinModal = new bootstrap.Modal(document.getElementById('hohPinModal'));
        }
        if (pinModal) {
          pinModal.show();
        }
        setTimeout(function () {
          document.getElementById('hohPinInput').focus();
        }, 200);
        return;
      }

      switchPerson(id, '')
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not switch profile');
        });
    });

    $('#hohPinSubmit').on('click', function () {
      var pin = $('#hohPinInput').val() || '';
      if (!pendingPersonId) {
        return;
      }
      $('#hohPinError').addClass('d-none').text('');
      switchPerson(pendingPersonId, pin)
        .then(function () {
          if (pinModal) {
            pinModal.hide();
          }
          pendingPersonId = null;
          window.location.reload();
        })
        .catch(function (err) {
          $('#hohPinError').removeClass('d-none').text(err.message || 'PIN incorrect');
        });
    });
  }

  function bindFirstHeadForm() {
    var $form = $('#firstHeadForm');
    if (!$form.length) {
      return;
    }
    $form.on('submit', function (e) {
      e.preventDefault();
      var payload = {
        name: $('#first_name').val(),
        pin: $('#first_pin').val(),
        favoriteColor: $('#first_favoriteColor').val()
      };
      postJson('/people_create_first.php', payload)
        .then(function () {
          showAlert($('#firstHeadFeedback'), 'success', 'Profile created. Reloading…');
          window.location.reload();
        })
        .catch(function (err) {
          showAlert($('#firstHeadFeedback'), 'danger', err.message || 'Could not create profile');
          $('#firstHeadFeedback').removeClass('d-none');
        });
    });
  }

  function toggleAddPinVisibility() {
    var role = $('#add_role').val();
    var $wrap = $('#add_pin_wrap');
    var $pin = $('#add_pin');
    if (role === 'head_of_household') {
      $wrap.show();
      $pin.prop('required', true);
    } else {
      $wrap.hide();
      $pin.prop('required', false);
      $pin.val('');
    }
  }

  function bindAddPersonForm() {
    var $form = $('#addPersonForm');
    if (!$form.length) {
      return;
    }
    $('#add_role').on('change', toggleAddPinVisibility);
    toggleAddPinVisibility();

    $form.on('submit', function (e) {
      e.preventDefault();
      var role = $('#add_role').val();
      var payload = {
        name: $('#add_name').val(),
        role: role,
        pin: role === 'head_of_household' ? $('#add_pin').val() : '',
        favoriteColor: $('#add_favoriteColor').val()
      };
      postJson('/people_create.php', payload)
        .then(function () {
          showAlert($('#addPersonFeedback'), 'success', 'Person added. Reloading…');
          $('#addPersonFeedback').removeClass('d-none');
          window.location.reload();
        })
        .catch(function (err) {
          showAlert($('#addPersonFeedback'), 'danger', err.message || 'Could not add person');
          $('#addPersonFeedback').removeClass('d-none');
        });
    });
  }

  function toggleEditPersonPinVisibility() {
    var initial = $('#edit_person_initial_role').val() || '';
    var role = $('#edit_role').val() || '';
    var $wrap = $('#edit_pin_wrap');
    var $pin = $('#edit_pin');
    var needPin = role === 'head_of_household' && initial !== 'head_of_household';
    if (needPin) {
      $wrap.show();
      $pin.prop('required', true);
    } else {
      $wrap.hide();
      $pin.prop('required', false);
      $pin.val('');
    }
  }

  function bindEditPersonForm() {
    var $form = $('#editPersonForm');
    if (!$form.length) {
      return;
    }

    var peopleById = {};
    var $payloadEl = $('#settingsPeopleEditPayload');
    if ($payloadEl.length) {
      try {
        var plist = JSON.parse($payloadEl.text());
        if (Array.isArray(plist)) {
          plist.forEach(function (row) {
            if (row && row.id) {
              peopleById[row.id] = row;
            }
          });
        }
      } catch (ignoreErr) {
        peopleById = {};
      }
    }

    var editModal = null;
    if (typeof bootstrap !== 'undefined') {
      var modalEl = document.getElementById('editPersonModal');
      if (modalEl) {
        editModal = new bootstrap.Modal(modalEl, { backdrop: false });
      }
    }

    $('#edit_role').on('change', toggleEditPersonPinVisibility);

    $(document).on('click', '.btn-edit-person', function () {
      var id = $(this).data('person-id');
      if (!id || !peopleById[id]) {
        window.alert('Could not load this person for editing.');
        return;
      }
      var p = peopleById[id];
      $('#edit_person_id').val(p.id);
      $('#edit_person_initial_role').val(p.role || '');
      $('#edit_name').val(p.name || '');
      $('#edit_role').val(p.role || 'child');
      var fc = String(p.favoriteColor || '').trim();
      if (!/^#[0-9A-Fa-f]{6}$/.test(fc)) {
        fc = '#4a90e2';
      }
      $('#edit_favoriteColor').val(fc);
      $('#edit_birthday').val(String(p.birthday || '').trim());
      $('#edit_description').val(p.description || '');
      $('#edit_pin').val('');
      $('#editPersonFeedback').addClass('d-none').removeClass('alert-success alert-danger alert-warning').text('');
      toggleEditPersonPinVisibility();
      if (editModal) {
        editModal.show();
      }
    });

    $form.on('submit', function (e) {
      e.preventDefault();
      var id = ($('#edit_person_id').val() || '').trim();
      var initial = $('#edit_person_initial_role').val() || '';
      var role = $('#edit_role').val() || '';
      var name = ($('#edit_name').val() || '').trim();
      if (!id || name === '') {
        return;
      }
      var needPin = role === 'head_of_household' && initial !== 'head_of_household';
      var pin = needPin ? ($('#edit_pin').val() || '') : '';
      if (needPin && pin.length < 4) {
        showAlert($('#editPersonFeedback'), 'danger', 'PIN must be at least 4 characters for a new Head of household.');
        $('#editPersonFeedback').removeClass('d-none');
        return;
      }
      if (initial === 'head_of_household' && role !== 'head_of_household') {
        if (!window.confirm('Demote this Head of household? Their PIN will be cleared until they are promoted again.')) {
          return;
        }
      }
      var payload = {
        id: id,
        name: name,
        role: role,
        favoriteColor: $('#edit_favoriteColor').val() || '#4a90e2',
        birthday: ($('#edit_birthday').val() || '').trim(),
        description: ($('#edit_description').val() || '').trim()
      };
      if (needPin) {
        payload.pin = pin;
      }
      postJson('/people_update.php', payload)
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          showAlert($('#editPersonFeedback'), 'danger', err.message || 'Could not save changes');
          $('#editPersonFeedback').removeClass('d-none');
        });
    });
  }

  function bindFamilySettingsForm() {
    var $form = $('#familySettingsForm');
    if (!$form.length) {
      return;
    }
    $form.on('submit', function (e) {
      e.preventDefault();
      var payload = {
        currency_symbol: $('#currency_symbol').val(),
        currency_name: $('#currency_name').val(),
        currency_permanence: $('#currency_permanence').val(),
        banking_enabled: $('#banking_enabled').is(':checked'),
        banking_auto_split_enabled: $('#banking_auto_split_enabled').is(':checked'),
        banking_auto_split_savings_pct: Number($('#banking_auto_split_savings_pct').val()) || 0,
        banking_auto_split_charity_pct: Number($('#banking_auto_split_charity_pct').val()) || 0,
        banking_savings_monthly_interest_rate: Number($('#banking_savings_monthly_interest_rate').val()) || 0,
        banking_roundup_destination: $('#banking_roundup_destination').val() || 'off',
        week_starts_on: parseInt($('#week_starts_on').val(), 10),
        nfc_base_url: ($('#nfc_base_url').val() || '').trim(),
        nfc_show_confirmation: $('#nfc_show_confirmation').is(':checked'),
        nfc_scan_cooldown_seconds: parseInt($('#nfc_scan_cooldown_seconds').val(), 10) || 0
      };
      postJson('/family_settings_save.php', payload)
        .then(function () {
          showAlert($('#familySettingsFeedback'), 'success', 'Saved.');
          $('#familySettingsFeedback').removeClass('d-none');
        })
        .catch(function (err) {
          showAlert($('#familySettingsFeedback'), 'danger', err.message || 'Could not save');
          $('#familySettingsFeedback').removeClass('d-none');
        });
    });

    $('#btnDisableAllChoreNfc').on('click', function () {
      if (!window.confirm('Disable NFC links for all chores?')) {
        return;
      }
      postJson('/family_nfc_admin.php', { action: 'disable_all_chore_nfc' })
        .then(function () {
          showAlert($('#familySettingsFeedback'), 'warning', 'All chore NFC links disabled.');
          $('#familySettingsFeedback').removeClass('d-none');
        })
        .catch(function (err) {
          showAlert($('#familySettingsFeedback'), 'danger', err.message || 'Could not disable NFC links');
          $('#familySettingsFeedback').removeClass('d-none');
        });
    });

    $('#btnRotateAllPersonNfcTokens').on('click', function () {
      if (!window.confirm('Rotate NFC submitter tokens for all people? Existing person-token links will stop working.')) {
        return;
      }
      postJson('/family_nfc_admin.php', { action: 'rotate_all_person_tokens' })
        .then(function () {
          showAlert($('#familySettingsFeedback'), 'success', 'All person NFC tokens rotated.');
          $('#familySettingsFeedback').removeClass('d-none');
        })
        .catch(function (err) {
          showAlert($('#familySettingsFeedback'), 'danger', err.message || 'Could not rotate tokens');
          $('#familySettingsFeedback').removeClass('d-none');
        });
    });
  }

  function collectBillDaysFromDom() {
    var out = [];
    $('#billDaysRows .bill-day-row').each(function () {
      var $row = $(this);
      var day = parseInt($row.find('.bill-day-dom').val(), 10);
      var title = ($row.find('.bill-day-title').val() || '').trim();
      if (title !== '' && day >= 1 && day <= 31) {
        out.push({ dayOfMonth: day, title: title });
      }
    });
    return out;
  }

  function bindCalendarSettingsForm() {
    var $form = $('#calendarSettingsForm');
    if (!$form.length) {
      return;
    }
    $('#btnAddBillDayRow').on('click', function () {
      var $first = $('#billDaysRows .bill-day-row').first();
      if (!$first.length) {
        return;
      }
      var $clone = $first.clone();
      $clone.find('.bill-day-dom').val('1');
      $clone.find('.bill-day-title').val('');
      $('#billDaysRows').append($clone);
    });
    $form.on('submit', function (e) {
      e.preventDefault();
      var payload = {
        timezone: $('#family_timezone').val(),
        calendar_two_way_google: $('#calendar_two_way_google').is(':checked'),
        calendar_bill_days: collectBillDaysFromDom()
      };
      postJson('/family_settings_save.php', payload)
        .then(function () {
          showAlert($('#calendarSettingsFeedback'), 'success', 'Saved.');
          $('#calendarSettingsFeedback').removeClass('d-none');
        })
        .catch(function (err) {
          showAlert($('#calendarSettingsFeedback'), 'danger', err.message || 'Could not save');
          $('#calendarSettingsFeedback').removeClass('d-none');
        });
    });
  }

  function choreListPayloadFromForm() {
    var type = $('#chore_list_type').val() || 'checkbox';
    var raw = $('#chore_list_items').val() || '';
    var lines = raw.split(/\r?\n/).map(function (l) { return l.trim(); }).filter(Boolean);
    if (lines.length === 0) {
      return [];
    }
    return [{ type: type, items: lines }];
  }

  function toggleChoreRecurrence() {
    var sched = $('#chore_schedule').val();
    if (sched === 'recurring') {
      $('#chore_recurrence_wrap').show();
    } else {
      $('#chore_recurrence_wrap').hide();
    }
  }

  function bindChoresPage() {
    var $choresTab = $('#chores');
    var $form = $('#choreForm');

    // Add/edit form exists only for HoH (or when editing). Cards and pending list still need handlers for everyone.
    if ($choresTab.length) {
    $(document).on('click', '.btn-chore-submit', function () {
      var id = $(this).data('id');
      if (!id || !window.confirm('Submit this chore as done? A Head of household will approve it before the reward is paid.')) {
        return;
      }
      postJson('/chore_submit.php', { id: id, note: '' })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not submit');
        });
    });

    $(document).on('click', '.btn-chore-approve', function () {
      var id = $(this).data('id');
      if (!id || !window.confirm('Approve and pay the reward?')) {
        return;
      }
      postJson('/chore_review.php', { id: id, decision: 'approve' })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not approve');
        });
    });

    $(document).on('click', '.btn-chore-reject', function () {
      var id = $(this).data('id');
      if (!id || !window.confirm('Reject this submission?')) {
        return;
      }
      postJson('/chore_review.php', { id: id, decision: 'reject' })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not reject');
        });
    });

    $(document).on('click', '.btn-chore-nfc-generate', function () {
      var id = $(this).data('id');
      if (!id) {
        return;
      }
      postJson('/chore_nfc_rotate.php', { id: id })
        .then(function (data) {
          var links = Array.isArray(data.links) ? data.links : [];
          var html = '<div class="small text-muted mb-1"><strong>NFC links (copy per person)</strong></div>';
          if (!links.length) {
            html += '<div class="small text-muted">No links were returned.</div>';
          } else {
            links.forEach(function (row) {
              html += '<label class="form-label small mb-1">' + escapeHtml(row.person_name || row.person_id || 'Person') + '</label>';
              html += '<div class="input-group input-group-sm mb-2">';
              html += '<input type="text" class="form-control chore-nfc-link-input" readonly value="' + escapeHtml(row.url || '') + '">';
              html += '<button class="btn btn-outline-secondary btn-copy-nfc-link" type="button">Copy</button>';
              html += '</div>';
            });
          }
          var $links = $('#chore_nfc_links_' + id);
          $links.html(html).removeClass('d-none');
          showAlert($('#chore_nfc_feedback_' + id), 'success', 'Generated new NFC links.');
          $('#chore_nfc_feedback_' + id).removeClass('d-none');
        })
        .catch(function (err) {
          showAlert($('#chore_nfc_feedback_' + id), 'danger', err.message || 'Could not generate links');
          $('#chore_nfc_feedback_' + id).removeClass('d-none');
        });
    });

    $(document).on('click', '.btn-copy-nfc-link', function () {
      var $input = $(this).closest('.input-group').find('.chore-nfc-link-input');
      var val = $input.val() || '';
      if (!val) {
        return;
      }
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(String(val));
      } else {
        $input.trigger('focus').trigger('select');
        document.execCommand('copy');
      }
    });

    $(document).on('click', '.btn-chore-nfc-toggle', function () {
      var id = $(this).data('id');
      var enable = String($(this).data('enable')) === '1';
      if (!id) {
        return;
      }
      postJson('/chore_nfc_toggle.php', { id: id, enabled: enable })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          showAlert($('#chore_nfc_feedback_' + id), 'danger', err.message || 'Could not update NFC status');
          $('#chore_nfc_feedback_' + id).removeClass('d-none');
        });
    });
    }

    if (!$form.length) {
      return;
    }

    $('#chore_schedule').on('change', toggleChoreRecurrence);
    toggleChoreRecurrence();

    $form.on('submit', function (e) {
      e.preventDefault();
      var assignees = [];
      $('.chore-assignee:checked').each(function () {
        assignees.push($(this).val());
      });
      var due = $('#chore_due_date').val() || '';
      var payload = {
        id: ($('#chore_id').val() || '').trim(),
        title: $('#chore_title').val(),
        description: $('#chore_description').val(),
        image: $('#chore_image').val(),
        value: Number($('#chore_value').val()) || 0,
        due_date: due,
        schedule: $('#chore_schedule').val(),
        recurrence_days: parseInt($('#chore_recurrence_days').val(), 10) || 7,
        assignee_ids: assignees,
        anyone_can_complete: $('#chore_anyone_can_complete').is(':checked'),
        lists: choreListPayloadFromForm()
      };
      postJson('/chore_save.php', payload)
        .then(function () {
          window.location.href = window.location.pathname + '?tab=chores';
        })
        .catch(function (err) {
          var $fb = $('#choreFormFeedback');
          showAlert($fb, 'danger', err.message || 'Could not save chore');
          $fb.removeClass('d-none');
        });
    });

    $('#choreDeleteBtn').on('click', function () {
      var id = $(this).data('id');
      if (!id || !window.confirm('Delete this chore?')) {
        return;
      }
      postJson('/chore_delete.php', { id: id })
        .then(function () {
          window.location.href = window.location.pathname + '?tab=chores';
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not delete');
        });
    });
  }

  function groceryReloadKeepParams() {
    var $tab = $('#groceries');
    var store = $tab.data('store-id') || $('#grocery_store_id').val();
    var params = new URLSearchParams(window.location.search);
    var filter = params.get('filter') || 'active';
    var q = '?tab=groceries&store=' + encodeURIComponent(store || '') + '&filter=' + encodeURIComponent(filter);
    window.location.href = window.location.pathname + q;
  }

  function bindStoreManagement() {
    $('#storeAddForm').on('submit', function (e) {
      e.preventDefault();
      var name = ($('#new_store_name').val() || '').trim();
      if (!name) {
        return;
      }
      postJson('/store_create.php', { name: name })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          var $fb = $('#storeAddFeedback');
          showAlert($fb, 'danger', err.message || 'Could not add store');
          $fb.removeClass('d-none');
        });
    });

    $(document).on('click', '.btn-store-delete', function () {
      var id = $(this).data('id');
      if (!id || !window.confirm('Delete this store? Lists must be empty.')) {
        return;
      }
      postJson('/store_delete.php', { id: id })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not delete');
        });
    });

    $(document).on('click', '.btn-store-rename', function () {
      var id = $(this).data('id');
      var current = $(this).data('name') || '';
      var name = window.prompt('Store name', current);
      if (name === null) {
        return;
      }
      name = String(name).trim();
      if (!name) {
        return;
      }
      postJson('/store_update.php', { id: id, name: name })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not rename');
        });
    });
  }

  function bindGroceryPage() {
    var $tab = $('#groceries');
    if (!$tab.length) {
      return;
    }

    $('#grocery_catalog_pick').on('change', function () {
      var v = $(this).val();
      if (!v) {
        return;
      }
      var $o = $(this).find('option:selected');
      $('#grocery_name').val($o.data('name') || '');
      $('#grocery_description').val($o.data('description') || '');
      $('#grocery_size').val($o.data('size') || '');
      $('#grocery_image').val($o.data('image') || '');
    });

    $('#groceryAddForm').on('submit', function (e) {
      e.preventDefault();
      var storeId = ($('#grocery_store_id').val() || $tab.data('store-id') || '').trim();
      var payload = {
        storeId: storeId,
        catalogPickId: $('#grocery_catalog_pick').val() || '',
        name: $('#grocery_name').val(),
        description: $('#grocery_description').val(),
        size: $('#grocery_size').val(),
        quantity: $('#grocery_quantity').val(),
        price: $('#grocery_price').val(),
        image: $('#grocery_image').val(),
        recurringIntervalDays: parseInt($('#grocery_recurring').val(), 10) || 0,
        source: 'manual'
      };
      postJson('/grocery_item_create.php', payload)
        .then(function () {
          groceryReloadKeepParams();
        })
        .catch(function (err) {
          var $fb = $('#groceryFormFeedback');
          showAlert($fb, 'danger', err.message || 'Could not add item');
          $fb.removeClass('d-none');
        });
    });

    $(document).on('click', '.btn-grocery-purchase', function () {
      var storeId = $tab.data('store-id');
      var itemId = $(this).data('item-id');
      var purchased = String($(this).data('purchased')) === '1';
      postJson('/grocery_item_purchase.php', {
        storeId: storeId,
        itemId: itemId,
        purchased: purchased
      })
        .then(function () {
          groceryReloadKeepParams();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not update');
        });
    });

    $(document).on('click', '.btn-grocery-delete', function () {
      var storeId = $tab.data('store-id');
      var itemId = $(this).data('item-id');
      if (!itemId || !window.confirm('Remove this item?')) {
        return;
      }
      postJson('/grocery_item_delete.php', { storeId: storeId, itemId: itemId })
        .then(function () {
          groceryReloadKeepParams();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not remove');
        });
    });

    $(document).on('click', '.btn-grocery-approve', function () {
      var storeId = $tab.data('store-id');
      var itemId = $(this).data('item-id');
      if (!itemId) {
        return;
      }
      postJson('/grocery_item_review.php', { storeId: storeId, itemId: itemId })
        .then(function () {
          groceryReloadKeepParams();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not approve');
        });
    });
  }

  function mealListPayloadFromForm() {
    var type = $('#meal_list_type').val() || 'checkbox';
    var raw = $('#meal_list_items').val() || '';
    var lines = raw.split(/\r?\n/).map(function (l) { return l.trim(); }).filter(Boolean);
    if (lines.length === 0) {
      return [];
    }
    return [{ type: type, items: lines }];
  }

  function mealGroceryRowsPayload() {
    var out = [];
    $('#mealGroceryRows .meal-grocery-row').each(function () {
      var $r = $(this);
      var name = ($r.find('.g-name').val() || '').trim();
      if (!name) {
        return;
      }
      out.push({
        name: name,
        storeId: ($r.find('.g-store').val() || '').trim(),
        quantity: ($r.find('.g-qty').val() || '1').trim() || '1',
        size: ($r.find('.g-size').val() || '').trim(),
        description: '',
        price: '',
        image: ''
      });
    });
    return out;
  }

  function readMealsLibrary() {
    var el = document.getElementById('mealsLibraryJson');
    if (!el) {
      return [];
    }
    var raw = (el.textContent || '').trim();
    if (raw === '') {
      return [];
    }
    try {
      return JSON.parse(raw);
    } catch (e) {
      return [];
    }
  }

  function hideMealSlotModal() {
    var el = document.getElementById('mealSlotModal');
    if (el && typeof bootstrap !== 'undefined') {
      var inst = bootstrap.Modal.getInstance(el);
      if (inst) {
        inst.hide();
      }
    }
  }

  function bindMealsPage() {
    var $tab = $('#meals');
    if (!$tab.length) {
      return;
    }

    var weekStart = ($tab.attr('data-week-start') || '').trim();
    var mealImportParsed = null;

    $(document).on('click', '.btn-meal-shopping-to-grocery', function () {
      var $btn = $(this);
      var mealId = $btn.data('meal-id');
      var itemIndex = parseInt($btn.attr('data-item-index'), 10);
      var label = ($btn.attr('data-item-name') || $btn.data('itemName') || '').trim() || 'Item';
      var $li = $btn.closest('li');
      var storeId = $li.find('.meal-shopping-store').val() || '';
      var $fb = $('#mealShoppingGroceryFeedback');
      if (!mealId || itemIndex < 0 || isNaN(itemIndex)) {
        return;
      }
      postJson('/meal_shopping_item_to_grocery.php', { mealId: mealId, itemIndex: itemIndex, storeId: storeId })
        .then(function () {
          if ($fb.length) {
            showAlert($fb, 'success', 'Added “' + label + '” to the grocery list (pending review).');
            $fb.removeClass('d-none');
            var el = $fb.get(0);
            if (el && typeof el.scrollIntoView === 'function') {
              el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
          }
        })
        .catch(function (err) {
          if ($fb.length) {
            showAlert($fb, 'danger', (err.message || 'Could not add') + ' — “' + label + '”');
            $fb.removeClass('d-none');
          }
        });
    });

    $(document).on('click', '.btn-ingredient-to-grocery', function () {
      var $btn = $(this);
      var mealId = $btn.data('meal-id');
      var ing = $btn.attr('data-ingredient') || $btn.data('ingredient') || '';
      var label = String(ing).trim() || 'Item';
      var $li = $btn.closest('li');
      var storeId = $li.find('.ingredient-store').val() || '';
      var $fb = $('#mealPantryGroceryFeedback');
      postJson('/meal_ingredient_to_grocery.php', { mealId: mealId, ingredient: ing, storeId: storeId })
        .then(function () {
          if ($fb.length) {
            showAlert($fb, 'success', 'Added “' + label + '” to the grocery list (pending review).');
            $fb.removeClass('d-none');
            var el = $fb.get(0);
            if (el && typeof el.scrollIntoView === 'function') {
              el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
          }
        })
        .catch(function (err) {
          if ($fb.length) {
            showAlert($fb, 'danger', (err.message || 'Could not add') + ' — “' + label + '”');
            $fb.removeClass('d-none');
          }
        });
    });

    $('#btnMealWeekApply').on('click', function () {
      var ws = ($('#meal_week_start').val() || '').trim();
      if (!ws) {
        return;
      }
      if (!window.confirm('Change the planning week? This clears the grid for the new week.')) {
        return;
      }
      postJson('/meal_plan_set_week.php', { weekStart: ws })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          showAlert($('#mealWeekFeedback'), 'danger', err.message || 'Could not set week');
          $('#mealWeekFeedback').removeClass('d-none');
        });
    });

    var slotModalEl = document.getElementById('mealSlotModal');
    var slotModal = null;
    if (slotModalEl && typeof bootstrap !== 'undefined') {
      slotModal = new bootstrap.Modal(slotModalEl, { backdrop: false });
    }

    function renderSlotPickerList() {
      var lib = readMealsLibrary();
      var filter = ($('#slotPickerFilter').val() || '').toLowerCase();
      var $ul = $('#slotPickerList');
      $ul.empty();
      lib.forEach(function (m) {
        if (filter) {
          var tags = (m.tags || []).map(function (t) { return String(t).toLowerCase(); });
          if (tags.indexOf(filter) === -1) {
            return;
          }
        }
        var $li = $('<li class="list-group-item list-group-item-action" role="button"></li>')
          .text(m.title || m.id)
          .attr('data-meal-id', m.id);
        $ul.append($li);
      });
      if ($ul.children().length === 0) {
        $ul.append('<li class="list-group-item text-muted">No meals match this filter.</li>');
      }
    }

    $(document).on('click', '.btn-open-slot-picker', function () {
      var day = $(this).data('day');
      var mealType = $(this).data('meal-type');
      $('#slotPickerDay').val(String(day));
      $('#slotPickerType').val(String(mealType));
      if (mealType) {
        $('#slotPickerFilter').val(String(mealType));
      }
      renderSlotPickerList();
      if (slotModal) {
        slotModal.show();
      }
    });

    $('#slotPickerFilter').on('change', renderSlotPickerList);

    $(document).on('click', '#slotPickerList li[data-meal-id]', function () {
      var mealId = $(this).attr('data-meal-id');
      if (!mealId || !weekStart) {
        return;
      }
      var day = parseInt($('#slotPickerDay').val(), 10);
      var mealType = $('#slotPickerType').val();
      var pushGrocery = $('#slotPickerPushGrocery').is(':checked');
      postJson('/meal_plan_set_slot.php', {
        weekStart: weekStart,
        day: day,
        mealType: mealType,
        mealId: String(mealId),
        pushGrocery: pushGrocery
      })
        .then(function () {
          hideMealSlotModal();
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not assign meal');
        });
    });

    $('#slotPickerClear').on('click', function () {
      if (!weekStart) {
        return;
      }
      var day = parseInt($('#slotPickerDay').val(), 10);
      var mealType = $('#slotPickerType').val();
      postJson('/meal_plan_set_slot.php', {
        weekStart: weekStart,
        day: day,
        mealType: mealType,
        mealId: '',
        pushGrocery: false
      })
        .then(function () {
          hideMealSlotModal();
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not clear slot');
        });
    });

    $('#mealSaveForm').on('submit', function (e) {
      e.preventDefault();
      var tags = [];
      $('.meal-tag-cb:checked').each(function () {
        tags.push($(this).val());
      });
      var ingRaw = $('#meal_ingredients').val() || '';
      var ingredients = ingRaw.split(/\r?\n/).map(function (l) { return l.trim(); }).filter(Boolean);
      var payload = {
        id: ($('#meal_id').val() || '').trim(),
        title: ($('#meal_title').val() || '').trim(),
        image: $('#meal_image').val(),
        description: $('#meal_description').val(),
        directions: $('#meal_directions').val(),
        lists: mealListPayloadFromForm(),
        tags: tags,
        ingredients: ingredients,
        items: mealGroceryRowsPayload()
      };
      postJson('/meal_save.php', payload)
        .then(function () {
          window.location.href = window.location.pathname + '?tab=meals';
        })
        .catch(function (err) {
          showAlert($('#mealSaveFeedback'), 'danger', err.message || 'Could not save');
          $('#mealSaveFeedback').removeClass('d-none');
        });
    });

    $('#btnAddGroceryRow').on('click', function () {
      var $first = $('#mealGroceryRows .meal-grocery-row').first();
      if (!$first.length) {
        return;
      }
      var $clone = $first.clone();
      $clone.find('input').val('');
      $clone.find('.g-qty').val('1');
      $clone.find('select').prop('selectedIndex', 0);
      $('#mealGroceryRows').append($clone);
    });

    $(document).on('click', '.btn-meal-delete', function () {
      var id = $(this).data('id');
      if (!id || !window.confirm('Delete this meal from the library?')) {
        return;
      }
      postJson('/meal_delete.php', { id: id })
        .then(function () {
          window.location.href = window.location.pathname + '?tab=meals';
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not delete');
        });
    });

    $(document).on('click', '.btn-meal-export-json', function () {
      var mealId = $(this).data('meal-id');
      if (!mealId) {
        return;
      }
      var safeId = String(mealId);
      fetch(apiBase + '/meal_export.php?mealId=' + encodeURIComponent(safeId), { credentials: 'same-origin' })
        .then(function (res) {
          return res.text().then(function (text) {
            var data = parseJsonResponseText(res, text);
            return { res: res, data: data };
          });
        })
        .then(function (r) {
          if (!r.res.ok || !r.data || !r.data.success) {
            var msg = (r.data && r.data.error) ? r.data.error : 'Export failed';
            throw new Error(msg);
          }
          var pkg = {
            version: r.data.version || '1',
            meals: r.data.meals,
            meal_plans: r.data.meal_plans
          };
          var json = JSON.stringify(pkg, null, 2);
          var blob = new Blob([json], { type: 'application/json' });
          var url = URL.createObjectURL(blob);
          var a = document.createElement('a');
          a.href = url;
          var ymd = new Date().toISOString().slice(0, 10);
          var fileStem = safeId.replace(/[^a-zA-Z0-9_-]+/g, '_').slice(0, 80);
          a.download = 'meal_export_' + fileStem + '_' + ymd + '.json';
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
          URL.revokeObjectURL(url);
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not export recipe');
        });
    });

    $('#mealImportFile').on('change', function () {
      mealImportParsed = null;
      var f = this.files && this.files[0];
      var $prev = $('#mealImportPreview');
      var $fb = $('#mealImportFeedback');
      if ($fb.length) {
        $fb.addClass('d-none').removeClass('alert-success alert-danger alert-warning');
      }
      $('#btnMealImportSubmit').prop('disabled', true);
      if (!$prev.length) {
        return;
      }
      if (!f) {
        $prev.text('—');
        return;
      }
      var reader = new FileReader();
      reader.onload = function () {
        try {
          var raw = String(reader.result || '').trim();
          if (raw === '') {
            throw new Error('File is empty.');
          }
          mealImportParsed = JSON.parse(raw);
          $prev.text(JSON.stringify(mealImportParsed, null, 2));
          $('#btnMealImportSubmit').prop('disabled', false);
          if ($fb.length) {
            $fb.addClass('d-none');
          }
        } catch (e) {
          mealImportParsed = null;
          $prev.text('Invalid JSON: ' + (e.message || String(e)));
          if ($fb.length) {
            showAlert($fb, 'danger', 'Could not parse JSON from this file.');
            $fb.removeClass('d-none');
          }
          $('#btnMealImportSubmit').prop('disabled', true);
        }
      };
      reader.readAsText(f);
    });

    $('#btnMealImportSubmit').on('click', function () {
      if (!mealImportParsed) {
        return;
      }
      var $fb = $('#mealImportFeedback');
      var $btn = $(this);
      $btn.prop('disabled', true);
      postJson('/meal_import.php', mealImportParsed)
        .then(function (data) {
          var n = typeof data.mealsUpserted === 'number' ? data.mealsUpserted : 0;
          var s = typeof data.slotsApplied === 'number' ? data.slotsApplied : 0;
          if ($fb.length) {
            showAlert($fb, 'success', 'Imported ' + n + ' meal(s), ' + s + ' slot(s). Reloading…');
            $fb.removeClass('d-none');
          }
          window.setTimeout(function () {
            window.location.href = window.location.pathname + '?tab=meals';
          }, 600);
        })
        .catch(function (err) {
          if ($fb.length) {
            showAlert($fb, 'danger', err.message || 'Import failed');
            $fb.removeClass('d-none');
          }
          $btn.prop('disabled', false);
        });
    });

    $('#mealImportModal').on('hidden.bs.modal', function () {
      mealImportParsed = null;
      $('#mealImportFile').val('');
      var $prev = $('#mealImportPreview');
      if ($prev.length) {
        $prev.text('—');
      }
      $('#mealImportFeedback').addClass('d-none');
      $('#btnMealImportSubmit').prop('disabled', true);
    });
  }

  function bindCurrencyPage() {
    var hasLegacyExpense = $('#expenseForm').length > 0;
    var hasBanking = $('#bankTransferForm').length > 0
      || $('#bankAdjustForm').length > 0
      || $('#charityOutflowForm').length > 0;
    if (!hasLegacyExpense && !hasBanking) {
      return;
    }
    if (hasLegacyExpense) {
      $('#expenseForm').on('submit', function (e) {
        e.preventDefault();
        var payload = {
          title: $('#expense_title').val(),
          description: $('#expense_description').val(),
          date: $('#expense_date').val(),
          value: Number($('#expense_value').val()),
          assignee_id: $('#expense_assignee').val()
        };
        postJson('/expense_create.php', payload)
          .then(function () {
            var $fb = $('#expenseFormFeedback');
            showAlert($fb, 'success', 'Expense recorded. Reloading…');
            $fb.removeClass('d-none');
            window.location.reload();
          })
          .catch(function (err) {
            var $fb = $('#expenseFormFeedback');
            showAlert($fb, 'danger', err.message || 'Could not record expense');
            $fb.removeClass('d-none');
          });
      });
    }

    $('#bankTransferForm').on('submit', function (e) {
      e.preventDefault();
      var from = $('#transfer_from_account').val();
      var to = $('#transfer_to_account').val();
      if (to === 'charity' && !window.confirm('Transfers into charity cannot be moved back out. Continue?')) {
        return;
      }
      postJson('/bank_transfer.php', {
        from_account: from,
        to_account: to,
        amount: Number($('#transfer_amount').val()) || 0,
        category: $('#transfer_category').val(),
        note: $('#transfer_note').val()
      })
        .then(function () {
          var $fb = $('#bankTransferFeedback');
          showAlert($fb, 'success', 'Transfer saved. Reloading…');
          $fb.removeClass('d-none');
          window.location.reload();
        })
        .catch(function (err) {
          var $fb = $('#bankTransferFeedback');
          showAlert($fb, 'danger', err.message || 'Could not transfer');
          $fb.removeClass('d-none');
        });
    });

    $('#bankAdjustForm').on('submit', function (e) {
      e.preventDefault();
      postJson('/bank_adjust_checking.php', {
        person_id: $('#bank_adjust_person').val(),
        type: $('#bank_adjust_type').val(),
        amount: Number($('#bank_adjust_amount').val()) || 0,
        category: $('#bank_adjust_category').val(),
        note: $('#bank_adjust_note').val()
      })
        .then(function () {
          var $fb = $('#bankAdjustFeedback');
          showAlert($fb, 'success', 'Checking transaction saved. Reloading…');
          $fb.removeClass('d-none');
          window.location.reload();
        })
        .catch(function (err) {
          var $fb = $('#bankAdjustFeedback');
          showAlert($fb, 'danger', err.message || 'Could not save transaction');
          $fb.removeClass('d-none');
        });
    });

    $('#charityOutflowForm').on('submit', function (e) {
      e.preventDefault();
      postJson('/bank_charity_outflow.php', {
        person_id: $('#charity_outflow_person').val(),
        amount: Number($('#charity_outflow_amount').val()) || 0,
        category: $('#charity_outflow_category').val(),
        note: $('#charity_outflow_note').val()
      })
        .then(function () {
          var $fb = $('#charityOutflowFeedback');
          showAlert($fb, 'success', 'Charity outflow logged. Reloading…');
          $fb.removeClass('d-none');
          window.location.reload();
        })
        .catch(function (err) {
          var $fb = $('#charityOutflowFeedback');
          showAlert($fb, 'danger', err.message || 'Could not log outflow');
          $fb.removeClass('d-none');
        });
    });

    $('#donationGoalForm').on('submit', function (e) {
      e.preventDefault();
      postJson('/bank_set_donation_goal.php', {
        person_id: $('#donation_goal_person').val(),
        goal_monthly: Number($('#donation_goal_monthly').val()) || 0
      })
        .then(function () {
          var $fb = $('#donationGoalFeedback');
          showAlert($fb, 'success', 'Donation goal saved. Reloading…');
          $fb.removeClass('d-none');
          window.location.reload();
        })
        .catch(function (err) {
          var $fb = $('#donationGoalFeedback');
          showAlert($fb, 'danger', err.message || 'Could not save goal');
          $fb.removeClass('d-none');
        });
    });

    $(document).on('click', '.btn-bank-reverse', function () {
      var txid = $(this).data('id');
      if (!txid) {
        return;
      }
      if (!window.confirm('Create a reversal entry for this transaction? This keeps audit history intact.')) {
        return;
      }
      postJson('/bank_reverse_transaction.php', { transaction_id: txid, note: 'Reversed by HoH' })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not reverse transaction');
        });
    });

    var periodDataEl = document.getElementById('currencyLeaderboardPeriodData');
    var periodBodyEl = document.getElementById('currencyLeaderboardPeriodBody');
    var periodTabsEl = document.getElementById('currencyLeaderboardPeriodTabs');
    if (periodDataEl && periodBodyEl && periodTabsEl) {
      var periodData = {};
      try {
        periodData = JSON.parse(periodDataEl.textContent || '{}');
      } catch (e) {
        periodData = {};
      }

      function renderLeaderboardPeriod(periodKey) {
        var rows = Array.isArray(periodData[periodKey]) ? periodData[periodKey] : [];
        var sym = periodBodyEl.getAttribute('data-currency-symbol') || '';
        var html = '';
        rows.forEach(function (row, idx) {
          var total = Number(row.period_total || 0);
          var isSelf = Boolean(row.is_self);
          html += '<tr class="' + (isSelf ? 'table-primary' : '') + '">';
          html += '<td class="text-muted">' + String(idx + 1) + '</td>';
          html += '<td>' + escapeHtml(row.name || '') + (isSelf ? ' <span class="badge text-bg-info">You</span>' : '') + '</td>';
          html += '<td class="text-end text-nowrap"><strong>' + escapeHtml(total.toFixed(2)) + '</strong> ' + escapeHtml(sym) + '</td>';
          html += '</tr>';
        });
        periodBodyEl.innerHTML = html;
      }

      periodTabsEl.querySelectorAll('[data-lb-period]').forEach(function (btn) {
        btn.addEventListener('click', function () {
          var next = btn.getAttribute('data-lb-period') || 'weekly';
          periodTabsEl.querySelectorAll('[data-lb-period]').forEach(function (b) {
            b.classList.remove('active');
            b.setAttribute('aria-selected', 'false');
          });
          btn.classList.add('active');
          btn.setAttribute('aria-selected', 'true');
          renderLeaderboardPeriod(next);

          var params = new URLSearchParams(window.location.search);
          params.set('tab', 'currency');
          params.set('lb_period', next);
          var nextUrl = window.location.pathname + '?' + params.toString();
          window.history.replaceState({}, '', nextUrl);
        });
      });
    }
  }

  function bindPeopleTable() {
    $(document).on('click', '.btn-delete-person', function () {
      var id = $(this).data('id');
      if (!id || !window.confirm('Remove this person from the hub?')) {
        return;
      }
      postJson('/people_delete.php', { id: id })
        .then(function () {
          window.location.reload();
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not remove');
        });
    });

    $(document).on('click', '.btn-reset-pin', function () {
      var id = $(this).data('id');
      var pin = window.prompt('Enter a new PIN (4+ characters) for this Head of household.') || '';
      if (pin.length < 4) {
        return;
      }
      postJson('/people_reset_pin.php', { targetPersonId: id, newPin: pin })
        .then(function () {
          window.alert('PIN updated.');
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not reset PIN');
        });
    });

    $(document).on('click', '.btn-rotate-person-nfc-token', function () {
      var id = $(this).data('id');
      var name = $(this).data('name') || 'this person';
      if (!id || !window.confirm('Rotate NFC submitter token for ' + name + '? Existing personal NFC links will stop working.')) {
        return;
      }
      postJson('/person_nfc_token_rotate.php', { person_id: id })
        .then(function () {
          window.alert('Token rotated for ' + name + '. Generate fresh chore NFC links as needed.');
        })
        .catch(function (err) {
          window.alert(err.message || 'Could not rotate NFC token');
        });
    });
  }

  $(function () {
    bindPersonaSwitcher();
    bindFirstHeadForm();
    bindAddPersonForm();
    bindEditPersonForm();
    bindFamilySettingsForm();
    bindCalendarSettingsForm();
    bindPeopleTable();
    bindChoresPage();
    bindCurrencyPage();
    bindStoreManagement();
    bindGroceryPage();
    bindMealsPage();
  });
})(jQuery);
