/* global React, db */
/* ============================================================
 * Flashcards — shared helpers, FSRS scheduler, and field types
 * Loaded once and attached to window for the rest of the
 * flashcard-* files to consume.
 * ============================================================ */

// ----------------------------------------------------------------
// Field type registry
// ----------------------------------------------------------------
const FIELD_TYPES = [
  { id: 'text',   label: 'Short text',  icon: 'short_text',         desc: 'Single-line input' },
  { id: 'long',   label: 'Long text',   icon: 'subject',            desc: 'Multi-line textarea' },
  { id: 'tags',   label: 'Tags',        icon: 'sell',               desc: 'Chip-style multi-value' },
  { id: 'number', label: 'Number',      icon: 'numbers',            desc: 'Numeric value' },
  { id: 'date',   label: 'Date',        icon: 'event',              desc: 'Calendar date' },
  { id: 'select', label: 'Select',      icon: 'arrow_drop_down_circle', desc: 'Choose from preset options' },
  { id: 'image',  label: 'Image',       icon: 'image',              desc: 'Drag and drop, stored locally' },
  { id: 'audio',  label: 'Audio',       icon: 'graphic_eq',         desc: 'Record or upload' },
];
const FIELD_TYPE_BY_ID = Object.fromEntries(FIELD_TYPES.map(t => [t.id, t]));

// ----------------------------------------------------------------
// Starter templates (per user selection in scoping: General language)
// ----------------------------------------------------------------
const STARTER_TEMPLATES = [
  {
    id: 'blank',
    name: 'Blank',
    icon: 'add',
    description: 'Start from scratch — design every field yourself.',
    schema: [],
    promptIdx: 0,
    answerIdxs: [],
  },
  {
    id: 'general',
    name: 'General language',
    icon: 'translate',
    description: 'Term · Translation · Example sentence · Tags. Works for most language pairs.',
    schema: [
      { name: 'Term',        type: 'text', required: true,  hint: 'Word or phrase in the target language' },
      { name: 'Translation', type: 'text', required: true,  hint: 'Your-language meaning' },
      { name: 'Example',     type: 'long', required: false, hint: 'A sentence using the term' },
      { name: 'Tags',        type: 'tags', required: false, hint: 'noun, verb, formal…' },
    ],
    promptIdx: 0,
    answerIdxs: [1, 2],
  },
  {
    id: 'definition',
    name: 'Concept · definition',
    icon: 'menu_book',
    description: 'Term and definition only. Good for non-language study.',
    schema: [
      { name: 'Term',       type: 'text', required: true,  hint: 'What you want to remember' },
      { name: 'Definition', type: 'long', required: true,  hint: 'In your own words' },
    ],
    promptIdx: 0,
    answerIdxs: [1],
  },
];

// ----------------------------------------------------------------
// FSRS-4.5 scheduler — a faithful but compact JS port
// ----------------------------------------------------------------
const FSRS_W = [
  0.4072, 1.1829, 3.1262, 15.4722, 7.2102,
  0.5316, 1.0651, 0.0234, 1.616,  0.1544,
  1.0824, 1.9813, 0.0953, 0.2975, 2.2042,
  0.2407, 2.9466, 0.5034, 0.6567,
];
const FSRS_DECAY = -0.5;
const FSRS_FACTOR = Math.pow(0.9, 1 / FSRS_DECAY) - 1; // ≈ 0.2346
const REQUEST_RETENTION = 0.9;
const DAY_MS = 86400000;
const MAX_INTERVAL_DAYS = 36500;

function fsrsForgettingCurve(elapsedDays, stability) {
  if (!stability || stability <= 0) return 0;
  return Math.pow(1 + FSRS_FACTOR * elapsedDays / stability, FSRS_DECAY);
}

function fsrsInitialStability(rating) {
  // rating 1=Again, 2=Hard, 3=Good, 4=Easy
  return Math.max(0.1, FSRS_W[rating - 1]);
}
function fsrsInitialDifficulty(rating) {
  const d = FSRS_W[4] - Math.exp(FSRS_W[5] * (rating - 1)) + 1;
  return Math.min(10, Math.max(1, d));
}
function fsrsNextDifficulty(d, rating) {
  const next = d - FSRS_W[6] * (rating - 3);
  const meanReversion = FSRS_W[7] * fsrsInitialDifficulty(4) + (1 - FSRS_W[7]) * next;
  return Math.min(10, Math.max(1, meanReversion));
}
function fsrsNextRecallStability(d, s, r, rating) {
  const hardPenalty = rating === 2 ? FSRS_W[15] : 1;
  const easyBonus   = rating === 4 ? FSRS_W[16] : 1;
  return s * (1 +
    Math.exp(FSRS_W[8]) *
    (11 - d) *
    Math.pow(s, -FSRS_W[9]) *
    (Math.exp(FSRS_W[10] * (1 - r)) - 1) *
    hardPenalty * easyBonus);
}
function fsrsNextForgetStability(d, s, r) {
  return FSRS_W[11] *
    Math.pow(d, -FSRS_W[12]) *
    (Math.pow(s + 1, FSRS_W[13]) - 1) *
    Math.exp(FSRS_W[14] * (1 - r));
}
function fsrsNextInterval(stability) {
  const days = (stability / FSRS_FACTOR) * (Math.pow(REQUEST_RETENTION, 1 / FSRS_DECAY) - 1);
  return Math.max(1, Math.min(MAX_INTERVAL_DAYS, Math.round(days)));
}

