export function mount({ root, state, host }) { state = state || {}; const __cleanup = []; const __setInterval = (...args) => { const id = window.setInterval(...args); __cleanup.push(() => window.clearInterval(id)); return id; }; const __setTimeout = (...args) => { const id = window.setTimeout(...args); __cleanup.push(() => window.clearTimeout(id)); return id; }; if (!(root instanceof HTMLElement)) return; const statusEl = root.querySelector("[data-cal-status]"); const subtitleEl = root.querySelector("[data-cal-subtitle]"); const dateEl = root.querySelector("[data-cal-date]"); const listEl = root.querySelector("[data-cal-list]"); const emptyEl = root.querySelector("[data-cal-empty]"); const updatedEl = root.querySelector("[data-cal-updated]"); if (!(statusEl instanceof HTMLElement) || !(subtitleEl instanceof HTMLElement) || !(dateEl instanceof HTMLElement) || !(listEl instanceof HTMLElement) || !(emptyEl instanceof HTMLElement) || !(updatedEl instanceof HTMLElement)) { return; } const configuredToolName = typeof state.tool_name === 'string' ? state.tool_name.trim() : ''; const configuredCalendarNames = Array.isArray(state.calendar_names) ? state.calendar_names.map((value) => String(value || '').trim()).filter(Boolean) : []; const refreshMsRaw = Number(state.refresh_ms); const REFRESH_MS = Number.isFinite(refreshMsRaw) && refreshMsRaw >= 60000 ? refreshMsRaw : 15 * 60 * 1000; const setStatus = (label, color) => { statusEl.textContent = label; statusEl.style.color = color; }; const updateLiveContent = (snapshot) => { host.setLiveContent(snapshot); }; const dayBounds = () => { const now = new Date(); const start = new Date(now); start.setHours(0, 0, 0, 0); const end = new Date(now); end.setHours(23, 59, 59, 999); return { start, end }; }; const formatDateHeader = () => { const now = new Date(); return now.toLocaleDateString([], { weekday: "long", month: "short", day: "numeric", year: "numeric" }); }; const normalizeDateValue = (value) => { if (typeof value === "string") return value; if (value && typeof value === "object") { if (typeof value.dateTime === "string") return value.dateTime; if (typeof value.date === "string") return value.date; } return ""; }; const formatTime = (value) => { const raw = normalizeDateValue(value); if (!raw) return "--:--"; if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) return "All day"; const d = new Date(raw); if (Number.isNaN(d.getTime())) return "--:--"; return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); }; const isAllDay = (start, end) => { const s = normalizeDateValue(start); const e = normalizeDateValue(end); return /^\d{4}-\d{2}-\d{2}$/.test(s) || /^\d{4}-\d{2}-\d{2}$/.test(e); }; const eventSortKey = (evt) => { const raw = normalizeDateValue(evt && evt.start); const t = new Date(raw).getTime(); return Number.isFinite(t) ? t : Number.MAX_SAFE_INTEGER; }; const extractEvents = (toolResult) => { if (toolResult && toolResult.parsed && typeof toolResult.parsed === 'object') { const parsed = toolResult.parsed; if (Array.isArray(parsed.result)) return parsed.result; } if (typeof toolResult?.content === 'string') { try { const parsed = JSON.parse(toolResult.content); if (parsed && typeof parsed === 'object' && Array.isArray(parsed.result)) { return parsed.result; } } catch { return []; } } return []; }; const resolveToolConfig = async () => { const fallbackName = configuredToolName || 'mcp_home_assistant_calendar_get_events'; if (!host.listTools) { return { name: fallbackName, calendars: configuredCalendarNames }; } try { const tools = await host.listTools(); const tool = Array.isArray(tools) ? tools.find((item) => /(^|_)calendar_get_events$/i.test(String(item?.name || ''))) : null; const enumValues = Array.isArray(tool?.parameters?.properties?.calendar?.enum) ? tool.parameters.properties.calendar.enum.map((value) => String(value || '').trim()).filter(Boolean) : []; return { name: tool?.name || fallbackName, calendars: configuredCalendarNames.length > 0 ? configuredCalendarNames : enumValues, }; } catch { return { name: fallbackName, calendars: configuredCalendarNames }; } }; const renderEvents = (events) => { listEl.innerHTML = ""; if (!Array.isArray(events) || events.length === 0) { emptyEl.style.display = "block"; return; } emptyEl.style.display = "none"; for (const evt of events) { const li = document.createElement("li"); li.style.border = "1px solid var(--theme-card-neutral-border)"; li.style.borderRadius = "10px"; li.style.padding = "10px 12px"; li.style.background = "#ffffff"; const summary = document.createElement("div"); summary.style.fontSize = "0.96rem"; summary.style.fontWeight = "600"; summary.style.color = "var(--theme-card-neutral-text)"; summary.textContent = String(evt.summary || "(No title)"); const timing = document.createElement("div"); timing.style.marginTop = "4px"; timing.style.fontSize = "0.86rem"; timing.style.color = "var(--theme-card-neutral-subtle)"; if (isAllDay(evt.start, evt.end)) { timing.textContent = "All day"; } else { timing.textContent = `${formatTime(evt.start)} - ${formatTime(evt.end)}`; } li.appendChild(summary); li.appendChild(timing); if (evt.location) { const location = document.createElement("div"); location.style.marginTop = "4px"; location.style.fontSize = "0.84rem"; location.style.color = "var(--theme-card-neutral-muted)"; location.textContent = `Location: ${String(evt.location)}`; li.appendChild(location); } listEl.appendChild(li); } }; const refresh = async () => { dateEl.textContent = formatDateHeader(); setStatus("Refreshing", "var(--theme-status-muted)"); try { const toolConfig = await resolveToolConfig(); if (!toolConfig.name) throw new Error('Calendar tool unavailable'); if (!Array.isArray(toolConfig.calendars) || toolConfig.calendars.length === 0) { subtitleEl.textContent = "No Home Assistant calendars available"; renderEvents([]); updatedEl.textContent = new Date().toLocaleTimeString(); setStatus("OK", "var(--theme-status-live)"); updateLiveContent({ kind: 'calendar_today', tool_name: toolConfig.name, calendar_names: [], updated_at: updatedEl.textContent || null, event_count: 0, events: [], }); return; } subtitleEl.textContent = `${toolConfig.calendars.length} calendar${toolConfig.calendars.length === 1 ? "" : "s"}`; const allEvents = []; for (const calendarName of toolConfig.calendars) { const toolResult = await host.callTool(toolConfig.name, { calendar: calendarName, range: 'today', }); const events = extractEvents(toolResult); for (const evt of events) { allEvents.push({ ...evt, _calendarName: calendarName }); } } allEvents.sort((a, b) => eventSortKey(a) - eventSortKey(b)); renderEvents(allEvents); updatedEl.textContent = new Date().toLocaleTimeString(); setStatus("Daily", "var(--theme-status-live)"); updateLiveContent({ kind: 'calendar_today', tool_name: toolConfig.name, calendar_names: toolConfig.calendars, updated_at: updatedEl.textContent || null, event_count: allEvents.length, events: allEvents.map((evt) => ({ summary: String(evt.summary || '(No title)'), start: normalizeDateValue(evt.start) || null, end: normalizeDateValue(evt.end) || null, location: typeof evt.location === 'string' ? evt.location : null, calendar_name: evt._calendarName || null, })), }); } catch (err) { subtitleEl.textContent = "Could not load Home Assistant calendar"; renderEvents([]); updatedEl.textContent = String(err); setStatus("Unavailable", "var(--theme-status-danger)"); updateLiveContent({ kind: 'calendar_today', tool_name: configuredToolName || 'mcp_home_assistant_calendar_get_events', calendar_names: configuredCalendarNames, updated_at: String(err), event_count: 0, events: [], error: String(err), }); } }; host.setRefreshHandler(() => { void refresh(); }); void refresh(); __setInterval(() => { void refresh(); }, REFRESH_MS); return { destroy() { host.setRefreshHandler(null); host.setLiveContent(null); host.clearSelection(); for (const cleanup of __cleanup.splice(0)) cleanup(); }, }; }