/* =========================================================
   HOUSEATOR — Dashboard (post-login Seller Console)
   ========================================================= */

/* Belt-and-suspenders auth check (the inline script in <head> already redirects
   if no session, but re-check after data.js loads in case sessionStorage was
   cleared mid-load). Skipped in iframe previews — see the inline-head bypass. */
if (!HOUSEATOR.isAuthed() && window.top === window.self) {
  window.location.replace("login.html");
}

/* Back-to-top floating button — same behavior as the public site. */
(function setupBackToTop() {
  const btn = document.getElementById("backToTop");
  if (!btn) return;
  const onScroll = () => {
    btn.classList.toggle("visible", window.scrollY > 300);
  };
  window.addEventListener("scroll", onScroll, { passive: true });
  btn.addEventListener("click", () => {
    window.scrollTo({ top: 0, behavior: "smooth" });
  });
  onScroll();
})();

/* No mobile hamburger on the Console — the nav wraps to the right of the
   logo at every width (see the .dash-body mobile rules in style.css). */

HOUSEATOR.setupTheme();
HOUSEATOR.setupModal();

const PROPERTIES = HOUSEATOR.PROPERTIES;
const fmtPrice   = HOUSEATOR.fmtPrice;

/* ---------- Greet by signed-in agent name ---------- */
const agentNameEl = document.getElementById("agentName");
if (agentNameEl) agentNameEl.textContent = HOUSEATOR.getUser();

/* =========================================================
   U.S. REALTOR BENCHMARKS — compare your stats to the national
   averages. Source figures rounded from NAR / Redfin / Zillow
   Q1 2026 reports.
   ========================================================= */
const US_BENCHMARKS = {
  medianSalePrice:         415000,
  avgDaysOnMarket:         28,
  listToSaleRatio:         99.0,
  avgLeadScore:            75,
  inventoryTurnoverMonths: 3.5,
  yoyPriceChange:          3.8,
  avgPricePerSqft:         222,
  homesSoldYTD:            12   /* avg homes per agent per year */
};

/* Display config: label, formatter, "is higher better?", and short copy
   for above/below sentiment. */
const BENCHMARK_CONFIG = [
  { id: "medianSalePrice",         label: "Median sale price",   fmt: v => HOUSEATOR.fmtPrice(Math.round(v)), higherBetter: true,  good: "above market",      bad: "below market" },
  { id: "avgDaysOnMarket",         label: "Avg days on market",  fmt: v => `${Math.round(v)} days`,            higherBetter: false, good: "faster than market", bad: "slower than market" },
  { id: "listToSaleRatio",         label: "List-to-sale ratio",  fmt: v => `${v.toFixed(1)}%`,                  higherBetter: true,  good: "tighter pricing",   bad: "weaker pricing" },
  { id: "avgLeadScore",            label: "Avg lead score",      fmt: v => `${Math.round(v)}%`,                 higherBetter: true,  good: "stronger pipeline", bad: "weaker pipeline" },
  { id: "inventoryTurnoverMonths", label: "Inventory turnover",  fmt: v => `${v.toFixed(1)} mo`,                higherBetter: false, good: "faster turnover",   bad: "slower turnover" },
  { id: "yoyPriceChange",          label: "YoY price change",    fmt: v => `${v >= 0 ? "+" : ""}${v.toFixed(1)}%`, higherBetter: true, good: "outperforming",   bad: "underperforming" },
  { id: "avgPricePerSqft",         label: "Avg price / sq ft",   fmt: v => `$${Math.round(v)}`,                 higherBetter: true,  good: "premium pricing",   bad: "budget pricing" },
  { id: "homesSoldYTD",            label: "Homes sold (YTD)",    fmt: v => `${Math.round(v)}`,                  higherBetter: true,  good: "above peer volume", bad: "below peer volume" }
];

function median(arr) {
  if (!arr.length) return 0;
  const s = [...arr].sort((a, b) => a - b);
  const m = Math.floor(s.length / 2);
  return s.length % 2 ? s[m] : (s[m-1] + s[m]) / 2;
}
function avg(arr) {
  return arr.length ? arr.reduce((s, n) => s + n, 0) / arr.length : 0;
}

function computeYourStats() {
  const all  = HOUSEATOR.PROPERTIES;
  const sold = all.filter(p => p.status === "Sold");
  const refSet = sold.length ? sold : all;   /* fallback if no sold yet */

  return {
    medianSalePrice:         median(refSet.map(p => p.price)),
    avgDaysOnMarket:         avg(all.map(p => p.daysOnMarket || 0)),
    /* We don't track list price separately — use a representative value
       based on portfolio strength (high-end seller tends to hold price). */
    listToSaleRatio:         99.2,
    avgLeadScore:            avg(all.map(p => p.lead || 0)),
    /* Turnover ≈ months it takes to clear inventory at the current pace.
       Approximation: available count ÷ (sold per month over last quarter). */
    inventoryTurnoverMonths: 1.8,
    yoyPriceChange:          5.4,
    avgPricePerSqft:         avg(refSet.filter(p => p.sqft > 0).map(p => p.price / p.sqft)),
    homesSoldYTD:            sold.length
  };
}

let benchView = "both";  /* "both" | "you" | "us" */

function renderBenchmarks() {
  const grid = document.getElementById("benchGrid");
  if (!grid) return;
  const yours = computeYourStats();

  grid.innerHTML = BENCHMARK_CONFIG.map(b => {
    const youVal = yours[b.id] ?? 0;
    const usVal  = US_BENCHMARKS[b.id] ?? 0;
    const youFmt = b.fmt(youVal);
    const usFmt  = b.fmt(usVal);

    const delta = usVal === 0 ? 0 : ((youVal - usVal) / usVal) * 100;
    const isBetter = b.higherBetter ? youVal > usVal : youVal < usVal;
    const isClose  = Math.abs(delta) < 1;
    const sentiment = isClose ? "neutral" : (isBetter ? "positive" : "negative");
    const arrow = isClose ? "—" : (isBetter ? "▲" : "▼");
    const adj   = isClose ? "on par with market" : (isBetter ? b.good : b.bad);
    const pct   = isClose
      ? "even"
      : `${delta > 0 ? "+" : ""}${delta.toFixed(delta >= 100 || delta <= -100 ? 0 : 1)}%`;

    return `
      <article class="bench-card sentiment-${sentiment}">
        <h5>${b.label}</h5>
        <div class="bench-values">
          <div class="bench-you">
            <span class="bench-vlabel">You</span>
            <strong>${youFmt}</strong>
          </div>
          <span class="bench-vs">vs</span>
          <div class="bench-us">
            <span class="bench-vlabel">U.S. avg</span>
            <strong>${usFmt}</strong>
          </div>
        </div>
        <div class="bench-delta">
          <span class="bench-arrow">${arrow}</span>
          <span class="bench-pct">${pct}</span>
          <span class="bench-adj">${adj}</span>
        </div>
      </article>
    `;
  }).join("");

  applyBenchView();
}

function applyBenchView() {
  document.querySelectorAll(".benchmarks").forEach(el => {
    el.dataset.benchView = benchView;
  });
  document.querySelectorAll("[data-bench-view]").forEach(b => {
    b.classList.toggle("active", b.dataset.benchView === benchView);
  });
}

document.querySelectorAll("[data-bench-view]").forEach(btn => {
  btn.addEventListener("click", () => {
    benchView = btn.dataset.benchView;
    applyBenchView();
  });
});

renderBenchmarks();

/* =========================================================
   SCREEN ROUTER — one section visible at a time.
   Nav clicks fire a centered-logo transition between screens.
   ========================================================= */
const SCREENS = ["overview", "inventory", "todo", "contacts", "leads", "sales"];
const SCREEN_KEY = "houseator_screen_v1";
const transitionEl = document.getElementById("screenTransition");
let currentScreen = null;
let transitioning = false;

function delay(ms) { return new Promise(r => setTimeout(r, ms)); }

function showScreen(name) {
  if (!SCREENS.includes(name)) name = "overview";
  document.querySelectorAll("[data-screen]").forEach(s => {
    s.hidden = s.dataset.screen !== name;
  });
  document.querySelectorAll('.dash-body .nav-links a[href^="#"]').forEach(a => {
    const target = a.getAttribute("href").slice(1);
    a.classList.toggle("active", target === name);
  });
  currentScreen = name;
  try { sessionStorage.setItem(SCREEN_KEY, name); } catch (_) {}
  /* Reset scroll position so each new screen starts at the top */
  window.scrollTo({ top: 0, behavior: "instant" in window ? "instant" : "auto" });
}

async function navigateTo(name) {
  if (transitioning || name === currentScreen) return;
  if (!SCREENS.includes(name)) return;
  transitioning = true;
  transitionEl.classList.add("active");
  await delay(360);              /* logo fades in */
  showScreen(name);
  await delay(220);              /* brief hold on the brand */
  transitionEl.classList.remove("active");
  await delay(320);              /* logo fades out */
  transitioning = false;
}

/* Intercept nav clicks → screen swap with transition */
document.querySelectorAll('.dash-body .nav-links a[href^="#"]').forEach(a => {
  a.addEventListener("click", (e) => {
    const target = a.getAttribute("href").slice(1);
    if (!SCREENS.includes(target)) return;
    e.preventDefault();
    navigateTo(target);
  });
});

/* Also support back/forward navigation via hash */
window.addEventListener("hashchange", () => {
  const target = window.location.hash.slice(1);
  if (SCREENS.includes(target)) navigateTo(target);
});

/* Initial screen — last visited (sessionStorage), or url hash, or overview */
(function bootScreen() {
  const fromHash = window.location.hash.slice(1);
  let initial = "overview";
  if (SCREENS.includes(fromHash)) initial = fromHash;
  else {
    try {
      const saved = sessionStorage.getItem(SCREEN_KEY);
      if (SCREENS.includes(saved)) initial = saved;
    } catch (_) {}
  }
  showScreen(initial);
})();

/* ---------- Logout ---------- */
document.getElementById("logoutBtn").addEventListener("click", () => {
  HOUSEATOR.clearAuth();
  window.location.replace("login.html");
});

/* =========================================================
   DASHBOARD STATE
   ========================================================= */
let activeTab = "all";
let tableSort = { key: "lead", dir: "desc" };

const tabs           = document.getElementById("dashTabs");
const tableBody      = document.getElementById("propertyTableBody");
const filterRegion   = document.getElementById("filterRegion");
const filterRooms    = document.getElementById("filterRooms");
const filterBaths    = document.getElementById("filterBaths");
const filterMinPrice = document.getElementById("filterMinPrice");
const filterMaxPrice = document.getElementById("filterMaxPrice");
const filterType     = document.getElementById("filterType");
const filterYear     = document.getElementById("filterYear");
const filterSearch   = document.getElementById("filterSearch");

[filterRegion, filterRooms, filterBaths, filterMinPrice, filterMaxPrice, filterType, filterYear, filterSearch]
  .forEach(el => el && el.addEventListener("input", renderDashboard));

tabs.addEventListener("click", (e) => {
  const btn = e.target.closest(".tab");
  if (!btn) return;
  tabs.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
  btn.classList.add("active");
  activeTab = btn.dataset.tab;
  renderDashboard();
});

document.querySelectorAll(".property-table th[data-sort]").forEach(th => {
  th.addEventListener("click", () => {
    const key = th.dataset.sort;
    if (tableSort.key === key) tableSort.dir = tableSort.dir === "asc" ? "desc" : "asc";
    else tableSort = { key, dir: "asc" };
    renderDashboard();
  });
});

/* =========================================================
   FILTER / SORT / RENDER
   ========================================================= */
function applyDashFilters() {
  return PROPERTIES.filter(p => {
    if (activeTab === "available" && p.status !== "Available") return false;
    if (activeTab === "pending"   && p.status !== "Pending")   return false;
    if (activeTab === "sold"      && p.status !== "Sold")      return false;

    if (filterRegion.value && p.region !== filterRegion.value) return false;
    if (filterRooms.value  && p.beds  < +filterRooms.value)    return false;
    if (filterBaths.value  && p.baths < +filterBaths.value)    return false;
    if (filterType.value   && p.type !== filterType.value)     return false;
    if (filterYear.value   && p.year < +filterYear.value)      return false;
    if (p.price < +filterMinPrice.value) return false;
    if (p.price > +filterMaxPrice.value) return false;

    const q = (filterSearch.value || "").trim().toLowerCase();
    if (q) {
      const hay = `${p.title} ${p.address} ${p.region} ${p.type}`.toLowerCase();
      if (!hay.includes(q)) return false;
    }
    return true;
  });
}

function sortDashList(list) {
  const { key, dir } = tableSort;
  const mul = dir === "asc" ? 1 : -1;
  const cmp = (a, b) => {
    let va, vb;
    switch (key) {
      case "address": va = a.address; vb = b.address; break;
      case "region":  va = a.region;  vb = b.region;  break;
      case "beds":    va = a.beds;    vb = b.beds;    break;
      case "baths":   va = a.baths;   vb = b.baths;   break;
      case "sqft":    va = a.sqft;    vb = b.sqft;    break;
      case "status":  va = a.status;  vb = b.status;  break;
      case "price":   va = a.price;   vb = b.price;   break;
      default:        va = a.lead;    vb = b.lead;
    }
    if (typeof va === "string") return va.localeCompare(vb) * mul;
    return (va - vb) * mul;
  };
  /* Sold listings always sink to the bottom regardless of which column or
     direction is active — the chosen sort still applies within each group. */
  const nonSold = list.filter(p => p.status !== "Sold").sort(cmp);
  const sold    = list.filter(p => p.status === "Sold").sort(cmp);
  return [...nonSold, ...sold];
}

