// AKC Course Booking - backend data adapter.
// This file intentionally keeps the exported shape expected by the new frontend
// while replacing demo data with the existing AKC backend API.

const AKC_API_BASE = (
  (window.AKCBookingConfig && window.AKCBookingConfig.restBase) ||
  new URLSearchParams(window.location.search).get('api') ||
  'http://127.0.0.1:4176/wp-json/akc/v1'
).replace(/\/$/, '');

const COURSES = [];
const GET_CACHE = new Map();
const GET_CACHE_TTL_MS = 60000;
const SMART_CATEGORY_GROUPS = [
  {
    label: 'WSH Courses',
    color: '#FFA239',
    colorLight: '#FFF6EC',
    subcategories: [
      'Worker Courses',
      'Supervisor Courses',
      'Work At Height Courses',
      'Forklift & MEWP Courses',
      'Manager Courses',
      'bizSAFE Courses',
      'WSH Coordinator Courses',
      'Problem Identification Courses',
    ],
  },
  {
    label: 'Food & Beverages Courses',
    color: '#2E7D32',
    colorLight: '#EBF5EC',
    subcategories: [
      'Food Safety Courses',
      'F&B Operations Courses',
      'Customer Service Excellence Courses',
      'Halal Certification Training',
    ],
  },
  {
    label: 'Employment Agency Courses',
    color: '#26A69A',
    colorLight: '#EBF6F5',
    subcategories: [
      'Certificate Of Employment Intermediaries',
      'SIP For MDWs | Settling-In Programme',
    ],
  },
  {
    label: 'First Aid & SCDF',
    color: '#C0392B',
    colorLight: '#FBEAE9',
    subcategories: [
      'First Aid Courses',
      'SCDF Courses',
    ],
  },
  {
    label: 'General Courses',
    color: '#6A4C93',
    colorLight: '#F3EDF7',
    subcategories: [
      'Security Courses',
      'Traffic Control Courses',
      'Environmental Services',
      'Mental Well-Being Courses',
      'Soft Skill Courses',
      'Digital Marketing Courses',
      'AI Courses',
      'Other',
    ],
  },
];
const SMART_CATEGORIES = SMART_CATEGORY_GROUPS.flatMap(group => group.subcategories);

function cachedGetKey(path, params) {
  const entries = Object.keys(params || {})
    .filter((key) => params[key])
    .sort()
    .map((key) => `${key}=${String(params[key])}`);
  return `${path}?${entries.join('&')}`;
}

function asArray(data) {
  if (Array.isArray(data)) return data;
  if (data && Array.isArray(data.value)) return data.value;
  if (data && Array.isArray(data.data)) return data.data;
  return [];
}

async function akcApiGet(path, params = {}) {
  const cacheKey = cachedGetKey(path, params);
  const cached = GET_CACHE.get(cacheKey);
  const now = Date.now();
  if (cached && cached.data && cached.expiresAt > now) {
    return cached.data;
  }
  if (cached && cached.promise) {
    return cached.promise;
  }

  const url = new URL(`${AKC_API_BASE}/${path}`, window.location.href);
  Object.keys(params).forEach((key) => {
    if (params[key]) url.searchParams.set(key, params[key]);
  });

  const promise = fetch(url.toString(), { credentials: 'same-origin' })
    .then(async (response) => {
      const data = await response.json().catch(() => ({}));
      if (!response.ok || data.success === false) {
        const error = new Error(data.message || 'AKC backend request failed.');
        error.status = response.status;
        throw error;
      }
      GET_CACHE.set(cacheKey, { data, expiresAt: Date.now() + GET_CACHE_TTL_MS });
      if (GET_CACHE.size > 80) {
        GET_CACHE.delete(GET_CACHE.keys().next().value);
      }
      return data;
    })
    .catch((error) => {
      GET_CACHE.delete(cacheKey);
      throw error;
    });

  GET_CACHE.set(cacheKey, { promise, expiresAt: now + GET_CACHE_TTL_MS });
  return promise;
}

async function akcApiPost(path, payload) {
  const response = await fetch(`${AKC_API_BASE}/${path}`, {
    method: 'POST',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
      'X-WP-Nonce': (window.AKCBookingConfig && window.AKCBookingConfig.nonce) || '',
    },
    body: JSON.stringify(payload),
  });
  const data = await response.json().catch(() => ({}));
  if (!response.ok || data.success === false) {
    throw new Error(data.message || 'AKC backend request failed.');
  }
  return data;
}

