// LSPD · API & Site-Konfiguration
window.LSPD = {
  site: null,
  user: null,
  permissions: [],

  async loadSite() {
    try {
      const r = await fetch('/api/site', { credentials: 'same-origin' });
      if (!r.ok) throw new Error('site ' + r.status);
      this.site = await r.json();
      window.__LSPD_SITE__ = this.site;
      if (this.site.user) this.user = this.site.user;
      const fav = this.site.logoUrl || '/img/lspd-logo-lsb.png';
      window.__LSPD_SITE__ = this.site;
      document.querySelectorAll('link[rel="icon"], link[rel="apple-touch-icon"]').forEach((el) => {
        el.href = fav;
      });
      return this.site;
    } catch (e) {
      console.error('[LSPD] loadSite', e);
      this.site = { logoUrl: '/img/lspd-logo-lsb.png', heroBannerUrl: '/img/lspd-banner.png', stats: {}, team: [] };
      window.__LSPD_SITE__ = this.site;
      return this.site;
    }
  },

  async loadMe() {
    try {
      const r = await fetch('/api/me', { credentials: 'same-origin', redirect: 'manual' });
      if (r.status === 401 || r.type === 'opaqueredirect' || r.status === 302) {
        this.user = null;
        this.permissions = [];
        return null;
      }
      if (!r.ok) throw new Error('me ' + r.status);
      const data = await r.json();
      this.user = data.user;
      this.permissions = data.permissions || [];
      this.isLeadership = !!(data.isLeadership || data.user?.is_leadership);
      return data;
    } catch (e) {
      console.error('[LSPD] loadMe', e);
      this.user = null;
      this.permissions = [];
      return null;
    }
  },

  hasPerm(key) {
    if (this.user?.platform_role === 'admin') return true;
    return (this.permissions || []).includes(key);
  },

  can(key) {
    return this.hasPerm(key);
  },

  logoUrl() {
    return this.site?.logoUrl || '/img/lspd-logo-lsb.png';
  },

  sortStaffByDienstnummer(staff) {
    const key = (m) => {
      const pipeNum = (raw) => {
        if (!raw) return null;
        const left = String(raw).split('|')[0].trim();
        const digits = left.replace(/[^\d]/g, '');
        if (!digits) return null;
        const n = parseInt(digits, 10);
        return Number.isFinite(n) ? n : null;
      };
      const fromEn = pipeNum(m?.employee_number);
      if (fromEn !== null) return fromEn;
      for (const raw of [m?.display_name, m?.ic_name, m?.name, m?.dn]) {
        const n = pipeNum(raw);
        if (n !== null) return n;
      }
      return null;
    };
    return [...(staff || [])].sort((a, b) => {
      const na = key(a);
      const nb = key(b);
      const aMiss = na === null;
      const bMiss = nb === null;
      if (aMiss !== bMiss) return aMiss ? 1 : -1;
      if (!aMiss && na !== nb) return na - nb;
      return (a.display_name || a.name || '').localeCompare(b.display_name || b.name || '', 'de');
    });
  },

  loginUrl(mode = 'staff', returnTo) {
    const q = new URLSearchParams({ mode });
    if (returnTo) q.set('returnTo', returnTo);
    return `/auth/discord?${q}`;
  },

  loginApplicant(returnTo = '/ticket') {
    window.location.href = `/auth/discord?mode=applicant&returnTo=${encodeURIComponent(returnTo)}`;
  },

  logoutApplicant(returnTo = '/ticket') {
    window.location.href = `/auth/applicant/logout?returnTo=${encodeURIComponent(returnTo)}`;
  },

  async loadApplicant() {
    const r = await fetch('/api/applicant/me', { credentials: 'same-origin' });
    if (!r.ok) return { loggedIn: false };
    return r.json();
  },

  logout(returnTo = '/') {
    const q = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
    window.location.href = `/auth/logout${q}`;
  },

  async loadNews() {
    const r = await fetch('/api/news', { credentials: 'same-origin' });
    if (r.status === 401 || r.status === 403) return { news: [] };
    if (!r.ok) throw new Error('news ' + r.status);
    return r.json();
  },

  async createNews(payload) {
    const r = await fetch('/api/news', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'news create ' + r.status);
    return data;
  },

  async updateNews(id, payload) {
    const r = await fetch(`/api/news/${id}`, {
      method: 'PUT',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'news update ' + r.status);
    return data;
  },

  async deleteNews(id) {
    const r = await fetch(`/api/news/${id}`, { method: 'DELETE', credentials: 'same-origin' });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'news delete ' + r.status);
    return data;
  },

  async loadStaff(opts = '') {
    const params = typeof opts === 'string' ? (opts ? { q: opts } : {}) : (opts || {});
    const r = await fetch('/api/staff?' + new URLSearchParams(params), { credentials: 'same-origin' });
    if (r.status === 401 || r.status === 403) return { staff: [], total: 0, departments: [] };
    if (!r.ok) throw new Error('staff ' + r.status);
    const data = await r.json();
    data.staff = this.sortStaffByDienstnummer(data.staff);
    return data;
  },

  async loadAkten(q = '') {
    const r = await fetch('/api/akten?' + new URLSearchParams(q ? { q } : {}), { credentials: 'same-origin' });
    if (r.status === 401 || r.status === 403) return { akten: [], total: 0 };
    if (!r.ok) throw new Error('akten ' + r.status);
    const data = await r.json();
    const akten = (data.akten || data.items || []).map(LSPD._mapAkteFromApi);
    LSPD._setCognateAkten(akten);
    return { ...data, akten };
  },

  async loadAkte(id) {
    const r = await fetch(`/api/akten/${encodeURIComponent(id)}`, { credentials: 'same-origin' });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'akte ' + r.status);
    const akte = LSPD._mapAkteFromApi(data.akte || data);
    return { akte };
  },

  async createAkte(body) {
    const r = await fetch('/api/akten', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(LSPD._mapAkteToApi(body)),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Akte konnte nicht angelegt werden');
    const akte = LSPD._mapAkteFromApi(data.akte || data);
    return { akte };
  },

  async updateAkte(id, body) {
    const r = await fetch(`/api/akten/${encodeURIComponent(id)}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(LSPD._mapAkteToApi(body)),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Speichern fehlgeschlagen');
    const akte = LSPD._mapAkteFromApi(data.akte || data);
    return { akte };
  },

  async deleteAkte(id) {
    const r = await fetch(`/api/akten/${encodeURIComponent(id)}`, { method: 'DELETE', credentials: 'same-origin' });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || 'Löschen fehlgeschlagen');
    return data;
  },

  async syncAkte(id) {
    const r = await fetch(`/api/akten/${encodeURIComponent(id)}/sync`, { method: 'POST', credentials: 'same-origin' });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Sync fehlgeschlagen');
    const akte = data.akte ? LSPD._mapAkteFromApi(data.akte) : null;
    return { ...data, akte };
  },

  async loadAkteEntries(akteId) {
    const { akte } = await this.loadAkte(akteId);
    return { entries: akte.akteneintraege || [] };
  },

  async addAkteEntry(akteId, body) {
    const r = await fetch(`/api/akten/${encodeURIComponent(akteId)}/entries`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(LSPD._mapEntryToApi(body)),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Eintrag konnte nicht gespeichert werden');
    const akte = data.akte ? LSPD._mapAkteFromApi(data.akte) : null;
    let entry = data.entry ? LSPD._mapEntryFromApi(data.entry) : null;
    if (!entry && data.entryId && akte?.akteneintraege?.length) {
      entry = akte.akteneintraege.find(e => e.dbId === data.entryId) || akte.akteneintraege[0];
    }
    return { entry, akte };
  },

  async updateAkteEntry(akteId, entryId, body) {
    const r = await fetch(`/api/akten/${encodeURIComponent(akteId)}/entries/${encodeURIComponent(entryId)}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(LSPD._mapEntryToApi(body)),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Eintrag konnte nicht gespeichert werden');
    return { entry: LSPD._mapEntryFromApi(data.entry || data) };
  },

  async deleteAkteEntry(akteId, entryId) {
    const r = await fetch(`/api/akten/${encodeURIComponent(akteId)}/entries/${encodeURIComponent(entryId)}`, {
      method: 'DELETE',
      credentials: 'same-origin',
    });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || 'Eintrag konnte nicht gelöscht werden');
    return data;
  },

  async loadFahrzeuge(q = '') {
    const r = await fetch('/api/fahrzeuge?' + new URLSearchParams(q ? { q } : {}), { credentials: 'same-origin' });
    if (r.status === 401 || r.status === 403) return { fahrzeuge: [], total: 0 };
    if (!r.ok) throw new Error('fahrzeuge ' + r.status);
    const data = await r.json();
    const fahrzeuge = (data.fahrzeuge || data.vehicles || data.items || []).map(LSPD._mapFahrzeugFromApi);
    if (window.vehicleStore) window.vehicleStore.list = fahrzeuge.slice();
    return { ...data, fahrzeuge };
  },

  async registerFahrzeug(body) {
    const r = await fetch('/api/fahrzeuge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(LSPD._mapFahrzeugToApi(body)),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Fahrzeug konnte nicht angemeldet werden');
    return { fahrzeug: LSPD._mapFahrzeugFromApi(data.fahrzeug || data) };
  },

  async updateFahrzeug(id, body) {
    const r = await fetch(`/api/fahrzeuge/${encodeURIComponent(id)}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(LSPD._mapFahrzeugToApi(body)),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Speichern fehlgeschlagen');
    return { fahrzeug: LSPD._mapFahrzeugFromApi(data.fahrzeug || data) };
  },

  async deleteFahrzeug(id) {
    const r = await fetch(`/api/fahrzeuge/${encodeURIComponent(id)}`, { method: 'DELETE', credentials: 'same-origin' });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || 'Löschen fehlgeschlagen');
    return data;
  },

  async submitBewerbung(payload) {
    const r = await fetch('/api/forms/bewerbung', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(payload),
    });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || 'Fehler beim Senden');
    return data;
  },

  async submitBeschwerde(payload) {
    const r = await fetch('/api/forms/beschwerde', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(payload),
    });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || 'Fehler beim Senden');
    return data;
  },

  async icTicketCategories() {
    const r = await fetch('/api/tickets/categories', { credentials: 'same-origin' });
    if (!r.ok) throw new Error('categories ' + r.status);
    return r.json();
  },

  async icMyTickets(closed = false) {
    const q = closed ? '?closed=1' : '';
    const r = await fetch('/api/tickets/my' + q, { credentials: 'same-origin' });
    if (r.status === 401) return { tickets: [] };
    if (!r.ok) throw new Error('tickets ' + r.status);
    return r.json();
  },

  async icCreateTicket(categoryId, form) {
    const r = await fetch('/api/tickets', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify({ categoryId, form }),
    });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || 'Ticket konnte nicht erstellt werden');
    return data;
  },

  async submitTicket(payload) {
    const r = await fetch('/api/forms/ticket', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(payload),
    });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || 'Fehler beim Senden');
    return data;
  },

  async loadPrices() {
    const r = await fetch('/api/prices', { credentials: 'same-origin' });
    if (!r.ok) throw new Error('prices ' + r.status);
    return r.json();
  },

  async saveSettings(body) {
    const r = await fetch('/api/settings', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(body),
    });
    const data = await r.json();
    if (!r.ok) throw new Error(data.error || 'Speichern fehlgeschlagen');
    return data;
  },

  _fmtDate(d) {
    if (!d) return '';
    if (/^\d{2}\.\d{2}\.\d{4}/.test(String(d))) return String(d).slice(0, 10);
    const dt = new Date(d);
    if (Number.isNaN(dt.getTime())) return String(d);
    return `${String(dt.getDate()).padStart(2, '0')}.${String(dt.getMonth() + 1).padStart(2, '0')}.${dt.getFullYear()}`;
  },

  _parseGermanDate(s) {
    if (!s) return null;
    if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10);
    const m = String(s).match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})/);
    if (!m) return null;
    return `${m[3]}-${m[2].padStart(2, '0')}-${m[1].padStart(2, '0')}`;
  },

  _normalizeAkteStatus(s) {
    if (!s || s === 'UNAUFFAELLIG') return 'FREI';
    if (s === 'FAHNDUNG') return 'GESUCHT';
    return s;
  },

  _parseJson(val, fallback) {
    if (val == null) return fallback;
    if (typeof val === 'object') return val;
    try { return JSON.parse(val); } catch { return fallback; }
  },

  _mapEntryFromApi(e) {
    return {
      dbId: e.id,
      id: e.entry_nummer || e.id,
      vorlage: e.vorlage_id || e.vorlage,
      ersteller: e.ersteller_name || e.ersteller || '—',
      erstellt: LSPD._fmtDate(e.created_at) || e.erstellt || '',
      abgeschlossen: !!(e.abgeschlossen),
      felder: LSPD._parseJson(e.felder, {}),
      straftaten: LSPD._parseJson(e.straftaten, []),
    };
  },

  _mapEntryToApi(e) {
    return {
      vorlage_id: e.vorlage,
      felder: e.felder || {},
      straftaten: e.straftaten || [],
      abgeschlossen: !!e.abgeschlossen,
      entry_nummer: e.id,
    };
  },

  _mapAkteFromApi(row) {
    const extra = LSPD._parseJson(row.extra_data, {});
    const entries = (row.entries || row.akteneintraege || []).map(LSPD._mapEntryFromApi);
    return {
      dbId: row.id,
      id: row.aktennummer || row.id,
      name: row.name || `${row.vorname || ''} ${row.nachname || ''}`.trim(),
      geschlecht: row.geschlecht || extra.geschlecht || 'M',
      gebdatum: LSPD._fmtDate(row.geburtsdatum || extra.gebdatum),
      tel: row.telefon || extra.tel || '—',
      adresse: extra.adresse || '—',
      groesse: extra.groesse || 175,
      augenfarbe: extra.augenfarbe || 'Braun',
      haarfarbe: extra.haarfarbe || 'Schwarz',
      blutgruppe: extra.blutgruppe || '',
      status: LSPD._normalizeAkteStatus(row.status),
      fahndungsgrund: row.fahndungsgrund || '',
      flags: extra.flags || { bewaffnet: false, gang: false, flucht: false },
      lizenzen: extra.lizenzen || [],
      vorstrafen: LSPD._parseJson(row.vorstrafen, []),
      fahrzeuge: extra.fahrzeuge || [],
      waffen: extra.waffen || [],
      notizen: row.notizen || extra.notizen || '',
      letzte: LSPD._fmtDate(row.updated_at) || extra.letzte || '',
      sachbearbeiter: extra.sachbearbeiter || '',
      akteneintraege: entries,
      telefon_locked: !!row.telefon_locked,
      mlv_synced_at: row.mlv_synced_at,
    };
  },

  _mapAkteToApi(akte) {
    const name = (akte.name || '').trim();
    const parts = name.split(/\s+/);
    return {
      aktennummer: akte.id,
      name,
      vorname: parts[0] || '',
      nachname: parts.slice(1).join(' ') || parts[0] || '',
      geburtsdatum: LSPD._parseGermanDate(akte.gebdatum),
      telefon: akte.tel,
      geschlecht: akte.geschlecht,
      status: akte.status,
      fahndungsgrund: akte.fahndungsgrund,
      vorstrafen: akte.vorstrafen || [],
      notizen: akte.notizen,
      extra_data: {
        adresse: akte.adresse,
        groesse: akte.groesse,
        augenfarbe: akte.augenfarbe,
        haarfarbe: akte.haarfarbe,
        blutgruppe: akte.blutgruppe,
        flags: akte.flags,
        lizenzen: akte.lizenzen,
        waffen: akte.waffen,
        fahrzeuge: akte.fahrzeuge,
        sachbearbeiter: akte.sachbearbeiter,
        gebdatum: akte.gebdatum,
        tel: akte.tel,
        notizen: akte.notizen,
      },
    };
  },

  _mapFahrzeugFromApi(row) {
    return {
      dbId: row.id,
      kennzeichen: row.kennzeichen,
      modell: row.modell,
      typ: row.typ || 'PKW',
      farbe: row.farbe || '',
      halter: row.halter_name || row.halter || '',
      vin: row.vin || '',
      quelle: row.quelle || 'lspd',
      status: row.status || 'ANGEMELDET',
      datum: LSPD._fmtDate(row.created_at) || row.datum || '',
    };
  },

  _mapFahrzeugToApi(v) {
    return {
      kennzeichen: v.kennzeichen,
      modell: v.modell,
      typ: v.typ,
      farbe: v.farbe,
      halter_name: v.halter,
      vin: v.vin,
      quelle: v.quelle,
      status: v.status,
    };
  },

  _setCognateAkten(akten) {
    window.COGNATE_AKTEN = akten;
  },
};