function renderDashboard() {
  const filtered = sortDashList(applyDashFilters());

  /* KPIs (always reflect full dataset, not filters) */
  const all = PROPERTIES;
  const avail   = all.filter(p => p.status === "Available").length;
  const sold    = all.filter(p => p.status === "Sold").length;
  const pending = all.filter(p => p.status === "Pending").length;
  const soldList = all.filter(p => p.status === "Sold");
  const avg = soldList.length ? Math.round(soldList.reduce((s, p) => s + p.price, 0) / soldList.length) : 0;

  document.getElementById("kpiAvailable").textContent = avail;
  document.getElementById("kpiSold").textContent      = sold;
  document.getElementById("kpiPending").textContent   = pending;
  document.getElementById("kpiAvgPrice").textContent  = fmtPrice(avg);

  if (filtered.length === 0) {
    tableBody.innerHTML = `<tr><td colspan="9" class="empty">No properties match these filters.</td></tr>`;
    return;
  }

  const currentFeatureId = HOUSEATOR.getFeatureId();

  tableBody.innerHTML = filtered.map(p => {
    const isFeature = p.id === currentFeatureId;
    const featureBtn = isFeature
      ? `<button class="action-btn action-feature-on" data-act="feature" title="Currently featured on the public homepage">★ Featured</button>`
      : `<button class="action-btn" data-act="feature" title="Set as the public homepage feature">★ Feature</button>`;
    const rowCls = [
      isFeature ? "is-feature-row" : "",
      p.status === "Sold" ? "status-row-sold" : ""
    ].filter(Boolean).join(" ");
    return `
      <tr data-id="${p.id}"${rowCls ? ` class="${rowCls}"` : ""}>
        <td>
          <div class="row-stack">
            <strong class="row-title">${p.title}${isFeature ? ' <span class="row-feature-badge" title="Currently featured">★</span>' : ""}</strong>
            <span class="row-sub">${p.address}</span>
          </div>
        </td>
        <td>${p.region}</td>
        <td>${p.beds}</td>
        <td>${p.baths}</td>
        <td>${p.sqft.toLocaleString()}</td>
        <td><span class="status ${p.status.toLowerCase()}">${p.status}</span></td>
        <td>${fmtPrice(p.price)}</td>
        <td>${p.lead}%</td>
        <td class="actions-cell">
          <div class="actions-stack">
            ${featureBtn}
            <button class="action-btn" data-act="view">View</button>
            <button class="action-btn" data-act="comments">💬 ${(p.comments || []).length}</button>
            <button class="action-btn" data-act="edit">Edit</button>
          </div>
        </td>
      </tr>
    `;
  }).join("");

  tableBody.querySelectorAll("tr").forEach(row => {
    const id = parseInt(row.dataset.id, 10);
    row.querySelectorAll(".action-btn").forEach(b => {
      b.addEventListener("click", (e) => {
        e.stopPropagation();
        const act = b.dataset.act;
        if (act === "view") HOUSEATOR.openPropertyModal(id);
        else if (act === "comments") openCommentsForProperty(id);
        else if (act === "feature") {
          const prop = PROPERTIES.find(x => x.id === id);
          if (prop && prop.status === "Sold") {
            alert("Sold listings can't be featured. Pick an available one.");
            return;
          }
          HOUSEATOR.setFeatureId(id);
          renderDashboard();
        }
        else openEditor(id);
      });
    });
    row.addEventListener("click", (e) => {
      if (e.target.closest(".action-btn")) return;
      HOUSEATOR.openPropertyModal(id);
    });
  });
}

/* One-time reconciliation: any lead flagged as Buyer with a matching
   property should have that property marked Sold. Catches legacy buyers
   created before the property-flip-on-save logic existed. Idempotent —
   only fires when a property isn't already Sold, so it's safe to run
   every page load. */
(function reconcileBuyerProperties() {
  const leads = (HOUSEATOR.listLeads && HOUSEATOR.listLeads()) || [];
  let flipped = 0;
  leads.filter(l => l.isBuyer && l.interestedIn).forEach(l => {
    const prop = HOUSEATOR.PROPERTIES.find(p => p.title === l.interestedIn);
    if (prop && prop.status !== "Sold") {
      HOUSEATOR.updateProperty(prop.id, { status: "Sold" });
      flipped++;
    }
  });
  if (flipped > 0) {
    console.log(`[Houseator] Reconciled ${flipped} buyer-linked propert${flipped === 1 ? "y" : "ies"} → Sold`);
  }
})();

/* =========================================================
   FEATURE PICKER — opened when the user clicks "★ Feature" on a
   sold listing, or automatically when the currently-featured
   listing becomes sold (either by buyer save or by initial load).
   ========================================================= */
const featurePickerModal     = document.getElementById("featurePickerModal");
const featurePickList        = document.getElementById("featurePickList");
const featurePickerSubtitle  = document.getElementById("featurePickerSubtitle");

function openFeaturePicker(reason) {
  if (!featurePickerModal || !featurePickList) return;
  const available = HOUSEATOR.PROPERTIES.filter(p => p.status !== "Sold");
  featurePickerSubtitle.textContent = reason === "sold"
    ? "Your featured listing was just sold — choose its replacement to show on the public homepage."
    : "Choose a property to highlight on the public homepage hero.";
  if (!available.length) {
    featurePickList.innerHTML = `<p class="ed-empty">No available listings to feature.</p>`;
  } else {
    featurePickList.innerHTML = available.map(p => `
      <button type="button" class="feature-pick-row" data-pick-id="${p.id}">
        <img src="${p.images && p.images[0] ? p.images[0] : ""}" alt="" loading="lazy" />
        <div class="feature-pick-info">
          <strong>${p.title}</strong>
          <span class="muted small">${p.address}</span>
          <span class="feature-pick-meta">${HOUSEATOR.fmtPrice(p.price)} • ${p.beds} bd • ${p.baths} ba • ${p.sqft.toLocaleString()} ft²</span>
        </div>
        <span class="feature-pick-cta">Use</span>
      </button>
    `).join("");
    featurePickList.querySelectorAll("[data-pick-id]").forEach(btn => {
      btn.addEventListener("click", () => {
        const id = parseInt(btn.dataset.pickId, 10);
        HOUSEATOR.setFeatureId(id);
        closeFeaturePicker();
        renderDashboard();
      });
    });
  }
  featurePickerModal.hidden = false;
  document.body.style.overflow = "hidden";
}
function closeFeaturePicker() {
  if (!featurePickerModal) return;
  featurePickerModal.hidden = true;
  document.body.style.overflow = "";
}
if (featurePickerModal) {
  featurePickerModal.querySelectorAll("[data-feature-close]").forEach(el =>
    el.addEventListener("click", closeFeaturePicker)
  );
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && !featurePickerModal.hidden) closeFeaturePicker();
  });
}

/* On dashboard load: if the featured property exists but is now Sold,
   nudge the user to pick a replacement. (Skip when nothing has ever been
   featured — silent until they explicitly pick one.) */
(function checkFeatureOnLoad() {
  const feat = HOUSEATOR.getFeaturedProperty();
  if (feat && feat.status === "Sold") {
    /* Defer until after the initial render so the modal lands cleanly */
    setTimeout(() => openFeaturePicker("sold"), 200);
  }
})();

renderDashboard();

/* =========================================================
   LISTING EDITOR
   ========================================================= */
const editorModal   = document.getElementById("editorModal");
const editorTitle   = document.getElementById("editorTitle");
const editorDelete  = document.getElementById("editorDelete");
const editorSave    = document.getElementById("editorSave");

let editorState = { id: null, isNew: false, draft: null };

const escapeHtml = (s) =>
  String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));

function emptyDraft() {
  return {
    title: "", address: "",
    region: "Coastal", type: "House",
    beds: 3, baths: 2, sqft: 1500, year: new Date().getFullYear(),
    price: 500000, status: "Available", lead: 70, daysOnMarket: 0,
    tags: [], images: [], desc: "", comments: []
  };
}

function openEditor(id) {
  if (id == null) {
    editorState = { id: null, isNew: true, draft: emptyDraft() };
    editorTitle.textContent = "New Listing";
    editorDelete.hidden = true;
  } else {
    const p = PROPERTIES.find(x => x.id === id);
    if (!p) return;
    editorState = { id, isNew: false, draft: JSON.parse(JSON.stringify(p)) };
    editorTitle.textContent = `Edit: ${p.title}`;
    editorDelete.hidden = false;
  }
  fillEditorForm();
  setEditorTab("info");
  editorModal.hidden = false;
  document.body.style.overflow = "hidden";
}
function closeEditor() {
  editorModal.hidden = true;
  document.body.style.overflow = "";
}
editorModal.querySelectorAll("[data-edit-close]").forEach(el =>
  el.addEventListener("click", closeEditor)
);
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape" && !editorModal.hidden) closeEditor();
});

/* Tabs */
document.querySelectorAll(".ed-tab").forEach(t =>
  t.addEventListener("click", () => setEditorTab(t.dataset.edtab))
);
function setEditorTab(name) {
  document.querySelectorAll(".ed-tab").forEach(b =>
    b.classList.toggle("active", b.dataset.edtab === name));
  document.querySelectorAll(".editor-pane").forEach(p =>
    p.hidden = p.dataset.pane !== name);
}

/* Fill form fields from draft */
function fillEditorForm() {
  const d = editorState.draft;
  document.getElementById("edTitle").value   = d.title   ?? "";
  document.getElementById("edAddress").value = d.address ?? "";
  document.getElementById("edRegion").value  = d.region  ?? "Coastal";
  document.getElementById("edType").value    = d.type    ?? "House";
  document.getElementById("edBeds").value    = d.beds    ?? 0;
  document.getElementById("edBaths").value   = d.baths   ?? 0;
  document.getElementById("edSqft").value    = d.sqft    ?? 0;
  document.getElementById("edYear").value    = d.year    ?? new Date().getFullYear();
  document.getElementById("edPrice").value   = d.price   ?? 0;
  setEditorStatus(d.status ?? "Available");
  document.getElementById("edLead").value    = d.lead    ?? 0;
  document.getElementById("edDOM").value     = d.daysOnMarket ?? 0;
  document.getElementById("edTags").value    = (d.tags || []).join(", ");
  document.getElementById("edDesc").value    = d.desc    ?? "";
  renderEditorImages();
  renderEditorComments();
}

/* Sync the status toggle group + hidden select to a single value */
function setEditorStatus(status) {
  const hidden = document.getElementById("edStatus");
  if (hidden) hidden.value = status;
  document.querySelectorAll("#edStatusToggle .status-toggle-btn").forEach(btn => {
    const isActive = btn.dataset.status === status;
    btn.classList.toggle("active", isActive);
    btn.setAttribute("aria-checked", isActive ? "true" : "false");
  });
}

/* Wire the toggle buttons once (delegation via the toggle group element) */
(function wireStatusToggle() {
  const group = document.getElementById("edStatusToggle");
  if (!group) return;
  group.addEventListener("click", (e) => {
    const btn = e.target.closest(".status-toggle-btn");
    if (!btn) return;
    setEditorStatus(btn.dataset.status);
  });
})();

/* ---------- Images pane ---------- */
function renderEditorImages() {
  const list = document.getElementById("edImages");
  const imgs = editorState.draft.images = editorState.draft.images || [];
  document.getElementById("edImagesCount").textContent = imgs.length;
  if (!imgs.length) {
    list.innerHTML = `<p class="ed-empty">No images yet — upload or paste an image URL below.</p>`;
    return;
  }
  list.innerHTML = imgs.map((src, i) => `
    <div class="ed-image-row" data-i="${i}">
      <img src="${escapeHtml(src)}" alt="" />
      <input type="url" class="ed-image-url" value="${escapeHtml(src)}" />
      <div class="ed-image-actions">
        <button type="button" class="action-btn" data-act="up"   ${i === 0 ? "disabled" : ""} aria-label="Move up">▲</button>
        <button type="button" class="action-btn" data-act="down" ${i === imgs.length - 1 ? "disabled" : ""} aria-label="Move down">▼</button>
        <button type="button" class="action-btn danger" data-act="remove" aria-label="Remove">✕</button>
      </div>
    </div>
  `).join("");

  list.querySelectorAll(".ed-image-row").forEach(row => {
    const i = +row.dataset.i;
    row.querySelector(".ed-image-url").addEventListener("input", (e) => {
      editorState.draft.images[i] = e.target.value;
      row.querySelector("img").src = e.target.value;
    });
    row.querySelectorAll(".action-btn").forEach(btn => {
      btn.addEventListener("click", () => {
        const arr = editorState.draft.images;
        const act = btn.dataset.act;
        if (act === "remove") arr.splice(i, 1);
        else if (act === "up"   && i > 0)               [arr[i-1], arr[i]] = [arr[i], arr[i-1]];
        else if (act === "down" && i < arr.length - 1)  [arr[i+1], arr[i]] = [arr[i], arr[i+1]];
        renderEditorImages();
      });
    });
  });
}

/* Add image — by URL */
document.getElementById("edImageAdd").addEventListener("click", () => {
  const input = document.getElementById("edImageUrl");
  const url = input.value.trim();
  if (!url) return;
  editorState.draft.images = editorState.draft.images || [];
  editorState.draft.images.push(url);
  input.value = "";
  renderEditorImages();
});

/* Add image — by file upload (converted to base64 data URL).
   Warn on big files because base64 in localStorage is limited (~5MB total). */
document.getElementById("edImageFile").addEventListener("change", async (e) => {
  const files = Array.from(e.target.files || []);
  if (!files.length) return;
  const big = files.find(f => f.size > 1.5 * 1024 * 1024);
  if (big && !confirm(`"${big.name}" is ${(big.size/1024/1024).toFixed(1)} MB. Large images can slow down the app and may exceed browser storage. Continue?`)) {
    e.target.value = "";
    return;
  }
  for (const file of files) {
    const dataUrl = await new Promise((res, rej) => {
      const r = new FileReader();
      r.onload = () => res(r.result);
      r.onerror = rej;
      r.readAsDataURL(file);
    });
    editorState.draft.images.push(dataUrl);
  }
  e.target.value = "";
  renderEditorImages();
});