function normalizeStatus(status) {
  const text = String(status || '').trim();
  if (!text || text === '_blank_') return 'open';
  if (/confirm/i.test(text)) return 'confirmed';
  if (/pending/i.test(text)) return 'pending';
  return text;
}

const LESSON_MONTHS = {
  jan: 'Jan', january: 'Jan',
  feb: 'Feb', february: 'Feb',
  mar: 'Mar', march: 'Mar',
  apr: 'Apr', april: 'Apr',
  may: 'May',
  jun: 'Jun', june: 'Jun',
  jul: 'Jul', july: 'Jul',
  aug: 'Aug', august: 'Aug',
  sep: 'Sep', sept: 'Sep', september: 'Sep',
  oct: 'Oct', october: 'Oct',
  nov: 'Nov', november: 'Nov',
  dec: 'Dec', december: 'Dec',
};

function joinLessonDays(days) {
  if (days.length <= 1) return days[0] || '';
  if (days.length === 2) return `${days[0]} & ${days[1]}`;
  return `${days.slice(0, -1).join(', ')} & ${days[days.length - 1]}`;
}

function normalizeLessonDates(value, fallbackStartDate = '') {
  const raw = String(value || '')
    .replace(/\s*\([^)]*CLASS\)\s*/gi, ' ')
    .replace(/\s+/g, ' ')
    .trim();
  if (!raw) return { text: '', count: 0 };

  const fallbackYear = String(fallbackStartDate || '').slice(0, 4);
  const groups = [];
  const monthPattern = Object.keys(LESSON_MONTHS).sort((a, b) => b.length - a.length).join('|');
  const pattern = new RegExp(`((?:\\d{1,2}\\s*,\\s*)*\\d{1,2})\\s+(${monthPattern})(?:\\s+(\\d{2,4}))?`, 'gi');
  let match;

  while ((match = pattern.exec(raw)) !== null) {
    const days = match[1].split(',').map(day => Number(day.trim())).filter(day => day >= 1 && day <= 31);
    const month = LESSON_MONTHS[String(match[2] || '').toLowerCase()];
    let year = String(match[3] || fallbackYear || '');
    if (year.length === 2) year = `20${year}`;
    if (days.length && month) groups.push({ days, month, year });
  }

  if (!groups.length) return { text: raw, count: 0 };

  const mergedGroups = [];
  groups.forEach((group) => {
    const previous = mergedGroups[mergedGroups.length - 1];
    if (previous && previous.month === group.month && previous.year === group.year) {
      previous.days.push(...group.days);
    } else {
      mergedGroups.push({ ...group, days: [...group.days] });
    }
  });

  const text = mergedGroups.map((group, index) => {
    const next = mergedGroups[index + 1];
    const showYear = Boolean(group.year) && (!next || next.year !== group.year);
    return `${joinLessonDays(group.days)} ${group.month}${showYear ? ` ${group.year}` : ''}`;
  }).join('; ');

  return {
    text,
    count: mergedGroups.reduce((sum, group) => sum + group.days.length, 0),
  };
}

function looksLikeLessonDates(value) {
  return /\b\d{1,2}\s*(?:,|\s)\s*\d{1,2}\b/i.test(String(value || ''))
    && /\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\b/i.test(String(value || ''));
}

function normalizeIntake(row) {
  const lessonDatesSource = row.lesson_dates || (looksLikeLessonDates(row.lesson_schedule) ? row.lesson_schedule : '');
  const lessonDateInfo = normalizeLessonDates(lessonDatesSource, row.start_date);
  const lessonSchedule = row.lesson_dates || !looksLikeLessonDates(row.lesson_schedule) ? row.lesson_schedule : '';

  return {
    id: row.intake_id || row.course_code,
    start: row.start_date || '',
    end: row.end_date || row.start_date || '',
    date_display: lessonDateInfo.text,
    lesson_count: lessonDateInfo.count,
    schedule_display: lessonSchedule || '',
    start_time: row.start_time || '',
    end_time: row.end_time || '',
    language: row.language || 'To be confirmed',
    venue: row.venue || 'To be confirmed',
    seats: Number(row.available_seats || row.max_seat_availability || 0),
    max_seats: Number(row.max_seats || row.max_seat_availability || row.available_seats || 1),
    status: normalizeStatus(row.class_confirmation),
    full_fee: Number(row.full_fee || 0),
    fee_preview: row.fee_preview || null,
    backend: row,
  };
}

