/**
 * Minimal REST-Client für /api/v1 — gleiche Origin wie sekretaer.html oder __CINEV_API_BASE__ (Cross-Origin).
 *
 * Wenn `__CINEV_API_BASE__` fehlt (z. B. Scoping-Client auf :3000, Komponenten von :3040), wird die Basis-URL
 * aus dem ersten absoluten `…/components/i18n.jsx`-Script abgeleitet — sonst landen REST-Calls auf dem
 * falschen Host (`Cannot GET /api/v1/...`).
 */
function cinevApiBaseFromBundledComponents() {
  if (typeof document === 'undefined' || typeof document.querySelector !== 'function') return '';
  const el = document.querySelector('script[src*="/components/i18n.jsx"]');
  if (!el || !el.src) return '';
  try {
    const u = new URL(el.src);
    if (u.protocol !== 'http:' && u.protocol !== 'https:') return '';
    if (!u.pathname.includes('/components/i18n.jsx')) return '';
    return u.origin;
  } catch {
    return '';
  }
}

/** Basis-URL ohne Pfad (nur Origin), ohne trailing slash; kein angehängtes `/api/v1`. */
function normalizeApiOrigin(base) {
  const s = String(base || '').trim().replace(/\/+$/, '');
  if (!s) return '';
  return s.replace(/\/api\/v1$/i, '');
}

function cinevApiBase() {
  if (typeof window === 'undefined') return '';
  const raw = window.__CINEV_API_BASE__;
  if (raw != null && String(raw).trim() !== '') {
    return normalizeApiOrigin(raw);
  }
  const fromScript = normalizeApiOrigin(cinevApiBaseFromBundledComponents());
  if (fromScript) return fromScript;
  if (
    typeof window.location?.origin === 'string' &&
    (window.location.protocol === 'http:' || window.location.protocol === 'https:')
  ) {
    return window.location.origin;
  }
  return '';
}

async function cinevApiFetch(path, options = {}) {
  const base = cinevApiBase();
  const pathPart = path.startsWith('/') ? path : `/${path}`;
  const url = base ? `${base}/api/v1${pathPart}` : `/api/v1${pathPart}`;
  const headers = { Accept: 'application/json', ...(options.headers || {}) };
  if (options.body && !headers['Content-Type']) headers['Content-Type'] = 'application/json';
  const org = typeof window !== 'undefined' && window.__CINEV_ORG_ID__;
  if (org) headers['X-Organization-Id'] = String(org);
  const res = await fetch(url, {
    ...options,
    headers,
    cache: options.cache != null ? options.cache : 'no-store',
  });
  const text = await res.text();
  let data = null;
  try {
    data = text ? JSON.parse(text) : null;
  } catch {
    data = { raw: text };
  }
  if (!res.ok) {
    const msg = (data && data.message) || text || res.statusText;
    const err = new Error(msg);
    err.status = res.status;
    err.body = data;
    throw err;
  }
  return data;
}

function cinevApiGet(path) {
  return cinevApiFetch(path, { method: 'GET' });
}

function cinevApiPatch(path, body) {
  return cinevApiFetch(path, { method: 'PATCH', body: body != null ? JSON.stringify(body) : undefined });
}

function cinevApiDelete(path) {
  return cinevApiFetch(path, { method: 'DELETE' });
}

function cinevApiPost(path, body) {
  return cinevApiFetch(path, { method: 'POST', body: body != null ? JSON.stringify(body) : undefined });
}

/** Vor-/Nachname aus API-Zeile (snake_case oder camelCase), ohne Zusatz-IDs. */
function cinevTherapistPersonNames(row) {
  const fn = String(row.first_name ?? row.firstName ?? '').trim();
  const ln = String(row.last_name ?? row.lastName ?? '').trim();
  return { fn, ln };
}

/** Therapeut:innen-Map für Avatar (UUID-String → { init, color, name, … }). */
function cinevTherapistMapFromItems(items) {
  const colors = ['#2c5f6f', '#8cc152', '#c96b3a', '#6b4d8a', '#3a8a7d', '#b58a2e'];
  const m = {};
  (items || []).forEach((row, i) => {
    if (row == null || row.id == null) return;
    const id = String(row.id);
    const { fn, ln } = cinevTherapistPersonNames(row);
    const name = [fn, ln].filter(Boolean).join(' ') || '?';
    const init = ((fn[0] || '') + (ln[0] || '')).toUpperCase() || '?';
    m[id] = {
      init,
      color: colors[i % colors.length],
      name,
      discipline: row.discipline,
      disciplines: row.disciplines,
    };
  });
  return m;
}

function cinevTherapistShortFromMap(map, id) {
  if (!map || id == null || id === '') return '?';
  const e = map[String(id)];
  if (!e || !e.name) return '?';
  return e.name.split(/\s+/).pop() || '?';
}

/** Voller Anzeigename (Vorname Nachname) für Map-Eintrag. */
function cinevTherapistFullNameFromMap(map, id) {
  if (!map || id == null || id === '') return '?';
  const e = map[String(id)];
  return e && e.name ? e.name : '?';
}