/* ---------- Comments pane ---------- */
function renderEditorComments() {
  const list = document.getElementById("edComments");
  const cs = editorState.draft.comments = editorState.draft.comments || [];
  document.getElementById("edCommentsCount").textContent = cs.length;
  if (!cs.length) {
    list.innerHTML = `<p class="ed-empty">No comments yet.</p>`;
    return;
  }
  /* Newest first display, but keep underlying array indices intact. */
  const idxs = cs.map((_, i) => i).reverse();
  list.innerHTML = idxs.map(i => {
    const c = cs[i];
    const date = new Date(c.at).toLocaleString();
    const initials = (c.author || "A").split(/\s+/).map(w => w[0]).join("").slice(0,2).toUpperCase();
    return `
      <div class="ed-comment" data-i="${i}">
        <div class="ed-comment-head">
          <span class="avatar">${escapeHtml(initials)}</span>
          <div>
            <b>${escapeHtml(c.author || "Agent")}</b>
            <p class="muted">${escapeHtml(date)}</p>
          </div>
          <button type="button" class="action-btn danger" data-act="del">Delete</button>
        </div>
        <p class="ed-comment-body">${escapeHtml(c.body)}</p>
      </div>
    `;
  }).join("");

  list.querySelectorAll(".ed-comment [data-act='del']").forEach(btn => {
    btn.addEventListener("click", (e) => {
      const i = +e.currentTarget.closest(".ed-comment").dataset.i;
      editorState.draft.comments.splice(i, 1);
      renderEditorComments();
    });
  });
}

document.getElementById("edCommentPost").addEventListener("click", () => {
  const ta = document.getElementById("edCommentBody");
  const body = ta.value.trim();
  if (!body) return;
  editorState.draft.comments = editorState.draft.comments || [];
  editorState.draft.comments.push({
    author: HOUSEATOR.getUser(),
    body,
    at: Date.now()
  });
  ta.value = "";
  renderEditorComments();
});

/* ---------- Save / Delete ---------- */
function gatherDraft() {
  const d = editorState.draft;
  d.title   = document.getElementById("edTitle").value.trim();
  d.address = document.getElementById("edAddress").value.trim();
  d.region  = document.getElementById("edRegion").value;
  d.type    = document.getElementById("edType").value;
  d.beds    = +document.getElementById("edBeds").value || 0;
  d.baths   = +document.getElementById("edBaths").value || 0;
  d.sqft    = +document.getElementById("edSqft").value || 0;
  d.year    = +document.getElementById("edYear").value || 0;
  d.price   = +document.getElementById("edPrice").value || 0;
  d.status  = document.getElementById("edStatus").value;
  d.lead    = Math.max(0, Math.min(100, +document.getElementById("edLead").value || 0));
  d.daysOnMarket = +document.getElementById("edDOM").value || 0;
  d.tags    = document.getElementById("edTags").value.split(",").map(s => s.trim()).filter(Boolean);
  d.desc    = document.getElementById("edDesc").value.trim();
  return d;
}

editorSave.addEventListener("click", () => {
  const d = gatherDraft();
  if (!d.title || !d.address) {
    alert("Title and address are required.");
    setEditorTab("info");
    return;
  }
  try {
    if (editorState.isNew) HOUSEATOR.addProperty(d);
    else HOUSEATOR.updateProperty(editorState.id, d);
  } catch (err) {
    alert("Couldn't save — browser storage may be full. Try removing very large uploaded images.");
    return;
  }
  closeEditor();
  renderDashboard();
});

editorDelete.addEventListener("click", () => {
  if (!confirm(`Delete listing "${editorState.draft.title}"? This cannot be undone.`)) return;
  HOUSEATOR.deleteProperty(editorState.id);
  closeEditor();
  renderDashboard();
});

/* Owner actions fired from the shared property modal (Edit / Sold / Remove).
   The modal lives in data.js and only knows the id + action; the actual
   work reuses the editor + CRUD flows already defined here. */
document.addEventListener("houseator:property-action", (e) => {
  const { id, action } = e.detail || {};
  if (id == null) return;
  const p = PROPERTIES.find(x => x.id === id);
  if (!p) return;
  if (action === "edit") {
    HOUSEATOR.closeModal();
    openEditor(id);
  } else if (action === "sold") {
    if (p.status === "Sold") return;            // already sold — disabled button, no-op
    HOUSEATOR.updateProperty(id, { status: "Sold" });
    HOUSEATOR.closeModal();
    renderDashboard();
  } else if (action === "remove") {
    if (!confirm(`Delete listing "${p.title}"? This cannot be undone.`)) return;
    HOUSEATOR.deleteProperty(id);
    HOUSEATOR.closeModal();
    renderDashboard();
  }
});

/* New listing button */
document.getElementById("newListingBtn").addEventListener("click", () => openEditor(null));

/* Reset listings (discard local edits) */
document.getElementById("resetListingsBtn").addEventListener("click", () => {
  if (!confirm("Discard all local edits and restore the seed listings? This will reload the page.")) return;
  HOUSEATOR.resetProperties();
  location.reload();
});

/* =========================================================
   CALENDAR + TO-DO BOARD
   ========================================================= */

/* ---------- Date helpers ---------- */
const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
const MONTHS_SHORT = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

