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 emptyEl = root.querySelector('[data-upcoming-empty]');
  const listEl = root.querySelector('[data-upcoming-list]');
  if (!(emptyEl instanceof HTMLElement) || !(listEl instanceof HTMLElement)) return;

  const configuredCalendarToolName = typeof state.calendar_tool_name === 'string' ? state.calendar_tool_name.trim() : '';
  const configuredForecastToolName = typeof state.forecast_tool_name === 'string' ? state.forecast_tool_name.trim() : 'exec';
  const forecastCommand = typeof state.forecast_command === 'string' ? state.forecast_command.trim() : '';
  const calendarNames = Array.isArray(state.calendar_names)
    ? state.calendar_names.map((value) => String(value || '').trim()).filter(Boolean)
    : [];
  const eventWindowHoursRaw = Number(state.event_window_hours);
  const eventWindowHours = Number.isFinite(eventWindowHoursRaw) && eventWindowHoursRaw >= 1 ? Math.min(eventWindowHoursRaw, 168) : 36;
  const maxEventsRaw = Number(state.max_events);
  const maxEvents = Number.isFinite(maxEventsRaw) && maxEventsRaw >= 1 ? Math.min(maxEventsRaw, 8) : 4;
  const refreshMsRaw = Number(state.refresh_ms);
  const refreshMs = Number.isFinite(refreshMsRaw) && refreshMsRaw >= 60000 ? refreshMsRaw : 15 * 60 * 1000;
  const emptyText = typeof state.empty_text === 'string' && state.empty_text.trim()
    ? state.empty_text.trim()
    : `No upcoming events in the next ${eventWindowHours} hours.`;

  emptyEl.textContent = emptyText;

  const updateLiveContent = (snapshot) => {
    host.setLiveContent(snapshot);
  };

  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 isAllDay = (start, end) => /^\d{4}-\d{2}-\d{2}$/.test(normalizeDateValue(start)) || /^\d{4}-\d{2}-\d{2}$/.test(normalizeDateValue(end));

  const eventTime = (value, allDay = false) => {
    const raw = normalizeDateValue(value);
    if (!raw) return Number.NaN;
    const normalized = /^\d{4}-\d{2}-\d{2}$/.test(raw)
      ? `${raw}T${allDay ? '12:00:00' : '00:00:00'}`
      : raw;
    return new Date(normalized).getTime();
  };

  const formatEventLabel = (event) => {
    const raw = normalizeDateValue(event?.start);
    if (!raw) return '--';
    if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
      const date = new Date(`${raw}T12:00:00`);
      return `${date.toLocaleDateString([], { weekday: 'short' })} · all day`;
    }
    const date = new Date(raw);
    if (Number.isNaN(date.getTime())) return '--';
    return date.toLocaleString([], {
      weekday: 'short',
      hour: 'numeric',
      minute: '2-digit',
    });
  };

  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 stripExecFooter = (value) => String(value || '').replace(/\n+\s*Exit code:\s*\d+\s*$/i, '').trim();

  const extractExecJson = (toolResult) => {
    const parsedText = stripExecFooter(toolResult?.content);
    if (!parsedText) return null;
    try {
      return JSON.parse(parsedText);
    } catch {
      return null;
    }
  };

  const resolveCalendarToolConfig = async () => {
    const fallbackName = configuredCalendarToolName || 'mcp_home_assistant_calendar_get_events';
    if (!host.listTools) {
      return { name: fallbackName, availableCalendars: calendarNames };
    }
    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,
        availableCalendars: enumValues,
      };
    } catch {
      return { name: fallbackName, availableCalendars: calendarNames };
    }
  };

  const resolveUpcomingEvents = async (toolConfig) => {
    const now = Date.now();
    const windowEnd = now + eventWindowHours * 60 * 60 * 1000;
    const selectedCalendars = calendarNames.length > 0 ? calendarNames : toolConfig.availableCalendars;
    if (!toolConfig.name) throw new Error('Calendar tool unavailable');
    if (!Array.isArray(selectedCalendars) || selectedCalendars.length === 0) {
      throw new Error('No calendars configured');
    }

    const upcomingEvents = [];
    for (const calendarName of selectedCalendars) {
      const toolResult = await host.callTool(toolConfig.name, {
        calendar: calendarName,
        range: 'week',
      });
      const events = extractEvents(toolResult);
      for (const event of events) {
        const allDay = isAllDay(event?.start, event?.end);
        const startTime = eventTime(event?.start, allDay);
        if (!Number.isFinite(startTime) || startTime < now || startTime > windowEnd) continue;
        upcomingEvents.push({ ...event, _calendarName: calendarName, _allDay: allDay, _startTime: startTime });
      }
    }

    upcomingEvents.sort((left, right) => left._startTime - right._startTime);
    return upcomingEvents.slice(0, maxEvents);
  };

  const resolveForecastBundle = async () => {
    if (!forecastCommand) throw new Error('Missing forecast_command');
    const toolResult = await host.callTool(configuredForecastToolName || 'exec', {
      command: forecastCommand,
      max_output_chars: 200000,
    });
    const payload = extractExecJson(toolResult);
    if (!payload || typeof payload !== 'object') {
      throw new Error('Invalid forecast payload');
    }
    return payload;
  };

  const forecastTime = (entry) => {
    const time = new Date(String(entry?.datetime || '')).getTime();
    return Number.isFinite(time) ? time : Number.NaN;
  };

  const nearestForecast = (entries, targetTime) => {
    if (!Array.isArray(entries) || entries.length === 0 || !Number.isFinite(targetTime)) return null;
    let bestEntry = null;
    let bestDistance = Number.POSITIVE_INFINITY;
    for (const entry of entries) {
      const entryTime = forecastTime(entry);
      if (!Number.isFinite(entryTime)) continue;
      const distance = Math.abs(entryTime - targetTime);
      if (distance < bestDistance) {
        bestDistance = distance;
        bestEntry = entry;
      }
    }
    return bestDistance <= 3 * 60 * 60 * 1000 ? bestEntry : null;
  };

  const computeUpcomingScore = (events) => {
    if (!Array.isArray(events) || events.length === 0) return 0;
    const now = Date.now();
    const soonestMs = events
      .map((event) => Number(event?._startTime))
      .filter((time) => Number.isFinite(time) && time >= now)
      .sort((left, right) => left - right)[0];
    const soonestHours = Number.isFinite(soonestMs) ? (soonestMs - now) / (60 * 60 * 1000) : null;
    let score = 44;
    if (soonestHours !== null) {
      if (soonestHours <= 1) score = 100;
      else if (soonestHours <= 3) score = 97;
      else if (soonestHours <= 8) score = 94;
      else if (soonestHours <= 24) score = 90;
      else if (soonestHours <= 36) score = 86;
      else if (soonestHours <= 48) score = 82;
      else if (soonestHours <= 72) score = 76;
      else if (soonestHours <= 168) score = 62;
    }
    score += Math.min(events.length, 3);
    return Math.max(0, Math.min(100, Math.round(score)));
  };

  const metricValue = (value, fallback = '--') => {
    if (value === null || value === undefined || value === '') return fallback;
    return String(value);
  };

  const createMetricCell = (glyph, label, value) => {
    const cell = document.createElement('div');
    cell.style.display = 'flex';
    cell.style.alignItems = 'baseline';
    cell.style.columnGap = '3px';
    cell.style.flex = '0 0 auto';
    cell.style.minWidth = '0';
    cell.style.whiteSpace = 'nowrap';

    cell.title = label;

    const glyphEl = document.createElement('div');
    glyphEl.style.fontSize = '0.54rem';
    glyphEl.style.lineHeight = '1';
    glyphEl.style.color = 'var(--theme-card-neutral-muted)';
    glyphEl.style.fontWeight = '700';
    glyphEl.style.fontFamily = "'BlexMono Nerd Font Mono', monospace";
    glyphEl.style.flex = '0 0 auto';
    glyphEl.textContent = glyph;
    cell.appendChild(glyphEl);

    const valueEl = document.createElement('div');
    valueEl.style.fontSize = '0.53rem';
    valueEl.style.lineHeight = '1.1';
    valueEl.style.color = 'var(--theme-card-neutral-text)';
    valueEl.style.fontWeight = '700';
    valueEl.style.fontFamily = "'BlexMono Nerd Font Mono', monospace";
    valueEl.style.whiteSpace = 'nowrap';
    valueEl.style.overflow = 'hidden';
    valueEl.style.textOverflow = 'ellipsis';
    valueEl.style.textAlign = 'right';
    valueEl.style.flex = '1 1 auto';
    valueEl.textContent = metricValue(value);
    cell.appendChild(valueEl);
    return cell;
  };

  const renderEvents = (items, temperatureUnit, windSpeedUnit) => {
    listEl.innerHTML = '';
    if (!Array.isArray(items) || items.length === 0) {
      emptyEl.style.display = 'block';
      return;
    }

    emptyEl.style.display = 'none';

    for (const [index, item] of items.entries()) {
      const event = item.event;
      const forecast = item.forecast;

      const entry = document.createElement('li');
      entry.style.padding = index === 0 ? '8px 0 0' : '8px 0 0';
      entry.style.borderTop = '1px solid var(--theme-card-neutral-border)';

      const whenEl = document.createElement('div');
      whenEl.style.fontSize = '0.72rem';
      whenEl.style.lineHeight = '1.2';
      whenEl.style.letterSpacing = '0.02em';
      whenEl.style.color = 'var(--theme-card-neutral-muted)';
      whenEl.style.fontWeight = '700';
      whenEl.textContent = formatEventLabel(event);
      entry.appendChild(whenEl);

      const titleEl = document.createElement('div');
      titleEl.style.marginTop = '3px';
      titleEl.style.fontSize = '0.9rem';
      titleEl.style.lineHeight = '1.25';
      titleEl.style.color = 'var(--theme-card-neutral-text)';
      titleEl.style.fontWeight = '700';
      titleEl.style.whiteSpace = 'normal';
      titleEl.style.wordBreak = 'break-word';
      titleEl.textContent = String(event.summary || '(No title)');
      entry.appendChild(titleEl);

      const detailGrid = document.createElement('div');
      detailGrid.style.marginTop = '4px';
      detailGrid.style.display = 'flex';
      detailGrid.style.flexWrap = 'nowrap';
      detailGrid.style.alignItems = 'baseline';
      detailGrid.style.gap = '6px';
      detailGrid.style.overflowX = 'auto';
      detailGrid.style.overflowY = 'hidden';
      detailGrid.style.scrollbarWidth = 'none';
      detailGrid.style.msOverflowStyle = 'none';
      detailGrid.style.webkitOverflowScrolling = 'touch';

      const tempValue = Number.isFinite(Number(forecast?.temperature))
        ? `${Math.round(Number(forecast.temperature))}${temperatureUnit || ''}`
        : null;
      const windValue = Number.isFinite(Number(forecast?.wind_speed))
        ? `${Math.round(Number(forecast.wind_speed))}${windSpeedUnit || ''}`
        : null;
      const rainValue = Number.isFinite(Number(forecast?.precipitation_probability))
        ? `${Math.round(Number(forecast.precipitation_probability))}%`
        : null;
      const uvValue = Number.isFinite(Number(forecast?.uv_index))
        ? `${Math.round(Number(forecast.uv_index))}`
        : null;

      detailGrid.appendChild(createMetricCell('\uf2c9', 'Temperature', tempValue));
      detailGrid.appendChild(createMetricCell('\uef16', 'Wind', windValue));
      detailGrid.appendChild(createMetricCell('\uf043', 'Rain', rainValue));
      detailGrid.appendChild(createMetricCell('\uf522', 'UV', uvValue));
      entry.appendChild(detailGrid);

      listEl.appendChild(entry);
    }
  };

  const refresh = async () => {
    const snapshot = {
      kind: 'upcoming_conditions',
      event_window_hours: eventWindowHours,
      updated_at: null,
      events: [],
      errors: {},
    };

    try {
      const [toolConfig, forecastBundle] = await Promise.all([
        resolveCalendarToolConfig(),
        resolveForecastBundle(),
      ]);
      const events = await resolveUpcomingEvents(toolConfig);

      const nwsSource = forecastBundle?.nws && typeof forecastBundle.nws === 'object' ? forecastBundle.nws : null;
      const uvSource = forecastBundle?.uv && typeof forecastBundle.uv === 'object' ? forecastBundle.uv : null;
      const temperatureUnit = String(nwsSource?.temperature_unit || uvSource?.temperature_unit || '°F');
      const windSpeedUnit = String(nwsSource?.wind_speed_unit || uvSource?.wind_speed_unit || 'mph');
      const mergedItems = events.map((event) => {
        const nwsForecast = nearestForecast(nwsSource?.forecast, event._startTime);
        const uvForecast = nearestForecast(uvSource?.forecast, event._startTime);
        return {
          event,
          forecast: {
            datetime: nwsForecast?.datetime || uvForecast?.datetime || null,
            condition: nwsForecast?.condition || uvForecast?.condition || null,
            temperature: nwsForecast?.temperature ?? uvForecast?.temperature ?? null,
            apparent_temperature: uvForecast?.apparent_temperature ?? null,
            precipitation_probability: nwsForecast?.precipitation_probability ?? uvForecast?.precipitation_probability ?? null,
            wind_speed: nwsForecast?.wind_speed ?? uvForecast?.wind_speed ?? null,
            uv_index: uvForecast?.uv_index ?? null,
          },
        };
      });

      renderEvents(mergedItems, temperatureUnit, windSpeedUnit);

      snapshot.events = mergedItems.map((item) => ({
        summary: String(item.event.summary || '(No title)'),
        start: normalizeDateValue(item.event.start) || null,
        end: normalizeDateValue(item.event.end) || null,
        all_day: Boolean(item.event._allDay),
        calendar_name: String(item.event._calendarName || ''),
        forecast_time: item.forecast.datetime || null,
        condition: item.forecast.condition || null,
        temperature: Number.isFinite(Number(item.forecast.temperature)) ? Number(item.forecast.temperature) : null,
        apparent_temperature: Number.isFinite(Number(item.forecast.apparent_temperature)) ? Number(item.forecast.apparent_temperature) : null,
        precipitation_probability: Number.isFinite(Number(item.forecast.precipitation_probability)) ? Number(item.forecast.precipitation_probability) : null,
        wind_speed: Number.isFinite(Number(item.forecast.wind_speed)) ? Number(item.forecast.wind_speed) : null,
        uv_index: Number.isFinite(Number(item.forecast.uv_index)) ? Number(item.forecast.uv_index) : null,
      }));
      snapshot.score = computeUpcomingScore(events);
    } catch (error) {
      listEl.innerHTML = '';
      emptyEl.style.display = 'block';
      emptyEl.textContent = String(error);
      snapshot.errors.load = String(error);
      snapshot.score = 0;
    }

    const updatedText = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
    snapshot.updated_at = updatedText;
    updateLiveContent(snapshot);
  };

  host.setRefreshHandler(() => {
    void refresh();
  });
  void refresh();
  __setInterval(() => { void refresh(); }, refreshMs);

  return {
    destroy() {
      host.setRefreshHandler(null);
      host.setLiveContent(null);
      host.clearSelection();
      for (const cleanup of __cleanup.splice(0)) cleanup();
    },
  };
}
