/* eslint-disable */
// ============================================================
// MOCK API — simulates the backend protocol described in the
// ActionConfigPayloadExample spec, with realistic latency and a
// shared event bus the devtools network log subscribes to.
//
// Endpoints implemented:
//   GET  /api/rbac/payload/{actionId}
//   GET  /api/rbac/related/{actionId}/{Section}
//   GET  /api/data/list/{actionId}
//   GET  /api/data/count/{actionId}
//   GET  /api/data/row-property-types/{actionId}
//   PUT  /api/rbac/payload/{actionId}/config
// ============================================================

// ---- Event bus ----
const __apiSubs = new Set();
const __apiLog = [];        // ring buffer of recent calls
const MAX_LOG = 80;

function apiEmit(event) {
  __apiLog.push(event);
  if (__apiLog.length > MAX_LOG) __apiLog.shift();
  __apiSubs.forEach((fn) => { try { fn(event); } catch (e) { console.error(e); } });
}

function apiSubscribe(fn) {
  __apiSubs.add(fn);
  return () => __apiSubs.delete(fn);
}
function apiLogSnapshot() { return __apiLog.slice(); }
function apiClearLog() { __apiLog.length = 0; apiEmit({ type: "log/clear" }); }

// ---- Persistent mutable state (so PUT actually changes things) ----
// We mutate these copies, never the raw constants from actions.jsx.
const __state = {
  actions: structuredClone(window.ACTIONS),
  related: structuredClone(window.RELATED),
  rbac: { ...window.RBAC, ...Object.fromEntries(Object.entries(window.RBAC).map(([k,v]) => [k, new Set(v)])) },
  menu: structuredClone(window.DEFAULT_MENU),
  savedViews: structuredClone(window.DEFAULT_SAVED_VIEWS),
  // Data is mutable so bulk-edit / inline-edit feel real
  data: structuredClone(window.DATA_SOURCES),
  // Theming preferences
  theme: { mode: "light", density: 1, primary: "#ad4a1a" },
};

// ---- Random latency for realism (configurable) ----
let __latency = { min: 80, max: 320, errorRate: 0 };
function setLatency(min, max, errorRate = 0) { __latency = { min, max, errorRate }; }
function getLatency() { return __latency; }

function delay() {
  const { min, max } = __latency;
  return new Promise((res) => setTimeout(res, min + Math.random() * (max - min)));
}

function maybeFail(method, url) {
  if (Math.random() < __latency.errorRate) {
    const err = { ok: false, status: 500, error: `Simulated ${method} ${url} failure` };
    return err;
  }
  return null;
}

// ---- Generic fake-fetch wrapper that logs the call ----
async function call({ method, url, params, body, run }) {
  const id = Math.random().toString(36).slice(2, 9);
  const startedAt = Date.now();
  apiEmit({ type: "call/start", id, method, url, params, body, startedAt });
  await delay();
  const fail = maybeFail(method, url);
  if (fail) {
    const dur = Date.now() - startedAt;
    apiEmit({ type: "call/end", id, method, url, status: 500, durationMs: dur, error: fail.error });
    throw new Error(fail.error);
  }
  let response;
  try {
    response = run();
  } catch (e) {
    const dur = Date.now() - startedAt;
    apiEmit({ type: "call/end", id, method, url, status: 500, durationMs: dur, error: e.message });
    throw e;
  }
  const dur = Date.now() - startedAt;
  apiEmit({
    type: "call/end", id, method, url,
    status: 200,
    durationMs: dur,
    responsePreview: previewify(response),
  });
  return response;
}

// Tiny preview for the network log (truncates arrays / huge objects).
function previewify(obj) {
  try {
    const s = JSON.stringify(obj);
    return s.length > 240 ? s.slice(0, 240) + "…" : s;
  } catch { return String(obj); }
}

// ============================================================
// ENDPOINTS
// ============================================================

// GET /api/rbac/payload/{actionId}
async function getActionPayload(actionId) {
  return call({
    method: "GET", url: `/api/rbac/payload/${actionId}`,
    run: () => {
      const a = __state.actions[actionId];
      if (!a) throw new Error(`Action ${actionId} not found`);
      // Return a deep clone so callers can't mutate state
      return structuredClone(a);
    },
  });
}

// GET /api/rbac/related/{actionId}/{Section}
async function getRelatedActions(actionId, section, role) {
  return call({
    method: "GET", url: `/api/rbac/related/${actionId}/${section}`,
    params: { role },
    run: () => {
      const childIds = (__state.related[actionId]?.[section]) || [];
      const allowed = __state.rbac[role] || new Set();
      // Intersect with the role's RBAC set (in production the server applies RBAC)
      const filtered = childIds.filter((id) => allowed.has(id));
      return filtered.map((id) => structuredClone(__state.actions[id])).filter(Boolean);
    },
  });
}

