// Home Hub data store (v3). Single-user. Persists a full snapshot of all four
// collections so additions AND edits survive reloads.

(function () {
  const KEY = 'homehub.state.v3';
  let hydrated = false;
  let counter = 0;

  const A = () => window.ASSETS;
  const R = () => window.ROUTINES;
  const C = () => window.CONTACTS;
  const T = () => window.TODOS;

  const load = () => { try { return JSON.parse(localStorage.getItem(KEY)); } catch (e) { return null; } };
  const snapshot = () => {
    try { localStorage.setItem(KEY, JSON.stringify({ assets: A(), routines: R(), contacts: C(), todos: T() })); }
    catch (e) {}
    if (window.Cloud && window.Cloud.saveState) window.Cloud.saveState();
  };

  const genId = (k) => `${k}-c${Date.now().toString(36)}${(counter++).toString(36)}`;
  const replace = (arr, next) => { arr.length = 0; (next || []).forEach(x => arr.push(x)); };
  const clean = (s) => (s || '').trim();

  const isMissing = (v) => !v || v === '—' || (typeof v === 'string' && v.startsWith('?'));

  const ASSET_GAP_FIELDS = [
    { key: 'cat', label: 'category' },
    { key: 'make', label: 'make' },
  ];

  function contactHasReach(c) {
    const ok = (v) => v && !isMissing(v);
    if (ok(c.phone) || ok(c.email)) return true;
    return (c.people || []).some(p => ok(p.phone) || ok(p.email));
  }

  function makeGapTodo(fields) {
    return {
      id: genId('todo'), type: 'Info Gap', priority: 'Normal',
      status: 'Open', due: '', custom: true, auto: true, ...fields,
    };
  }

  function syncAssetGaps(a) {
    ASSET_GAP_FIELDS.forEach(f => {
      const missing = isMissing(a[f.key]);
      const existing = T().find(td => td.asset === a.id && td.gapField === f.key);
      if (missing && !existing) {
        T().push(makeGapTodo({ title: `Add ${f.label} for ${a.name}`, asset: a.id, gapField: f.key }));
      } else if (!missing && existing) {
        const i = T().indexOf(existing);
        if (i >= 0) T().splice(i, 1);
      }
    });
  }

  function syncContactGaps(c) {
    const need = !contactHasReach(c);
    const existing = T().find(td => td.contact === c.id && td.gapField === 'reach');
    if (need && !existing) {
      T().push(makeGapTodo({ title: `Add a phone or email for ${c.co}`, asset: null, contact: c.id, gapField: 'reach' }));
    } else if (!need && existing) {
      const i = T().indexOf(existing);
      if (i >= 0) T().splice(i, 1);
    }
  }

  // ---- date utilities ----
  const DAY_MS = 86400000;
  const startOfDay = (d) => { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; };
  const todayDate = () => startOfDay(new Date());
  const toISO = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;

  function freqInterval(freq) {
    switch (freq) {
      case 'Weekly':         return { days: 7 };
      case 'Every 2 weeks':  return { days: 14 };
      case 'Monthly':        return { months: 1 };
      case 'Every 2 months': return { months: 2 };
      case 'Quarterly':      return { months: 3 };
      case 'Twice yearly':   return { months: 6 };
      case 'Annually':       return { months: 12 };
      default:               return null;
    }
  }

  function addInterval(date, iv, n = 1) {
    const d = new Date(date);
    if (!iv) return d;
    if (iv.days) { d.setDate(d.getDate() + iv.days * n); return d; }
    const target = d.getMonth() + iv.months * n;
    const y = d.getFullYear() + Math.floor(target / 12);
    const m = ((target % 12) + 12) % 12;
    const dim = new Date(y, m + 1, 0).getDate();
    return new Date(y, m, Math.min(d.getDate(), dim));
  }

  function schedule(dateStr) {
    if (!dateStr) return { date: null, day: null, status: 'ok', nextLabel: 'Not scheduled' };
    const d = startOfDay(new Date(dateStr + 'T00:00:00'));
    if (isNaN(d)) return { date: null, day: null, status: 'ok', nextLabel: 'Not scheduled' };
    const today = todayDate();
    const diff = Math.round((d - today) / DAY_MS);
    const sameYear = d.getFullYear() === today.getFullYear();
    const fmt = d.toLocaleDateString('en-US', sameYear ? { month: 'short', day: 'numeric' } : { month: 'short', day: 'numeric', year: 'numeric' });
    let status, nextLabel;
    if (diff < 0)        { status = 'overdue'; nextLabel = `${Math.abs(diff)} day${Math.abs(diff) > 1 ? 's' : ''} late`; }
    else if (diff === 0) { status = 'soon';    nextLabel = 'today'; }
    else if (diff <= 14) { status = 'soon';    nextLabel = `in ${diff} day${diff > 1 ? 's' : ''}`; }
    else                 { status = 'ok';      nextLabel = fmt; }
    return { date: toISO(d), day: d.getDate(), status, nextLabel };
  }

  function refreshStatuses() {
    R().forEach(r => {
      if (!r.date && typeof r.day === 'number') r.date = `2026-05-${String(r.day).padStart(2, '0')}`;
      if (r.date) {
        const s = schedule(r.date);
        r.date = s.date; r.day = s.day; r.status = s.status; r.nextLabel = s.nextLabel;
      } else if (r.day != null) {
        r.day = null;
      }
      // Ensure new fields exist on older records.
      if (!Array.isArray(r.completions)) r.completions = [];
      if (r.reminder === undefined) r.reminder = null;
    });
  }

  // ---- Reminders ----
  // r.reminder = { value: number, unit: 'days' | 'weeks' | 'months' } | null
  // Reminder date = next due date minus that interval.
  function reminderDate(r) {
    if (!r || !r.date || !r.reminder) return null;
    const v = Number(r.reminder.value);
    if (!v || v < 0) return null;
    const d = startOfDay(new Date(r.date + 'T00:00:00'));
    if (r.reminder.unit === 'days')   d.setDate(d.getDate() - v);
    else if (r.reminder.unit === 'weeks') d.setDate(d.getDate() - v * 7);
    else if (r.reminder.unit === 'months') {
      const day = d.getDate();
      d.setDate(1);
      d.setMonth(d.getMonth() - v);
      const dim = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
      d.setDate(Math.min(day, dim));
    } else return null;
    return d;
  }
  function reminderLabel(r) {
    if (!r || !r.reminder || !r.reminder.value) return '';
    const v = r.reminder.value, u = r.reminder.unit;
    return `${v} ${v === 1 ? u.replace(/s$/, '') : u} before`;
  }

  function build(kind, f, id) {
    if (kind === 'asset') return {
      id, name: clean(f.name), cat: clean(f.cat),
      make: clean(f.make) || '—', model: clean(f.model) || '—',
      serial: clean(f.serial), year: clean(f.year), notes: clean(f.notes), specs: {}, custom: true,
    };
    if (kind === 'routine') {
      const s = schedule(f.dueDate);
      return {
        id, task: clean(f.task), asset: f.asset || null, freq: f.freq || '—',
        season: f.season || '—', who: f.who || 'Owner', contact: f.contact || null,
        synced: !!f.synced,
        reminder: f.reminder && f.reminder.value ? { value: Number(f.reminder.value), unit: f.reminder.unit || 'weeks' } : null,
        completions: [],
        last: '—', nextLabel: s.nextLabel, date: s.date, day: s.day, status: s.status,
        notes: clean(f.notes), custom: true,
      };
    }
    if (kind === 'contact') {
      const people = [];
      if (clean(f.phone) || clean(f.email) || clean(f.person)) {
        people.push({ name: clean(f.person) || 'Primary contact', role: clean(f.role),
          phone: clean(f.phone) || '? gap', email: clean(f.email) || '? gap' });
      }
      return { id, cat: f.cat || 'General Contractor', co: clean(f.co), people, assets: [], notes: clean(f.notes), custom: true };
    }
    return {
      id, title: clean(f.title), type: f.type || 'Task', priority: f.priority || 'Normal',
      asset: f.asset || null, status: 'Open', due: clean(f.due), custom: true,
    };
  }

  const listFor = (kind) => ({ asset: A(), routine: R(), contact: C(), todo: T() }[kind]);

  function hydrate() {
    if (hydrated) return;
    hydrated = true;
    const data = load();
    if (!data || !Array.isArray(data.assets)) return;
    replace(A(), data.assets);
    replace(R(), data.routines);
    replace(C(), data.contacts);
    replace(T(), data.todos);
    if (window.normalizeRecords) window.normalizeRecords();
    refreshStatuses();
  }

  function create(kind, fields) {
    const rec = build(kind, fields, genId(kind));
    listFor(kind).push(rec);
    if (kind === 'asset') syncAssetGaps(rec);
    if (kind === 'contact') syncContactGaps(rec);
    snapshot();
    return rec;
  }

  function update(kind, id, patch) {
    const rec = listFor(kind).find(x => x.id === id);
    if (!rec) return null;
    if (kind === 'asset') {
      Object.assign(rec, {
        name: clean(patch.name) || rec.name, cat: clean(patch.cat),
        make: clean(patch.make) || '—', model: clean(patch.model) || '—',
        serial: clean(patch.serial), year: clean(patch.year), notes: clean(patch.notes),
      });
    } else if (kind === 'contact') {
      Object.assign(rec, {
        co: clean(patch.co) || rec.co, cat: patch.cat || rec.cat, notes: clean(patch.notes),
        website: patch.website !== undefined ? clean(patch.website) : rec.website,
        phone:   patch.phone   !== undefined ? clean(patch.phone)   : rec.phone,
        email:   patch.email   !== undefined ? clean(patch.email)   : rec.email,
        address: patch.address !== undefined ? clean(patch.address) : rec.address,
      });
    } else if (kind === 'routine') {
      const s = patch.dueDate !== undefined && patch.dueDate !== ''
        ? schedule(patch.dueDate)
        : { date: rec.date, day: rec.day, status: rec.status, nextLabel: rec.nextLabel };
      const nextReminder = patch.reminder !== undefined
        ? (patch.reminder && patch.reminder.value ? { value: Number(patch.reminder.value), unit: patch.reminder.unit || 'weeks' } : null)
        : rec.reminder;
      Object.assign(rec, {
        task: clean(patch.task) || rec.task, asset: patch.asset ?? rec.asset,
        freq: patch.freq || rec.freq, season: patch.season || rec.season,
        who: patch.who || rec.who, contact: patch.contact ?? rec.contact,
        reminder: nextReminder,
        notes: clean(patch.notes), date: s.date, day: s.day, status: s.status, nextLabel: s.nextLabel,
      });
    } else {
      Object.assign(rec, {
        title: clean(patch.title) || rec.title, type: patch.type || rec.type,
        priority: patch.priority || rec.priority, asset: patch.asset ?? rec.asset,
        due: clean(patch.due),
      });
    }
    if (kind === 'asset') syncAssetGaps(rec);
    if (kind === 'contact') syncContactGaps(rec);
    snapshot();
    return rec;
  }

  function personRecord(person) {
    return {
      name: clean(person.name) || 'Contact', role: clean(person.role),
      phone: clean(person.phone) || '? gap', email: clean(person.email) || '? gap',
    };
  }
  function addPerson(contactId, person) {
    const c = C().find(x => x.id === contactId);
    if (!c) return;
    c.people = c.people || [];
    c.people.push(personRecord(person));
    syncContactGaps(c); snapshot();
  }
  function updatePerson(contactId, index, person) {
    const c = C().find(x => x.id === contactId);
    if (!c || !c.people || !c.people[index]) return;
    c.people[index] = personRecord(person);
    syncContactGaps(c); snapshot();
  }
  function removePerson(contactId, index) {
    const c = C().find(x => x.id === contactId);
    if (!c || !c.people || index == null || index < 0 || index >= c.people.length) return;
    c.people.splice(index, 1);
    syncContactGaps(c); snapshot();
  }
  function addSpec(assetId, key, value) {
    const a = A().find(x => x.id === assetId);
    if (!a) return;
    a.specs = a.specs || {};
    a.specs[clean(key)] = clean(value);
    snapshot();
  }
  function linkAsset(contactId, assetId) {
    const c = C().find(x => x.id === contactId);
    if (!c) return;
    c.assets = c.assets || [];
    const i = c.assets.indexOf(assetId);
    if (i === -1) c.assets.push(assetId); else c.assets.splice(i, 1);
    snapshot();
  }
  function toggleTodo(id) {
    const td = T().find(x => x.id === id);
    if (!td) return;
    td.status = td.status === 'Done' ? 'Open' : 'Done';
    snapshot();
  }

  // Mark this cycle done. Recurring → roll forward (skip past cycles). One-off → take it off the calendar.
  // Also push an entry onto the completions log (newest first, capped at 12).
  function completeRoutine(id) {
    const r = R().find(x => x.id === id);
    if (!r) return;
    const today = todayDate();
    const todayISO = toISO(today);
    const todayLabel = today.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
    r.last = todayLabel;
    if (!Array.isArray(r.completions)) r.completions = [];
    r.completions.unshift({ at: todayISO, label: todayLabel });
    r.completions = r.completions.slice(0, 12);

    const iv = freqInterval(r.freq);
    if (iv && r.date) {
      let next = addInterval(new Date(r.date + 'T00:00:00'), iv, 1);
      let guard = 0;
      while (startOfDay(next) <= today && guard++ < 240) next = addInterval(next, iv, 1);
      const s = schedule(toISO(next));
      r.date = s.date; r.day = s.day; r.status = s.status; r.nextLabel = s.nextLabel;
    } else {
      r.status = 'ok';
      r.nextLabel = 'Done · next cycle';
      r.date = null;
      r.day = null;
    }
    snapshot();
    return r;
  }
  function toggleSync(id) {
    const r = R().find(x => x.id === id);
    if (!r) return;
    r.synced = !r.synced;
    snapshot();
  }
  function deleteRoutine(id) {
    const i = R().findIndex(x => x.id === id);
    if (i === -1) return;
    R().splice(i, 1);
    snapshot();
  }
  function unscheduleRoutine(id) {
    const r = R().find(x => x.id === id);
    if (!r) return;
    r.date = null; r.day = null;
    r.status = 'ok'; r.nextLabel = 'Unscheduled';
    r.synced = false;
    snapshot();
  }
  function resolveGaps(assetId) {
    let n = 0;
    T().forEach(td => { if (td.asset === assetId && td.type === 'Info Gap' && td.status !== 'Done') { td.status = 'Done'; n++; } });
    if (n) snapshot();
    return n;
  }
  function reset() { try { localStorage.removeItem(KEY); } catch (e) {} }
  function reconcileGaps() {
    A().forEach(a => syncAssetGaps(a));
    C().forEach(c => syncContactGaps(c));
    snapshot();
  }

  window.Hub = {
    KEY, hydrate, create, update,
    addPerson, updatePerson, removePerson, addSpec, linkAsset,
    toggleTodo, completeRoutine, toggleSync, deleteRoutine, unscheduleRoutine,
    resolveGaps, reconcileGaps,
    schedule, freqInterval, addInterval, refreshStatuses,
    reminderDate, reminderLabel,
    reset,
  };
  hydrate();
})();