function daysBetween(start, end) {
  const s = new Date(`${start}T00:00:00`);
  const e = new Date(`${end || start}T00:00:00`);
  if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) return 1;
  return Math.max(1, Math.round((e - s) / 86400000) + 1);
}

function groupIntakes(rows) {
  const map = new Map();
  asArray(rows).forEach((row) => {
    const courseCode = row.course_code || row.intake_id || 'AKC';
    const displayCode = row.web_main_course || baseCourseCode(courseCode);
    const courseName = canonicalCourseName(row);
    const smartCategory = inferSmartCategory(displayCode, courseName, row);
    const smartMainCategory = smartMainCategoryFor(smartCategory);
    const key = `${displayCode}|${courseName.toLowerCase()}`;
    const intake = normalizeIntake(row);

    if (!map.has(key)) {
      map.set(key, {
        group_key: key,
        code: displayCode,
        name: courseName,
        category: smartCategory,
        main_category: smartMainCategory,
        sub_category: smartCategory,
        nav_category: row.category || '',
        course_category: row.course_category || '',
        web_course_category: row.web_course_category || '',
        web_display_style: row.web_display_style || '',
        description: canonicalCourseDescription(courseName, row),
        duration: '',
        full_fee: Number(row.full_fee || 0),
        from_price: row.fee_preview ? Number(row.fee_preview.estimated_total_payable || 0) : 0,
        fee_includes_gst: false,
        funding_track: 'backend',
        funding_eligible: Boolean(row.fee_preview?.funding_eligible),
        tags: ['Live availability', row.fee_preview?.funding_eligible ? 'Funding estimate' : 'SkillsFuture Credit option'],
        intakes: [],
        backend: row,
      });
    }

    const course = map.get(key);
    if (row.fee_preview?.funding_eligible && !course.funding_eligible) {
      course.funding_eligible = true;
      course.tags = ['Live availability', 'Funding estimate'];
    }
    if (intake.full_fee && (!course.full_fee || intake.full_fee < course.full_fee)) {
      course.full_fee = intake.full_fee;
    }
    if (intake.fee_preview && intake.fee_preview.estimated_total_payable) {
      const previewPrice = Number(intake.fee_preview.estimated_total_payable || 0);
      if (previewPrice && (!course.from_price || previewPrice < course.from_price)) {
        course.from_price = previewPrice;
      }
    }
    course.intakes.push(intake);
  });

  return Array.from(map.values()).map((course) => {
    const lessonCounts = course.intakes.map(intake => intake.lesson_count).filter(Boolean);
    const distinctCounts = [...new Set(lessonCounts)].sort((a, b) => a - b);
    let duration = '';
    if (distinctCounts.length === 1) {
      duration = `${distinctCounts[0]} lesson${distinctCounts[0] === 1 ? '' : 's'}`;
    } else if (distinctCounts.length > 1) {
      duration = `${distinctCounts[0]}-${distinctCounts[distinctCounts.length - 1]} lessons`;
    } else {
      const fallbackDays = daysBetween(course.intakes[0]?.start, course.intakes[0]?.end);
      duration = `${fallbackDays} lesson${fallbackDays === 1 ? '' : 's'}`;
    }

    return {
      ...course,
      duration,
      intakes: course.intakes.sort((a, b) => `${a.start || ''} ${a.start_time || ''}`.localeCompare(`${b.start || ''} ${b.start_time || ''}`)),
    };
  }).sort((a, b) => a.name.localeCompare(b.name));
}

function cleanCourseName(name) {
  const text = String(name || '').trim();
  const languagePattern = /\s*\((english|mandarin|bengali|tamil|bahasa indonesia|tagalog|myanmar|burmese|hindi|telugu|malay|malayalam|kannada|chinese|thai|vietnamese)\)\s*$/i;
  return text.replace(languagePattern, '').trim() || text;
}