// GET /api/data/list/{actionId}?page=&pageSize=
async function listData(actionId, { page = 1, pageSize = 50, scopeKey = null, scopeValue = null } = {}) {
  return call({
    method: "GET", url: `/api/data/list/${actionId}`,
    params: { page, pageSize, scopeKey, scopeValue },
    run: () => {
      const a = __state.actions[actionId];
      if (!a) throw new Error(`Action ${actionId} not found`);
      const ds = a.MergedParams?.DataSource;
      if (!ds || !__state.data[ds]) {
        return { TotalCount: 0, PageNumber: page, PageSize: pageSize, TotalPages: 0, Items: [] };
      }
      let rows = __state.data[ds];
      if (scopeKey && scopeValue != null) {
        rows = rows.filter((r) => r[scopeKey] === scopeValue);
      }
      // The spec says items come server-pre-sorted-and-filtered. For the prototype
      // we return everything in stable order and let the client sort/filter/group.
      const total = rows.length;
      const start = (page - 1) * pageSize;
      const items = rows.slice(start, start + pageSize);
      return {
        TotalCount: total,
        PageNumber: page,
        PageSize: pageSize,
        TotalPages: Math.max(1, Math.ceil(total / pageSize)),
        Items: structuredClone(items),
      };
    },
  });
}

// GET /api/data/count/{actionId}  — returns decimal[] (per spec, used by KPIs)
async function countData(actionId) {
  return call({
    method: "GET", url: `/api/data/count/${actionId}`,
    run: () => {
      const a = __state.actions[actionId];
      if (!a) throw new Error(`Action ${actionId} not found`);
      const mp = a.MergedParams || {};
      // Custom precomputed value (e.g. health score) — just return it
      if (mp.Aggregation === "custom" && typeof mp.Value === "number") {
        return [mp.Value];
      }
      const ds = mp.DataSource;
      let rows = __state.data[ds] || [];
      if (mp.Where) {
        rows = rows.filter((r) => r[mp.Where.field] === mp.Where.eq);
      }
      switch (mp.Aggregation) {
        case "count":
          return [rows.length];
        case "avg": {
          if (!mp.Field) return [0];
          const nums = rows.map((r) => Number(r[mp.Field])).filter((n) => !isNaN(n));
          return [nums.length ? nums.reduce((s, v) => s + v, 0) / nums.length : 0];
        }
        case "sum": {
          if (!mp.Field) return [0];
          const nums = rows.map((r) => Number(r[mp.Field])).filter((n) => !isNaN(n));
          return [nums.reduce((s, v) => s + v, 0)];
        }
        default:
          return [0];
      }
    },
  });
}

// GET /api/data/row-property-types/{actionId}
async function getRowPropertyTypes(actionId) {
  return call({
    method: "GET", url: `/api/data/row-property-types/${actionId}`,
    run: () => {
      const a = __state.actions[actionId];
      if (!a) throw new Error(`Action ${actionId} not found`);
      const ds = a.MergedParams?.DataSource;
      const rows = __state.data[ds] || [];
      const sample = rows[0] || {};
      const out = {};
      for (const k of Object.keys(sample)) {
        const v = sample[k];
        out[k] =
          typeof v === "boolean" ? "bool" :
          typeof v === "number" ? (Number.isInteger(v) ? "int" : "decimal") :
          (typeof v === "string" && /^\d{4}-\d{2}-\d{2}T/.test(v)) ? "datetime" :
          "string";
      }
      return out;
    },
  });
}

// PUT /api/rbac/payload/{actionId}/config — replaces MergedParams
async function putActionConfig(actionId, mergedParams) {
  return call({
    method: "PUT", url: `/api/rbac/payload/${actionId}/config`,
    body: mergedParams,
    run: () => {
      const a = __state.actions[actionId];
      if (!a) throw new Error(`Action ${actionId} not found`);
      a.MergedParams = structuredClone(mergedParams);
      return { ok: true, ActionId: actionId };
    },
  });
}

// PUT /api/rbac/role/{role}/access — toggle RBAC for one actionId
async function putRoleAccess(role, actionId, allowed) {
  return call({
    method: "PUT", url: `/api/rbac/role/${role}/access`,
    body: { actionId, allowed },
    run: () => {
      const set = __state.rbac[role] || new Set();
      if (allowed) set.add(actionId); else set.delete(actionId);
      __state.rbac[role] = set;
      return { ok: true };
    },
  });
}

// PUT /api/menu/{role} — reshape role sidebar
async function putRoleMenu(role, items) {
  return call({
    method: "PUT", url: `/api/menu/${role}`,
    body: items,
    run: () => {
      __state.menu[role] = structuredClone(items);
      return { ok: true };
    },
  });
}