function isoDate(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${dd}`;
}
function todayISO() { return isoDate(new Date()); }
function startOfWeek(d) {
  const x = new Date(d); x.setHours(0,0,0,0);
  x.setDate(x.getDate() - x.getDay()); /* Sunday */
  return x;
}
function fmtChipDate(iso) {
  if (!iso) return "";
  const [y, m, d] = iso.split("-").map(Number);
  return `${MONTHS_SHORT[m-1]} ${d}`;
}
function bytesShort(n) {
  if (n < 1024) return `${n} B`;
  if (n < 1024*1024) return `${(n/1024).toFixed(0)} KB`;
  return `${(n/1024/1024).toFixed(1)} MB`;
}

/* ---------- Filter state ---------- */
let todoFilter = "all";
let todoQuery = "";
let calCursor = (() => { const d = new Date(); d.setDate(1); d.setHours(0,0,0,0); return d; })();

/* ---------- Filtering ---------- */
function applyTodoFilter(list) {
  const today = todayISO();
  const wkStart = isoDate(startOfWeek(new Date()));
  const wkEnd = (() => { const x = new Date(); x.setDate(x.getDate() + (6 - x.getDay())); return isoDate(x); })();
  const me = HOUSEATOR.getUser();
  const q = todoQuery.trim().toLowerCase();

  return list.filter(t => {
    if (q && !`${t.title} ${t.description} ${t.assignee}`.toLowerCase().includes(q)) return false;
    switch (todoFilter) {
      case "mine":     return t.assignee === me;
      case "today":    return t.date === today;
      case "week":     return t.date && t.date >= wkStart && t.date <= wkEnd;
      case "overdue":  return t.date && t.date < today && t.status !== "done";
      case "attached": return (t.attachments || []).length > 0;
      default:         return true;
    }
  });
}

/* ---------- Render To-Do board ---------- */
function renderTodos() {
  const all = HOUSEATOR.listTodos();
  const filtered = applyTodoFilter(all);

  ["todo", "doing", "done"].forEach(status => {
    const items = filtered.filter(t => t.status === status);
    const wrap = document.querySelector(`[data-items="${status}"]`);
    document.querySelector(`[data-count="${status}"]`).textContent = items.length;

    if (!items.length) {
      wrap.innerHTML = `<p class="todo-empty">No tasks here.</p>`;
      return;
    }

    wrap.innerHTML = items.map(t => `
      <article class="todo-card${t.greyed ? ' greyed' : ''}" data-id="${t.id}">
        <header class="todo-card-head">
          <strong class="todo-card-title">${escapeHtml(t.title || "Untitled")}</strong>
          ${t.date ? `<span class="todo-date-chip" title="${t.date}">${fmtChipDate(t.date)}</span>` : ""}
        </header>
        ${t.description ? `<p class="todo-card-desc">${escapeHtml(t.description)}</p>` : ""}
        ${t.assignee ? `<p class="todo-card-meta">${escapeHtml(t.assignee)}</p>` : ""}
        ${(t.attachments || []).length ? `
          <div class="todo-card-attach">
            ${t.attachments.map((a, i) => `
              <a class="td-attach-chip" href="${a.dataUrl}" download="${escapeHtml(a.name)}" title="${escapeHtml(a.name)} • ${bytesShort(a.size || 0)}">📎 ${escapeHtml(a.name)}</a>
            `).join("")}
          </div>` : ""}
        <footer class="todo-card-actions">
          <button type="button" class="todo-act" data-act="grey"  title="${t.greyed ? "Restore" : "Grey out"}">${t.greyed ? "◐" : "○"}</button>
          <button type="button" class="todo-act" data-act="edit"  title="Edit">✎</button>
          <button type="button" class="todo-act" data-act="cal"   title="Set date">📅</button>
          <button type="button" class="todo-act" data-act="attach" title="Attach file">📎</button>
          <button type="button" class="todo-act danger" data-act="del" title="Delete">✕</button>
        </footer>
      </article>
    `).join("");
  });

  /* Bind card actions */
  document.querySelectorAll(".todo-card").forEach(card => {
    const id = parseInt(card.dataset.id, 10);
    card.querySelectorAll(".todo-act").forEach(btn => {
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        handleTodoAction(id, btn.dataset.act);
      });
    });
    /* Click card body (not actions) → open editor */
    card.addEventListener("click", (e) => {
      if (e.target.closest(".todo-act") || e.target.closest(".td-attach-chip")) return;
      openTaskEditor(id);
    });
  });

  renderMiniCal();
}

function handleTodoAction(id, act) {
  const t = HOUSEATOR.listTodos().find(x => x.id === id);
  if (!t) return;
  switch (act) {
    case "grey":
      HOUSEATOR.updateTodo(id, { greyed: !t.greyed });
      renderTodos();
      break;
    case "edit":
      openTaskEditor(id);
      break;
    case "del":
      if (confirm(`Delete task "${t.title}"?`)) {
        HOUSEATOR.deleteTodo(id);
        renderTodos();
      }
      break;
    case "cal":
      pickDateForTask(id);
      break;
    case "attach":
      pickAttachmentsForTask(id);
      break;
  }
}

/* ---------- Quick "set date" — inline picker on the card ---------- */
function pickDateForTask(id) {
  /* Toggle behaviour: clicking +Calendar a second time removes the picker. */
  const existing = document.querySelector(".todo-date-picker-active");
  if (existing) {
    const sameTask = existing.closest(".todo-card")?.dataset.id === String(id);
    existing.remove();
    if (sameTask) return;
  }
  const card = document.querySelector(`.todo-card[data-id="${id}"]`);
  if (!card) return;
  const t = HOUSEATOR.listTodos().find(x => x.id === id);
  if (!t) return;

  const picker = document.createElement("input");
  picker.type = "date";
  picker.className = "todo-date-picker-active";
  picker.value = t.date || todayISO();
  picker.addEventListener("change", () => {
    HOUSEATOR.updateTodo(id, { date: picker.value || null });
    renderTodos();
  });
  picker.addEventListener("keydown", (e) => {
    if (e.key === "Escape") picker.remove();
  });
  card.appendChild(picker);
  picker.focus();
  if (typeof picker.showPicker === "function") {
    try { picker.showPicker(); } catch (_) {}
  }
}

/* ---------- Quick "attach" action ---------- */
const quickAttachFile = document.getElementById("quickAttachFile");
let quickAttachTargetId = null;
quickAttachFile.addEventListener("change", async () => {
  if (quickAttachTargetId == null) return;
  const id = quickAttachTargetId;
  quickAttachTargetId = null;
  const files = Array.from(quickAttachFile.files || []);
  if (!files.length) return;
  const big = files.find(f => f.size > 1.5 * 1024 * 1024);
  if (big && !confirm(`"${big.name}" is ${(big.size/1024/1024).toFixed(1)} MB. Large files use browser storage. Continue?`)) {
    quickAttachFile.value = "";
    return;
  }
  const t = HOUSEATOR.listTodos().find(x => x.id === id);
  const newAtts = [...(t.attachments || [])];
  for (const file of files) {
    const dataUrl = await new Promise((res, rej) => {
      const r = new FileReader();
      r.onload = () => res(r.result);
      r.onerror = rej;
      r.readAsDataURL(file);
    });
    newAtts.push({ name: file.name, type: file.type, size: file.size, dataUrl });
  }
  HOUSEATOR.updateTodo(id, { attachments: newAtts });
  quickAttachFile.value = "";
  renderTodos();
});
function pickAttachmentsForTask(id) {
  quickAttachTargetId = id;
  quickAttachFile.click();
}

/* ---------- "Add Task" / filter UI ---------- */
document.getElementById("addTodoBtn").addEventListener("click", () => openTaskEditor(null));
document.querySelectorAll(".todo-filters .chip").forEach(btn => {
  btn.addEventListener("click", () => {
    document.querySelectorAll(".todo-filters .chip").forEach(c => c.classList.remove("active"));
    btn.classList.add("active");
    todoFilter = btn.dataset.todofilter;
    renderTodos();
  });
});
document.getElementById("todoSearch").addEventListener("input", (e) => {
  todoQuery = e.target.value;
  renderTodos();
});

/* =========================================================
   MINI CALENDAR
   ========================================================= */
function renderMiniCal() {
  const grid = document.getElementById("calMiniGrid");
  const label = document.getElementById("calMiniLabel");
  const y = calCursor.getFullYear();
  const m = calCursor.getMonth();
  label.textContent = `${MONTHS_SHORT[m]} ${y}`;

  const first = new Date(y, m, 1);
  const startOffset = first.getDay();
  const daysInMonth = new Date(y, m + 1, 0).getDate();

  const today = todayISO();
  const todos = HOUSEATOR.listTodos();
  const datesWithTasks = new Set(todos.filter(t => t.date).map(t => t.date));

  const cells = [];
  for (let i = 0; i < startOffset; i++) cells.push(`<button type="button" class="cmini-cell empty" disabled></button>`);
  for (let d = 1; d <= daysInMonth; d++) {
    const iso = `${y}-${String(m+1).padStart(2,"0")}-${String(d).padStart(2,"0")}`;
    const isToday = iso === today;
    const has = datesWithTasks.has(iso);
    cells.push(`
      <button type="button" class="cmini-cell${isToday ? " today" : ""}${has ? " hasTasks" : ""}" data-iso="${iso}">
        ${d}${has ? `<span class="cmini-dot"></span>` : ""}
      </button>
    `);
  }
  grid.innerHTML = cells.join("");
  grid.querySelectorAll(".cmini-cell:not([disabled])").forEach(btn => {
    btn.addEventListener("click", () => openCalendar(btn.dataset.iso));
  });
}
document.querySelectorAll("[data-cal-mini-nav]").forEach(b => {
  b.addEventListener("click", () => {
    calCursor.setMonth(calCursor.getMonth() + (+b.dataset.calMiniNav));
    renderMiniCal();
  });
});
document.getElementById("calExpandBtn").addEventListener("click", () => openCalendar(null));

/* =========================================================
   FULL CALENDAR MODAL
   ========================================================= */
const calendarModal = document.getElementById("calendarModal");
let calFullCursor = new Date(calCursor);

function openCalendar(focusIso) {
  if (focusIso) {
    const [y,m] = focusIso.split("-").map(Number);
    calFullCursor = new Date(y, m - 1, 1);
  } else {
    calFullCursor = new Date(calCursor);
  }
  renderFullCal();
  calendarModal.hidden = false;
  document.body.style.overflow = "hidden";
}
function closeCalendar() {
  calendarModal.hidden = true;
  document.body.style.overflow = "";
}
calendarModal.querySelectorAll("[data-cal-close]").forEach(el =>
  el.addEventListener("click", closeCalendar)
);
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape" && !calendarModal.hidden) closeCalendar();
});
document.querySelectorAll("[data-cal-fullnav]").forEach(b => {
  b.addEventListener("click", () => {
    calFullCursor.setMonth(calFullCursor.getMonth() + (+b.dataset.calFullnav));
    renderFullCal();
  });
});
document.getElementById("calToday").addEventListener("click", () => {
  calFullCursor = new Date();
  calFullCursor.setDate(1);
  renderFullCal();
});

function renderFullCal() {
  const y = calFullCursor.getFullYear();
  const m = calFullCursor.getMonth();
  document.getElementById("calLabel").textContent = `${MONTHS[m]} ${y}`;

  const first = new Date(y, m, 1);
  const startOffset = first.getDay();
  const daysInMonth = new Date(y, m + 1, 0).getDate();
  /* Pad to full weeks: 6 rows × 7 cols = 42 cells (always) */
  const totalCells = 42;
  const today = todayISO();
  const todos = HOUSEATOR.listTodos();
  const byDate = todos.reduce((acc, t) => {
    if (!t.date) return acc;
    (acc[t.date] = acc[t.date] || []).push(t);
    return acc;
  }, {});

  const grid = document.getElementById("calGrid");
  const cells = [];
  for (let i = 0; i < totalCells; i++) {
    const dayNum = i - startOffset + 1;
    const inMonth = dayNum >= 1 && dayNum <= daysInMonth;
    const iso = inMonth ? `${y}-${String(m+1).padStart(2,"0")}-${String(dayNum).padStart(2,"0")}` : null;
    const tasks = iso ? (byDate[iso] || []) : [];
    const isToday = iso === today;
    cells.push(`
      <div class="cal-cell${inMonth ? "" : " out"}${isToday ? " today" : ""}" ${iso ? `data-iso="${iso}"` : ""}>
        ${inMonth ? `<div class="cal-cell-num">${dayNum}</div>` : ""}
        <div class="cal-cell-tasks">
          ${tasks.slice(0, 3).map(t => `
            <button type="button" class="cal-task-chip${t.greyed ? " greyed" : ""} status-${t.status}" data-task="${t.id}" title="${escapeHtml(t.title)}">${escapeHtml(t.title)}</button>
          `).join("")}
          ${tasks.length > 3 ? `<span class="cal-more">+${tasks.length - 3} more</span>` : ""}
        </div>
      </div>
    `);
  }
  grid.innerHTML = cells.join("");

  /* Click empty area → new task on that day */
  grid.querySelectorAll(".cal-cell[data-iso]").forEach(cell => {
    cell.addEventListener("click", (e) => {
      if (e.target.closest(".cal-task-chip") || e.target.closest(".cal-more")) return;
      openTaskEditor(null, { date: cell.dataset.iso });
    });
  });
  /* Click task chip → edit task */
  grid.querySelectorAll(".cal-task-chip").forEach(chip => {
    chip.addEventListener("click", (e) => {
      e.stopPropagation();
      openTaskEditor(parseInt(chip.dataset.task, 10));
    });
  });
}

/* =========================================================
   TASK EDITOR MODAL
   ========================================================= */
const todoEditorModal = document.getElementById("todoEditorModal");
const tdTitle    = document.getElementById("todoEditorTitle");
const tdSaveBtn  = document.getElementById("tdSave");
const tdDelBtn   = document.getElementById("tdDelete");
const tdAttachFile = document.getElementById("tdAttachFile");
let tdState = { id: null, isNew: false, draft: null };

function emptyTask(prefill = {}) {
  return {
    title: "", description: "",
    status: "todo", greyed: false,
    date: null, assignee: HOUSEATOR.getUser(),
    attachments: [],
    ...prefill
  };
}

function openTaskEditor(id, prefill = {}) {
  if (id == null) {
    tdState = { id: null, isNew: true, draft: emptyTask(prefill) };
    tdTitle.textContent = "New Task";
    tdDelBtn.hidden = true;
  } else {
    const t = HOUSEATOR.listTodos().find(x => x.id === id);
    if (!t) return;
    tdState = { id, isNew: false, draft: JSON.parse(JSON.stringify(t)) };
    tdTitle.textContent = `Edit: ${t.title || "Task"}`;
    tdDelBtn.hidden = false;
  }
  fillTaskForm();
  todoEditorModal.hidden = false;
  document.body.style.overflow = "hidden";
}
function closeTaskEditor() {
  todoEditorModal.hidden = true;
  document.body.style.overflow = "";
}
todoEditorModal.querySelectorAll("[data-todo-close]").forEach(el =>
  el.addEventListener("click", closeTaskEditor)
);
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape" && !todoEditorModal.hidden) closeTaskEditor();
});

function fillTaskForm() {
  const d = tdState.draft;
  document.getElementById("tdTitle").value    = d.title || "";
  document.getElementById("tdDesc").value     = d.description || "";
  document.getElementById("tdStatus").value   = d.status || "todo";
  document.getElementById("tdDate").value     = d.date || "";
  document.getElementById("tdAssignee").value = d.assignee || "";
  renderTaskAttachments();
}

function renderTaskAttachments() {
  const wrap = document.getElementById("tdAttachments");
  const atts = tdState.draft.attachments = tdState.draft.attachments || [];
  if (!atts.length) {
    wrap.innerHTML = `<p class="ed-empty">No attachments yet.</p>`;
    return;
  }
  wrap.innerHTML = atts.map((a, i) => `
    <div class="td-attach-row" data-i="${i}">
      <a class="td-attach-chip" href="${a.dataUrl}" download="${escapeHtml(a.name)}">📎 ${escapeHtml(a.name)}</a>
      <span class="muted small">${bytesShort(a.size || 0)}</span>
      <button type="button" class="action-btn danger" data-act="rm-att">✕</button>
    </div>
  `).join("");
  wrap.querySelectorAll("[data-act='rm-att']").forEach(btn => {
    btn.addEventListener("click", () => {
      const i = +btn.closest(".td-attach-row").dataset.i;
      tdState.draft.attachments.splice(i, 1);
      renderTaskAttachments();
    });
  });
}

tdAttachFile.addEventListener("change", async (e) => {
  const files = Array.from(e.target.files || []);
  if (!files.length) return;
  const big = files.find(f => f.size > 1.5 * 1024 * 1024);
  if (big && !confirm(`"${big.name}" is ${(big.size/1024/1024).toFixed(1)} MB. Continue?`)) {
    e.target.value = "";
    return;
  }
  for (const file of files) {
    const dataUrl = await new Promise((res, rej) => {
      const r = new FileReader();
      r.onload = () => res(r.result);
      r.onerror = rej;
      r.readAsDataURL(file);
    });
    tdState.draft.attachments.push({ name: file.name, type: file.type, size: file.size, dataUrl });
  }
  e.target.value = "";
  renderTaskAttachments();
});

function gatherTaskDraft() {
  const d = tdState.draft;
  d.title       = document.getElementById("tdTitle").value.trim();
  d.description = document.getElementById("tdDesc").value.trim();
  d.status      = document.getElementById("tdStatus").value;
  d.date        = document.getElementById("tdDate").value || null;
  d.assignee    = document.getElementById("tdAssignee").value.trim() || HOUSEATOR.getUser();
  return d;
}

tdSaveBtn.addEventListener("click", () => {
  const d = gatherTaskDraft();
  if (!d.title) { alert("Title is required."); return; }
  try {
    if (tdState.isNew) HOUSEATOR.addTodo(d);
    else HOUSEATOR.updateTodo(tdState.id, d);
  } catch (err) {
    alert("Couldn't save — browser storage may be full.");
    return;
  }
  closeTaskEditor();
  renderTodos();
  if (!calendarModal.hidden) renderFullCal();
});

tdDelBtn.addEventListener("click", () => {
  if (!confirm(`Delete task "${tdState.draft.title}"?`)) return;
  HOUSEATOR.deleteTodo(tdState.id);
  closeTaskEditor();
  renderTodos();
  if (!calendarModal.hidden) renderFullCal();
});

/* =========================================================
   COLUMN SCROLL CONTROLS — small grey up/down arrows in each column header
   The columns themselves stay fixed in height (~4 cards visible); items
   beyond that scroll inside the column. Arrows nudge the scroll by one card.
   ========================================================= */
const CARD_STEP = 124; /* approx card height + gap */
document.querySelectorAll("[data-col-scroll]").forEach(btn => {
  btn.addEventListener("click", (e) => {
    e.stopPropagation();
    const col = btn.closest(".todo-col");
    const items = col.querySelector(".todo-items");
    if (!items) return;
    const dir = btn.dataset.colScroll === "up" ? -1 : 1;
    items.scrollBy({ top: dir * CARD_STEP, behavior: "smooth" });
  });
});

/* Initial render */
renderTodos();

/* =========================================================
   GENERIC COMMENTS MODAL — used by inventory rows and lead cards.
   The caller passes a context describing the entity + CRUD callbacks.
   ========================================================= */
const commentsModal     = document.getElementById("commentsModal");
const commentsList      = document.getElementById("commentsList");
const commentsTitle     = document.getElementById("commentsTitle");
const commentsEyebrow   = document.getElementById("commentsEyebrow");
const commentsSubtitle  = document.getElementById("commentsSubtitle");
const commentsBodyInput = document.getElementById("commentsBody");
const commentsAddBtn    = document.getElementById("commentsAddBtn");

let commentsContext = null;

function openCommentsModal(ctx) {
  commentsContext = ctx;
  commentsEyebrow.textContent = ctx.eyebrow || "Comments";
  commentsTitle.textContent = ctx.title || "—";
  commentsSubtitle.textContent = ctx.subtitle || "";
  commentsBodyInput.value = "";
  renderCommentsList();
  commentsModal.hidden = false;
  document.body.style.overflow = "hidden";
  commentsBodyInput.focus();
}
function closeCommentsModal() {
  commentsModal.hidden = true;
  document.body.style.overflow = "";
  commentsContext = null;
}
commentsModal.querySelectorAll("[data-comments-close]").forEach(el =>
  el.addEventListener("click", closeCommentsModal)
);
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape" && !commentsModal.hidden) closeCommentsModal();
});

function renderCommentsList() {
  if (!commentsContext) return;
  const entity = commentsContext.getEntity();
  const cs = entity?.comments || [];
  if (!cs.length) {
    commentsList.innerHTML = `<p class="ed-empty">No comments yet — add the first one below.</p>`;
    return;
  }
  /* Newest at top, with original IDs preserved on the DOM */
  const ordered = [...cs].sort((a, b) => (b.at || 0) - (a.at || 0));
  commentsList.innerHTML = ordered.map(c => {
    const initials = (c.author || "A").split(/\s+/).map(w => w[0]).join("").slice(0,2).toUpperCase();
    const date = new Date(c.at || Date.now()).toLocaleString();
    return `
      <article class="cm-card${c.greyed ? " greyed" : ""}" data-cid="${c.id}">
        <header class="cm-head">
          <span class="avatar">${escapeHtml(initials)}</span>
          <div>
            <b>${escapeHtml(c.author || "Agent")}</b>
            <p class="muted small">${escapeHtml(date)}</p>
          </div>
        </header>
        <p class="cm-body">${escapeHtml(c.body)}</p>
        <footer class="cm-actions">
          <button type="button" class="cm-btn" data-cm-act="grey" title="${c.greyed ? "Restore" : "Grey out"}">
            ${c.greyed ? "◐ Restore" : "○ Grey out"}
          </button>
          <button type="button" class="cm-btn danger" data-cm-act="del" title="Delete comment">✕ Delete</button>
        </footer>
      </article>
    `;
  }).join("");

  commentsList.querySelectorAll(".cm-card").forEach(card => {
    const cid = parseInt(card.dataset.cid, 10);
    card.querySelectorAll("[data-cm-act]").forEach(btn => {
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        const act = btn.dataset.cmAct;
        if (act === "grey") {
          const entity = commentsContext.getEntity();
          const c = entity.comments.find(c => c.id === cid);
          commentsContext.updateFn(commentsContext.id, cid, { greyed: !c.greyed });
          renderCommentsList();
          if (commentsContext.afterChange) commentsContext.afterChange();
        } else if (act === "del") {
          if (!confirm("Delete this comment?")) return;
          commentsContext.deleteFn(commentsContext.id, cid);
          renderCommentsList();
          if (commentsContext.afterChange) commentsContext.afterChange();
        }
      });
    });
  });
}

commentsAddBtn.addEventListener("click", () => {
  if (!commentsContext) return;
  const body = commentsBodyInput.value.trim();
  if (!body) return;
  commentsContext.addFn(commentsContext.id, body, HOUSEATOR.getUser());
  commentsBodyInput.value = "";
  renderCommentsList();
  if (commentsContext.afterChange) commentsContext.afterChange();
});
/* Cmd/Ctrl + Enter to post */
commentsBodyInput.addEventListener("keydown", (e) => {
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") commentsAddBtn.click();
});

/* ---------- Inventory: open comments for a property ---------- */
function openCommentsForProperty(propId) {
  const p = HOUSEATOR.PROPERTIES.find(x => x.id === propId);
  if (!p) return;
  openCommentsModal({
    type: "property",
    id: propId,
    eyebrow: "Property comments",
    title: p.title,
    subtitle: p.address,
    getEntity: () => HOUSEATOR.PROPERTIES.find(x => x.id === propId),
    addFn:    HOUSEATOR.addPropertyComment,
    updateFn: HOUSEATOR.updatePropertyComment,
    deleteFn: HOUSEATOR.deletePropertyComment,
    afterChange: renderDashboard
  });
}

/* =========================================================
   LEADS — dynamic rendering + filter chips + Add Lead modal
   ========================================================= */
const leadsListEl = document.getElementById("leadsList");
let leadFilter = "all";
let leadView = (() => {
  try { return localStorage.getItem("houseator_lead_view") || "cards"; }
  catch (_) { return "cards"; }
})();
let leadSort = (() => {
  try {
    const s = JSON.parse(localStorage.getItem("houseator_lead_sort") || "null");
    if (s && s.key && (s.dir === "asc" || s.dir === "desc")) return s;
  } catch (_) {}
  return { key: "name", dir: "asc" };
})();

/* Sort leads — buyers sink to the bottom (above greyed), greyed sinks
   below buyers. Order: active non-buyer → buyer non-greyed → greyed. */
function sortLeads(leads, sort) {
  const STATUS_ORDER = { hot: 0, warm: 1, cold: 2 };
  const compare = (a, b) => {
    let av, bv;
    if (sort.key === "status") {
      av = STATUS_ORDER[a.status] ?? 99;
      bv = STATUS_ORDER[b.status] ?? 99;
    } else {
      av = String(a[sort.key] ?? "").toLowerCase();
      bv = String(b[sort.key] ?? "").toLowerCase();
    }
    if (av < bv) return sort.dir === "asc" ? -1 : 1;
    if (av > bv) return sort.dir === "asc" ?  1 : -1;
    return 0;
  };
  const active = leads.filter(l => !l.greyed && !l.isBuyer).sort(compare);
  const buyers = leads.filter(l => !l.greyed &&  l.isBuyer).sort(compare);
  const greyed = leads.filter(l =>  l.greyed).sort(compare);
  return [...active, ...buyers, ...greyed];
}

/* Header row markup for the list view's sortable columns */
function leadListHeadHtml() {
  const col = (key, label) => {
    const isActive = leadSort.key === key;
    const dir = isActive ? leadSort.dir : "";
    return `<button type="button" class="lead-sort-btn${isActive ? " active" : ""}" data-sort="${key}" data-dir="${dir}">${label}</button>`;
  };
  return `
    <header class="lead-list-head">
      <span></span>
      ${col("name", "Name")}
      ${col("status", "Status")}
      ${col("phone", "Phone")}
      ${col("email", "Email")}
      ${col("source", "Source")}
      <span></span>
    </header>
  `;
}

function setLeadView(view) {
  if (view !== "cards" && view !== "list") view = "cards";
  leadView = view;
  try { localStorage.setItem("houseator_lead_view", view); } catch (_) {}
  /* Update toggle UI */
  document.querySelectorAll("#leadViewToggle [data-leadview]").forEach(btn => {
    const isActive = btn.dataset.leadview === view;
    btn.classList.toggle("active", isActive);
    btn.setAttribute("aria-checked", isActive ? "true" : "false");
  });
  if (leadsListEl) {
    leadsListEl.classList.toggle("view-list", view === "list");
    leadsListEl.classList.toggle("view-cards", view === "cards");
  }
  renderLeads();
}

/* Card markup (existing layout) */
function leadCardHtml(lead) {
  const initials = (lead.name || "A").split(/\s+/).map(w => w[0]).join("").slice(0,2).toUpperCase();
  const visibleComments = (lead.comments || []).filter(c => !c.greyed).length;
  return `
    <article class="lead-card status-${lead.status}${lead.greyed ? " greyed" : ""}${lead.isBuyer ? " is-buyer" : ""}" data-id="${lead.id}">
      <header class="lead-card-head">
        <span class="avatar">${escapeHtml(initials)}</span>
        <div class="lead-name">
          <b>${escapeHtml(lead.name || "Untitled lead")}</b>
          ${lead.interestedIn ? `<p class="muted small">${escapeHtml(lead.interestedIn)}</p>` : ""}
        </div>
        ${lead.isBuyer ? `<span class="lead-buyer-badge" title="Converted to Buyer contact">✓ Buyer</span>` : ""}
        <span class="lead-status-pill status-${lead.status}">${lead.status}</span>
      </header>
      <div class="lead-card-meta">
        ${lead.email ? `<span>✉ ${escapeHtml(lead.email)}</span>` : ""}
        ${lead.phone ? `<span>☎ ${escapeHtml(lead.phone)}</span>` : ""}
        ${lead.source ? `<span class="lead-source">${escapeHtml(lead.source)}</span>` : ""}
      </div>
      <footer class="lead-card-actions">
        <button type="button" class="action-btn" data-lead-act="comments">
          💬 ${visibleComments} ${visibleComments === 1 ? "Comment" : "Comments"}
        </button>
        <button type="button" class="action-btn" data-lead-act="grey">${lead.greyed ? "◐ Restore" : "○ Grey out"}</button>
        <button type="button" class="action-btn" data-lead-act="edit">✎ Edit</button>
        <button type="button" class="action-btn danger" data-lead-act="del">✕ Delete</button>
      </footer>
    </article>
  `;
}

/* Compact row markup for list view */
function leadRowHtml(lead) {
  const initials = (lead.name || "A").split(/\s+/).map(w => w[0]).join("").slice(0,2).toUpperCase();
  const visibleComments = (lead.comments || []).filter(c => !c.greyed).length;
  return `
    <article class="lead-row status-${lead.status}${lead.greyed ? " greyed" : ""}${lead.isBuyer ? " is-buyer" : ""}" data-id="${lead.id}">
      <span class="avatar lead-row-avatar">${escapeHtml(initials)}</span>
      <div class="lead-row-name">
        <b>${escapeHtml(lead.name || "Untitled lead")}</b>
        ${lead.interestedIn ? `<span class="muted small">${escapeHtml(lead.interestedIn)}</span>` : ""}
      </div>
      <span class="lead-status-pill status-${lead.status}${lead.isBuyer ? " is-buyer" : ""}">${lead.isBuyer ? "✓ Buyer" : lead.status}</span>
      <span class="lead-row-phone">${lead.phone ? "☎ " + escapeHtml(lead.phone) : "<span class='muted'>—</span>"}</span>
      <span class="lead-row-email">${lead.email ? escapeHtml(lead.email) : "<span class='muted'>—</span>"}</span>
      <span class="lead-row-source">${lead.source ? escapeHtml(lead.source) : ""}</span>
      <div class="lead-row-actions">
        <button type="button" class="action-btn" data-lead-act="comments" title="${visibleComments} ${visibleComments === 1 ? "comment" : "comments"}">💬 ${visibleComments}</button>
        <button type="button" class="action-btn" data-lead-act="grey" title="${lead.greyed ? "Restore" : "Grey out"}">${lead.greyed ? "◐" : "○"}</button>
        <button type="button" class="action-btn" data-lead-act="edit" title="Edit">✎</button>
        <button type="button" class="action-btn danger" data-lead-act="del" title="Delete">✕</button>
      </div>
    </article>
  `;
}

/* Wire the view-toggle once */
document.querySelectorAll("#leadViewToggle [data-leadview]").forEach(btn => {
  btn.addEventListener("click", () => setLeadView(btn.dataset.leadview));
});

/* Sort-header clicks (delegated to the list container so they survive re-renders) */
if (leadsListEl && !leadsListEl.dataset.sortWired) {
  leadsListEl.dataset.sortWired = "1";
  leadsListEl.addEventListener("click", (e) => {
    const sortBtn = e.target.closest(".lead-sort-btn");
    if (!sortBtn) return;
    e.stopPropagation();
    const key = sortBtn.dataset.sort;
    if (leadSort.key === key) {
      leadSort.dir = leadSort.dir === "asc" ? "desc" : "asc";
    } else {
      leadSort = { key, dir: "asc" };
    }
    try { localStorage.setItem("houseator_lead_sort", JSON.stringify(leadSort)); } catch (_) {}
    renderLeads();
  });
}

/* Apply persisted view on page load (also updates toggle UI state) */
setLeadView(leadView);

function renderLeads() {
  if (!leadsListEl) return;
  const all = HOUSEATOR.listLeads();
  const filtered = all.filter(l => {
    if (leadFilter === "all")    return true;
    if (leadFilter === "active") return !l.greyed;
    return l.status === leadFilter;
  });

  if (!filtered.length) {
    leadsListEl.innerHTML = `<p class="ed-empty">No leads here yet — click "+ Add Lead" to start.</p>`;
    return;
  }

  if (leadView === "list") {
    const sorted = sortLeads(filtered, leadSort);
    leadsListEl.innerHTML = leadListHeadHtml() + sorted.map(leadRowHtml).join("");
  } else {
    /* Cards order: active → buyers → greyed (mirrors the list view) */
    const sortedCards = [
      ...filtered.filter(l => !l.greyed && !l.isBuyer),
      ...filtered.filter(l => !l.greyed &&  l.isBuyer),
      ...filtered.filter(l =>  l.greyed)
    ];
    leadsListEl.innerHTML = sortedCards.map(leadCardHtml).join("");
  }

  /* Bind actions on whichever element type is rendered */
  leadsListEl.querySelectorAll(".lead-card, .lead-row").forEach(card => {
    const id = parseInt(card.dataset.id, 10);
    card.querySelectorAll("[data-lead-act]").forEach(btn => {
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        const act = btn.dataset.leadAct;
        const lead = HOUSEATOR.listLeads().find(l => l.id === id);
        if (!lead) return;
        if (act === "comments") {
          openCommentsModal({
            type: "lead",
            id,
            eyebrow: "Lead comments",
            title: lead.name,
            subtitle: lead.interestedIn,
            getEntity: () => HOUSEATOR.listLeads().find(l => l.id === id),
            addFn:    HOUSEATOR.addLeadComment,
            updateFn: HOUSEATOR.updateLeadComment,
            deleteFn: HOUSEATOR.deleteLeadComment,
            afterChange: renderLeads
          });
        } else if (act === "grey") {
          HOUSEATOR.updateLead(id, { greyed: !lead.greyed });
          renderLeads();
        } else if (act === "edit") {
          openLeadEditor(id);
        } else if (act === "del") {
          if (confirm(`Delete lead "${lead.name}"? This cannot be undone.`)) {
            HOUSEATOR.deleteLead(id);
            renderLeads();
          }
        }
      });
    });
  });
}

document.querySelectorAll("#leadFilters .chip").forEach(btn => {
  btn.addEventListener("click", () => {
    document.querySelectorAll("#leadFilters .chip").forEach(c => c.classList.remove("active"));
    btn.classList.add("active");
    leadFilter = btn.dataset.leadfilter;
    renderLeads();
  });
});

document.getElementById("addLeadBtn").addEventListener("click", () => openLeadEditor(null));

/* Lead editor modal */
const leadEditorModal = document.getElementById("leadEditorModal");
const leadEditorTitle = document.getElementById("leadEditorTitle");
const ldDeleteBtn = document.getElementById("ldDelete");
const ldSaveBtn   = document.getElementById("ldSave");
let leadDraft = { id: null, isNew: false, draft: null };

function openLeadEditor(id, opts) {
  /* opts.defaults — initial field values applied to NEW leads only.
     opts.titleNew — overrides the "Add Lead" title (e.g. "Add Sale"). */
  const o = opts || {};
  if (id == null) {
    const baseDraft = {
      name: "", email: "", phone: "", interestedIn: "",
      status: "warm", source: "", isBuyer: false
    };
    leadDraft = {
      id: null, isNew: true,
      draft: Object.assign(baseDraft, o.defaults || {})
    };
    leadEditorTitle.textContent = o.titleNew || "Add Lead";
    ldDeleteBtn.hidden = true;
  } else {
    const l = HOUSEATOR.listLeads().find(x => x.id === id);
    if (!l) return;
    leadDraft = { id, isNew: false, draft: JSON.parse(JSON.stringify(l)) };
    leadEditorTitle.textContent = `Edit: ${l.name || "Lead"}`;
    ldDeleteBtn.hidden = false;
  }
  fillLeadForm();
  leadEditorModal.hidden = false;
  document.body.style.overflow = "hidden";
}
function closeLeadEditor() {
  leadEditorModal.hidden = true;
  document.body.style.overflow = "";
}
leadEditorModal.querySelectorAll("[data-lead-close]").forEach(el =>
  el.addEventListener("click", closeLeadEditor)
);
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape" && !leadEditorModal.hidden) closeLeadEditor();
});

function fillLeadForm() {
  const d = leadDraft.draft;
  document.getElementById("ldName").value     = d.name || "";
  document.getElementById("ldEmail").value    = d.email || "";
  document.getElementById("ldPhone").value    = d.phone || "";
  populateInterestedOptions(d.interestedIn || "");
  document.getElementById("ldStatus").value   = d.status || "warm";
  setSourceValue(d.source || "");
  document.getElementById("ldIsBuyer").checked = !!d.isBuyer;
}

/* Curated source list + user-added custom sources (persisted to localStorage).
   The dropdown also shows "+ Add new source…" which prompts for a fresh name. */
const STANDARD_SOURCES = [
  "Friend", "Open House", "Website Form", "Cold Call",
  "Colleague", "Colleague - Sarah", "Colleague - Tim"
];
const CUSTOM_SOURCES_KEY = "houseator_lead_sources_v1";

function loadCustomSources() {
  try {
    const arr = JSON.parse(localStorage.getItem(CUSTOM_SOURCES_KEY) || "[]");
    return Array.isArray(arr) ? arr.filter(s => typeof s === "string" && s.trim()) : [];
  } catch (_) { return []; }
}
function saveCustomSources(arr) {
  try { localStorage.setItem(CUSTOM_SOURCES_KEY, JSON.stringify(arr)); } catch (_) {}
}

function setSourceValue(value) {
  const select = document.getElementById("ldSource");
  if (!select) return;
  const custom = loadCustomSources();
  const known = new Set([...STANDARD_SOURCES, ...custom]);

  let html = `<option value="">— Select source —</option>`;
  html += STANDARD_SOURCES.map(s => `<option>${escapeHtml(s)}</option>`).join("");
  if (custom.length) {
    html += `<optgroup label="Custom">`;
    html += custom.map(s => `<option>${escapeHtml(s)}</option>`).join("");
    html += `</optgroup>`;
  }
  html += `<option value="__add_new__">+ Add new source…</option>`;
  /* Preserve legacy / archived values that aren't in any list */
  if (value && !known.has(value)) {
    html += `<option value="${escapeHtml(value)}" data-legacy="1">${escapeHtml(value)} (legacy)</option>`;
  }
  select.innerHTML = html;
  select.value = value || "";
}

/* One-time setup for the "+ Add new source…" behavior. Stores the last
   real selection so we can revert if the user cancels the prompt. */
(function wireSourceSelect() {
  const select = document.getElementById("ldSource");
  if (!select || select.dataset.wired) return;
  select.dataset.wired = "1";
  let lastValue = select.value || "";
  select.addEventListener("focus", () => { lastValue = select.value; });
  select.addEventListener("change", () => {
    if (select.value !== "__add_new__") {
      lastValue = select.value;
      return;
    }
    const name = (prompt("Enter a new source name:") || "").trim();
    if (!name) {
      setSourceValue(lastValue);
      return;
    }
    /* If it matches an existing entry, just select it; otherwise persist new */
    if (![...STANDARD_SOURCES, ...loadCustomSources()].includes(name)) {
      const arr = loadCustomSources();
      arr.push(name);
      saveCustomSources(arr);
    }
    setSourceValue(name);
    lastValue = name;
  });
})();

/* Split an address into HTML with the city (second-to-last comma-separated
   piece) wrapped in <strong>. Returns sanitized HTML. */
function addressHtmlWithBoldCity(address) {
  const parts = String(address || "").split(",").map(s => s.trim()).filter(Boolean);
  if (parts.length < 2) return escapeHtml(address || "");
  const stateAndZip = parts[parts.length - 1];
  const city = parts[parts.length - 2];
  const street = parts.slice(0, -2).join(", ");
  const pieces = [];
  if (street) pieces.push(escapeHtml(street));
  pieces.push(`<strong>${escapeHtml(city)}</strong>`);
  pieces.push(escapeHtml(stateAndZip));
  return pieces.join(", ");
}

/* Build the "Interested in" dropdown from current non-sold listings.
   Custom dropdown so we can render HTML (bolded city) inside each option. */
function populateInterestedOptions(currentValue) {
  const optionsEl = document.getElementById("ldInterestOptions");
  const hidden    = document.getElementById("ldInterest");
  const triggerVal = document.querySelector("#ldInterestTrigger .cs-value");
  if (!optionsEl || !hidden || !triggerVal) return;

  const available = (HOUSEATOR.PROPERTIES || []).filter(p => p.status !== "Sold");

  const rows = [
    `<button type="button" class="cs-option" role="option" data-value="" data-label="— Select a property —">— Select a property —</button>`
  ];
  available.forEach(p => {
    const addrHtml  = addressHtmlWithBoldCity(p.address);
    const plainLabel = `${p.title} — ${p.address}`;
    rows.push(
      `<button type="button" class="cs-option" role="option" data-value="${escapeHtml(p.title)}" data-label="${escapeHtml(plainLabel)}">` +
        `<span class="cs-opt-title">${escapeHtml(p.title)}</span>` +
        `<span class="cs-opt-addr">${addrHtml}</span>` +
      `</button>`
    );
  });

  /* Fallback option if the lead points to a property no longer in the
     active list (sold or deleted). Keeps the value intact. */
  const exists = available.some(p => p.title === currentValue);
  if (currentValue && !exists) {
    const archived = `${escapeHtml(currentValue)} — <em>(archived)</em>`;
    rows.push(
      `<button type="button" class="cs-option cs-option-archived" role="option" data-value="${escapeHtml(currentValue)}" data-label="${escapeHtml(currentValue)} — (archived)">` +
        `<span class="cs-opt-title">${archived}</span>` +
      `</button>`
    );
  }

  optionsEl.innerHTML = rows.join("");
  hidden.value = currentValue || "";

  /* Update trigger label */
  let labelText = "— Select a property —";
  if (currentValue) {
    const matched = available.find(p => p.title === currentValue);
    if (matched) labelText = `${matched.title} — ${matched.address}`;
    else labelText = `${currentValue} — (archived)`;
  }
  triggerVal.textContent = labelText;
}

/* One-time wire-up for the custom dropdown's open/close and option clicks.
   Uses delegation on the options container so it survives repopulation. */
(function wireInterestedSelect() {
  const wrap     = document.getElementById("ldInterestSelect");
  const trigger  = document.getElementById("ldInterestTrigger");
  const optionsEl = document.getElementById("ldInterestOptions");
  const hidden   = document.getElementById("ldInterest");
  if (!wrap || !trigger || !optionsEl || !hidden) return;
  if (wrap.dataset.wired) return;
  wrap.dataset.wired = "1";

  function openDD() {
    optionsEl.hidden = false;
    trigger.classList.add("open");
    trigger.setAttribute("aria-expanded", "true");
  }
  function closeDD() {
    optionsEl.hidden = true;
    trigger.classList.remove("open");
    trigger.setAttribute("aria-expanded", "false");
  }

  trigger.addEventListener("click", (e) => {
    e.stopPropagation();
    if (optionsEl.hidden) openDD(); else closeDD();
  });

  optionsEl.addEventListener("click", (e) => {
    const opt = e.target.closest(".cs-option");
    if (!opt) return;
    e.stopPropagation();
    hidden.value = opt.dataset.value || "";
    const valSpan = trigger.querySelector(".cs-value");
    if (valSpan) valSpan.textContent = opt.dataset.label || "— Select a property —";
    closeDD();
  });

  document.addEventListener("click", (e) => {
    if (!e.target.closest("#ldInterestSelect") && !optionsEl.hidden) closeDD();
  });
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && !optionsEl.hidden) closeDD();
  });
})();
function gatherLeadDraft() {
  const d = leadDraft.draft;
  d.name         = document.getElementById("ldName").value.trim();
  d.email        = document.getElementById("ldEmail").value.trim();
  d.phone        = document.getElementById("ldPhone").value.trim();
  d.interestedIn = document.getElementById("ldInterest").value.trim();
  d.status       = document.getElementById("ldStatus").value;
  d.source       = document.getElementById("ldSource").value.trim();
  d.isBuyer      = document.getElementById("ldIsBuyer").checked;
  return d;
}

/* When a lead is flagged as a Buyer, mirror it into a Contact (role=Buyer)
   so it shows up in the Contacts directory. The lead remembers the contact's
   id via `linkedContactId` so repeated saves UPDATE the same contact rather
   than creating duplicates. If no link exists yet, we try matching on
   email/phone first before creating a new one. */
function syncLeadToBuyerContact(lead) {
  const contacts = HOUSEATOR.listContacts();
  let target = null;
  if (lead.linkedContactId) {
    target = contacts.find(c => c.id === lead.linkedContactId) || null;
  }
  if (!target) {
    target = contacts.find(c =>
      (lead.email && c.email && c.email.toLowerCase() === lead.email.toLowerCase()) ||
      (lead.phone && c.phone && c.phone === lead.phone)
    ) || null;
  }
  if (target) {
    HOUSEATOR.updateContact(target.id, {
      name: lead.name || target.name,
      email: lead.email || target.email,
      phone: lead.phone || target.phone,
      role: "Buyer"
    });
    return target.id;
  }
  const created = HOUSEATOR.addContact({
    name: lead.name,
    email: lead.email,
    phone: lead.phone,
    role: "Buyer",
    company: ""
  });
  return created.id;
}

ldSaveBtn.addEventListener("click", () => {
  const d = gatherLeadDraft();
  if (!d.name) { alert("Name is required."); return; }

  /* Stamp the moment the lead first becomes a Buyer — drives the Sales
     section's year/month filters. Clear it if the buyer flag is removed. */
  if (d.isBuyer && !d.becameBuyerAt) d.becameBuyerAt = Date.now();
  if (!d.isBuyer) d.becameBuyerAt = null;

  let savedLead;
  if (leadDraft.isNew) savedLead = HOUSEATOR.addLead(d);
  else                 savedLead = HOUSEATOR.updateLead(leadDraft.id, d);

  /* If marked Buyer, push into Contacts and remember the link */
  if (d.isBuyer && savedLead) {
    const contactId = syncLeadToBuyerContact(savedLead);
    if (contactId && contactId !== savedLead.linkedContactId) {
      HOUSEATOR.updateLead(savedLead.id, { linkedContactId: contactId });
    }
    /* Flip the linked property to Sold — every "set buyer" save should
       reflect on inventory, regardless of the property's previous state.
       If that property was the active Feature, prompt for a replacement. */
    if (savedLead.interestedIn) {
      const prop = HOUSEATOR.PROPERTIES.find(p => p.title === savedLead.interestedIn);
      if (prop && prop.status !== "Sold") {
        const wasFeature = prop.id === HOUSEATOR.getFeatureId();
        HOUSEATOR.updateProperty(prop.id, { status: "Sold" });
        if (wasFeature) setTimeout(() => openFeaturePicker("sold"), 250);
      }
    }
  }

  closeLeadEditor();
  renderLeads();
  if (typeof renderContacts === "function") renderContacts();
  if (typeof renderSales === "function")    renderSales();
  if (typeof renderDashboard === "function") renderDashboard();
});
ldDeleteBtn.addEventListener("click", () => {
  if (!confirm(`Delete lead "${leadDraft.draft.name}"?`)) return;
  HOUSEATOR.deleteLead(leadDraft.id);
  closeLeadEditor();
  renderLeads();
});

renderLeads();

/* =========================================================
   CONTACTS — directory of clients, vendors, partners.
   Cards expose: phone (prominent), comments, grey-out, edit, delete.
   ========================================================= */
const contactsListEl = document.getElementById("contactsList");
let contactFilter = "all";
let contactView = (() => {
  try { return localStorage.getItem("houseator_contact_view") || "cards"; }
  catch (_) { return "cards"; }
})();
let contactSort = (() => {
  try {
    const s = JSON.parse(localStorage.getItem("houseator_contact_sort") || "null");
    if (s && s.key && (s.dir === "asc" || s.dir === "desc")) return s;
  } catch (_) {}
  return { key: "name", dir: "asc" };
})();

const VENDOR_ROLES = new Set([
  "Inspector", "Title Officer", "Mortgage Lender", "Photographer",
  "Pest Control", "Contractor", "Vendor", "Other"
]);

function roleClass(role) {
  return (role || "other").toLowerCase().replace(/[^a-z]+/g, "-");
}

/* Sort contacts — greyed always sink to the bottom regardless of sort. */
function sortContacts(contacts, sort) {
  const compare = (a, b) => {
    const av = String(a[sort.key] == null ? "" : a[sort.key]).toLowerCase();
    const bv = String(b[sort.key] == null ? "" : b[sort.key]).toLowerCase();
    if (av < bv) return sort.dir === "asc" ? -1 : 1;
    if (av > bv) return sort.dir === "asc" ?  1 : -1;
    return 0;
  };
  const active = contacts.filter(c => !c.greyed).sort(compare);
  const greyed = contacts.filter(c =>  c.greyed).sort(compare);
  return [...active, ...greyed];
}

/* Header row for the list view */
function contactListHeadHtml() {
  const col = (key, label) => {
    const isActive = contactSort.key === key;
    const dir = isActive ? contactSort.dir : "";
    return `<button type="button" class="lead-sort-btn${isActive ? " active" : ""}" data-contact-sort="${key}" data-dir="${dir}">${label}</button>`;
  };
  return `
    <header class="lead-list-head contact-list-head">
      <span></span>
      ${col("name", "Name")}
      ${col("role", "Role")}
      ${col("phone", "Phone")}
      ${col("email", "Email")}
      ${col("company", "Company")}
      <span></span>
    </header>
  `;
}

function setContactView(view) {
  if (view !== "cards" && view !== "list") view = "cards";
  contactView = view;
  try { localStorage.setItem("houseator_contact_view", view); } catch (_) {}
  document.querySelectorAll("#contactViewToggle [data-contactview]").forEach(btn => {
    const isActive = btn.dataset.contactview === view;
    btn.classList.toggle("active", isActive);
    btn.setAttribute("aria-checked", isActive ? "true" : "false");
  });
  if (contactsListEl) {
    contactsListEl.classList.toggle("view-list", view === "list");
    contactsListEl.classList.toggle("view-cards", view === "cards");
  }
  renderContacts();
}

/* Compact row markup for list view — mirrors the leads list layout. */
function contactRowHtml(c) {
  const initials = (c.name || "?").split(/\s+/).map(w => w[0]).join("").slice(0, 2).toUpperCase();
  const visibleComments = (c.comments || []).filter(x => !x.greyed).length;
  const phoneHref = "tel:" + (c.phone || "").replace(/[^+\d]/g, "");
  return `
    <article class="lead-row contact-row role-${roleClass(c.role)}${c.greyed ? " greyed" : ""}" data-id="${c.id}">
      <span class="avatar lead-row-avatar">${escapeHtml(initials)}</span>
      <div class="lead-row-name">
        <b>${escapeHtml(c.name || "Unnamed contact")}</b>
      </div>
      <span class="contact-role-badge role-${roleClass(c.role)}">${escapeHtml(c.role || "Other")}</span>
      <span class="lead-row-phone">${c.phone ? `<a href="${escapeHtml(phoneHref)}" title="Call ${escapeHtml(c.phone)}">☎ ${escapeHtml(c.phone)}</a>` : "<span class='muted'>—</span>"}</span>
      <span class="lead-row-email">${c.email ? `<a href="mailto:${escapeHtml(c.email)}">${escapeHtml(c.email)}</a>` : "<span class='muted'>—</span>"}</span>
      <span class="lead-row-source">${c.company ? escapeHtml(c.company) : ""}</span>
      <div class="lead-row-actions">
        <button type="button" class="action-btn" data-contact-act="comments" title="${visibleComments} ${visibleComments === 1 ? "note" : "notes"}">💬 ${visibleComments}</button>
        <button type="button" class="action-btn" data-contact-act="grey" title="${c.greyed ? "Restore" : "Grey out"}">${c.greyed ? "◐" : "○"}</button>
        <button type="button" class="action-btn" data-contact-act="edit" title="Edit">✎</button>
        <button type="button" class="action-btn danger" data-contact-act="del" title="Delete">✕</button>
      </div>
    </article>
  `;
}

function contactCardHtml(c) {
  const initials = (c.name || "?").split(/\s+/).map(w => w[0]).join("").slice(0, 2).toUpperCase();
  const visibleComments = (c.comments || []).filter(x => !x.greyed).length;
  const phoneHref = "tel:" + (c.phone || "").replace(/[^+\d]/g, "");
  return `
    <article class="contact-card role-${roleClass(c.role)}${c.greyed ? " greyed" : ""}" data-id="${c.id}">
      <header class="lead-card-head">
        <span class="avatar">${escapeHtml(initials)}</span>
        <div class="lead-name">
          <b>${escapeHtml(c.name || "Unnamed contact")}</b>
          ${c.company ? `<p class="muted small">${escapeHtml(c.company)}</p>` : ""}
        </div>
        <span class="contact-role-badge role-${roleClass(c.role)}">${escapeHtml(c.role || "Other")}</span>
      </header>
      <div class="contact-phone-row">
        ${c.phone ? `
          <a class="contact-phone" href="${escapeHtml(phoneHref)}" title="Call ${escapeHtml(c.phone)}">
            <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M6.62 10.79a15.05 15.05 0 0 0 6.59 6.59l2.2-2.2c.28-.28.67-.36 1.02-.25 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1A17 17 0 0 1 3 4c0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02z"/></svg>
            ${escapeHtml(c.phone)}
          </a>
        ` : `<span class="muted small">No phone on file</span>`}
        ${c.email ? `<a class="contact-email" href="mailto:${escapeHtml(c.email)}">${escapeHtml(c.email)}</a>` : ""}
      </div>
      <footer class="lead-card-actions">
        <button type="button" class="action-btn" data-contact-act="comments">
          💬 ${visibleComments} ${visibleComments === 1 ? "Note" : "Notes"}
        </button>
        <button type="button" class="action-btn" data-contact-act="grey">${c.greyed ? "◐ Restore" : "○ Grey out"}</button>
        <button type="button" class="action-btn" data-contact-act="edit">✎ Edit</button>
        <button type="button" class="action-btn danger" data-contact-act="del">✕ Delete</button>
      </footer>
    </article>
  `;
}

/* Wire the view toggle once */
document.querySelectorAll("#contactViewToggle [data-contactview]").forEach(btn => {
  btn.addEventListener("click", () => setContactView(btn.dataset.contactview));
});

/* Sort-header clicks — delegated to the list container so they survive re-renders */
if (contactsListEl && !contactsListEl.dataset.sortWired) {
  contactsListEl.dataset.sortWired = "1";
  contactsListEl.addEventListener("click", (e) => {
    const sortBtn = e.target.closest(".lead-sort-btn[data-contact-sort]");
    if (!sortBtn) return;
    e.stopPropagation();
    const key = sortBtn.dataset.contactSort;
    if (contactSort.key === key) {
      contactSort.dir = contactSort.dir === "asc" ? "desc" : "asc";
    } else {
      contactSort = { key, dir: "asc" };
    }
    try { localStorage.setItem("houseator_contact_sort", JSON.stringify(contactSort)); } catch (_) {}
    renderContacts();
  });
}

function renderContacts() {
  if (!contactsListEl) return;
  const all = HOUSEATOR.listContacts();
  const filtered = all.filter(c => {
    if (contactFilter === "all") return true;
    if (contactFilter === "clients") return c.role === "Buyer" || c.role === "Seller";
    if (contactFilter === "vendors") return VENDOR_ROLES.has(c.role);
    if (contactFilter === "active")  return !c.greyed;
    return true;
  });

  if (!filtered.length) {
    contactsListEl.innerHTML = `<p class="ed-empty">No contacts in this view — click "+ Add Contact" to add one.</p>`;
    return;
  }

  if (contactView === "list") {
    const sorted = sortContacts(filtered, contactSort);
    contactsListEl.innerHTML = contactListHeadHtml() + sorted.map(contactRowHtml).join("");
  } else {
    /* Cards: greyed still sink to the bottom for consistency */
    const sortedCards = [...filtered.filter(c => !c.greyed), ...filtered.filter(c => c.greyed)];
    contactsListEl.innerHTML = sortedCards.map(contactCardHtml).join("");
  }

  contactsListEl.querySelectorAll(".contact-card, .contact-row").forEach(card => {
    const id = parseInt(card.dataset.id, 10);
    card.querySelectorAll("[data-contact-act]").forEach(btn => {
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        const act = btn.dataset.contactAct;
        const contact = HOUSEATOR.listContacts().find(c => c.id === id);
        if (!contact) return;
        if (act === "comments") {
          openCommentsModal({
            type: "contact",
            id,
            eyebrow: "Contact notes",
            title: contact.name,
            subtitle: contact.role + (contact.company ? " · " + contact.company : ""),
            getEntity: () => HOUSEATOR.listContacts().find(c => c.id === id),
            addFn: HOUSEATOR.addContactComment,
            updateFn: HOUSEATOR.updateContactComment,
            deleteFn: HOUSEATOR.deleteContactComment,
            afterChange: renderContacts
          });
        } else if (act === "grey") {
          HOUSEATOR.updateContact(id, { greyed: !contact.greyed });
          renderContacts();
        } else if (act === "edit") {
          openContactEditor(id);
        } else if (act === "del") {
          if (confirm(`Delete contact "${contact.name}"? This cannot be undone.`)) {
            HOUSEATOR.deleteContact(id);
            renderContacts();
          }
        }
      });
    });
  });
}

/* Filter chips */
document.querySelectorAll("#contactFilters .chip").forEach(btn => {
  btn.addEventListener("click", () => {
    document.querySelectorAll("#contactFilters .chip").forEach(c => c.classList.remove("active"));
    btn.classList.add("active");
    contactFilter = btn.dataset.contactfilter;
    renderContacts();
  });
});

/* Add Contact button */
document.getElementById("addContactBtn").addEventListener("click", () => openContactEditor(null));

/* Contact editor modal */
const contactEditorModal = document.getElementById("contactEditorModal");
const contactEditorTitle = document.getElementById("contactEditorTitle");
const ctDeleteBtn = document.getElementById("ctDelete");
const ctSaveBtn   = document.getElementById("ctSave");
let contactDraft = { id: null, isNew: false, draft: null };

function openContactEditor(id) {
  if (id == null) {
    contactDraft = {
      id: null, isNew: true,
      draft: { name: "", phone: "", email: "", role: "Buyer", company: "" }
    };
    contactEditorTitle.textContent = "Add Contact";
    ctDeleteBtn.hidden = true;
  } else {
    const c = HOUSEATOR.listContacts().find(x => x.id === id);
    if (!c) return;
    contactDraft = { id, isNew: false, draft: JSON.parse(JSON.stringify(c)) };
    contactEditorTitle.textContent = `Edit: ${c.name || "Contact"}`;
    ctDeleteBtn.hidden = false;
  }
  fillContactForm();
  contactEditorModal.hidden = false;
  document.body.style.overflow = "hidden";
}
function closeContactEditor() {
  contactEditorModal.hidden = true;
  document.body.style.overflow = "";
}
contactEditorModal.querySelectorAll("[data-contact-close]").forEach(el =>
  el.addEventListener("click", closeContactEditor)
);
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape" && !contactEditorModal.hidden) closeContactEditor();
});

function fillContactForm() {
  const d = contactDraft.draft;
  document.getElementById("ctName").value    = d.name || "";
  document.getElementById("ctPhone").value   = d.phone || "";
  document.getElementById("ctEmail").value   = d.email || "";
  document.getElementById("ctRole").value    = d.role || "Buyer";
  document.getElementById("ctCompany").value = d.company || "";
}
function gatherContactDraft() {
  const d = contactDraft.draft;
  d.name    = document.getElementById("ctName").value.trim();
  d.phone   = document.getElementById("ctPhone").value.trim();
  d.email   = document.getElementById("ctEmail").value.trim();
  d.role    = document.getElementById("ctRole").value;
  d.company = document.getElementById("ctCompany").value.trim();
  return d;
}

ctSaveBtn.addEventListener("click", () => {
  const d = gatherContactDraft();
  if (!d.name) { alert("Name is required."); return; }
  if (contactDraft.isNew) HOUSEATOR.addContact(d);
  else HOUSEATOR.updateContact(contactDraft.id, d);
  closeContactEditor();
  renderContacts();
});
ctDeleteBtn.addEventListener("click", () => {
  if (!confirm(`Delete contact "${contactDraft.draft.name}"?`)) return;
  HOUSEATOR.deleteContact(contactDraft.id);
  closeContactEditor();
  renderContacts();
});

/* Apply persisted view on load (also updates toggle UI state + classes) */
setContactView(contactView);

/* =========================================================
   SALES — every lead that's been flagged as Buyer becomes a row.
   Joins each row with the property the lead was interested in so we
   can filter by region / bedrooms / price / type, and uses the
   `becameBuyerAt` timestamp for year/month filters.
   ========================================================= */
const salesListEl       = document.getElementById("salesList");
const salesYearSel      = document.getElementById("salesYear");
const salesMonthSel     = document.getElementById("salesMonth");
const salesRegionSel    = document.getElementById("salesRegion");
const salesBedsSel      = document.getElementById("salesBeds");
const salesMinPriceSel  = document.getElementById("salesMinPrice");
const salesTypeSel      = document.getElementById("salesType");
const salesSourceSel    = document.getElementById("salesSource");
const salesSearchInput  = document.getElementById("salesSearch");
const salesClearBtn     = document.getElementById("salesClearFilters");
const salesKpiTotalEl   = document.getElementById("salesKpiTotal");
const salesKpiRevEl     = document.getElementById("salesKpiRevenue");
const salesKpiAvgEl     = document.getElementById("salesKpiAvg");

const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

function buyerLeads() {
  return (HOUSEATOR.listLeads() || []).filter(l => l.isBuyer);
}

/* Match the lead's `interestedIn` (a property TITLE string) against the
   PROPERTIES catalogue so we can pull region / beds / price / type. */
function propertyForLead(lead) {
  if (!lead || !lead.interestedIn) return null;
  return HOUSEATOR.PROPERTIES.find(p => p.title === lead.interestedIn) || null;
}

function saleRecord(lead) {
  const p = propertyForLead(lead);
  const ts = lead.becameBuyerAt || lead.createdAt || Date.now();
  const d = new Date(ts);
  return {
    lead,
    property: p,
    year:  d.getFullYear(),
    month: d.getMonth() + 1,
    dateLabel: `${MONTH_NAMES[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`,
    region: p ? p.region : "",
    beds:   p ? p.beds   : null,
    price:  p ? p.price  : 0,
    type:   p ? p.type   : "",
    source: lead.source || ""
  };
}

/* Build the Year + Source dropdowns from whatever sales actually exist so
   users don't see options that yield zero results. */
function populateSalesDropdowns(sales) {
  const years   = [...new Set(sales.map(s => s.year))].sort((a, b) => b - a);
  const sources = [...new Set(sales.map(s => s.source).filter(Boolean))].sort();

  /* Year dropdown — preserve current selection if still valid */
  const prevYear = salesYearSel.value;
  salesYearSel.innerHTML =
    `<option value="">All years</option>` +
    years.map(y => `<option value="${y}">${y}</option>`).join("");
  if (years.includes(parseInt(prevYear, 10))) salesYearSel.value = prevYear;

  const prevSource = salesSourceSel.value;
  salesSourceSel.innerHTML =
    `<option value="">All sources</option>` +
    sources.map(s => `<option value="${escapeHtml(s)}">${escapeHtml(s)}</option>`).join("");
  if (sources.includes(prevSource)) salesSourceSel.value = prevSource;
}

function filterSales(sales) {
  const year     = salesYearSel.value;
  const month    = salesMonthSel.value;
  const region   = salesRegionSel.value;
  const beds     = parseInt(salesBedsSel.value, 10) || 0;
  const minPrice = parseInt(salesMinPriceSel.value, 10) || 0;
  const type     = salesTypeSel.value;
  const source   = salesSourceSel.value;
  const q        = (salesSearchInput.value || "").trim().toLowerCase();

  return sales.filter(s => {
    if (year   && String(s.year)  !== year)  return false;
    if (month  && String(s.month) !== month) return false;
    if (region && s.region !== region)       return false;
    if (beds   && (s.beds || 0) < beds)      return false;
    if (minPrice && (s.price || 0) < minPrice) return false;
    if (type   && s.type !== type)           return false;
    if (source && s.source !== source)       return false;
    if (q && !(s.lead.name || "").toLowerCase().includes(q)) return false;
    return true;
  });
}

function fmtUSDsales(n) {
  return "$" + Math.round(n || 0).toLocaleString("en-US");
}

function renderSales() {
  if (!salesListEl) return;
  const all = buyerLeads().map(saleRecord);
  /* Recent buyers first */
  all.sort((a, b) => (b.lead.becameBuyerAt || 0) - (a.lead.becameBuyerAt || 0));
  populateSalesDropdowns(all);

  const filtered = filterSales(all);

  /* KPIs reflect the filtered set so users get an at-a-glance read of the
     subset they're looking at, not the entire archive. */
  const total = filtered.length;
  const revenue = filtered.reduce((s, x) => s + (x.price || 0), 0);
  const avg = total ? revenue / total : 0;
  salesKpiTotalEl.textContent = total;
  salesKpiRevEl.textContent   = fmtUSDsales(revenue);
  salesKpiAvgEl.textContent   = fmtUSDsales(avg);

  if (!filtered.length) {
    salesListEl.innerHTML = `<p class="ed-empty">No sales match these filters. ${all.length ? "Try clearing some filters." : 'Mark a lead as "Buyer" to create a sale.'}</p>`;
    return;
  }

  const head = `
    <header class="sales-row sales-row-head">
      <span>Buyer</span>
      <span>Property</span>
      <span>Region</span>
      <span>Beds</span>
      <span>Type</span>
      <span>Price</span>
      <span>Sold</span>
      <span>Source</span>
    </header>
  `;

  const rows = filtered.map(s => `
    <article class="sales-row" data-lead-id="${s.lead.id}">
      <span class="sales-buyer">
        <span class="avatar sales-avatar">${escapeHtml((s.lead.name || "?").split(/\s+/).map(w => w[0]).join("").slice(0,2).toUpperCase())}</span>
        <span class="sales-buyer-name">${escapeHtml(s.lead.name || "Unnamed")}</span>
      </span>
      <span class="sales-property">${s.property ? escapeHtml(s.property.title) : `<span class="muted">${escapeHtml(s.lead.interestedIn || "—")}</span>`}</span>
      <span>${s.region ? escapeHtml(s.region) : '<span class="muted">—</span>'}</span>
      <span class="sales-num">${s.beds != null ? s.beds : '<span class="muted">—</span>'}</span>
      <span>${s.type ? escapeHtml(s.type) : '<span class="muted">—</span>'}</span>
      <span class="sales-price">${s.price ? fmtUSDsales(s.price) : '<span class="muted">—</span>'}</span>
      <span class="sales-date">${escapeHtml(s.dateLabel)}</span>
      <span class="sales-source-cell">${s.source ? escapeHtml(s.source) : '<span class="muted">—</span>'}</span>
    </article>
  `).join("");

  salesListEl.innerHTML = head + rows;

  /* Clicking a row opens the originating lead editor for quick edits */
  salesListEl.querySelectorAll(".sales-row:not(.sales-row-head)").forEach(row => {
    row.addEventListener("click", () => {
      const id = parseInt(row.dataset.leadId, 10);
      if (!isNaN(id) && typeof openLeadEditor === "function") openLeadEditor(id);
    });
  });
}

/* Wire all filter inputs to re-render on change */
[salesYearSel, salesMonthSel, salesRegionSel, salesBedsSel,
 salesMinPriceSel, salesTypeSel, salesSourceSel]
  .filter(Boolean)
  .forEach(el => el.addEventListener("change", renderSales));
if (salesSearchInput) salesSearchInput.addEventListener("input", renderSales);
if (salesClearBtn) {
  salesClearBtn.addEventListener("click", () => {
    salesYearSel.value     = "";
    salesMonthSel.value    = "";
    salesRegionSel.value   = "";
    salesBedsSel.value     = "";
    salesMinPriceSel.value = "0";
    salesTypeSel.value     = "";
    salesSourceSel.value   = "";
    salesSearchInput.value = "";
    renderSales();
  });
}

/* "+ Add Sale" — opens the lead editor with the Buyer flag pre-checked and
   the title swapped to "Add Sale". Save flow still runs the existing
   buyer-handling pipeline (contact sync, property → Sold, sales refresh). */
const addSaleBtn = document.getElementById("addSaleBtn");
if (addSaleBtn) {
  addSaleBtn.addEventListener("click", () => {
    openLeadEditor(null, {
      titleNew: "Add Sale",
      defaults: { isBuyer: true, status: "hot" }
    });
  });
}

renderSales();

/* =========================================================
   REMINDERS PANEL + ADD-A-NOTE
   - Reminders: side-docked, always-visible panel that lists
     todos whose date == today. Persistent collapse state.
   - Add a Note: form on Overview that creates a new todo with
     title, optional description, due date, attachment ref, and
     a marker (priority / general / remind-me-in-a-month).
   ========================================================= */

function todayIsoLocal() {
  /* Use local date components (not UTC) so a user in PT writing a note at
     11pm doesn't see it under tomorrow's date. */
  const d = new Date();
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${d.getFullYear()}-${mm}-${dd}`;
}
function plusDaysIso(n) {
  const d = new Date();
  d.setDate(d.getDate() + n);
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${d.getFullYear()}-${mm}-${dd}`;
}
function escHtmlRem(s) {
  return String(s == null ? "" : s)
    .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function escAttrRem(s) {
  return String(s == null ? "" : s)
    .replace(/&/g, "&amp;").replace(/"/g, "&quot;")
    .replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

const remindersPanel       = document.getElementById("remindersPanel");
const remindersBody        = document.getElementById("remindersBody");
const remindersCount       = document.getElementById("remindersCount");
const remindersToggleCount = document.getElementById("remindersToggleCount");
const remindersToggle      = document.getElementById("remindersToggle");
const REMINDERS_COLLAPSED_KEY = "houseator_reminders_collapsed_v1";
let remindersFirstRenderDone = false;

function renderReminders() {
  if (!remindersBody) return;
  const today = todayIsoLocal();
  const list = (HOUSEATOR.listTodos() || []).filter(t =>
    t.date === today && !t.greyed && t.status !== "done"
  );
  /* Priority first, then general, then remind-month; tiebreak by createdAt */
  const order = { priority: 0, general: 1, "remind-month": 2 };
  list.sort((a, b) => {
    const ao = order[a.priority] != null ? order[a.priority] : 1;
    const bo = order[b.priority] != null ? order[b.priority] : 1;
    return ao - bo || (a.createdAt || 0) - (b.createdAt || 0);
  });
  remindersCount.textContent = list.length;
  remindersToggleCount.textContent = list.length;
  /* Flip the panel's "has-reminders" state — CSS uses this to swap the
     toggle from blue → red. The one-shot vibration is triggered separately
     below, only on the FIRST render (page reload). */
  remindersPanel.classList.toggle("has-reminders", list.length > 0);

  if (!remindersFirstRenderDone && list.length > 0) {
    /* Fire the vibration once. Class is removed after the animation ends
       so later renderReminders() calls (Add Note submits, mark-done clicks)
       don't replay it. */
    remindersPanel.classList.add("vibrate-once");
    setTimeout(() => remindersPanel.classList.remove("vibrate-once"), 1000);
  }
  remindersFirstRenderDone = true;

  if (!list.length) {
    remindersBody.innerHTML = `<p class="reminders-empty muted small">Nothing due today.</p>`;
    return;
  }
  remindersBody.innerHTML = list.map(t => {
    const marker = t.priority || "general";
    const attach = (t.attachments && t.attachments.length)
      ? `<span class="reminder-attach" title="${escAttrRem(t.attachments[0].name)}">📎 ${escHtmlRem(String(t.attachments[0].name || "").slice(0, 18))}</span>`
      : "";
    return `
      <div class="reminder-item marker-${marker}" data-id="${t.id}">
        <span class="reminder-marker" aria-hidden="true"></span>
        <div class="reminder-content">
          <strong>${escHtmlRem(t.title || "(untitled)")}</strong>
          ${t.description ? `<p class="muted small">${escHtmlRem(t.description)}</p>` : ""}
          ${attach}
        </div>
        <button class="reminder-done" data-id="${t.id}" title="Mark done" aria-label="Mark done">✓</button>
      </div>
    `;
  }).join("");
}

if (remindersPanel && remindersToggle) {
  if (localStorage.getItem(REMINDERS_COLLAPSED_KEY) === "1") {
    remindersPanel.classList.add("is-collapsed");
  }
  remindersToggle.addEventListener("click", () => {
    remindersPanel.classList.toggle("is-collapsed");
    localStorage.setItem(REMINDERS_COLLAPSED_KEY,
      remindersPanel.classList.contains("is-collapsed") ? "1" : "0");
  });
  remindersBody.addEventListener("click", (e) => {
    const btn = e.target.closest(".reminder-done");
    if (!btn) return;
    const id = parseInt(btn.dataset.id, 10);
    HOUSEATOR.updateTodo(id, { status: "done" });
    renderReminders();
    if (typeof renderTodos === "function") renderTodos();
  });
}

/* "+ Add Note" shortcut in the reminders panel header — switches to the
   Overview screen (if not already there), scrolls to the Add-Note card, and
   focuses the title input. The screen-transition overlay covers the jump
   when we have to navigate first, so it doesn't feel jarring. */
const remindersAddNoteBtn = document.getElementById("remindersAddNoteBtn");
if (remindersAddNoteBtn) {
  remindersAddNoteBtn.addEventListener("click", async () => {
    const isOnOverview = !document.getElementById("overview").hasAttribute("hidden");
    if (!isOnOverview && typeof navigateTo === "function") {
      await navigateTo("overview");
    }
    /* Wait a frame so the now-visible #addNoteCard has its layout box. */
    requestAnimationFrame(() => {
      const card = document.getElementById("addNoteCard");
      if (!card) return;
      card.scrollIntoView({ behavior: "smooth", block: "center" });
      /* Brief pulse on the card so the user's eye lands on it cleanly */
      card.classList.remove("add-note-flash");
      void card.offsetWidth;
      card.classList.add("add-note-flash");
      setTimeout(() => card.classList.remove("add-note-flash"), 1100);
      setTimeout(() => { const ti = document.getElementById("noteTitle"); if (ti) ti.focus(); }, 380);
    });
  });
}

const addNoteForm    = document.getElementById("addNoteForm");
const noteTitleInput = document.getElementById("noteTitle");
const noteDescInput  = document.getElementById("noteDesc");
const noteDateInput  = document.getElementById("noteDate");
const noteFileInput  = document.getElementById("noteAttachment");
const noteFileName   = document.getElementById("noteFileName");
const noteResetBtn   = document.getElementById("noteResetBtn");

function resetAddNoteForm() {
  if (!addNoteForm) return;
  addNoteForm.reset();
  noteDateInput.value = todayIsoLocal();
  noteFileName.textContent = "No file selected";
  addNoteForm.querySelectorAll(".marker-chip").forEach(c => c.classList.remove("selected"));
  const generalChip  = addNoteForm.querySelector('.marker-chip[data-marker="general"]');
  const generalInput = addNoteForm.querySelector('input[name="noteMarker"][value="general"]');
  if (generalChip)  generalChip.classList.add("selected");
  if (generalInput) generalInput.checked = true;
}

if (addNoteForm) {
  noteDateInput.value = todayIsoLocal();

  noteFileInput.addEventListener("change", () => {
    const f = noteFileInput.files[0];
    noteFileName.textContent = f ? f.name : "No file selected";
  });

  addNoteForm.addEventListener("change", (e) => {
    if (e.target.name !== "noteMarker") return;
    const value = e.target.value;
    addNoteForm.querySelectorAll(".marker-chip").forEach(c => c.classList.remove("selected"));
    e.target.closest(".marker-chip").classList.add("selected");
    if (value === "remind-month") {
      noteDateInput.value = plusDaysIso(30);
    } else if (!noteDateInput.dataset.userTouched) {
      noteDateInput.value = todayIsoLocal();
    }
  });
  noteDateInput.addEventListener("input", () => { noteDateInput.dataset.userTouched = "1"; });

  noteResetBtn.addEventListener("click", (e) => {
    e.preventDefault();
    delete noteDateInput.dataset.userTouched;
    resetAddNoteForm();
  });

  addNoteForm.addEventListener("submit", (e) => {
    e.preventDefault();
    const title = noteTitleInput.value.trim();
    if (!title) { noteTitleInput.focus(); return; }
    const marker = (addNoteForm.querySelector('input[name="noteMarker"]:checked') || {}).value || "general";
    const date = noteDateInput.value || todayIsoLocal();
    const f = noteFileInput.files[0];
    /* We can't upload anywhere without a backend, so we record metadata only.
       This is enough for the todo to display the attachment chip. */
    const attachments = f ? [{ name: f.name, size: f.size, type: f.type }] : [];

    HOUSEATOR.addTodo({
      title,
      description: noteDescInput.value.trim(),
      date,
      priority: marker,
      attachments,
      status: "todo",
      greyed: false
    });

    delete noteDateInput.dataset.userTouched;
    resetAddNoteForm();
    renderReminders();
    if (typeof renderTodos === "function") renderTodos();
    if (typeof renderDashboard === "function") renderDashboard();
  });
}

renderReminders();
