// Lynxe — API layer (loaded before all other JSX)

// ── Cache ──────────────────────────────────────────────────────────────────
const _cache = {};

async function fnGetCached(path, ttl = 120_000) {
  const now = Date.now();
  const hit = _cache[path];
  if (hit && now - hit.ts < ttl) return hit.data;
  const data = await fnGet(path);
  _cache[path] = { data, ts: now };
  return data;
}

async function dbGetCached(path, ttl = 120_000) {
  const now = Date.now();
  const key = `db:${path}`;
  const hit = _cache[key];
  if (hit && now - hit.ts < ttl) return hit.data;
  const data = await dbGet(path);
  _cache[key] = { data, ts: now };
  return data;
}

function invalidateCache(...paths) {
  if (!paths.length) Object.keys(_cache).forEach(k => delete _cache[k]);
  else paths.forEach(p => { delete _cache[p]; delete _cache[`db:${p}`]; });
}

// ── Notification builder ────────────────────────────────────────────────────
function buildNotifications() {
  const notes = [];
  const today = new Date().toISOString().split('T')[0];

  // Missions due soon
  (window.MISSIONS || []).forEach(m => {
    if (m.daysLeft != null) {
      notes.push({
        id: `m-${m.id}`,
        icon: 'Mission',
        title: `${m.title} in ${m.daysLeft} day${m.daysLeft === 1 ? '' : 's'}`,
        body: `${Math.round(m.progress * 100)}% complete · ${(window.TODAY_TASKS || []).filter(t => t.missionId === m.id && !t.done).length} tasks today`,
        subject: m.subject,
        route: ['mission', { id: m.id }],
        priority: m.daysLeft,
      });
    }
  });

  // Recent classroom posts (last 3 days)
  (window.CLASSROOM_POSTS || []).slice(0, 10).forEach(p => {
    if (!p.date) return;
    notes.push({
      id: `p-${p.id}`,
      icon: 'Class',
      title: `${p.type} in ${(SUBJ_BY_ID[p.subject] || {}).short || p.subject}`,
      body: p.title,
      time: p.date,
      subject: p.subject,
      route: ['classroom', { focus: p.id }],
      priority: 999,
    });
  });

  // Recent lecture notes ready
  (window.LECTURES || []).slice(0, 5).forEach(l => {
    if (!l.notionUrl || l.notionUrl === NOTION_BASE) return;
    notes.push({
      id: `l-${l.id}`,
      icon: 'Note',
      title: 'Lecture notes ready',
      body: `"${l.title}" has been processed in Notion.`,
      time: l.date,
      subject: l.subject,
      external: true,
      externalUrl: l.notionUrl,
      priority: 9999,
    });
  });

  return notes.sort((a, b) => a.priority - b.priority);
}

const SUPABASE_URL = 'https://sazxafygrlwnugnenens.supabase.co';
const ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNhenhhZnlncmx3bnVnbmVuZW5zIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzYzMjEwNzAsImV4cCI6MjA5MTg5NzA3MH0.e6XrpuFOKT6rtUNgudGBwBv7nwL5hqUXC5asnhmYCgk';
const FN = `${SUPABASE_URL}/functions/v1`;
const DB_BASE = `${SUPABASE_URL}/rest/v1`;

const AUTH_HEADERS = {
  'apikey': ANON_KEY,
  'Authorization': `Bearer ${ANON_KEY}`,
  'Content-Type': 'application/json',
};

async function fnGet(path) {
  const res = await fetch(`${FN}/${path}`, { headers: AUTH_HEADERS });
  if (!res.ok) throw new Error(`${path}: ${res.status}`);
  return res.json();
}