function mapAppointmentStatusToSessionDot(st) {
  if (st === 'done') return 'completed';
  if (st === 'no_show') return 'no_show';
  if (st === 'rescheduled') return 'rescheduled';
  return 'scheduled';
}

/** Zähler für Patientenliste: erledigt / gesamt + nächster Termin + 12-Slot-Verlauf (chronologisch). */
async function cinevAppointmentSummaryForPatient(patientId) {
  const from = '2020-01-01';
  const to = '2035-12-31';
  const data = await cinevApiGet(
    `/appointments?patientId=${encodeURIComponent(patientId)}&from=${from}&to=${to}&limit=500`
  );
  const items = data.items || [];
  const done = items.filter((a) => a.status === 'done').length;
  const total = items.length;
  const now = Date.now();
  const upcoming = items
    .filter((a) => a.start_at && new Date(a.start_at).getTime() > now && a.status !== 'cancelled')
    .sort((a, b) => new Date(a.start_at) - new Date(b.start_at));
  let nextLabel = '—';
  if (upcoming[0] && upcoming[0].start_at) {
    const d = new Date(upcoming[0].start_at);
    const dd = String(d.getDate()).padStart(2, '0');
    const mm = String(d.getMonth() + 1).padStart(2, '0');
    const hh = String(d.getHours()).padStart(2, '0');
    const min = String(d.getMinutes()).padStart(2, '0');
    nextLabel = `${dd}.${mm}. · ${hh}:${min}`;
  }
  const sorted = [...items]
    .filter((a) => a.start_at)
    .sort((a, b) => new Date(a.start_at) - new Date(b.start_at));
  const slotStatuses = Array.from({ length: 12 }, (_, i) => {
    const a = sorted[i];
    return a ? mapAppointmentStatusToSessionDot(a.status) : 'scheduled';
  });
  return { done, total, nextLabel, slotStatuses };
}

/** Verhindert DB-Pool-Erschöpfung: parallele /appointments pro Patient in kleinen Batches. */
async function cinevFetchAppointmentSummariesInBatches(patientIds, batchSize = 6) {
  const emptySlots = () => Array.from({ length: 12 }, () => 'scheduled');
  const out = [];
  for (let i = 0; i < patientIds.length; i += batchSize) {
    const slice = patientIds.slice(i, i + batchSize);
    const batch = await Promise.all(
      slice.map((id) =>
        cinevAppointmentSummaryForPatient(id).catch(() => ({
          done: 0,
          total: 0,
          nextLabel: '—',
          slotStatuses: emptySlots(),
        }))
      )
    );
    out.push(...batch);
  }
  return out;
}

/** Kalenderwoche (Montag–Sonntag) zu einem Datum — Server liefert ISO-Woche + Termine. */
async function cinevFetchAnchorWeek(dateYmd, therapistId) {
  let qs = `date=${encodeURIComponent(dateYmd)}`;
  if (therapistId) qs += `&therapistId=${encodeURIComponent(therapistId)}`;

  const wk = await cinevApiGet(`/calendar/week?${qs}`);
  if (wk && wk.anchorDate === dateYmd && wk.isoWeek) {
    return { isoWeek: wk.isoWeek, anchorDate: wk.anchorDate, items: wk.items || [] };
  }

  try {
    const anch = await cinevApiGet(`/calendar/anchor-week?${qs}`);
    return { isoWeek: anch.isoWeek, anchorDate: anch.anchorDate, items: anch.items || [] };
  } catch (e) {
    const msg =
      'Kalender-API ist veraltet oder nicht neu geladen. Bitte Backend neu starten (Strg+C im API-Terminal, dann im Repo-Root: npm run api).';
    const err = new Error(msg);
    err.status = e.status;
    err.cause = e;
    throw err;
  }
}

/** IANA-Zeitzone für UI (Kalender/Woche) — gleiches Setting wie Backend CINEV_TIMEZONE. */
function cinevPracticeWallTimezone() {
  if (typeof window !== 'undefined' && window.__CINEV_WALL_TIMEZONE) {
    const z = String(window.__CINEV_WALL_TIMEZONE).trim();
    if (z) return z;
  }
  return 'America/Bogota';
}

/** Termine an einem Kalendertag in der Praxis-Zeitzone (Backend CINEV_TIMEZONE). */
async function cinevFetchAppointmentsOnDate(ymd, opts = {}) {
  const lim = opts.limit != null ? opts.limit : 200;
  let path = `/appointments?onDate=${encodeURIComponent(ymd)}&limit=${lim}`;
  if (opts.therapistId) path += `&therapistId=${encodeURIComponent(opts.therapistId)}`;
  return cinevApiGet(path);
}