function collapseRepeatedCourseText(name) {
  const text = String(name || '').trim();
  const words = text.split(/\s+/).filter(Boolean);
  if (words.length < 2) return text;
  const wordsLower = words.map((word) => word.toLowerCase());
  const maxSize = Math.floor(words.length / 2);
  for (let size = maxSize; size > 0; size -= 1) {
    const first = wordsLower.slice(0, size).join(' ');
    const second = wordsLower.slice(size, size * 2).join(' ');
    if (first === second) {
      return [...words.slice(0, size), ...words.slice(size * 2)].join(' ').trim();
    }
  }
  return text;
}

function mergeOverlappingCourseText(title, detail) {
  const cleanTitle = String(title || '').trim();
  const cleanDetail = String(detail || '').trim();
  if (!cleanTitle) return cleanDetail;
  if (!cleanDetail) return cleanTitle;

  const titleLower = cleanTitle.toLowerCase();
  const detailLower = cleanDetail.toLowerCase();
  if (detailLower.startsWith(titleLower)) return cleanDetail;
  if (titleLower.startsWith(detailLower)) return cleanTitle;

  const titleWords = cleanTitle.split(/\s+/);
  const detailWords = cleanDetail.split(/\s+/);
  const titleWordsLower = titleWords.map((word) => word.toLowerCase());
  const detailWordsLower = detailWords.map((word) => word.toLowerCase());
  const maxOverlap = Math.min(titleWords.length, detailWords.length);
  for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
    const titleTail = titleWordsLower.slice(-overlap).join(' ');
    const detailHead = detailWordsLower.slice(0, overlap).join(' ');
    if (titleTail === detailHead) {
      return [...titleWords, ...detailWords.slice(overlap)].join(' ').trim();
    }
  }

  return '';
}

function courseDetailText(row) {
  const primary = cleanCourseName(row.course_description || row.class_description || '');
  const secondary = cleanCourseName(row.description_2 || '');
  if (primary && secondary) {
    return mergeOverlappingCourseText(primary, secondary) || `${primary} ${secondary}`.trim();
  }
  return primary || secondary;
}