// GET / PUT / DELETE /api/views/{actionId}
async function listSavedViews(actionId) {
  return call({
    method: "GET", url: `/api/views/${actionId}`,
    run: () => structuredClone(__state.savedViews[actionId] || []),
  });
}
async function createSavedView(actionId, view) {
  return call({
    method: "POST", url: `/api/views/${actionId}`,
    body: view,
    run: () => {
      __state.savedViews[actionId] = __state.savedViews[actionId] || [];
      const v = { ...view, id: view.id || `sv-${Math.random().toString(36).slice(2, 8)}` };
      __state.savedViews[actionId].push(v);
      return v;
    },
  });
}
async function deleteSavedView(actionId, viewId) {
  return call({
    method: "DELETE", url: `/api/views/${actionId}/${viewId}`,
    run: () => {
      __state.savedViews[actionId] = (__state.savedViews[actionId] || []).filter((v) => v.id !== viewId);
      return { ok: true };
    },
  });
}

// PATCH a single row (inline edit)
async function patchRow(actionId, rowId, patch) {
  return call({
    method: "PATCH", url: `/api/data/row/${actionId}/${rowId}`,
    body: patch,
    run: () => {
      const a = __state.actions[actionId];
      const ds = a?.MergedParams?.DataSource;
      const rows = __state.data[ds];
      if (!rows) throw new Error(`No data source for action ${actionId}`);
      const row = rows.find((r) => String(r.Id) === String(rowId));
      if (!row) throw new Error(`Row ${rowId} not found`);
      Object.assign(row, patch);
      return structuredClone(row);
    },
  });
}

// Run a bulk handler against selected ids (server-side multi-action)
async function runBulk(actionId, handler, rowIds, opts = {}) {
  return call({
    method: "POST", url: `/api/bulk/${actionId}/${handler}`,
    body: { rowIds, opts },
    run: () => {
      const a = __state.actions[actionId];
      const ds = a?.MergedParams?.DataSource;
      const rows = __state.data[ds];
      if (!rows) throw new Error("No data source");
      const set = new Set(rowIds.map(String));
      let touched = 0;
      switch (handler) {
        case "bulkApprove":
          rows.forEach((r) => { if (set.has(String(r.Id))) { r.Recommendation_Value = "Approved"; r.Status = "Active"; touched++; } });
          break;
        case "bulkReject":
          rows.forEach((r) => { if (set.has(String(r.Id))) { r.Recommendation_Value = "Rejected"; touched++; } });
          break;
        case "bulkHold":
          rows.forEach((r) => { if (set.has(String(r.Id))) { r.Recommendation_Value = "On Hold"; touched++; } });
          break;
        case "bulkDelete": {
          const before = rows.length;
          __state.data[ds] = rows.filter((r) => !set.has(String(r.Id)));
          touched = before - __state.data[ds].length;
          break;
        }
        default:
          throw new Error(`Unknown bulk handler: ${handler}`);
      }
      return { ok: true, affected: touched };
    },
  });
}

// ============================================================
// Detail-tab data fetchers (mocked)
// ============================================================
async function getProblemConversations(problemId) {
  return call({
    method: "GET", url: `/api/data/conversations/${problemId}`,
    run: () => structuredClone(window.PROBLEM_CONVERSATIONS[problemId] || []),
  });
}
async function getProblemActivity(problemId) {
  return call({
    method: "GET", url: `/api/data/activity/${problemId}`,
    run: () => structuredClone(window.PROBLEM_ACTIVITY[problemId] || []),
  });
}

// State read accessors (used by admin views that need a synchronous local snapshot)
const apiState = {
  getAction: (id) => structuredClone(__state.actions[id]),
  getMenu:   (role) => structuredClone(__state.menu[role] || []),
  getRbac:   (role) => new Set(__state.rbac[role] || []),
  getAllRbac: () => Object.fromEntries(Object.entries(__state.rbac).map(([r, s]) => [r, new Set(s)])),
  getRelated: (id) => structuredClone(__state.related[id] || {}),
  getAllActions: () => structuredClone(__state.actions),
  getTheme: () => ({ ...__state.theme }),
  setTheme: (patch) => { Object.assign(__state.theme, patch); apiEmit({ type: "theme/change", theme: { ...__state.theme } }); },
};

// Convenience: dispatch by name, used by the Grid for toolbar/row/bulk handlers
const mockApi = {
  getActionPayload, getRelatedActions, listData, countData, getRowPropertyTypes,
  putActionConfig, putRoleAccess, putRoleMenu,
  listSavedViews, createSavedView, deleteSavedView,
  patchRow, runBulk,
  getProblemConversations, getProblemActivity,
  apiSubscribe, apiLogSnapshot, apiClearLog,
  setLatency, getLatency,
  state: apiState,
};

Object.assign(window, { mockApi, apiSubscribe, apiLogSnapshot, apiClearLog });
