// API-backed data layer. Replaces the prototype's hardcoded fixtures
// with promise-returning loaders that fetch from /api/app/* AND reshape
// the responses into the prototype's expected shapes — so screens keep
// reading `wapp.title`, `result.findings[i].evidence`, etc., unchanged.
//
// Sources of truth:
//   - GET /api/app/workapps         (directory)
//   - GET /api/app/workapps/:slug   (WorkAppPublic detail)
//   - GET /api/app/experts          (list)
//   - GET /api/app/experts/:slug    (profile + workapps)
//   - GET /api/app/runs/:id         (WorkAppRunOutput)
//   - GET /api/me                   (auth state)
//
// Fields the API does NOT provide (analytics counts, category tags,
// time estimates, recommendation prose paragraphs) are stubbed with
// sensible defaults so the visual renders. Backing them with real
// data is follow-up work.

(function () {
  // ── Low-level fetch helpers ────────────────────────────────────────────
  async function jget(path, extraHeaders) {
    const r = await fetch(path, {
      credentials: 'include',
      headers: extraHeaders || {},
    });
    if (!r.ok) {
      const err = new Error('GET ' + path + ' → ' + r.status);
      err.status = r.status;
      throw err;
    }
    return r.json();
  }
  async function jpost(path, body, extraHeaders) {
    var headers = Object.assign(
      { 'Content-Type': 'application/json', 'x-csrf-token': '1' },
      extraHeaders || {}
    );
    const r = await fetch(path, {
      method: 'POST',
      credentials: 'include',
      headers: headers,
      body: JSON.stringify(body || {}),
    });
    if (!r.ok) {
      const err = new Error('POST ' + path + ' → ' + r.status);
      err.status = r.status;
      throw err;
    }
    return r.json();
  }

  async function jput(path, body) {
    const r = await fetch(path, {
      method: 'PUT',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json', 'x-csrf-token': '1' },
      body: JSON.stringify(body || {}),
    });
    if (!r.ok) {
      const err = new Error('PUT ' + path + ' → ' + r.status);
      err.status = r.status;
      throw err;
    }
    return r.json();
  }

  // Pull pure helpers from data-helpers.js (loaded before this script).
  const { gradientFor, initialFor, splitItalic } = window.DataHelpers;

  // ── Reshapers ──────────────────────────────────────────────────────────
  function expertCard(apiExpert) {
    // apiExpert is from WorkAppPublic.expert OR /api/app/experts entries.
    return {
      name: apiExpert.name,
      slug: apiExpert.slug,
      initial: initialFor(apiExpert.name),
      photo: apiExpert.photoUrl || undefined,
      title: apiExpert.title || '',
      specialty: apiExpert.proofLine || '',          // used by detail + directory
      proofLine: apiExpert.proofLine || '',          // used by result method panel
      bio: apiExpert.bioShort || apiExpert.proofLine || '',
      bioLong: apiExpert.bioLong || '',
      tags: Array.isArray(apiExpert.tags) ? apiExpert.tags : [],
      linkedinUrl: apiExpert.linkedinUrl || '',
      websiteUrl: apiExpert.websiteUrl || '',
      servicePackages: Array.isArray(apiExpert.servicePackages) ? apiExpert.servicePackages : [],
      avatarBg: gradientFor(apiExpert.name),
      workAppsCount: Number(apiExpert.workAppsCount) || 0,
      workAppTitles: Array.isArray(apiExpert.workAppTitles) ? apiExpert.workAppTitles : [],
      totalRuns: '',
      totalUseful: 0,
    };
  }

  function workappCard(detail) {
    // detail is a full WorkAppPublic from /api/app/workapps/:slug.
    const { title, italic } = splitItalic(detail.identity.title);
    const ms = typeof detail.avgDurationMs === 'number' ? detail.avgDurationMs : null;
    const timeLabel = ms ? '~' + Math.max(5, Math.round(ms / 1000)) + ' seconds' : '~60 seconds';
    return {
      title: title,
      italic: italic,
      subtitle: detail.identity.subtitle,
      category: '',
      type: '',
      audience: detail.identity.defaultAudience || '',
      time: timeLabel,
      runs: '',
      shares: '',
      saves: '',
      useful: 0,
      usefulVotes: 0,
    };
  }

  function relatedCard(summary) {
    // summary from /api/app/workapps list — has slug, title, subtitle, expert, avgDurationMs.
    const { title } = splitItalic(summary.title);
    const ms = typeof summary.avgDurationMs === 'number' ? summary.avgDurationMs : null;
    const timeLabel = ms ? '~' + Math.max(5, Math.round(ms / 1000)) + 's' : '~60s';
    return {
      slug: summary.slug,
      title: title,
      outcome: summary.subtitle,
      type: '',
      cat: '',
      time: timeLabel,
      expert: summary.expert ? summary.expert.name : '',
      initial: initialFor(summary.expert ? summary.expert.name : ''),
      bg: gradientFor(summary.expert ? summary.expert.name : 'default'),
    };
  }

  function runSteps(apiSteps) {
    // apiSteps from WorkAppRunOutput.runSteps. Map durationMs → duration.
    if (!Array.isArray(apiSteps)) return [];
    return apiSteps.map(function (s) {
      return {
        name: s.name,
        tool: s.tool,
        duration: s.durationMs,
        finding: s.finding,
        reason: s.reason,
      };
    });
  }

  function result(output) {
    // output is a full WorkAppRunOutput. Reshape into the prototype's
    // RESULT shape. recommendation is a string; combine heading + body if
    // body is present.
    if (!output) return null;
    var rec = output.recommendation || {};
    var recStr = rec.heading || '';
    if (rec.body) recStr += ' ' + rec.body;
    var findings = (output.findings || []).slice(0, 4).map(function (f) {
      return {
        title: f.title,
        why: f.why,
        evidence: f.evidenceQuote,
        evidenceSrc: f.evidenceLocation || '',
        action: f.action,
        priority: f.priority,
      };
    });
    var v = output.verdict || {};
    return {
      verdictLabel: 'Verdict',
      verdictHeadline: v.headline || '',
      verdictSub: v.sub || '',
      confidence: (v.confidence || 'medium').replace(/^./, function (c) { return c.toUpperCase(); }),
      basis: v.scope || '',
      findings: findings,
      recommendation: recStr,
      ignore: output.ignore || [],
      caveats: [],   // no direct API analogue; v.scope is already used as `basis`
      // Some workapps return a single rendered HTML blob instead of the
      // canonical verdict/findings shape. Pass it through so consumers (the
      // SharedResultScreen iframe, share-text builders) can use it.
      html: typeof output.html === 'string' ? output.html : '',
    };
  }

  // ── Guest-run helpers ──────────────────────────────────────────────────
  function getRunCount() {
    return parseInt(localStorage.getItem('sb_run_count') || '0', 10);
  }
  function incrementRunCount() {
    localStorage.setItem('sb_run_count', String(getRunCount() + 1));
  }
  function getOrCreateGuestId() {
    var existing = localStorage.getItem('sb_guest_id');
    if (existing) return existing;
    var id = crypto.randomUUID();
    localStorage.setItem('sb_guest_id', id);
    return id;
  }

  // ── Public API on window.AppData ──────────────────────────────────────
  window.AppData = {
    // Directory + detail
    listWorkapps: function () {
      return jget('/api/app/workapps').then(function (d) {
        return (d.workapps || []).map(relatedCard);
      });
    },
    getWorkapp: function (slug) {
      // Both fetches start immediately — they run in parallel.
      var directoryP = jget('/api/app/workapps');
      return jget('/api/app/workapps/' + encodeURIComponent(slug)).then(function (detail) {
        return {
          slug: slug,
          wapp: workappCard(detail),
          expert: expertCard(detail.expert),
          essay: detail.essay,
          methodology: detail.methodology,
          evidenceSources: detail.evidenceSources,
          sourcePost: detail.sourcePost,
          // Input field definitions from the workapp spec — drives the run form
          // so every workapp shows its own fields instead of a hardcoded URL form.
          inputs: detail.inputs || [],
          // Related — best-effort: fetch directory and exclude current slug.
          // Returned as a separate Promise to keep this loader synchronous-ish.
          relatedPromise: directoryP.then(function (d) {
            return (d.workapps || [])
              .filter(function (w) { return w.slug !== slug; })
              .slice(0, 6)
              .map(relatedCard);
          }),
        };
      });
    },
    listExperts: function () {
      return jget('/api/app/experts').then(function (d) {
        return (d.experts || []).map(expertCard);
      });
    },
    getExpert: function (slug) {
      return jget('/api/app/experts/' + encodeURIComponent(slug)).then(function (d) {
        return {
          expert: expertCard(d.expert),
          workapps: (d.workapps || []).map(function (w) {
            return { slug: w.slug, title: splitItalic(w.title).title };
          }),
          stats: d.stats || null,
        };
      });
    },

    // ── Marketplace publishing (super-admin redirected from builder only) ──
    // Every call must include the signed publish token the MCP tool minted.
    // The token is in `?t=` after the hash path, e.g. #/publish/<id>?t=xxx.
    getMarketplaceDraft: function (workappId, token) {
      return jget('/api/workapps/' + encodeURIComponent(workappId) + '/marketplace/draft' + (token ? '?t=' + encodeURIComponent(token) : ''));
    },
    saveMarketplaceDraft: function (workappId, draft, token) {
      return jput('/api/workapps/' + encodeURIComponent(workappId) + '/marketplace/draft' + (token ? '?t=' + encodeURIComponent(token) : ''), { draft: draft });
    },
    regenerateMarketplaceDraft: function (workappId, token) {
      return jpost('/api/workapps/' + encodeURIComponent(workappId) + '/marketplace/draft' + (token ? '?t=' + encodeURIComponent(token) : ''), {});
    },
    publishMarketplace: function (workappId, token) {
      return jpost('/api/workapps/' + encodeURIComponent(workappId) + '/marketplace/publish' + (token ? '?t=' + encodeURIComponent(token) : ''), {});
    },

    // Runs (auth-gated)
    me: function () { return jget('/api/me'); },
    getRunCount: getRunCount,
    incrementRunCount: incrementRunCount,

    guestHeaders: function() {
      return { 'X-Guest-Id': getOrCreateGuestId() };
    },

    // Returns the SSE stream URL for a run. The server plants an HttpOnly
    // `wab_guest_id` cookie on the POST that creates the run; EventSource
    // attaches same-origin cookies automatically, so no credential needs to
    // appear in the URL. (Putting the guest id in the query string leaked
    // it to access logs / browser history / Referer headers.)
    streamUrl: function(runId) {
      return '/api/app/runs/' + encodeURIComponent(runId) + '/stream';
    },

    startRun: function (slug, input) {
      return jpost('/api/app/runs', { slug: slug, input: input }, window.AppData.guestHeaders());
    },
    getRun: function (runId) {
      return jget('/api/app/runs/' + encodeURIComponent(runId), window.AppData.guestHeaders())
        .then(function (r) {
          return {
            status: r.status,
            error: r.error,
            input: r.input || null,
            runSteps: runSteps(r.output && r.output.runSteps),
            result: result(r.output),
            runMeta: r.output && r.output.runMeta,
            workappSlug: r.workapp_slug || r.workappSlug || '',
          };
        });
    },
    myRuns: function () { return jget('/api/app/me/runs').then(function (d) { return d.runs; }); },
    saveRun: function (runId) { return jpost('/api/app/me/runs/' + encodeURIComponent(runId) + '/save', {}); },
    shareRun: function (runId) { return jpost('/api/app/me/runs/' + encodeURIComponent(runId) + '/share', {}); },
    giveFeedback: function (runId, useful, comment) {
      return jpost('/api/app/me/runs/' + encodeURIComponent(runId) + '/feedback', { useful: useful, comment: comment });
    },
    // GET /api/r/:token — public, no auth. Server response shape is
    //   { status: 'done'|'running'|..., workappSlug: string, output: WorkAppRunOutput|null }
    // We pass `output` through the standard result() reshaper so the
    // SharedResultScreen consumes the same shape as the authenticated path.
    getSharedRun: function (token) {
      return jget('/api/r/' + encodeURIComponent(token)).then(function (r) {
        var reshaped = result(r.output) || {};
        // Decorate with the workapp + expert metadata the server now returns
        // so the shared result screen can render the same rich UI as the
        // authenticated result page (expert card, identity title, etc.).
        reshaped.workappTitle = r.workappTitle || '';
        reshaped.workappSubtitle = r.workappSubtitle || '';
        reshaped.expertName = r.expertName || '';
        reshaped.expertTitle = r.expertTitle || '';
        reshaped.expertPhotoUrl = r.expertPhotoUrl || '';
        reshaped.expertBioShort = r.expertBioShort || '';
        reshaped.expertProofLine = r.expertProofLine || '';
        reshaped.inputs = r.inputs || null;
        return { status: r.status, workappSlug: r.workappSlug, result: reshaped };
      });
    },

    // Auth UX
    requestMagicLink: function (email) { return jpost('/api/auth/magic-link', { email: email }); },
    // Forward the current location as `return_to` so the server can redirect
    // back to the route the user was on when auth was triggered. Matches the
    // redirectToLogin() contract — without it, in-app modal/AuthScreen verify
    // would always land at `/` instead of the originating hash route.
    verifyMagicCode: function (email, code) {
      var rt = window.location.pathname + window.location.search + window.location.hash;
      var qs = rt && rt !== '/' ? '?return_to=' + encodeURIComponent(rt) : '';
      return jpost('/api/auth/magic-link/verify' + qs, { email: email, code: code });
    },
    redirectToLogin: function () {
      var r = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash);
      window.location.href = '/login?return_to=' + r;
    },

    // Intent logging — best-effort, fire-and-forget. Posts to /api/app/intent
    // (server route in routes/app/intent.ts → app_intent_events table). Errors
    // are swallowed so UX is never blocked by a logging failure.
    logIntent: function(type, payload) {
      try {
        fetch('/api/app/intent', {
          method: 'POST',
          credentials: 'include',
          headers: { 'Content-Type': 'application/json', 'x-csrf-token': '1' },
          body: JSON.stringify({ type: type, payload: payload || {} }),
        }).catch(function() {});
      } catch (e) {}
    },

    // Re-exports of small utilities for screens that want them
    initialFor: initialFor,
    gradientFor: gradientFor,
    splitItalic: splitItalic,
  };

  // Compatibility shim: the original prototype set window.EXPERT etc.
  // directly. Some screens may still read those during the transition
  // before T4 lands. Initialize them as null so the screens render their
  // empty/loading state until T4 wires useEffect.
  window.EXPERT = null;
  window.WORKAPP = null;
  window.RELATED = null;
  window.RUN_STEPS = null;
  window.RESULT = null;

  // Production stubs for tweaks-panel.jsx components — that file is excluded
  // from the production build. These render nothing so the dev panel is
  // invisible in production while keeping JSX compilation happy.
  window.TweaksPanel = function() { return null; };
  window.TweakSection = function() { return null; };
  window.TweakRadio   = function() { return null; };
  window.TweakColor   = function() { return null; };
  window.TweakToggle  = function() { return null; };

  // Production stub for window.useTweaks — replaces the dev-panel hook
  // from tweaks-panel.jsx which is excluded from the production build.
  // Returns the defaults as static React state; setTweak is a no-op.
  window.useTweaks = function(defaults) {
    var result = React.useState(defaults);
    var setTweak = function() {};   // dev-only; ignored in production
    return [result[0], setTweak];
  };
})();