function canonicalCourseName(row) {
  const title = collapseRepeatedCourseText(cleanCourseName(row.course_name || row.course_code || 'AKC course'));
  const detail = courseDetailText(row);
  if (detail && (/\s(in|for|to|of)$/i.test(title) || /^((in|for|to|of|and)\s|&\s|\()/i.test(detail))) {
    return mergeOverlappingCourseText(title, detail) || `${title} ${detail}`.trim();
  }
  return title;
}

function canonicalCourseDescription(courseName, row) {
  const detail = courseDetailText(row);
  if (detail && !courseName.toLowerCase().includes(detail.toLowerCase())) {
    return '';
  }
  const search = cleanCourseName(row.search_description || '');
  return search && !courseName.toLowerCase().includes(search.toLowerCase()) ? '' : '';
}

function baseCourseCode(code) {
  let text = String(code || '').trim().toUpperCase();
  text = text.replace(/^(HT|MP|WL|LY)\d{2}/, '');
  text = text.replace(/(EN|MN|BD|TM|MY|BM|BU|HI|TL|TH|ID|TG|VN|ML|KN)\d*$/, '');
  return text || String(code || '').trim();
}

function inferSmartCategory(code, courseName, row = {}) {
  const text = [
    code,
    courseName,
    row.course_description,
    row.search_description,
  ].map((value) => String(value || '').toLowerCase()).join(' ');

  if (hasAny(text, ['ai for workforce', 'artificial intelligence', 'ai fundamentals', 'generative ai', 'chatgpt'])) return 'AI Courses';
  if (hasAny(text, ['digital marketing', 'social media marketing', 'content marketing'])) return 'Digital Marketing Courses';
  if (hasAny(text, ['customer service excellence'])) return 'Customer Service Excellence Courses';
  if (hasAny(text, ['halal'])) return 'Halal Certification Training';
  if (hasAny(text, ['f&b operations', 'food and beverage operations', 'food & beverage operations'])) return 'F&B Operations Courses';
  if (hasAny(text, ['food safety', 'food hygiene', 'hygiene officer'])) return 'Food Safety Courses';
  if (hasAny(text, ['bizsafe'])) return 'bizSAFE Courses';
  if (hasAny(text, ['traffic controller', 'traffic control', 'basic traffic manager'])) return 'Traffic Control Courses';
  if (hasAny(text, ['top executive', 'tewp', 'risk management implementation', 'wshms', 'chief executives', 'board of directors', 'wsh committee', 'manage workplace safety and health in construction sites'])) return 'Manager Courses';
  if (hasAny(text, ['supervise ', 'supervisor', 'bcss', 'wsh management in construction industry', 'workplace safety and health management in construction industry', 'implementing total workplace safety', 'fire warden'])) return 'Supervisor Courses';
  if (hasAny(text, ['settling-in programme', 'settling in programme'])) return 'SIP For MDWs | Settling-In Programme';
  if (hasAny(text, ['occupational first aid', 'standard first aid', 'cardiac life support', 'cpr', 'aed', 'first aider'])) return 'First Aid Courses';
  if (hasAny(text, ['forklift', 'mewp', 'boom lift', 'scissor lift', 'vertical personnel platform'])) return 'Forklift & MEWP Courses';
  if (hasAny(text, ['work at height', 'work-at-height', 'fall prevention'])) return 'Work At Height Courses';
  if (hasAny(text, ['certificate for employment intermediaries', 'employment intermediaries', 'cei'])) return 'Certificate Of Employment Intermediaries';
  if (hasAny(text, ['respond to fire', 'fire emergency', 'hazmat', 'incident management processes', 'scdf'])) return 'SCDF Courses';
  if (hasAny(text, ['security', 'guard and patrol', 'access control', 'threat observation', 'deterrence', 'security screening', 'security surveillance', 'handle security incidents'])) return 'Security Courses';
  if (hasAny(text, ['mental', 'well-being', 'wellbeing', 'resilience', 'wellness', 'compass'])) return 'Mental Well-Being Courses';
  if (hasAny(text, ['soft skill', 'soft skills', 'communication skills', 'service mindset', 'leadership', 'problem solving', 'teamwork'])) return 'Soft Skill Courses';
  if (hasAny(text, ['surface maintenance', 'washroom', 'furniture', 'furnishing', 'customer management', 'public hygiene', 'cleaning supervisor', 'equipment and inventory', 'environmental services', 'wsh practices implementation'])) return 'Environmental Services';
  if (hasAny(text, ['problem identification'])) return 'Problem Identification Courses';
  if (hasAny(text, ['advanced certificate in wsh', 'wsh coordinator', 'measure, monitor and report', 'workplace communications', 'assess confined space for safe entry'])) return 'WSH Coordinator Courses';
  if (hasAny(text, [
    'apply wsh',
    'apply workplace safety and health',
    'construction safety orientation',
    'csoc',
    'msoc',
    'basic english for migrant workers',
    'banksmen',
    'earthworks',
    'rigger',
    'signalman',
    'lorry crane',
    'confined space operations',
    'process plant',
    'metal scaffold',
    'suspended scaffold',
    'explosive powered tools',
    'crane operation',
    'tunnelling',
    'shipyard',
    'logistics and transportation',
    'chemical handling',
    'formwork activities',
    'fire watchmen',
    'safety orientation course',
  ])) return 'Worker Courses';
  return 'Other';
}

function hasAny(text, needles) {
  return needles.some((needle) => text.includes(needle));
}

function smartMainCategoryFor(subcategory) {
  const group = SMART_CATEGORY_GROUPS.find(item => item.subcategories.includes(subcategory));
  return group ? group.label : 'General Courses';
}

async function fetchGroupedCourses(filters = {}) {
  const rows = await akcApiGet('intakes', filters);
  return groupIntakes(rows);
}

async function fetchBrowseData(filters = {}) {
  try {
    const data = await akcApiGet('browse', filters);
    const rows = asArray(data.intakes);
    return {
      courses: groupIntakes(rows),
      options: {
        languages: Array.isArray(data.languages) ? data.languages : uniqueFromIntakes(rows, 'language'),
        locations: Array.isArray(data.locations) ? data.locations : uniqueFromIntakes(rows, 'venue'),
      },
      cache: data.cache || null,
    };
  } catch (error) {
    if (error.status !== 404) {
      throw error;
    }
    const [rows, options] = await Promise.all([
      akcApiGet('intakes', filters),
      fetchFilterOptions(filters),
    ]);
    return {
      courses: groupIntakes(rows),
      options,
      cache: null,
    };
  }
}

async function fetchFilterOptions(filters = {}) {
  const [languageRows, locationRows] = await Promise.all([
    akcApiGet('intakes', { ...filters, language: '' }),
    akcApiGet('intakes', { ...filters, location: '' }),
  ]);
  return {
    languages: uniqueFromIntakes(languageRows, 'language'),
    locations: uniqueFromIntakes(locationRows, 'venue'),
  };
}

function uniqueFromIntakes(rows, key) {
  return Array.from(new Set(asArray(rows).map((row) => String(row[key] || '').trim()).filter(Boolean))).sort();
}

// Nationality is now stored as the MOM canonical "LABEL | CODE" string
// (e.g. "SINGAPORE CITIZEN | SG"). The backend reads either part for funding.
function nationalityLabel(value) {
  return value || '';
}

function selectedIntakePayload(course, intake) {
  const raw = intake.backend || {};
  return {
    intake_id: raw.intake_id || intake.id || '',
    class_no: raw.class_no || raw.intake_id || intake.id || '',
    id_key: raw.id_key || raw.class_no || raw.intake_id || intake.id || '',
    course_code: raw.course_code || course.code || '',
    web_main_course: raw.web_main_course || course.code || '',
    course_name: raw.course_name || course.name || '',
    class_description: raw.class_description || '',
    description_2: raw.description_2 || raw.course_description || '',
    start_date: raw.start_date || intake.start || '',
    end_date: raw.end_date || intake.end || intake.start || '',
    start_time: raw.start_time || intake.start_time || '',
    end_time: raw.end_time || intake.end_time || '',
    language: raw.language || intake.language || '',
    course_language: raw.course_language || '',
    venue: raw.venue || intake.venue || '',
    branch_code: raw.branch_code || '',
    is_tpg: Boolean(raw.is_tpg),
    mom_reference_no: raw.mom_reference_no || '',
    vendor_item_no: raw.vendor_item_no || '',
    tpgateway_upload_date: raw.tpgateway_upload_date || '',
    course_category: raw.course_category || '',
    web_course_category: raw.web_course_category || '',
    web_display_style: raw.web_display_style || '',
    view_generated_at: raw.view_generated_at || '',
    full_fee: Number(raw.full_fee || intake.full_fee || course.full_fee || 0),
  };
}

function buildRegistrationPayload({ course, intake, learners, fees, useSkillsFuture, preferredContact, pdpaAcknowledged, termsAcknowledged, wshTrsConsentAcknowledged, marketingConsentAcknowledged }) {
  // Individual-only payload. Company registrations are handled by the old form.
  const sfc = useSkillsFuture ? 'yes' : 'no';
  const sourceParam = new URLSearchParams(window.location.search).get('source');
  const isWhatsappPath = /\/booking\/whatsapp\/?$/i.test(window.location.pathname);
  const source = isWhatsappPath || String(sourceParam || '').toLowerCase() === 'whatsapp' ? 'Whatsapp' : 'Website';
  return {
    selected_intake: selectedIntakePayload(course, intake),
    registration_type: 'individual',
    company_mode: null,
    company: {},
    learners: learners.map((learner) => ({
      learner_name: learner.learner_name || '',
      date_of_birth: learner.dob || '',
      learner_email: learner.email || '',
      learner_mobile_no: learner.mobile || '',
      id_type: learner.id_type || '',
      id_number: learner.id_number || '',
      nationality: nationalityLabel(learner.nationality),
      race: learner.race || '',
    })),
    funding: {
      applying_for_subsidy: 'yes',
      using_skillsfuture_credit: sfc,
      company_applying_funding: '',
      preferred_payment_method: 'Not sure',
      remarks: '',
    },
    preferences: {
      use_skillsfuture_credit: useSkillsFuture || false,
      preferred_contact_method: preferredContact || '',
    },
    fee_estimate: fees && fees.backendEstimate ? fees.backendEstimate : {},
    internal: {
      registration_id: '',
      submission_status: '',
      created_at: '',
      updated_at: '',
      form_version: 'agoda-style-backend-linked-individual',
      source_url: window.location.href.split('#')[0],
      source,
      pdpa_acknowledged: !!pdpaAcknowledged,
      terms_acknowledged: !!termsAcknowledged,
      wsh_trs_consent_acknowledged: !!wshTrsConsentAcknowledged,
      marketing_consent_acknowledged: !!marketingConsentAcknowledged,
    },
  };
}

function feeFromBackend(estimate, learnerCount) {
  const n = Math.max(1, Number(learnerCount || 1));
  const total = Number(estimate.estimated_total_payable || 0);
  const funding = Number(estimate.estimated_funding_amount || 0);
  return {
    rate: Number(estimate.funding_percent_used || 0) / 100,
    feeBeforeGst: Number(estimate.full_course_fee || 0) / n,
    funding: funding / n,
    nett: Number(estimate.estimated_nett_fee || 0) / n,
    gst: Number(estimate.gst_amount || 0) / n,
    perLearner: total / n,
    learners: n,
    total,
    totalFundingValue: funding,
    fundingEligible: estimate.funding_eligible !== false,
    backendEstimate: estimate,
  };
}

function calcFee(course, profile, learnerCount = 1) {
  const n = Math.max(1, learnerCount);
  // The database stores GST-inclusive prices (default_fee_includes_gst = true).
  // Strip GST out so feeBeforeGst, gst, and total are internally consistent
  // and match what the backend returns for the no-funding scenario.
  const gstInclusiveFee = Number(course.full_fee || 0);
  const gstRate = 0.09;
  const feeBeforeGst = gstInclusiveFee > 0 ? Math.round(gstInclusiveFee / (1 + gstRate) * 100) / 100 : 0;
  const gst = Math.round(feeBeforeGst * gstRate * 100) / 100;
  return {
    rate: 0,
    feeBeforeGst,
    funding: 0,
    nett: feeBeforeGst,
    gst,
    perLearner: feeBeforeGst + gst,
    learners: n,
    total: (feeBeforeGst + gst) * n,
    totalFundingValue: 0,
    fundingEligible: null,
    backendEstimate: null,
  };
}

async function calculateBackendFee({ course, intake, learners }) {
  const fallback = calcFee({ ...course, full_fee: intake.full_fee || course.full_fee }, { registrantType: 'individual' }, learners.length);
  const payload = buildRegistrationPayload({ course, intake, learners, fees: fallback });
  const estimate = await akcApiPost('calculate-fee', payload);
  return feeFromBackend(estimate, learners.length);
}

async function submitBackendRegistration({ course, intake, learners, fees, useSkillsFuture, preferredContact, pdpaAcknowledged, termsAcknowledged, wshTrsConsentAcknowledged, marketingConsentAcknowledged, captchaToken }) {
  const payload = buildRegistrationPayload({ course, intake, learners, fees, useSkillsFuture, preferredContact, pdpaAcknowledged, termsAcknowledged, wshTrsConsentAcknowledged, marketingConsentAcknowledged });
  if (captchaToken) payload.captcha_token = captchaToken;
  return akcApiPost('registrations', payload);
}

function money(n) {
  return '$' + (Math.round(Number(n || 0) * 100) / 100).toLocaleString('en-SG', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });
}

