/**
 * FC backend client. Talks to /api/credentials (FC-3b), /api/sources
 * (FC-3a), and the new /api/extension/* endpoints (FC-3g).
 */

class FabledCuratorAPI {
  constructor() {
    this.baseUrl = null;
    this.apiKey = null;
  }

  async init() {
    const cfg = await browser.storage.local.get(['apiUrl', 'apiKey']);
    // Normalize on READ, not just on save: configs stored before the options
    // page started normalizing are missing the `/api` suffix, and this heals
    // them without the operator having to reopen Settings.
    this.baseUrl = normalizeApiUrl(cfg.apiUrl) || null;
    this.apiKey = cfg.apiKey || null;
    return this.isConfigured();
  }

  isConfigured() {
    return !!(this.baseUrl && this.apiKey);
  }

  async request(method, endpoint, data = null) {
    if (!this.isConfigured()) {
      throw new Error('Not configured. Set FC URL + API key in settings.');
    }
    const url = `${this.baseUrl.replace(/\/+$/, '')}${endpoint}`;
    const options = {
      method,
      headers: {
        'Content-Type': 'application/json',
        'X-Extension-Key': this.apiKey,
      },
    };
    if (data) options.body = JSON.stringify(data);

    let response;
    try {
      response = await fetch(url, options);
    } catch (e) {
      throw new Error('Cannot connect to FabledCurator. Check URL.');
    }

    if (!response.ok) {
      let message;
      try {
        const body = await response.json();
        message = body.error || `HTTP ${response.status}`;
        if (body.detail) message += `: ${body.detail}`;
      } catch {
        message = `HTTP ${response.status}: ${response.statusText}`;
      }
      // 404/405 from FC almost always means the request never reached the JSON
      // API — it fell through to the SPA catch-all, which serves HTML on GET
      // and rejects everything else. Say so, rather than making the operator
      // decode "Method Not Allowed" on an endpoint that plainly allows POST.
      if (response.status === 404 || response.status === 405) {
        message += ` — ${url} isn't the FC API. Check the FC URL in settings.`;
      }
      const err = new Error(message);
      err.status = response.status;
      throw err;
    }

    if (response.status === 204) return { success: true };
    return response.json();
  }

  // FC-3b — credentials.
  uploadCredentials(platform, credentialType, data) {
    return this.request('POST', '/credentials', {
      platform,
      credential_type: credentialType,
      data,
    });
  }
  getCredentials() {
    return this.request('GET', '/credentials');
  }

  // FC-3a — sources.
  listSources() {
    return this.request('GET', '/sources');
  }
  triggerSourceCheck(sourceId) {
    return this.request('POST', `/sources/${sourceId}/check`);
  }

  // FC-3g — extension-specific.
  quickAddSource(url) {
    return this.request('POST', '/extension/quick-add-source', { url });
  }
  probeSource(url) {
    // Read-only existence check. Drives the content-script chip's
    // color/copy BEFORE the operator clicks Add.
    const qs = new URLSearchParams({ url }).toString();
    return this.request('GET', `/extension/probe?${qs}`);
  }
  // Latest published extension version on this instance — drives the in-app
  // update prompt. Public endpoint (no key needed, but request() sends it
  // harmlessly). Returns {version, xpi_url, latest_url, sha256}.
  getExtensionManifest() {
    return this.request('GET', '/extension/manifest');
  }

  // The web/SPA root: where the Vue router (artist pages) and the served XPI
  // live, NOT the JSON API. Used by OPEN_ARTIST_PAGE + the self-update check.
  webRoot() {
    return webRootFromApiUrl(this.baseUrl);
  }

  // Connection test = the cheapest read with auth.
  testConnection() {
    return this.request('GET', '/credentials');
  }
}

const api = new FabledCuratorAPI();
