/* NRI Property Guard — Property Manager web dashboard logic (v1)
   Covers: hero overview + visit pipeline, Assigned Properties (schedule visit),
   Site Visits (reassign), Pending Approvals (approve/reject/revisit + per-photo/
   video decisions), a read-only Executive roster, and Support ticket replies.
   Live GPS tracking is shown as plain coordinates in v1 (a map view is a
   documented v2 follow-up — see the Workflow & Deployment Guide, Section 4.1). */
(function () {
  var sb = window.sbClient;
  var N = window.NRIPG;

  var State = {
    session: null, userId: null, profile: null, managerId: null, region: '',
    properties: [], visits: [], executives: [], tickets: [], currentView: 'home', openReviewVisitId: null
  };

  /* ============================== BOOT ============================== */
  async function boot() {
    var { data } = await sb.auth.getSession();
    if (!data || !data.session) { location.href = 'index.html'; return; }
    State.session = data.session; State.userId = data.session.user.id;

    var roleRes = await sb.from('users').select('role').eq('id', State.userId).maybeSingle();
    var role = (roleRes.data && roleRes.data.role) || 'customer';
    if (role !== 'property_manager') { location.href = 'index.html'; return; }

    var mgrRes = await sb.from('property_managers').select('id, region').eq('user_id', State.userId).maybeSingle();
    State.managerId = mgrRes.data ? mgrRes.data.id : null;
    State.region = (mgrRes.data && mgrRes.data.region) || '';

    var userRes = await sb.from('users').select('*').eq('id', State.userId).maybeSingle();
    State.profile = userRes.data || { name: data.session.user.email, email: data.session.user.email };

    document.getElementById('avatarChip').textContent = N.initials(State.profile.name || State.profile.email);
    document.getElementById('heroGreeting').textContent = 'Welcome back, ' + (State.profile.name || 'there').split(' ')[0] + ' 👋';
    document.getElementById('heroSub').textContent = State.region ? 'Region: ' + State.region : 'Here’s your progress across every assigned property.';

    document.getElementById('loadingGate').style.display = 'none';
    document.getElementById('appShell').style.display = '';

    bindNav();
    bindModals();
    bindForms();

    await loadProperties();
    await loadVisits();
    renderOverview();
    // Silent 30s backstop so this dashboard reflects backend changes (a new
    // assignment, a submitted inspection, a ticket reply) without a manual
    // reload; pauses automatically while the tab is out of focus.
    N.startAutoRefresh(function () { refreshCurrentView(); }, 30000);
  }

  async function refreshCurrentView() {
    await loadProperties();
    await loadVisits();
    renderOverview();
    if (State.currentView === 'approvals') loadApprovals();
    if (State.currentView === 'team') loadTeam();
    if (State.currentView === 'support') loadSupport();
    if (State.openReviewVisitId && document.getElementById('reviewModal').classList.contains('open')) {
      openReview(State.openReviewVisitId);
    }
  }

  /* ============================== NAV ============================== */
  function bindNav() {
    document.querySelectorAll('.side-nav a[data-view]').forEach(function (a) {
      a.addEventListener('click', function () { switchView(a.dataset.view); });
    });
    document.getElementById('menuBtn').addEventListener('click', function () {
      document.getElementById('sidebar').classList.toggle('open');
    });
    document.getElementById('logoutBtn').addEventListener('click', async function () {
      await sb.auth.signOut();
      location.href = 'index.html';
    });
  }

  var TITLES = { home: 'Dashboard', properties: 'Assigned Properties', visits: 'Site Visits', approvals: 'Pending Approvals', team: 'Property Executives', support: 'Support' };

  function switchView(name) {
    document.querySelectorAll('.view').forEach(function (v) { v.classList.remove('active'); });
    var el = document.getElementById('view-' + name);
    if (el) el.classList.add('active');
    document.querySelectorAll('.side-nav a[data-view]').forEach(function (a) { a.classList.toggle('active', a.dataset.view === name); });
    document.getElementById('viewTitle').textContent = TITLES[name] || name;
    document.getElementById('sidebar').classList.remove('open');
    State.currentView = name;

    if (name === 'approvals') loadApprovals();
    if (name === 'team' && !State.executives.length) loadTeam();
    if (name === 'support') loadSupport();
  }

  function emptyState(icon, msg) { return '<div class="empty-state"><div class="ic">' + icon + '</div><p class="mb-0">' + msg + '</p></div>'; }

  /* ============================== OVERVIEW ============================== */
  function renderOverview() {
    var pending = State.visits.filter(function (v) { return ['scheduled', 'assigned'].indexOf(v.status) !== -1; }).length;
    var inProgress = State.visits.filter(function (v) { return ['reached', 'in_progress', 'pending_approval'].indexOf(v.status) !== -1; }).length;
    var completed = State.visits.filter(function (v) { return v.status === 'approved' || v.status === 'completed'; }).length;
    var total = pending + inProgress + completed || 1;

    document.getElementById('heroStats').innerHTML =
      '<div><div class="val">' + State.properties.length + '</div><div class="lbl">Assigned Properties</div></div>' +
      '<div><div class="val">' + State.visits.length + '</div><div class="lbl">Total Visits</div></div>' +
      '<div><div class="val">' + completed + '</div><div class="lbl">Completed</div></div>';

    document.getElementById('pipelineBar').innerHTML =
      '<span class="seg-pending" style="width:' + (100 * pending / total) + '%;"></span>' +
      '<span class="seg-progress" style="width:' + (100 * inProgress / total) + '%;"></span>' +
      '<span class="seg-completed" style="width:' + (100 * completed / total) + '%;"></span>';

    var needsExec = State.properties.filter(function (p) { return p.workflow_status === 'assigned_to_manager'; });
    var el = document.getElementById('needsExecList');
    el.innerHTML = needsExec.length ? needsExec.map(function (p) {
      return '<div class="prop-card" style="cursor:default;"><div class="row1"><b>' + N.escapeHtml(p.property_name) + '</b><button class="btn btn-primary btn-sm" data-schedule="' + p.id + '">Schedule Visit</button></div>' +
        '<div class="meta">' + N.escapeHtml(p.city) + ', ' + N.escapeHtml(p.state) + '</div></div>';
    }).join('') : emptyState('✅', 'No properties waiting on an Executive assignment.');
    bindScheduleButtons(el);
  }

  /* ============================== ASSIGNED PROPERTIES ============================== */
  async function loadProperties() {
    var res = await sb.from('properties').select('*, owner:customer_id(user_id)').order('created_at', { ascending: false });
    if (res.error) { N.toast(res.error.message || 'Could not load properties.', 'error'); State.properties = []; }
    else State.properties = res.data || [];

    var userIds = State.properties.filter(function (p) { return p.owner && p.owner.user_id; }).map(function (p) { return p.owner.user_id; });
    var names = {};
    if (userIds.length) {
      var uRes = await sb.from('users').select('id, name').in('id', userIds);
      (uRes.data || []).forEach(function (u) { names[u.id] = u.name; });
    }
    State.properties.forEach(function (p) { p._ownerName = p.owner && p.owner.user_id ? (names[p.owner.user_id] || '—') : '—'; });

    renderPropertiesList();
  }

  function renderPropertiesList() {
    var el = document.getElementById('propertiesList');
    if (!State.properties.length) { el.innerHTML = emptyState('🏘️', 'No properties assigned to you yet.'); return; }
    el.innerHTML = State.properties.map(function (p) {
      return '<div class="prop-card" style="cursor:default;"><div class="row1"><b>' + N.escapeHtml(p.property_name) + '</b><span class="status-chip status-' + p.workflow_status + '">' + N.statusLabel(p.workflow_status) + '</span></div>' +
        '<div class="meta">Owner: ' + N.escapeHtml(p._ownerName) + ' · ' + N.escapeHtml(p.city) + ', ' + N.escapeHtml(p.state) + '</div>' +
        '<div class="chip-row"><span class="mini-chip">Priority: ' + N.escapeHtml(p.priority || 'medium') + '</span><span class="mini-chip">Deadline: ' + (p.visit_deadline ? N.fmtDate(p.visit_deadline) : '—') + '</span>' +
        (p.workflow_status === 'assigned_to_manager' ? '<button class="btn btn-primary btn-sm" data-schedule="' + p.id + '" style="margin-left:auto;">Schedule Visit</button>' : '') +
        '</div></div>';
    }).join('');
    bindScheduleButtons(el);
  }

  function bindScheduleButtons(scope) {
    scope.querySelectorAll('[data-schedule]').forEach(function (btn) {
      btn.addEventListener('click', async function () {
        document.querySelector('#scheduleForm [name=property_id]').value = btn.dataset.schedule;
        if (!State.executives.length) await loadTeam();
        document.getElementById('scheduleExecSelect').innerHTML = State.executives.map(function (e) { return '<option value="' + e.id + '">' + N.escapeHtml(e._name) + (e.region ? ' — ' + N.escapeHtml(e.region) : '') + '</option>'; }).join('');
        document.getElementById('scheduleAlert').className = 'alert';
        openModal('scheduleModal');
      });
    });
  }

  /* ============================== SITE VISITS ============================== */
  async function loadVisits() {
    var res = await sb.from('site_visits').select('*, properties(property_name), customer:customer_id(user_id)').order('scheduled_date', { ascending: false });
    if (res.error) { N.toast(res.error.message || 'Could not load site visits.', 'error'); State.visits = []; }
    else State.visits = res.data || [];

    var visitIds = State.visits.map(function (v) { return v.id; });
    var execByVisit = {};
    var userIds = State.visits.filter(function (v) { return v.customer && v.customer.user_id; }).map(function (v) { return v.customer.user_id; });
    var names = {};
    if (visitIds.length) {
      var vaRes = await sb.from('visit_assignments').select('site_visit_id, status, executive:executive_id(id, user_id)').in('site_visit_id', visitIds).neq('status', 'cancelled');
      var execUserIds = (vaRes.data || []).filter(function (a) { return a.executive && a.executive.user_id; }).map(function (a) { return a.executive.user_id; });
      userIds = userIds.concat(execUserIds);
      if (userIds.length) {
        var uRes = await sb.from('users').select('id, name').in('id', userIds);
        (uRes.data || []).forEach(function (u) { names[u.id] = u.name; });
      }
      (vaRes.data || []).forEach(function (a) { execByVisit[a.site_visit_id] = (a.executive && a.executive.user_id) ? (names[a.executive.user_id] || '—') : '—'; });
    } else if (userIds.length) {
      var uRes2 = await sb.from('users').select('id, name').in('id', userIds);
      (uRes2.data || []).forEach(function (u) { names[u.id] = u.name; });
    }
    State.visits.forEach(function (v) {
      v._execName = execByVisit[v.id] || '—';
      v._ownerName = (v.customer && v.customer.user_id) ? (names[v.customer.user_id] || '—') : '—';
    });

    renderVisitsList();
  }

  function renderVisitsList() {
    var el = document.getElementById('visitsList');
    if (!State.visits.length) { el.innerHTML = emptyState('📅', 'No site visits yet.'); return; }
    el.innerHTML = '<table class="table-simple"><thead><tr><th>Property</th><th>Executive</th><th>Scheduled</th><th>Status</th><th></th></tr></thead><tbody>' +
      State.visits.map(function (v) {
        var canReassign = ['scheduled', 'assigned', 'reached'].indexOf(v.status) !== -1;
        var isLive = ['reached', 'in_progress'].indexOf(v.status) !== -1;
        return '<tr><td>' + N.escapeHtml((v.properties && v.properties.property_name) || '—') + '</td><td>' + N.escapeHtml(v._execName) + '</td><td>' + N.fmtDateTime(v.scheduled_date) + '</td><td><span class="status-chip status-' + v.status + '">' + N.statusLabel(v.status) + '</span></td>' +
          '<td style="white-space:nowrap;">' + (isLive ? '<button class="btn btn-outline btn-sm" data-locate="' + v.id + '" style="margin-right:6px;">📍 Live Location</button>' : '') + (canReassign ? '<button class="btn btn-outline btn-sm" data-reassign="' + v.id + '">Reassign</button>' : '') + '</td></tr>';
      }).join('') + '</tbody></table>';
    el.querySelectorAll('[data-reassign]').forEach(function (btn) {
      btn.addEventListener('click', async function () {
        var v = State.visits.find(function (x) { return x.id === btn.dataset.reassign; });
        document.querySelector('#reassignForm [name=site_visit_id]').value = btn.dataset.reassign;
        if (!State.executives.length) await loadTeam();
        document.getElementById('reassignExecSelect').innerHTML = State.executives.map(function (e) { return '<option value="' + e.id + '">' + N.escapeHtml(e._name) + (e.region ? ' — ' + N.escapeHtml(e.region) : '') + '</option>'; }).join('');
        var ctxEl = document.getElementById('reassignContextInfo');
        if (v) {
          ctxEl.innerHTML =
            '<span class="mini-chip">Owner: ' + N.escapeHtml(v._ownerName) + '</span>' +
            '<span class="mini-chip">Property: ' + N.escapeHtml((v.properties && v.properties.property_name) || '—') + '</span>' +
            '<span class="mini-chip">Scheduled: ' + N.escapeHtml(N.fmtDateTime(v.scheduled_date)) + '</span>' +
            '<span class="mini-chip">Currently: ' + N.escapeHtml(v._execName) + '</span>';
        } else {
          ctxEl.innerHTML = '';
        }
        document.getElementById('reassignAlert').className = 'alert';
        openModal('reassignModal');
      });
    });
    el.querySelectorAll('[data-locate]').forEach(function (btn) {
      btn.addEventListener('click', async function () {
        // v1 shows plain coordinates + a Google Maps link rather than an embedded
        // live map — a full live-tracking map view is a documented v2 follow-up.
        var res = await sb.from('executive_current_location').select('*').eq('site_visit_id', btn.dataset.locate).maybeSingle();
        if (res.error || !res.data) { N.toast('No live location reported for this visit yet.', 'info'); return; }
        var loc = res.data;
        window.open('https://www.google.com/maps/search/?api=1&query=' + loc.lat + ',' + loc.lng, '_blank');
        N.toast('Last updated ' + N.fmtDateTime(loc.updated_at) + ' — opening in Google Maps.', 'info');
      });
    });
  }

  /* ============================== PENDING APPROVALS ============================== */
  async function loadApprovals() {
    var pending = State.visits.filter(function (v) { return v.status === 'pending_approval'; });
    var el = document.getElementById('approvalsList');
    if (!pending.length) { el.innerHTML = emptyState('✅', 'Nothing waiting on your review right now.'); return; }
    el.innerHTML = pending.map(function (v) {
      return '<div class="card"><div class="card-head"><h3>' + N.escapeHtml((v.properties && v.properties.property_name) || 'Property') + '</h3><span class="status-chip status-pending_approval">Awaiting Review</span></div>' +
        '<p style="color:var(--ink-500);">Visit completed ' + N.fmtDateTime(v.scheduled_date) + '</p>' +
        '<button class="btn btn-primary btn-sm" data-review="' + v.id + '">Open Review</button></div>';
    }).join('');
    el.querySelectorAll('[data-review]').forEach(function (btn) {
      btn.addEventListener('click', function () { openReview(btn.dataset.review); });
    });
  }

  async function openReview(siteVisitId) {
    State.openReviewVisitId = siteVisitId;
    var v = State.visits.find(function (x) { return x.id === siteVisitId; });
    document.getElementById('reviewAlert').className = 'alert';
    var body = document.getElementById('reviewBody');
    body.innerHTML = '<div class="skel" style="height:160px;"></div>';
    openModal('reviewModal');

    var reportRes = await sb.from('inspection_reports').select('*').eq('site_visit_id', siteVisitId).order('submitted_at', { ascending: false }).limit(1).maybeSingle();
    var report = reportRes.data;
    var photosRes = await sb.from('visit_photos').select('*').eq('site_visit_id', siteVisitId).order('captured_at', { ascending: false });
    var videosRes = await sb.from('visit_videos').select('*').eq('site_visit_id', siteVisitId).order('captured_at', { ascending: false });
    var photos = photosRes.data || [], videos = videosRes.data || [];

    body.innerHTML =
      (report ? '<div class="card"><h3>Inspection Notes</h3>' +
        '<div class="chip-row"><span class="mini-chip">Condition: ' + N.statusLabel(report.property_condition) + '</span>' + (report.gps_lat ? '<span class="mini-chip">GPS: ' + report.gps_lat.toFixed(5) + ', ' + report.gps_lng.toFixed(5) + '</span>' : '') + '</div>' +
        '<p style="margin-top:10px;">' + N.escapeHtml(report.notes || 'No notes.') + '</p>' +
        (report.observations ? '<p><b>Observations:</b> ' + N.escapeHtml(report.observations) + '</p>' : '') + '</div>' : '') +
      '<div class="card"><h3>Photos (' + photos.length + ')</h3>' + (photos.length ? '<div class="media-grid">' + photos.map(mediaReviewTile.bind(null, 'property-images', 'photo', 'visit_photos')).join('') + '</div>' : emptyState('🖼️', 'No photos submitted.')) + '</div>' +
      '<div class="card"><h3>Videos (' + videos.length + ')</h3>' + (videos.length ? '<div class="media-grid">' + videos.map(mediaReviewTile.bind(null, 'property-videos', 'video', 'visit_videos')).join('') + '</div>' : emptyState('🎬', 'No videos submitted.')) + '</div>' +
      '<div class="card"><h3>Decision</h3>' +
        '<div class="field"><label>Comments (optional)</label><textarea id="reviewComments"></textarea></div>' +
        '<div style="display:flex;gap:10px;flex-wrap:wrap;">' +
          '<button class="btn btn-primary btn-sm" data-decide="approved">Approve</button>' +
          '<button class="btn btn-outline btn-sm" data-decide="revisit_requested">Request Re-visit</button>' +
          '<button class="btn btn-danger-outline btn-sm" data-decide="rejected">Reject</button>' +
        '</div></div>';

    body.querySelectorAll('.media-tile').forEach(function (tile) {
      tile.addEventListener('click', async function () {
        var bucket = tile.dataset.mediaBucket, path = tile.dataset.mediaPath, kind = tile.dataset.mediaKind;
        var signed = await sb.storage.from(bucket).createSignedUrl(path, 600);
        if (signed.error || !signed.data) { N.toast('Could not open this file.', 'error'); return; }
        // No timestamp/GPS overlay on the media itself (Phase 8, per explicit
        // request) — that metadata is still captured and stored on the row for
        // the audit trail; a Manager reviewing a submission just sees the plain
        // photo/video here.
        document.getElementById('lightboxBody').innerHTML = '<div class="lightbox-media-wrap">' + (kind === 'photo'
          ? '<img src="' + signed.data.signedUrl + '">'
          : '<video src="' + signed.data.signedUrl + '" controls autoplay></video>') + '</div>';
        openModal('lightboxModal');
      });
    });
    body.querySelectorAll('[data-media-decide]').forEach(function (btn) {
      btn.addEventListener('click', async function (e) {
        e.stopPropagation();
        var table = btn.dataset.mediaTable, id = btn.dataset.mediaId, decision = btn.dataset.mediaDecide;
        var rpc = table === 'visit_photos' ? 'manager_decide_photo' : 'manager_decide_video';
        var paramId = table === 'visit_photos' ? 'p_photo_id' : 'p_video_id';
        var params = {}; params[paramId] = id; params.p_decision = decision; params.p_remarks = '';
        var res = await sb.rpc(rpc, params);
        if (res.error) { N.toast(res.error.message, 'error'); return; }
        N.toast('Updated.', 'success');
        openReview(siteVisitId);
      });
    });
    body.querySelectorAll('[data-decide]').forEach(function (btn) {
      btn.addEventListener('click', async function () {
        var decision = btn.dataset.decide;
        var comments = document.getElementById('reviewComments').value;
        btn.disabled = true;
        try {
          var res = await sb.rpc('manager_review_inspection', { p_site_visit_id: siteVisitId, p_decision: decision, p_comments: comments });
          if (res.error) throw res.error;
          N.toast('Decision recorded.', 'success');
          closeModal('reviewModal');
          await loadVisits(); loadApprovals(); renderOverview();
        } catch (err) {
          document.getElementById('reviewAlert').textContent = err.message || 'Could not record this decision.';
          document.getElementById('reviewAlert').className = 'alert error show';
          btn.disabled = false;
        }
      });
    });
  }

  function mediaReviewTile(bucket, kind, table, m) {
    return '<div class="media-review-tile">' +
      '<div class="thumb media-tile" data-media-bucket="' + bucket + '" data-media-path="' + N.escapeHtml(m.storage_path) + '" data-media-kind="' + kind + '">' +
        (kind === 'photo' ? '🖼️' : '🎬') +
      '</div>' +
      '<div class="actions">' +
        '<button class="btn btn-outline" data-media-table="' + table + '" data-media-id="' + m.id + '" data-media-decide="approved">Approve</button>' +
        '<button class="btn btn-danger-outline" data-media-table="' + table + '" data-media-id="' + m.id + '" data-media-decide="rejected">Reject</button>' +
      '</div></div>';
  }

  /* ============================== TEAM (EXECUTIVE ROSTER) ============================== */
  async function loadTeam() {
    var res = await sb.from('property_executives').select('*, users:user_id(name, phone_number, status)').order('created_at', { ascending: false });
    if (res.error) { N.toast(res.error.message || 'Could not load Executives.', 'error'); State.executives = []; }
    else State.executives = (res.data || []).map(function (e) { e._name = (e.users && e.users.name) || '—'; return e; });
    renderTeamList();
  }

  function renderTeamList() {
    var el = document.getElementById('teamList');
    if (!State.executives.length) { el.innerHTML = emptyState('🧑‍🔧', 'No Property Executives found.'); return; }
    el.innerHTML = '<table class="table-simple"><thead><tr><th>Name</th><th>Phone</th><th>Region</th><th>Status</th></tr></thead><tbody>' +
      State.executives.map(function (e) {
        return '<tr><td>' + N.escapeHtml(e._name) + '</td><td>' + N.escapeHtml((e.users && e.users.phone_number) || '—') + '</td><td>' + N.escapeHtml(e.region || '—') + '</td><td><span class="status-chip status-' + ((e.users && e.users.status) || 'active') + '">' + N.statusLabel((e.users && e.users.status) || 'active') + '</span></td></tr>';
      }).join('') + '</tbody></table>';
  }

  /* ============================== SUPPORT ============================== */
  async function loadSupport() {
    var res = await sb.from('support_tickets').select('*, properties(property_name)').order('created_at', { ascending: false });
    if (res.error) { N.toast(res.error.message || 'Could not load tickets.', 'error'); State.tickets = []; }
    else State.tickets = res.data || [];
    var el = document.getElementById('ticketsList');
    if (!State.tickets.length) { el.innerHTML = emptyState('🎫', 'No support tickets for your properties.'); return; }
    el.innerHTML = State.tickets.map(function (t) {
      return '<div class="prop-card" data-ticket-id="' + t.id + '"><div class="row1"><b>' + N.escapeHtml(t.subject) + '</b><span class="status-chip status-' + t.status + '">' + N.statusLabel(t.status) + '</span></div>' +
        '<div class="meta">' + (t.properties ? N.escapeHtml(t.properties.property_name) + ' · ' : '') + N.fmtDateTime(t.created_at) + ' · Priority: ' + t.priority + '</div></div>';
    }).join('');
    el.querySelectorAll('[data-ticket-id]').forEach(function (card) {
      card.addEventListener('click', function () { openThread(card.dataset.ticketId); });
    });
  }

  var currentThreadId = null;
  async function openThread(ticketId) {
    currentThreadId = ticketId;
    var t = State.tickets.find(function (x) { return x.id === ticketId; });
    document.getElementById('threadSubject').textContent = t ? t.subject : 'Ticket';
    document.getElementById('threadStatus').innerHTML = t ? ('<span class="status-chip status-' + t.status + '">' + N.statusLabel(t.status) + '</span> <span style="color:var(--ink-500);font-size:.85rem;">' + N.escapeHtml(t.description) + '</span>') : '';
    document.getElementById('threadReplies').innerHTML = '<div class="skel" style="height:40px;"></div>';
    openModal('threadModal');

    var res = await sb.from('support_ticket_replies').select('*, users:sender_id(name, role)').eq('ticket_id', ticketId).order('created_at', { ascending: true });
    var replies = res.data || [];
    document.getElementById('threadReplies').innerHTML = replies.length ? replies.map(function (r) {
      var mine = r.sender_id === State.userId;
      return '<div class="reply-bubble ' + (mine ? 'me' : '') + '"><div class="who">' + (mine ? 'You' : N.escapeHtml((r.users && r.users.name) || 'Team')) + ' · ' + N.fmtDateTime(r.created_at) + '</div>' + N.escapeHtml(r.body) + '</div>';
    }).join('') : '<p class="help-note">No replies yet.</p>';
  }

  async function onReplySubmit(e) {
    e.preventDefault();
    var body = document.getElementById('replyBody').value.trim();
    if (!body || !currentThreadId) return;
    try {
      var res = await sb.rpc('reply_support_ticket', { p_ticket_id: currentThreadId, p_body: body });
      if (res.error) throw res.error;
      document.getElementById('replyBody').value = '';
      openThread(currentThreadId);
    } catch (err) { N.toast(err.message || 'Could not send reply.', 'error'); }
  }

  /* ============================== FORMS ============================== */
  function bindForms() {
    document.getElementById('scheduleForm').addEventListener('submit', onScheduleSubmit);
    document.getElementById('reassignForm').addEventListener('submit', onReassignSubmit);
    document.getElementById('replyForm').addEventListener('submit', onReplySubmit);
  }

  async function onScheduleSubmit(e) {
    e.preventDefault();
    var alertEl = document.getElementById('scheduleAlert');
    alertEl.className = 'alert';
    var fd = new FormData(e.target);
    try {
      var res = await sb.rpc('manager_schedule_inspection', {
        p_property_id: fd.get('property_id'),
        p_executive_id: fd.get('executive_id'),
        p_visit_date: new Date(fd.get('visit_date')).toISOString(),
        p_instructions: fd.get('instructions') || ''
      });
      if (res.error) throw res.error;
      alertEl.textContent = 'Visit scheduled.'; alertEl.className = 'alert success show';
      await loadProperties(); await loadVisits(); renderOverview();
      setTimeout(function () { closeModal('scheduleModal'); }, 800);
    } catch (err) {
      alertEl.textContent = err.message || 'Could not schedule this visit.'; alertEl.className = 'alert error show';
    }
  }

  async function onReassignSubmit(e) {
    e.preventDefault();
    var alertEl = document.getElementById('reassignAlert');
    alertEl.className = 'alert';
    var fd = new FormData(e.target);
    try {
      var res = await sb.rpc('create_visit_assignment', { p_site_visit_id: fd.get('site_visit_id'), p_executive_id: fd.get('executive_id') });
      if (res.error) throw res.error;
      alertEl.textContent = 'Visit reassigned.'; alertEl.className = 'alert success show';
      await loadVisits();
      setTimeout(function () { closeModal('reassignModal'); }, 800);
    } catch (err) {
      alertEl.textContent = err.message || 'Could not reassign this visit.'; alertEl.className = 'alert error show';
    }
  }

  /* ============================== MODALS ============================== */
  function bindModals() {
    document.querySelectorAll('[data-close-modal]').forEach(function (btn) {
      btn.addEventListener('click', function () { closeModal(btn.dataset.closeModal); });
    });
    document.querySelectorAll('[data-maximize-modal]').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var backdrop = document.getElementById(btn.dataset.maximizeModal);
        N.toggleModalMaximize(backdrop.querySelector('.lightbox-modal') || backdrop);
      });
    });
    document.querySelectorAll('.modal-backdrop').forEach(function (backdrop) {
      backdrop.addEventListener('click', function (e) { if (e.target === backdrop) closeModal(backdrop.id); });
    });
  }
  function openModal(id) { document.getElementById(id).classList.add('open'); }
  function closeModal(id) {
    var el = document.getElementById(id);
    el.classList.remove('open');
    N.resetModalMaximize(el.querySelector('.lightbox-modal') || el);
  }

  boot();
})();