/**
 * Given a card's current FSRS state and a rating (1-4), compute the
 * next state. Returns a NEW fsrs object — pure, no DB writes.
 *
 *   review(fsrs, rating, now) → fsrs'
 *
 * Card.fsrs shape: { state, stability, difficulty, due, lastReview, reps, lapses }
 *   state: 'new' | 'learning' | 'review' | 'relearning'
 *
 * Short-interval handling: brand new + Again/Hard cards stay in
 * "learning" with a short minute-level next-due so they recycle in the
 * same session. Anything graded Good/Easy on first sight goes straight
 * to the spaced-out review state.
 */
function fsrsReview(fsrs, rating, now = Date.now()) {
  const prev = fsrs || { state: 'new', stability: 0, difficulty: 0, due: null, lastReview: null, reps: 0, lapses: 0 };
  const isFirst = !prev.lastReview || prev.state === 'new';

  let stability, difficulty;
  let retrievability = 1;

  if (isFirst) {
    stability = fsrsInitialStability(rating);
    difficulty = fsrsInitialDifficulty(rating);
  } else {
    const elapsed = Math.max(0, (now - prev.lastReview) / DAY_MS);
    retrievability = fsrsForgettingCurve(elapsed, prev.stability || 1);
    difficulty = fsrsNextDifficulty(prev.difficulty || 5, rating);
    if (rating === 1) {
      stability = fsrsNextForgetStability(prev.difficulty || 5, prev.stability || 1, retrievability);
    } else {
      stability = fsrsNextRecallStability(prev.difficulty || 5, prev.stability || 1, retrievability, rating);
    }
  }

  // Decide next state + interval.
  // Re-learning loop: Again/Hard on a brand-new card OR Again on a review card
  // stays short (minutes) so we see it again this session.
  let nextState = 'review';
  let nextDue;
  if (isFirst && rating <= 2) {
    nextState = 'learning';
    nextDue = now + (rating === 1 ? 1 : 5) * 60 * 1000;
  } else if (!isFirst && rating === 1) {
    nextState = 'relearning';
    nextDue = now + 10 * 60 * 1000;
  } else {
    const days = fsrsNextInterval(stability);
    nextDue = now + days * DAY_MS;
  }

  return {
    state: nextState,
    stability,
    difficulty,
    due: nextDue,
    lastReview: now,
    reps: (prev.reps || 0) + 1,
    lapses: (prev.lapses || 0) + (rating === 1 ? 1 : 0),
  };
}

// Format the "next interval" preview shown above each rating button.
function fsrsPreviewInterval(fsrs, rating, now = Date.now()) {
  const next = fsrsReview(fsrs, rating, now);
  const diff = Math.max(0, next.due - now);
  if (diff < 60 * 60 * 1000) return `<${Math.max(1, Math.round(diff / 60000))}m`;
  if (diff < DAY_MS)        return `${Math.round(diff / (60 * 60 * 1000))}h`;
  if (diff < DAY_MS * 30)   return `${Math.round(diff / DAY_MS)}d`;
  if (diff < DAY_MS * 365)  return `${Math.round(diff / (DAY_MS * 30))}mo`;
  return `${Math.round(diff / (DAY_MS * 365))}y`;
}

function isDue(card, now = Date.now()) {
  const f = card?.fsrs;
  if (!f) return true;
  if (f.state === 'new' && f.due == null) return true;
  return f.due != null && f.due <= now;
}

// ----------------------------------------------------------------
// Misc helpers
// ----------------------------------------------------------------
function fieldUid() {
  return 'f_' + Math.random().toString(36).slice(2, 9);
}

// Plain-text preview of any field value, for table cells + study summary.
function fieldPreview(value, type) {
  if (value == null || value === '') return '';
  if (type === 'tags') return Array.isArray(value) ? value.join(', ') : String(value);
  if (type === 'image') return '🖼 image';
  if (type === 'audio') return '🔊 audio';
  if (type === 'date') {
    const n = Number(value);
    if (Number.isFinite(n)) return new Date(n).toLocaleDateString();
    return String(value);
  }
  if (typeof value === 'string') return value.length > 120 ? value.slice(0, 120) + '…' : value;
  return String(value);
}

// Given a list of cards, return how many are due now and the soonest upcoming due-time.
function dueStats(cards, now = Date.now()) {
  let due = 0, upcoming = null;
  for (const c of cards) {
    if (isDue(c, now)) due++;
    else if (c.fsrs?.due != null && (upcoming == null || c.fsrs.due < upcoming)) upcoming = c.fsrs.due;
  }
  return { due, upcoming, total: cards.length };
}

// ----------------------------------------------------------------
// Export to window
// ----------------------------------------------------------------
window.flashcards = {
  FIELD_TYPES,
  FIELD_TYPE_BY_ID,
  STARTER_TEMPLATES,
  fsrsReview,
  fsrsPreviewInterval,
  isDue,
  dueStats,
  fieldUid,
  fieldPreview,
  DAY_MS,
};