async function fnPost(name, body) {
  const res = await fetch(`${FN}/${name}`, {
    method: 'POST',
    headers: AUTH_HEADERS,
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    let detail = '';
    try { const j = await res.json(); detail = j.error || JSON.stringify(j); } catch { try { detail = await res.text(); } catch {} }
    throw new Error(`${name} ${res.status}${detail ? `: ${detail}` : ''}`);
  }
  return res.json();
}

async function dbGet(path) {
  const res = await fetch(`${DB_BASE}/${path}`, {
    headers: { ...AUTH_HEADERS, Accept: 'application/json' },
  });
  if (!res.ok) throw new Error(`DB ${path}: ${res.status}`);
  return res.json();
}

// NSW course code → subject
// Keys include 2-letter, 3-letter, and 4-letter codes to handle variants like MSB, ENE, ECO
const COURSE_CODE_MAP = {
  MA: 'maths',    MX: 'maths',  MS: 'maths',  ME: 'maths',  MB: 'maths',
  MST: 'maths',   MSB: 'maths', MXE: 'maths',
  PH: 'physics',  CH: 'physics', BI: 'physics', SC: 'physics',
  EN: 'english',  EX: 'english', EA: 'english', EE: 'english', ENE: 'english', ENX: 'english',
  LE: 'legal',    LA: 'legal',   LS: 'legal',
  BS: 'business', BU: 'business', BUS: 'business',
  EC: 'economics', ECO: 'economics',
};

function detectSubject(courseName) {
  if (!courseName) return 'maths';
  const u = courseName.toUpperCase();
  // NSW: year level (11/12) immediately followed by 2–4 uppercase letters
  // Handles spaces like "11EC 5" and longer codes like "11MSB2", "11ENE1"
  const m = u.match(/\b(?:11|12)([A-Z]{2,4})/);
  if (m) {
    const code = m[1];
    if (COURSE_CODE_MAP[code]) return COURSE_CODE_MAP[code];
    // Try 2-letter prefix for codes like "MSB" → "MS"
    if (code.length > 2 && COURSE_CODE_MAP[code.slice(0, 2)]) return COURSE_CODE_MAP[code.slice(0, 2)];
  }
  if (u.includes('MATH') || u.includes('CALC')) return 'maths';
  if (u.includes('PHYS') || u.includes('CHEM') || u.includes('BIOL') || u.includes('SCIENCE')) return 'physics';
  if (u.includes('ENGLISH') || u.includes('LITERATURE')) return 'english';
  if (u.includes('LEGAL') || u.includes('LAW')) return 'legal';
  if (u.includes('ECONOMICS') || u.includes('ECON')) return 'economics';
  if (u.includes('BUSINESS') || u.includes('COMMERCE')) return 'business';
  return 'maths';
}

function fmtDuration(secs) {
  if (!secs) return '—';
  const m = Math.floor(secs / 60);
  const s = secs % 60;
  return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
}

function relativeDate(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  const now = new Date();
  const diff = Math.floor((now - d) / 86400000);
  if (diff === 0) return `Today, ${d.toLocaleTimeString('en', { hour: 'numeric', minute: '2-digit' })}`;
  if (diff === 1) return 'Yesterday';
  return d.toLocaleDateString('en', { weekday: 'short', day: 'numeric', month: 'short' });
}

function daysUntil(dateStr) {
  if (!dateStr) return null;
  const now = new Date();
  now.setHours(0, 0, 0, 0);
  return Math.ceil((new Date(dateStr) - now) / 86400000);
}

function fmtDue(dateStr) {
  if (!dateStr) return '';
  return new Date(dateStr).toLocaleDateString('en', { weekday: 'short', day: 'numeric', month: 'short' });
}

function transformLecture(row) {
  return {
    id: row.id,
    subject: row.subject || 'maths',
    date: relativeDate(row.recorded_at),
    title: row.title,
    status: 'ready',
    duration: fmtDuration(row.duration_secs),
    notionUrl: row.notion_url || NOTION_BASE,
  };
}

function transformPost(row) {
  return {
    id: row.id,
    subject: row.subject || detectSubject(row.course_name),
    type: row.post_type === 'assignment' ? 'Assignment'
        : row.post_type === 'material' ? 'Material'
        : row.post_type === 'question' ? 'Question'
        : 'Announcement',
    title: row.title || '(Untitled)',
    teacher: row.teacher_name || '',
    date: relativeDate(row.synced_at),
    excerpt: (row.description || '').slice(0, 140),
    body: row.description || '',
    files: (row.files || []).map(f => (typeof f === 'string' ? f : f.name)),
    done: row.done || false,
    courseId: row.course_id,
    postId: row.id,
  };
}

function transformMission(row) {
  const dl = daysUntil(row.due_date);
  const tasks = row.mission_tasks || [];
  const progress = tasks.length > 0 ? tasks.filter(t => t.completed).length / tasks.length : 0;
  return {
    id: row.id,
    subject: row.subject || 'maths',
    type: row.mission_type === 'test' ? 'Test' : 'Project',
    title: row.title,
    due: fmtDue(row.due_date),
    daysLeft: dl ?? 0,
    progress,
    description: row.description || '',
  };
}

function transformMissionDetail(row) {
  const today = new Date().toISOString().split('T')[0];
  const tasks = row.mission_tasks || [];
  const volumes = (row.mission_volumes || []).sort((a, b) => a.volume_number - b.volume_number);
  const totalTasks = tasks.length;
  const doneTasks = tasks.filter(t => t.completed).length;
  const progress = totalTasks > 0 ? doneTasks / totalTasks : 0;

  const todayTasks = tasks
    .filter(t => t.task_date === today)
    .sort((a, b) => a.sort_order - b.sort_order)
    .map(t => ({ id: t.id, label: t.title, est: `${t.estimated_minutes || 30} min`, done: t.completed || false }));

  // Group tasks into roadmap by date
  const dateMap = new Map();
  for (const t of tasks) {
    if (!dateMap.has(t.task_date)) dateMap.set(t.task_date, []);
    dateMap.get(t.task_date).push(t.title);
  }
  const roadmap = [...dateMap.entries()]
    .sort((a, b) => a[0].localeCompare(b[0]))
    .map(([dateStr, taskLabels]) => {
      const d = new Date(dateStr);
      const diff = Math.round((new Date(dateStr) - new Date(today)) / 86400000);
      let label = d.toLocaleDateString('en', { weekday: 'short', day: 'numeric', month: 'short' });
      let state = '';
      if (diff === 0) { label = 'Today'; state = 'today'; }
      else if (diff < 0) state = 'done';
      else if (dateStr === row.due_date) { label = 'Due date'; state = 'due'; }
      return {
        date: d.toLocaleDateString('en', { weekday: 'short', day: 'numeric', month: 'short' }),
        label,
        state,
        tasks: taskLabels.join(' · '),
        notes: [],
      };
    });

  return {
    id: row.id,
    subject: row.subject || 'maths',
    type: row.mission_type === 'test' ? 'Test' : 'Project',
    title: row.title,
    due: fmtDue(row.due_date),
    daysLeft: daysUntil(row.due_date) ?? 0,
    progress,
    description: row.description || '',
    todayTasks,
    roadmap,
    volumes: volumes.map(v => ({
      id: v.id,
      title: v.title,
      summary: v.summary || '',
      done: false,
      notionUrl: v.notion_url || null,
      flashcards: [],
    })),
  };
}

function transformTask(row) {
  if (row.task_type === 'lecture') {
    return {
      id: row.id,
      taskType: 'lecture',
      lectureId: row.lecture_session_id,
      missionId: null,
      subject: row.lecture_sessions?.subject || 'maths',
      mission: 'Notes review',
      label: row.title,
      est: `${row.estimated_minutes || 15} min`,
      done: row.completed || false,
    };
  }
  return {
    id: row.id,
    taskType: 'mission',
    missionId: row.mission_id,
    subject: row.missions?.subject || 'maths',
    mission: row.missions?.title || '',
    label: row.title,
    est: `${row.estimated_minutes || 30} min`,
    done: row.completed || false,
  };
}

// SSE streaming chat hook
function useSSEChat(initialText) {
  const [msgs, setMsgs] = React.useState(
    initialText ? [{ role: 'ai', label: 'Lynxe', text: initialText }] : []
  );
  const [streamIdx, setStreamIdx] = React.useState(-1);
  const [busy, setBusy] = React.useState(false);

  const send = React.useCallback(async (userText, endpoint, body) => {
    setMsgs(m => [...m, { role: 'user', text: userText }]);
    setBusy(true);
    setMsgs(m => { setStreamIdx(m.length); return [...m, { role: 'ai', label: 'Lynxe', text: '' }]; });

    try {
      const res = await fetch(`${FN}/${endpoint}`, {
        method: 'POST',
        headers: AUTH_HEADERS,
        body: JSON.stringify(body),
      });
      if (!res.ok) throw new Error(res.status);

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buf = '';

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buf += decoder.decode(value, { stream: true });
        const lines = buf.split('\n');
        buf = lines.pop() ?? '';
        for (const line of lines) {
          if (!line.startsWith('data: ')) continue;
          const raw = line.slice(6).trim();
          if (raw === '[DONE]') break;
          try {
            const ev = JSON.parse(raw);
            if (ev.type === 'text' && ev.text) {
              setMsgs(m => {
                const next = [...m];
                next[next.length - 1] = { ...next[next.length - 1], text: next[next.length - 1].text + ev.text };
                return next;
              });
            }
          } catch {}
        }
      }
    } catch {
      setMsgs(m => { const n = [...m]; n[n.length - 1] = { ...n[n.length - 1], text: 'Something went wrong. Try again.' }; return n; });
    } finally {
      setStreamIdx(-1);
      setBusy(false);
    }
  }, []);

  return [msgs, send, streamIdx, busy];
}

// Upload blob to Supabase Storage
async function storageUpload(bucket, path, blob) {
  const res = await fetch(`${SUPABASE_URL}/storage/v1/object/${bucket}/${path}`, {
    method: 'POST',
    headers: {
      apikey: ANON_KEY,
      Authorization: `Bearer ${ANON_KEY}`,
      'Content-Type': blob.type || 'audio/webm',
    },
    body: blob,
  });
  if (!res.ok) throw new Error(`Storage upload: ${await res.text()}`);
  return res.json();
}

Object.assign(window, {
  SUPABASE_URL, ANON_KEY, FN, AUTH_HEADERS,
  fnGet, fnPost, dbGet,
  fnGetCached, dbGetCached, invalidateCache, buildNotifications,
  detectSubject, fmtDuration, relativeDate, daysUntil, fmtDue,
  transformLecture, transformPost, transformMission, transformMissionDetail, transformTask,
  useSSEChat, storageUpload,
});