function fmtDateRange(startIso, endIso) {
  if (!startIso) return 'To be confirmed';
  const s = new Date(`${startIso}T00:00:00`);
  const e = new Date(`${endIso || startIso}T00:00:00`);
  if (Number.isNaN(s.getTime())) return startIso;
  if (Number.isNaN(e.getTime()) || startIso === endIso) {
    return s.toLocaleDateString('en-SG', { day: 'numeric', month: 'short', year: 'numeric' });
  }
  const sameMonth = s.getMonth() === e.getMonth() && s.getFullYear() === e.getFullYear();
  if (sameMonth) {
    return `${s.getDate()}-${e.getDate()} ${s.toLocaleDateString('en-SG', { month: 'short', year: 'numeric' })}`;
  }
  return `${s.toLocaleDateString('en-SG', { day: 'numeric', month: 'short' })} - ${e.toLocaleDateString('en-SG', { day: 'numeric', month: 'short', year: 'numeric' })}`;
}

function fmtWeekday(startIso) {
  if (!startIso) return '';
  return new Date(`${startIso}T00:00:00`).toLocaleDateString('en-SG', { weekday: 'short' });
}

Object.assign(window, {
  AKC_API_BASE,
  COURSES,
  SMART_CATEGORIES,
  SMART_CATEGORY_GROUPS,
  smartMainCategoryFor,
  akcApiGet,
  akcApiPost,
  fetchGroupedCourses,
  fetchBrowseData,
  fetchFilterOptions,
  calculateBackendFee,
  submitBackendRegistration,
  buildRegistrationPayload,
  calcFee,
  money,
  fmtDateRange,
  fmtWeekday,
});
