// Screenshot → events via Gemini multimodal API.
// Per-occurrence events only (no recurrence detection — user re-imports weekly).
// Tags each event with source="screenshot" so a re-import can wipe the prior batch.

const GEMINI_KEY_LS  = "personal-calendar:gemini-key:v1";
const GEMINI_LAST_LS = "personal-calendar:gemini-last:v1";

// In-memory undo: snapshot of the events array taken just before each import.
const UNDO = { snapshot: null };

// OpenAPI-3 subset (Gemini's responseSchema dialect — uppercase types, nullable flag).
const SCHEMA = {
  type: "OBJECT",
  properties: {
    weekStartDate: {
      type: "STRING",
      nullable: true,
      description: "ISO YYYY-MM-DD of the Monday that begins the visible week. Null if unknowable.",
    },
    events: {
      type: "ARRAY",
      items: {
        type: "OBJECT",
        properties: {
          title:     { type: "STRING" },
          dayOfWeek: { type: "STRING", enum: ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"] },
          startTime: { type: "STRING", nullable: true, description: "HH:MM 24h, or null for all-day." },
          endTime:   { type: "STRING", nullable: true, description: "HH:MM 24h, or null for all-day." },
          allDay:    { type: "BOOLEAN" },
          ooo:       { type: "BOOLEAN", description: "True if this is an Out-of-office / leave block." },
        },
        required: ["title", "dayOfWeek", "allDay", "ooo"],
      },
    },
  },
  required: ["weekStartDate", "events"],
};

const PROMPT = `You are looking at a screenshot of a Google Calendar week view.

Extract every meeting and event you can see, INCLUDING entries in the all-day strip across the top of the week (often shows OOO blocks, multi-day holidays, etc).

For each event return:
- title — verbatim text from the screenshot.
- dayOfWeek — Mon/Tue/Wed/Thu/Fri/Sat/Sun, based on which column it sits in.
- startTime / endTime — HH:MM 24-hour, or null when allDay is true.
- allDay — true only if the entry is in the all-day strip at the top.
- ooo — true if this represents Out of office / OOO / Annual leave / On leave.

Also infer weekStartDate: the Monday of the visible week, in YYYY-MM-DD. Use the date numbers in the column headers (e.g. "Mon 5") with the visible month label. If you cannot determine the year, return null. Times in screenshot are local time — do not adjust for timezone.

Return ONLY the JSON object matching the schema. No commentary, no markdown.`;

// ──── helpers ────────────────────────────────────────────────────────
function fileToBase64(file) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => {
      const s = r.result;
      const i = s.indexOf(",");
      resolve(i >= 0 ? s.slice(i + 1) : s);
    };
    r.onerror = reject;
    r.readAsDataURL(file);
  });
}

function todayMondayKey() {
  const t = new Date();
  const dow = (t.getDay() + 6) % 7; // Mon=0..Sun=6
  const mon = new Date(t.getFullYear(), t.getMonth(), t.getDate() - dow);
  return `${mon.getFullYear()}-${String(mon.getMonth() + 1).padStart(2,"0")}-${String(mon.getDate()).padStart(2,"0")}`;
}

function addDaysKey(dateKey, n) {
  const [y, m, d] = dateKey.split("-").map(Number);
  const dt = new Date(y, m - 1, d + n);
  return `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,"0")}-${String(dt.getDate()).padStart(2,"0")}`;
}

const DOW_INDEX = { Mon: 0, Tue: 1, Wed: 2, Thu: 3, Fri: 4, Sat: 5, Sun: 6 };