/** YYYY-MM-DD aus lokalem Browser-Datum (einfacher Anker für Wochen-Navigation). */
function cinevYmdLocal(d = new Date()) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${y}-${m}-${day}`;
}

/** YMD ± n Tage (YYYY-MM-DD). Rein gregorianisch per UTC — vermeidet Versatz zur Praxis-Wandzeit (`weekYmdPractice`). */
function cinevYmdAddDays(ymd, deltaDays) {
  const [y, mo, da] = ymd.split('-').map(Number);
  const d = new Date(Date.UTC(y, mo - 1, da, 12, 0, 0));
  d.setUTCDate(d.getUTCDate() + deltaDays);
  return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`;
}

/** Kalendertag YYYY-MM-DD in der Praxis-Wandzeitzone (wie Backend CINEV_TIMEZONE). */
function cinevYmdWallToday() {
  return new Intl.DateTimeFormat('sv-SE', { timeZone: cinevPracticeWallTimezone() }).format(new Date());
}

/** ISO-Zeitstempel → YYYY-MM-DD in Praxis-Wandzeitzone. */
function cinevYmdWallFromIso(iso) {
  if (!iso) return '';
  return new Intl.DateTimeFormat('sv-SE', { timeZone: cinevPracticeWallTimezone() }).format(new Date(iso));
}

/** Stunde/Minute (0–23, 0–59) in der Praxis-Wandzeitzone — für Matrix & Kalender. */
function cinevWallHourMinuteFromIso(iso) {
  if (!iso) return { h: NaN, m: NaN };
  const parts = new Intl.DateTimeFormat('en-GB', {
    timeZone: cinevPracticeWallTimezone(),
    hour: '2-digit',
    minute: '2-digit',
    hour12: false,
  }).formatToParts(new Date(iso));
  const h = Number(parts.find((p) => p.type === 'hour')?.value);
  const m = Number(parts.find((p) => p.type === 'minute')?.value);
  return { h, m };
}

/** Wandzeit-String `YYYY-MM-DDTHH:mm` (24h) aus UTC-ISO — UI nutzt getrennte date/time-Felder mit 15-Min-Raster. */
function cinevWallDatetimeLocalFromIso(iso) {
  if (!iso) return '';
  const tz = cinevPracticeWallTimezone();
  const d = new Date(iso);
  const ymd = new Intl.DateTimeFormat('sv-SE', { timeZone: tz }).format(d);
  const parts = new Intl.DateTimeFormat('en-GB', {
    timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: false,
  }).formatToParts(d);
  const hh = String(parts.find((p) => p.type === 'hour')?.value ?? '00').padStart(2, '0');
  const mm = String(parts.find((p) => p.type === 'minute')?.value ?? '00').padStart(2, '0');
  return `${ymd}T${hh}:${mm}`;
}

/** `YYYY-MM-DDTHH:mm` (Praxis-Wandzeit, 24h) → UTC-ISO (minutengenau, ±36 h Suchraster). */
function cinevIsoFromWallDatetimeLocal(ymdHm) {
  const tz = cinevPracticeWallTimezone();
  const [dateStr, timeStr] = String(ymdHm || '').split('T');
  if (!dateStr || !timeStr) return null;
  const [Y, M, D] = dateStr.split('-').map(Number);
  const [hH, mM] = timeStr.split(':').map(Number);
  if (!Number.isFinite(Y) || !Number.isFinite(M) || !Number.isFinite(D) || !Number.isFinite(hH) || !Number.isFinite(mM)) {
    return null;
  }
  const start = Date.UTC(Y, M - 1, D, 0, 0, 0);
  const end = start + 36 * 60 * 60 * 1000;
  for (let g = start; g <= end; g += 60_000) {
    const ymd = new Intl.DateTimeFormat('sv-SE', { timeZone: tz }).format(new Date(g));
    if (ymd !== dateStr) continue;
    const p = new Intl.DateTimeFormat('en-GB', {
      timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: false,
    }).formatToParts(new Date(g));
    const hh = Number(p.find((p2) => p2.type === 'hour')?.value);
    const mm = Number(p.find((p2) => p2.type === 'minute')?.value);
    if (hh === hH && mm === mM) return new Date(g).toISOString();
  }
  return null;
}

Object.assign(window, {
  cinevApiBase,
  cinevApiFetch,
  cinevApiGet,
  cinevApiPatch,
  cinevApiDelete,
  cinevApiPost,
  cinevTherapistPersonNames,
  cinevTherapistMapFromItems,
  cinevTherapistShortFromMap,
  cinevTherapistFullNameFromMap,
  cinevAppointmentSummaryForPatient,
  cinevFetchAppointmentSummariesInBatches,
  cinevFetchAnchorWeek,
  cinevFetchAppointmentsOnDate,
  cinevYmdLocal,
  cinevYmdAddDays,
  cinevYmdWallToday,
  cinevYmdWallFromIso,
  cinevWallHourMinuteFromIso,
  cinevPracticeWallTimezone,
  cinevWallDatetimeLocalFromIso,
  cinevIsoFromWallDatetimeLocal,
  mapAppointmentStatusToSessionDot,
});
