// google-calendar.jsx — ICS feed parser + fetch helper
// Use Google Calendar's "Secret address in iCal format" so we don't need OAuth.
// Direct fetch won't work from a static page (CORS), so we route through a public
// CORS proxy. corsproxy.io is the default; fallback to allorigins if it fails.
// All errors surface as user-readable strings so the Settings panel can show them.

const ICS_PROXIES = [
  (url) => `https://corsproxy.io/?${encodeURIComponent(url)}`,
  (url) => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`,
];

async function fetchICS(url) {
  if (!url || !url.trim()) throw new Error('Geen URL ingesteld');
  // Normalize webcal:// → https://
  url = url.replace(/^webcal:\/\//, 'https://').trim();
  let lastErr;
  for (const proxy of ICS_PROXIES) {
    try {
      const r = await fetch(proxy(url), { cache: 'no-cache' });
      if (!r.ok) { lastErr = new Error(`Proxy fout ${r.status}`); continue; }
      const text = await r.text();
      if (!text.includes('BEGIN:VCALENDAR')) { lastErr = new Error('Geen geldige iCal feed'); continue; }
      return text;
    } catch (e) { lastErr = e; }
  }
  throw lastErr || new Error('Kon agenda niet ophalen');
}

function unescapeICS(v) {
  return v.replace(/\\n/gi, ' ').replace(/\\,/g, ',').replace(/\\;/g, ';').replace(/\\\\/g, '\\');
}

function parseICSDate(value, params) {
  if (!value) return null;
  // All-day: YYYYMMDD
  if (/^\d{8}$/.test(value)) {
    const y = +value.slice(0,4), m = +value.slice(4,6)-1, d = +value.slice(6,8);
    return { date: new Date(y, m, d), allDay: true };
  }
  // With time: YYYYMMDDTHHMMSS[Z]
  const m = value.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)$/);
  if (!m) return null;
  const [, y, mo, da, h, mi, s, z] = m;
  if (z === 'Z') return { date: new Date(Date.UTC(+y, +mo-1, +da, +h, +mi, +s)), allDay: false };
  return { date: new Date(+y, +mo-1, +da, +h, +mi, +s), allDay: false };
}

function parseICS(text) {
  // Unfold line continuations (RFC5545: continuation lines start with SP or HT)
  const unfolded = text.replace(/\r?\n[ \t]/g, '');
  const events = [];
  const blocks = unfolded.split('BEGIN:VEVENT').slice(1);
  for (const block of blocks) {
    const endIdx = block.indexOf('END:VEVENT');
    const body = endIdx >= 0 ? block.slice(0, endIdx) : block;
    const ev = {};
    for (const line of body.split(/\r?\n/)) {
      if (!line) continue;
      const colonIdx = line.indexOf(':');
      if (colonIdx < 0) continue;
      const keyPart = line.slice(0, colonIdx);
      const value = line.slice(colonIdx + 1);
      const [key, ...rawParams] = keyPart.split(';');
      const params = Object.fromEntries(rawParams.map(p => p.split('=')));
      if (key === 'SUMMARY')   ev.summary = unescapeICS(value);
      else if (key === 'DTSTART') ev.start = parseICSDate(value, params);
      else if (key === 'DTEND')   ev.end   = parseICSDate(value, params);
      else if (key === 'LOCATION') ev.location = unescapeICS(value);
      else if (key === 'UID')     ev.uid = value;
      else if (key === 'RRULE')   ev.rrule = Object.fromEntries(value.split(';').map(p => p.split('=')));
      else if (key === 'STATUS')  ev.status = value;
    }
    if (ev.start && ev.summary && ev.status !== 'CANCELLED') events.push(ev);
  }
  return events;
}

// Expand an event over the next N days, supporting simple RRULE (DAILY/WEEKLY/MONTHLY/YEARLY).
// Returns array of {start: Date, allDay, summary, location, uid}
function expandEvent(ev, fromDate, days) {
  const out = [];
  const rangeStart = new Date(fromDate); rangeStart.setHours(0,0,0,0);
  const rangeEnd = new Date(rangeStart); rangeEnd.setDate(rangeEnd.getDate() + days);
  const base = ev.start.date;
  const allDay = ev.start.allDay;

  const push = (d) => {
    if (d >= rangeStart && d < rangeEnd) {
      out.push({ start: new Date(d), allDay, summary: ev.summary, location: ev.location, uid: ev.uid });
    }
  };

  if (!ev.rrule) {
    push(base);
    return out;
  }

  const freq = ev.rrule.FREQ;
  const interval = +(ev.rrule.INTERVAL || 1);
  const until = ev.rrule.UNTIL ? parseICSDate(ev.rrule.UNTIL, {})?.date : null;
  const count = ev.rrule.COUNT ? +ev.rrule.COUNT : null;
  const byday = ev.rrule.BYDAY ? ev.rrule.BYDAY.split(',') : null;
  const dayMap = { SU:0, MO:1, TU:2, WE:3, TH:4, FR:5, SA:6 };

  // Walk forward up to 366 iterations or 'days * 2' whichever bigger
  let cur = new Date(base);
  let i = 0;
  const maxIter = Math.max(366, days * 2);
  while (i < maxIter) {
    if (until && cur > until) break;
    if (count && i >= count) break;
    if (cur >= rangeEnd) break;
    if (cur >= rangeStart) {
      if (byday) {
        const dayCode = ['SU','MO','TU','WE','TH','FR','SA'][cur.getDay()];
        if (byday.includes(dayCode)) push(cur);
      } else {
        push(cur);
      }
    }
    if (freq === 'DAILY')   cur = addDays(cur, interval);
    else if (freq === 'WEEKLY')  cur = addDays(cur, 7 * interval);
    else if (freq === 'MONTHLY') { const nx = new Date(cur); nx.setMonth(nx.getMonth() + interval); cur = nx; }
    else if (freq === 'YEARLY')  { const nx = new Date(cur); nx.setFullYear(nx.getFullYear() + interval); cur = nx; }
    else break;
    i++;
  }
  return out;
}

function addDays(d, n) { const x = new Date(d); x.setDate(x.getDate() + n); return x; }

// Get all expanded events in the next `days` days, sorted by time.
function expandFeed(events, fromDate, days) {
  const out = [];
  for (const ev of events) out.push(...expandEvent(ev, fromDate, days));
  out.sort((a, b) => a.start - b.start);
  return out;
}

// Hook: keeps an in-state events list synced from the URL on store.
// Usage: const { events, sync, syncing, error, lastSync } = useGoogleCalendar();
function useGoogleCalendar() {
  const [state, set] = useDashboard();
  const [syncing, setSyncing] = React.useState(false);
  const [error, setError] = React.useState(null);

  const sync = React.useCallback(async () => {
    if (!state.googleICSUrl) { setError('Geen agenda-URL ingesteld'); return; }
    setSyncing(true); setError(null);
    try {
      const text = await fetchICS(state.googleICSUrl);
      const parsed = parseICS(text);
      // Only keep the next 30 days worth of expanded events (cap to keep storage tiny)
      const expanded = expandFeed(parsed, new Date(), 30);
      const slim = expanded.map(e => ({
        start: e.start.toISOString(),
        allDay: !!e.allDay,
        summary: e.summary,
        location: e.location || null,
        uid: e.uid || null,
      })).slice(0, 200);
      set(s => ({ ...s, googleEvents: slim, googleLastSync: new Date().toISOString() }));
    } catch (e) {
      setError(e.message || String(e));
    } finally {
      setSyncing(false);
    }
  }, [state.googleICSUrl, set]);

  // Auto-sync on first mount if URL exists and we haven't synced in 30 min
  React.useEffect(() => {
    if (!state.googleICSUrl) return;
    const lastSync = state.googleLastSync ? new Date(state.googleLastSync) : null;
    const stale = !lastSync || (Date.now() - lastSync.getTime() > 30 * 60 * 1000);
    if (stale) sync();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [state.googleICSUrl]);

  return { events: state.googleEvents || [], sync, syncing, error, lastSync: state.googleLastSync };
}

// Filter Google events to a specific day offset from TODAY
function googleEventsForDay(events, dayOffset) {
  const target = addDays(TODAY, dayOffset);
  target.setHours(0,0,0,0);
  const next = addDays(target, 1);
  return events
    .map(e => ({ ...e, startDate: new Date(e.start) }))
    .filter(e => e.startDate >= target && e.startDate < next)
    .sort((a, b) => a.startDate - b.startDate);
}

function formatGoogleTime(ev) {
  if (ev.allDay) return 'hele dag';
  const d = new Date(ev.start);
  return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
}

Object.assign(window, {
  fetchICS, parseICS, expandFeed, useGoogleCalendar,
  googleEventsForDay, formatGoogleTime,
});