// ──── network ────────────────────────────────────────────────────────
async function callGemini(file, key) {
  const b64 = await fileToBase64(file);
  const body = {
    contents: [{
      parts: [
        { inlineData: { mimeType: file.type || "image/png", data: b64 } },
        { text: PROMPT },
      ],
    }],
    generationConfig: {
      responseMimeType: "application/json",
      responseSchema: SCHEMA,
      maxOutputTokens: 4096,
      temperature: 0.1,
    },
    safetySettings: [
      { category: "HARM_CATEGORY_HARASSMENT",        threshold: "BLOCK_NONE" },
      { category: "HARM_CATEGORY_HATE_SPEECH",       threshold: "BLOCK_NONE" },
      { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
      { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
    ],
  };
  const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${encodeURIComponent(key)}`;
  const r = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!r.ok) {
    const errText = await r.text().catch(() => "");
    throw new Error(`Gemini ${r.status}${errText ? ": " + errText.slice(0, 240) : ""}`);
  }
  const json = await r.json();
  const cand = json.candidates && json.candidates[0];
  if (!cand) throw new Error("Gemini returned no candidates.");
  if (cand.finishReason && cand.finishReason !== "STOP") {
    throw new Error(`Model stopped: ${cand.finishReason}.`);
  }
  const text = cand.content && cand.content.parts && cand.content.parts[0] && cand.content.parts[0].text;
  if (!text) throw new Error("Gemini response had no text.");
  let parsed;
  try { parsed = JSON.parse(text); }
  catch { throw new Error("Gemini response was not valid JSON: " + text.slice(0, 200)); }
  return parsed;
}

// ──── normalization ──────────────────────────────────────────────────
function normalize(parsed, fallbackWeekStart) {
  const cats = window.CALENDAR_DATA.CATEGORIES;
  const workCat  = cats.work  ? "work"  : Object.keys(cats)[0];
  const leaveCat = cats.leave ? "leave" : workCat;

  const weekStart = parsed.weekStartDate && /^\d{4}-\d{2}-\d{2}$/.test(parsed.weekStartDate)
    ? parsed.weekStartDate
    : fallbackWeekStart;

  const out = [];
  for (const ev of parsed.events || []) {
    const offset = DOW_INDEX[ev.dayOfWeek];
    if (offset == null) continue;
    const date = addDaysKey(weekStart, offset);
    const titleStripped = (ev.title || "Untitled").trim();
    const isOOO = ev.ooo || /^\s*(OOO|Out of office|On leave|Annual leave)\b/i.test(titleStripped);

    const payload = {
      title: titleStripped,
      date,
      cat: isOOO ? leaveCat : workCat,
    };
    if (ev.allDay || isOOO) {
      payload.allDay = true;
    } else if (ev.startTime && ev.endTime) {
      payload.start = ev.startTime;
      payload.end   = ev.endTime;
    } else if (ev.startTime) {
      payload.start = ev.startTime;
      const [hh, mm] = ev.startTime.split(":").map(Number);
      const total = hh * 60 + mm + 30;
      const eh = Math.floor(total / 60) % 24;
      const em = total % 60;
      payload.end = `${String(eh).padStart(2,"0")}:${String(em).padStart(2,"0")}`;
    } else {
      payload.allDay = true;
    }
    out.push(payload);
  }
  return { weekStart, payloads: out, weekInferred: !!parsed.weekStartDate };
}

// ──── public API ─────────────────────────────────────────────────────
async function runImport(file) {
  const key = (localStorage.getItem(GEMINI_KEY_LS) || "").trim();
  if (!key) throw new Error("No Gemini API key. Add one in Tweaks → Work calendar import.");
  if (!file) throw new Error("No file provided.");
  if (!/^image\//.test(file.type || "")) throw new Error("File is not an image.");

  const parsed = await callGemini(file, key);
  const { weekStart, payloads, weekInferred } = normalize(parsed, todayMondayKey());

  // Snapshot for undo (raw store contents, NOT the recurrence-expanded view).
  UNDO.snapshot = window.eventStore.getEvents().slice();

  window.eventStore.replaceBySource("screenshot", payloads);

  const stamp = { at: Date.now(), count: payloads.length, weekStart, weekInferred };
  try { localStorage.setItem(GEMINI_LAST_LS, JSON.stringify(stamp)); } catch {}
  return stamp;
}

function undoLast() {
  if (!UNDO.snapshot) return false;
  window.eventStore.replaceAll(UNDO.snapshot);
  UNDO.snapshot = null;
  return true;
}

window.GeminiImport = {
  runImport,
  undoLast,
  hasUndo: () => !!UNDO.snapshot,
  getKey: () => localStorage.getItem(GEMINI_KEY_LS) || "",
  setKey: (v) => { try { localStorage.setItem(GEMINI_KEY_LS, (v || "").trim()); } catch {} },
  getLast: () => {
    try { return JSON.parse(localStorage.getItem(GEMINI_LAST_LS) || "null"); }
    catch { return null; }
  },
};
