pragma Singleton

import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.Location

Singleton {
  id: root

  readonly property string khalEventsScript: Quickshell.shellDir + '/Scripts/python/src/calendar/khal-events.py'

  function init() {
    availabilityCheckProcess.running = true;
  }
  function loadCalendars() {
    listCalendarsProcess.running = true;
  }
  function loadEvents(daysAhead = 31, daysBehind = 14) {
    CalendarService.loading = true;
    CalendarService.lastError = "";

    const now = new Date();
    const startDate = new Date(now.getTime() - (daysBehind * 24 * 60 * 60 * 1000));

    loadEventsProcess.startTime = formatDateYMD(startDate);
    loadEventsProcess.duration = `${daysAhead + daysBehind}d`;
    loadEventsProcess.running = true;

    Logger.d("Calendar", `Loading events (${daysBehind} days behind, ${daysAhead} days ahead): ${loadEventsProcess.startTime}  ${loadEventsProcess.duration}`);
  }

  // Process to check for khal installation
  Process {
    id: availabilityCheckProcess
    running: false
    command: ["sh", "-c", "command -v khal >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1"]
    onExited: function (exitCode) {
      if (exitCode === 0) {
        CalendarService.available = true;
        CalendarService.dataProvider = CalendarService.dataProvider || root;
      }

      if (CalendarService.available) {
        Logger.i("Calendar", "Khal available");
        loadCalendars();
      } else {
        Logger.w("Calendar", "Khal not available");
        CalendarService.lastError = "khal binary not installed";
      }
    }
  }

  // Process to list available calendars
  Process {
    id: listCalendarsProcess
    running: false
    command: ["khal", "printcalendars"]

    stdout: StdioCollector {
      onStreamFinished: {
        try {
          const calendars = text.trim().split("\n");
          CalendarService.setCalendars(calendars);

          Logger.d("Calendar", `Found ${calendars.length} calendar(s)`);

          // Auto-load events after discovering calendars
          if (calendars.length > 0) {
            loadEvents();
          }
        } catch (e) {
          Logger.d("Calendar", "Failed to parse calendars: " + e);
          CalendarService.lastError = "Failed to parse calendar list";
        }
      }
    }

    stderr: StdioCollector {
      onStreamFinished: {
        if (text.trim()) {
          Logger.d("Calendar", "List calendars error: " + text);
          CalendarService.lastError = text.trim();
        }
      }
    }
  }

  // Process to load events
  Process {
    id: loadEventsProcess
    running: false
    property string startTime: ""
    property string duration: ""

    command: ["python3", root.khalEventsScript, startTime, duration]

    stdout: StdioCollector {
      onStreamFinished: {
        CalendarService.loading = false;

        try {
          const events = parseEvents(text.trim());
          CalendarService.setEvents(events);
          Logger.d("Calendar", `Loaded ${events.length} events(s)`);
        } catch (e) {
          Logger.d("Calendar", "Failed to parse events: " + e);
          CalendarService.lastError = "Failed to parse events";
          // Fall back to cached events
          CalendarService.loadCachedEvents();
          Logger.d("Calendar", "Using cached events");
        }
      }
    }

    stderr: StdioCollector {
      onStreamFinished: {
        CalendarService.loading = false;

        if (text.trim()) {
          Logger.d("Calendar", "Load events error: " + text);
          CalendarService.lastError = text.trim();
          // Fall back to cached events
          CalendarService.loadCachedEvents();
          Logger.d("Calendar", "Using cached events");
        }
      }
    }
  }

  function parseEvents(text) {
    const result = [];
    const duplicates = new Set();

    for (const line of text.split("\n")) {
      if (!line.trim())
        continue;
      const dayEvents = JSON.parse(line);
      for (const event of dayEvents) {
        if (event["repeat-pattern"] !== "") {
          // if there is a repeat pattern, the event must be included each time
          result.push({
                        uid: event.uid,
                        calendar: event.calendar,
                        summary: event.title,
                        start: parseTimestamp(event["start-long-full"]),
                        end: parseTimestamp(event["end-long-full"]),
                        location: event.location,
                        description: event.description
                      });
        } else if (!duplicates.has(event.uid)) {
          // in any other cases, we must remove duplicates using the uid of the event
          result.push({
                        uid: event.uid,
                        calendar: event.calendar,
                        summary: event.title,
                        start: parseTimestamp(event["start-long-full"]),
                        end: parseTimestamp(event["end-long-full"]),
                        location: event.location,
                        description: event.description
                      });
          duplicates.add(event.uid);
        }
      }
    }

    return result;
  }

  function parseTimestamp(timeStr) {
    // expects ISO8601 format
    return Math.floor((Date.parse(timeStr)).valueOf() / 1000);
  }

  function formatDateYMD(date) {
    const y = date.getFullYear();
    const m = String(date.getMonth() + 1).padStart(2, "0");
    const d = String(date.getDate()).padStart(2, "0");
    return y + "-" + m + "-" + d;
  }
}
