function defaultLogLossStreams() {
  return [
    { name: "log_monitor", query: "full_log:log_monitor OR rule.id:100411", min_count: 1 },
    {
      name: "fortigate",
      query: "full_log:fortigate OR full_log:FGT80F OR full_log:FGT60F OR full_log:FGT40F OR full_log:FGT501E",
      min_count: 1,
    },
    { name: "windows_agent", query: "full_log:windows_agent OR full_log:windows", min_count: 1 },
  ];
}

function parseJsonOrThrow(text, label) {
  const raw = (text || "").trim();
  if (!raw) {
    return {};
  }
  try {
    return JSON.parse(raw);
  } catch (_err) {
    throw new Error(`Invalid JSON in ${label}`);
  }
}

const SIM_SCRIPT_DESCRIPTIONS = {
  fortigate: "FortiGate firewall telemetry simulator (40F/60F/80F/501E) for network and security events.",
  endpoint: "Windows/macOS/Linux endpoint-agent simulator for process, auth, and endpoint behavior logs.",
  cisco: "Cisco ASA/IOS network log simulator for parser/rule coverage and telemetry validation.",
  proposal_required: "Appendix A required-use-case simulator (A1-A4) for proposal/UAT coverage.",
  proposal_appendix_b: "Appendix B optional-use-case simulator (B1-B3), including log-loss test flow.",
  proposal_appendix_c: "Appendix C future-enhancement simulator (C1-C3 currently implemented).",
  wazuh_test: "Generic Wazuh baseline test events for connectivity and ingest sanity checks.",
};

const SIM_SCRIPT_TARGET_OPTIONS = {
  fortigate: ["all", "501E", "80F", "60F", "40F"],
  endpoint: ["all", "windows", "mac", "linux"],
  cisco: ["all", "asa_acl_deny", "asa_vpn_auth_fail", "ios_login_fail", "ios_config_change"],
  proposal_required: ["all", "a1", "a2", "a3", "a4"],
  proposal_appendix_b: ["all", "b1", "b2", "b3"],
  proposal_appendix_c: ["all", "c1", "c2", "c3"],
  wazuh_test: ["all", "ioc_dns", "ioc_ips", "vpn_outside_th", "windows_auth_fail"],
};

window.socUi = function socUi() {
  return {
    tabs: [
      { key: "overview", label: "Overview" },
      { key: "systems", label: "Systems" },
      { key: "database", label: "Database" },
      { key: "monitoring", label: "Monitoring" },
      { key: "ioc", label: "IOC" },
      { key: "geoip", label: "GeoIP" },
      { key: "iris", label: "IRIS" },
      { key: "shuffle", label: "Shuffle" },
      { key: "wazuh", label: "Wazuh" },
      { key: "mvp", label: "MVP" },
      { key: "explorer", label: "API Explorer" },
    ],
    activeTab: "overview",
    apiBase: window.location.origin,
    internalApiKey: "dev-internal-key",
    errorMessage: "",

    overview: { health: null, autoSync: null },
    systemsMonitor: {
      data: null,
      loading: false,
      autoRefresh: true,
      paused: false,
      intervalSeconds: 20,
      minutes: 60,
      limit: 20,
      lastRefreshAt: null,
      timerId: null,
    },
    dbTables: {
      data: null,
      loading: false,
      lastRefreshAt: null,
    },
    dbBrowser: {
      selectedTable: "",
      rows: null,
      loading: false,
      limit: 50,
      offset: 0,
    },
    simLogs: {
      runs: null,
      startResult: null,
      selectedRunId: "",
      output: null,
      outputLimit: 200,
      autoRefresh: true,
      intervalSeconds: 3,
      timerId: null,
      form: {
        script: "fortigate",
        targets: ["all"],
        scenario: "all",
        count: 1,
        delay_seconds: 0.3,
        forever: false,
      },
    },
    simWazuh: {
      latest: null,
      limit: 100,
      autoRefresh: true,
      showQuery: false,
    },
    systemsCardMeta: [
      { key: "wazuh", label: "Wazuh" },
      { key: "shuffle", label: "Shuffle" },
      { key: "iris", label: "IRIS" },
      { key: "pagerduty", label: "PagerDuty" },
    ],

    logLoss: { result: null },
    cDetections: { state: null, evaluate: null, history: null },

    ioc: { enrich: null, evaluate: null, history: null, upload: null, analysis: null, fileEval: null },
    geoip: { ip: "8.8.8.8", result: null },
    iris: { create: null, list: null },
    shuffle: { status: null, catalog: null, execute: null },
    wazuh: { status: null, list: null, sync: null },
    mvp: {
      status: null,
      ingest: null,
      evaluate: null,
      policyText: JSON.stringify({ risk_thresholds: { high: 80, medium: 50 } }, null, 2),
      ingestText: JSON.stringify(
        {
          source: "wazuh",
          event_type: "vpn_geo_anomaly",
          event_id: "evt-demo-1",
          timestamp: new Date().toISOString(),
          severity: "high",
          title: "VPN login anomaly",
          description: "User login from unusual country",
          asset: { user: "alice", hostname: "win-dc01" },
          network: { src_ip: "8.8.8.8", country: "US" },
          tags: ["vpn", "geo-anomaly"],
          risk_context: { admin_account: false },
          raw: { full_log: "soc_mvp_test=true" },
          payload: {},
        },
        null,
        2,
      ),
      iocEvalText: JSON.stringify({ ioc_type: "ip", ioc_value: "8.8.8.8", source_event: { event_id: "evt-demo-1" } }, null, 2),
      vpnEvalText: JSON.stringify(
        {
          user: "alice",
          src_ip: "8.8.8.8",
          country_code: "US",
          success: true,
          event_time: new Date().toISOString(),
          is_admin: false,
          off_hours: true,
          first_seen_country: true,
          event_id: "vpn-demo-1",
        },
        null,
        2,
      ),
    },

    logLossForm: { minutes: 5, createTicket: false, streams: defaultLogLossStreams() },
    cEvalForm: { minutes: 30, query: "soc_mvp_test=true", selectorsText: "c1,c2,c3", dry_run: true, limit: 200 },
    iocForm: {
      ioc_type: "ip",
      ioc_value: "8.8.8.8",
      providersText: "virustotal,abuseipdb",
      malicious_threshold: 1,
      suspicious_threshold: 3,
    },
    iocFileForm: { file: null, analysis_id: "", poll_timeout_seconds: 30, poll_interval_seconds: 2 },
    irisForm: {
      title: "SOC Demo Ticket",
      description: "Created from SOC Integrator UI",
      case_customer: 1,
      case_soc_id: "soc-prod",
    },
    irisList: { limit: 50, offset: 0 },
    shuffleExec: { workflow_id: "", payloadText: JSON.stringify({ hello: "world" }, null, 2) },
    wazuhList: { limit: 50, offset: 0, q: "" },
    wazuhSync: { minutes: 120, limit: 50, q: "soc_mvp_test=true OR event_type:*" },
    explorer: {
      spec: null,
      endpoints: [],
      selectedKey: "",
      selected: null,
      pathParamsText: "{}",
      queryText: "{}",
      bodyText: "{}",
      result: null,
    },

    tabClass(key) {
      return this.activeTab === key
        ? "bg-sky-600 text-white border-sky-600"
        : "bg-white text-slate-700 hover:bg-slate-50";
    },

    init() {
      this.loadHealth();
      this.loadAutoSync();
      this.loadCState();
      this.loadSystemsMonitor();
      this.loadDbTables();
      this.loadSimRuns();
      this.startSimLogsAutoRefresh();
      this.startSystemsMonitorAutoRefresh();
      this.loadOpenApiSpec();
    },

    simScriptUsesScenario() {
      return this.simLogs.form.script === "endpoint";
    },

    simScriptDescription() {
      const key = String(this.simLogs.form.script || "").trim();
      return SIM_SCRIPT_DESCRIPTIONS[key] || "No description available for this script.";
    },

    simTargetOptions() {
      const key = String(this.simLogs.form.script || "").trim();
      return SIM_SCRIPT_TARGET_OPTIONS[key] || ["all"];
    },

    simTargetSelectionChanged() {
      const options = this.simTargetOptions();
      const selected = Array.isArray(this.simLogs.form.targets) ? [...this.simLogs.form.targets] : [];
      let valid = selected.filter((item) => options.includes(item));
      if (valid.includes("all") && valid.length > 1) {
        valid = ["all"];
      }
      if (!valid.length) {
        valid = ["all"];
      }
      this.simLogs.form.targets = valid;
    },

    onSimScriptChange() {
      this.simTargetSelectionChanged();
    },

    selectedTargetsForRun() {
      const options = this.simTargetOptions();
      let selected = Array.isArray(this.simLogs.form.targets) ? [...this.simLogs.form.targets] : [];
      selected = selected.filter((item) => options.includes(item));
      if (!selected.length || selected.includes("all")) {
        return ["all"];
      }
      return selected;
    },

    systemsStatusClass(status) {
      if (status === "ok") {
        return "status-ok";
      }
      if (status === "degraded") {
        return "status-warn";
      }
      return "status-down";
    },

    systemsCards() {
      const root = this.unwrapApiData(this.systemsMonitor.data) || {};
      return root.cards || {};
    },

    systemsCard(key) {
      const cards = this.systemsCards();
      return cards[key] || {};
    },

    systemsRecentRows(key) {
      const recent = this.systemsCard(key).recent;
      if (!Array.isArray(recent)) {
        return [];
      }
      return recent.map((row, index) => this.normalizeTableRow(row, index));
    },

    systemsRecentColumns(key) {
      return this.tableColumns(this.systemsRecentRows(key));
    },

    systemsPipelineRows() {
      const root = this.unwrapApiData(this.systemsMonitor.data) || {};
      const pipeline = root.pipeline || {};
      return Object.entries(pipeline).map(([key, value]) => ({ key, value: this.cellText(value) }));
    },

    systemsSetAutoRefresh(enabled) {
      this.systemsMonitor.autoRefresh = Boolean(enabled);
      this.startSystemsMonitorAutoRefresh();
    },

    systemsTogglePaused() {
      this.systemsMonitor.paused = !this.systemsMonitor.paused;
    },

    systemsSetInterval(seconds) {
      const parsed = Number(seconds || 20);
      this.systemsMonitor.intervalSeconds = parsed > 0 ? parsed : 20;
      this.startSystemsMonitorAutoRefresh();
    },

    stopSystemsMonitorAutoRefresh() {
      if (this.systemsMonitor.timerId) {
        clearInterval(this.systemsMonitor.timerId);
        this.systemsMonitor.timerId = null;
      }
    },

    startSystemsMonitorAutoRefresh() {
      this.stopSystemsMonitorAutoRefresh();
      if (!this.systemsMonitor.autoRefresh) {
        return;
      }
      this.systemsMonitor.timerId = setInterval(() => {
        if (!this.systemsMonitor.paused) {
          this.loadSystemsMonitor();
        }
      }, Math.max(5, Number(this.systemsMonitor.intervalSeconds || 20)) * 1000);
    },

    async loadSystemsMonitor() {
      try {
        if (!this.internalApiKey) {
          return;
        }
        this.systemsMonitor.loading = true;
        const params = new URLSearchParams({
          minutes: String(Math.max(1, Number(this.systemsMonitor.minutes || 60))),
          limit: String(Math.max(1, Number(this.systemsMonitor.limit || 20))),
        });
        this.systemsMonitor.data = await this.apiCall(`/monitor/systems?${params.toString()}`, {
          internal: true,
        });
        this.systemsMonitor.lastRefreshAt = new Date().toISOString();
      } catch (err) {
        this.setErr("Systems monitor failed", err);
      } finally {
        this.systemsMonitor.loading = false;
      }
    },

    async loadDbTables() {
      try {
        if (!this.internalApiKey) {
          return;
        }
        this.dbTables.loading = true;
        this.dbTables.data = await this.apiCall("/monitor/db/tables", {
          internal: true,
        });
        this.dbTables.lastRefreshAt = new Date().toISOString();
        if (!this.dbBrowser.selectedTable) {
          const tables = this.dbTableRows();
          if (tables.length > 0) {
            this.dbBrowser.selectedTable = String(tables[0].table || "");
            await this.loadDbRows();
          }
        }
      } catch (err) {
        this.setErr("Database tables failed", err);
      } finally {
        this.dbTables.loading = false;
      }
    },

    dbTableRows() {
      const root = this.unwrapApiData(this.dbTables.data) || {};
      const tables = Array.isArray(root.tables) ? root.tables : [];
      return tables.map((row, index) => this.normalizeTableRow(row, index));
    },

    async loadDbRows() {
      try {
        const table = String(this.dbBrowser.selectedTable || "").trim();
        if (!table) {
          this.dbBrowser.rows = null;
          return;
        }
        this.dbBrowser.loading = true;
        const limit = Math.max(1, Math.min(500, Number(this.dbBrowser.limit || 50)));
        const offset = Math.max(0, Number(this.dbBrowser.offset || 0));
        this.dbBrowser.rows = await this.apiCall(
          `/monitor/db/tables/${encodeURIComponent(table)}/rows?limit=${limit}&offset=${offset}`,
          { internal: true },
        );
      } catch (err) {
        this.setErr("Database rows failed", err);
      } finally {
        this.dbBrowser.loading = false;
      }
    },

    dbSelectedRows() {
      const root = this.unwrapApiData(this.dbBrowser.rows) || {};
      const rows = Array.isArray(root.rows) ? root.rows : [];
      return rows.map((row, index) => this.normalizeTableRow(row, index));
    },

    dbSelectedColumns() {
      return this.tableColumns(this.dbSelectedRows());
    },

    async loadSimRuns() {
      try {
        if (!this.internalApiKey) {
          return;
        }
        this.simLogs.runs = await this.apiCall("/sim/logs/runs", { internal: true });
        const rows = this.extractRows(this.simLogs.runs);
        if (!this.simLogs.selectedRunId && rows.length) {
          this.simLogs.selectedRunId = rows[0].run_id || "";
          if (this.simLogs.selectedRunId) {
            await this.loadSimWazuhLatest(this.simLogs.selectedRunId);
          }
        }
      } catch (err) {
        this.setErr("Load sim runs failed", err);
      }
    },

    async startSimRun() {
      try {
        const targets = this.selectedTargetsForRun();
        const results = [];
        for (const target of targets) {
          const payload = {
            script: this.simLogs.form.script,
            target,
            scenario: this.simLogs.form.scenario || "all",
            count: Number(this.simLogs.form.count || 1),
            delay_seconds: Number(this.simLogs.form.delay_seconds || 0.3),
            forever: Boolean(this.simLogs.form.forever),
          };
          const result = await this.apiCall("/sim/logs/start", {
            method: "POST",
            internal: true,
            json: payload,
          });
          results.push({ target, result });
        }
        this.simLogs.startResult = {
          data: {
            started_targets: targets,
            runs: results.map((item) => ({
              target: item.target,
              run: this.unwrapApiData(item.result)?.run || null,
            })),
          },
        };
        await this.loadSimRuns();
        const started = results
          .map((item) => this.unwrapApiData(item.result)?.run)
          .find((run) => run && run.run_id);
        if (started && started.run_id) {
          this.simLogs.selectedRunId = started.run_id;
          await this.loadSimOutput(started.run_id);
          await this.loadSimWazuhLatest(started.run_id);
        }
      } catch (err) {
        this.setErr("Start sim run failed", err);
      }
    },

    async stopSimRun(runId) {
      try {
        await this.apiCall(`/sim/logs/stop/${encodeURIComponent(runId)}`, {
          method: "POST",
          internal: true,
        });
        await this.loadSimRuns();
        if (this.simLogs.selectedRunId === runId) {
          await this.loadSimOutput(runId);
          await this.loadSimWazuhLatest(runId);
        }
      } catch (err) {
        this.setErr("Stop sim run failed", err);
      }
    },

    async stopRunningSimRuns() {
      try {
        await this.apiCall("/sim/logs/stop-running", {
          method: "POST",
          internal: true,
        });
        await this.loadSimRuns();
        if (this.simLogs.selectedRunId) {
          await this.loadSimOutput(this.simLogs.selectedRunId);
          await this.loadSimWazuhLatest(this.simLogs.selectedRunId);
        }
      } catch (err) {
        this.setErr("Stop running sim runs failed", err);
      }
    },

    simRunRows() {
      return this.extractRows(this.simLogs.runs);
    },

    selectSimRun(runId) {
      this.simLogs.selectedRunId = runId || "";
      this.loadSimOutput(this.simLogs.selectedRunId);
      this.loadSimWazuhLatest(this.simLogs.selectedRunId);
    },

    simSelectedRun() {
      const rows = this.simRunRows();
      return rows.find((row) => row.run_id === this.simLogs.selectedRunId) || null;
    },

    stopSimLogsAutoRefresh() {
      if (this.simLogs.timerId) {
        clearInterval(this.simLogs.timerId);
        this.simLogs.timerId = null;
      }
    },

    startSimLogsAutoRefresh() {
      this.stopSimLogsAutoRefresh();
      if (!this.simLogs.autoRefresh) {
        return;
      }
      this.simLogs.timerId = setInterval(async () => {
        if (this.activeTab !== "systems") {
          return;
        }
        try {
          await this.loadSimRuns();
          if (this.simLogs.selectedRunId) {
            await this.loadSimOutput(this.simLogs.selectedRunId);
            await this.loadSimWazuhLatest(this.simLogs.selectedRunId);
          }
        } catch (_err) {
          // per-request error is already handled by each loader
        }
      }, 5000);
    },

    async loadSimOutput(runId = "") {
      const selectedRunId = runId || this.simLogs.selectedRunId;
      if (!selectedRunId) {
        this.simLogs.output = null;
        return;
      }
      try {
        const limit = Math.max(10, Number(this.simLogs.outputLimit || 200));
        this.simLogs.output = await this.apiCall(
          `/sim/logs/output/${encodeURIComponent(selectedRunId)}?limit=${limit}`,
          { internal: true },
        );
      } catch (err) {
        this.setErr("Load sim output failed", err);
      }
    },

    async loadSimWazuhLatest(runId = "") {
      const selectedRunId = runId || this.simLogs.selectedRunId;
      if (!selectedRunId) {
        this.simWazuh.latest = null;
        return;
      }
      try {
        const limit = 100;
        this.simWazuh.latest = await this.apiCall(
          `/sim/logs/wazuh-latest/${encodeURIComponent(selectedRunId)}?limit=${limit}&minutes=1440&include_raw=true`,
          { internal: true },
        );
      } catch (err) {
        this.setErr("Load sim Wazuh latest failed", err);
      }
    },

    simWazuhEventsRows() {
      const root = this.unwrapApiData(this.simWazuh.latest) || {};
      const events = Array.isArray(root.events) ? root.events : [];
      return events.map((row, index) => this.normalizeTableRow(row, index));
    },

    simWazuhRulesRows() {
      const root = this.unwrapApiData(this.simWazuh.latest) || {};
      const rules = Array.isArray(root.rules) ? root.rules : [];
      return rules.map((row, index) => this.normalizeTableRow(row, index));
    },

    simWazuhEventTableRows() {
      return this.simWazuhEventsRows().map((row) => ({
        time: row["@timestamp"] || row.timestamp || "",
        rule_id: row.rule_id || row.rule?.id || "",
        rule_description: row.rule_description || row.rule?.description || "",
        full_log: row.full_log || "",
      }));
    },

    parseFullLog(fullLog) {
      const text = String(fullLog || "").trim();
      if (!text) {
        return {};
      }
      const parsed = {};
      const regex = /([A-Za-z0-9_.-]+)=("([^"]*)"|[^\s]+)/g;
      let match = null;
      while ((match = regex.exec(text)) !== null) {
        const key = match[1];
        let value = match[3] !== undefined ? match[3] : match[2];
        if (value === "true") {
          value = true;
        } else if (value === "false") {
          value = false;
        } else if (/^-?\d+$/.test(value)) {
          value = Number(value);
        } else if (/^-?\d+\.\d+$/.test(value)) {
          value = Number(value);
        }
        parsed[key] = value;
      }
      if (Object.keys(parsed).length === 0) {
        return { message: text };
      }
      return parsed;
    },

    fullLogAsJsonText(fullLog) {
      return JSON.stringify(this.parseFullLog(fullLog), null, 2);
    },

    pretty(value) {
      if (value === null || value === undefined) {
        return "No data yet";
      }
      try {
        return JSON.stringify(value, null, 2);
      } catch (_err) {
        return String(value);
      }
    },

    unwrapApiData(payload) {
      if (payload && typeof payload === "object" && !Array.isArray(payload) && Object.prototype.hasOwnProperty.call(payload, "data")) {
        return payload.data;
      }
      return payload;
    },

    findFirstArray(node, depth = 0) {
      if (depth > 5 || node === null || node === undefined) {
        return null;
      }
      if (Array.isArray(node)) {
        return node;
      }
      if (typeof node !== "object") {
        return null;
      }

      const preferredKeys = ["items", "matches", "results", "alerts", "agents", "workflows", "apps", "rows", "data"];
      for (const key of preferredKeys) {
        if (Array.isArray(node[key])) {
          return node[key];
        }
      }

      for (const value of Object.values(node)) {
        const found = this.findFirstArray(value, depth + 1);
        if (found) {
          return found;
        }
      }
      return null;
    },

    normalizeTableRow(row, index) {
      if (row && typeof row === "object" && !Array.isArray(row)) {
        return row;
      }
      return { row: index + 1, value: row };
    },

    extractRows(payload) {
      const root = this.unwrapApiData(payload);
      const rows = this.findFirstArray(root) || [];
      return rows.map((row, index) => this.normalizeTableRow(row, index));
    },

    tableColumns(rows) {
      const colSet = new Set();
      const sample = rows.slice(0, 30);
      for (const row of sample) {
        if (!row || typeof row !== "object" || Array.isArray(row)) {
          continue;
        }
        for (const key of Object.keys(row)) {
          if (colSet.size >= 10) {
            break;
          }
          colSet.add(key);
        }
        if (colSet.size >= 10) {
          break;
        }
      }
      return Array.from(colSet);
    },

    cellText(value) {
      if (value === null || value === undefined) {
        return "";
      }
      if (typeof value === "string") {
        return value;
      }
      if (typeof value === "number" || typeof value === "boolean") {
        return String(value);
      }
      return JSON.stringify(value);
    },

    keyValueRows(payload) {
      const root = this.unwrapApiData(payload);
      if (!root || typeof root !== "object" || Array.isArray(root)) {
        return [];
      }
      return Object.entries(root).map(([key, value]) => ({
        key,
        value: this.cellText(value),
      }));
    },

    providersList() {
      return (this.iocForm.providersText || "")
        .split(",")
        .map((s) => s.trim().toLowerCase())
        .filter((s) => s.length > 0);
    },

    internalHeaders() {
      return this.internalApiKey ? { "x-internal-api-key": this.internalApiKey } : {};
    },

    async apiCall(path, options = {}) {
      this.errorMessage = "";
      const headers = {
        Accept: "application/json",
        ...(options.json ? { "Content-Type": "application/json" } : {}),
        ...(options.internal ? this.internalHeaders() : {}),
        ...(options.headers || {}),
      };
      const response = await fetch(`${this.apiBase}${path}`, {
        method: options.method || "GET",
        headers,
        body: options.json ? JSON.stringify(options.json) : options.body,
      });

      let data = null;
      try {
        data = await response.json();
      } catch (_err) {
        data = { detail: "Non-JSON response", status: response.status };
      }

      if (!response.ok) {
        const detail = data && data.detail ? data.detail : `HTTP ${response.status}`;
        throw new Error(detail);
      }
      return data;
    },

    setErr(prefix, err) {
      this.errorMessage = `${prefix}: ${err.message}`;
    },

    async loadHealth() {
      try {
        this.overview.health = await this.apiCall("/health");
      } catch (err) {
        this.setErr("Health failed", err);
      }
    },
    async loadAutoSync() {
      try {
        this.overview.autoSync = await this.apiCall("/wazuh/auto-sync/status");
      } catch (err) {
        this.setErr("Auto-sync failed", err);
      }
    },

    applyLogLossPreset(preset) {
      if (preset === "b2") {
        this.logLossForm.minutes = 10;
        this.logLossForm.streams = [{ name: "b2_log_monitor", query: "full_log:log_monitor OR rule.id:100411", min_count: 1 }];
      } else {
        this.logLossForm.minutes = 5;
        this.logLossForm.streams = defaultLogLossStreams();
      }
    },
    addLogLossStream() {
      this.logLossForm.streams.push({ name: "", query: "", min_count: 1 });
    },
    removeLogLossStream(index) {
      this.logLossForm.streams.splice(index, 1);
    },
    async runLogLossCheck() {
      try {
        this.logLoss.result = await this.apiCall(
          `/monitor/log-loss/check?create_ticket=${this.logLossForm.createTicket ? "true" : "false"}`,
          {
            method: "POST",
            internal: true,
            json: {
              minutes: Number(this.logLossForm.minutes || 5),
              streams: this.logLossForm.streams.map((s) => ({
                name: s.name,
                query: s.query,
                min_count: Number(s.min_count || 0),
              })),
            },
          },
        );
      } catch (err) {
        this.setErr("Log-loss check failed", err);
      }
    },

    async loadCState() {
      try {
        this.cDetections.state = await this.apiCall("/monitor/c-detections/state", { internal: true });
      } catch (err) {
        this.setErr("C state failed", err);
      }
    },
    async runCEvaluate() {
      try {
        const selectors = this.cEvalForm.selectorsText
          .split(",")
          .map((x) => x.trim())
          .filter((x) => x.length > 0);
        this.cDetections.evaluate = await this.apiCall("/monitor/c-detections/evaluate", {
          method: "POST",
          internal: true,
          json: {
            minutes: Number(this.cEvalForm.minutes || 30),
            query: this.cEvalForm.query,
            selectors,
            dry_run: Boolean(this.cEvalForm.dry_run),
            limit: Number(this.cEvalForm.limit || 200),
          },
        });
      } catch (err) {
        this.setErr("C evaluate failed", err);
      }
    },
    async loadCHistory() {
      try {
        this.cDetections.history = await this.apiCall("/monitor/c-detections/history?limit=50&offset=0", { internal: true });
      } catch (err) {
        this.setErr("C history failed", err);
      }
    },

    async runIocEnrich() {
      try {
        this.ioc.enrich = await this.apiCall("/ioc/enrich", {
          method: "POST",
          json: {
            ioc_type: this.iocForm.ioc_type,
            ioc_value: this.iocForm.ioc_value,
            providers: this.providersList(),
          },
        });
      } catch (err) {
        this.setErr("IOC enrich failed", err);
      }
    },
    async runIocEvaluate() {
      try {
        this.ioc.evaluate = await this.apiCall("/ioc/evaluate", {
          method: "POST",
          json: {
            ioc_type: this.iocForm.ioc_type,
            ioc_value: this.iocForm.ioc_value,
            providers: this.providersList(),
            malicious_threshold: Number(this.iocForm.malicious_threshold || 1),
            suspicious_threshold: Number(this.iocForm.suspicious_threshold || 3),
          },
        });
      } catch (err) {
        this.setErr("IOC evaluate failed", err);
      }
    },
    async loadIocHistory() {
      try {
        this.ioc.history = await this.apiCall("/ioc/history?limit=50&offset=0");
      } catch (err) {
        this.setErr("IOC history failed", err);
      }
    },
    async lookupGeoIp() {
      try {
        const ip = String(this.geoip.ip || "").trim();
        if (!ip) {
          throw new Error("IP is required");
        }
        this.geoip.result = await this.apiCall(`/geoip/${encodeURIComponent(ip)}`);
      } catch (err) {
        this.setErr("GeoIP lookup failed", err);
      }
    },
    onFileSelected(event) {
      const files = event && event.target ? event.target.files : null;
      this.iocFileForm.file = files && files.length ? files[0] : null;
    },
    async uploadIocFile() {
      try {
        if (!this.iocFileForm.file) {
          throw new Error("Select a file first");
        }
        const form = new FormData();
        form.append("file", this.iocFileForm.file);
        this.ioc.upload = await this.apiCall("/ioc/upload-file", { method: "POST", body: form });
      } catch (err) {
        this.setErr("IOC upload failed", err);
      }
    },
    async evaluateIocFile() {
      try {
        if (!this.iocFileForm.file) {
          throw new Error("Select a file first");
        }
        const params = new URLSearchParams({
          malicious_threshold: String(this.iocForm.malicious_threshold || 1),
          suspicious_threshold: String(this.iocForm.suspicious_threshold || 3),
          poll_timeout_seconds: String(this.iocFileForm.poll_timeout_seconds || 30),
          poll_interval_seconds: String(this.iocFileForm.poll_interval_seconds || 2),
        });
        const form = new FormData();
        form.append("file", this.iocFileForm.file);
        this.ioc.fileEval = await this.apiCall(`/ioc/evaluate-file?${params.toString()}`, { method: "POST", body: form });
      } catch (err) {
        this.setErr("IOC file evaluate failed", err);
      }
    },
    async getIocAnalysis() {
      try {
        if (!this.iocFileForm.analysis_id.trim()) {
          throw new Error("analysis_id is required");
        }
        this.ioc.analysis = await this.apiCall(`/ioc/analysis/${encodeURIComponent(this.iocFileForm.analysis_id.trim())}`);
      } catch (err) {
        this.setErr("IOC analysis failed", err);
      }
    },

    async createIrisTicket() {
      try {
        this.iris.create = await this.apiCall("/iris/tickets", {
          method: "POST",
          json: {
            title: this.irisForm.title,
            description: this.irisForm.description,
            case_customer: Number(this.irisForm.case_customer || 1),
            case_soc_id: this.irisForm.case_soc_id,
            payload: {},
          },
        });
      } catch (err) {
        this.setErr("IRIS create failed", err);
      }
    },
    async loadIrisTickets() {
      try {
        this.iris.list = await this.apiCall(`/iris/tickets?limit=${Number(this.irisList.limit || 50)}&offset=${Number(this.irisList.offset || 0)}`);
      } catch (err) {
        this.setErr("IRIS list failed", err);
      }
    },

    async loadShuffleHealth() {
      try {
        this.shuffle.status = await this.apiCall("/shuffle/health");
      } catch (err) {
        this.setErr("Shuffle health failed", err);
      }
    },
    async loadShuffleAuth() {
      try {
        this.shuffle.status = await this.apiCall("/shuffle/auth-test");
      } catch (err) {
        this.setErr("Shuffle auth failed", err);
      }
    },
    async loadShuffleApps() {
      try {
        this.shuffle.catalog = await this.apiCall("/shuffle/apps");
      } catch (err) {
        this.setErr("Shuffle apps failed", err);
      }
    },
    async loadShuffleWorkflows() {
      try {
        this.shuffle.catalog = await this.apiCall("/shuffle/workflows");
      } catch (err) {
        this.setErr("Shuffle workflows failed", err);
      }
    },
    async executeShuffleWorkflow() {
      try {
        if (!this.shuffleExec.workflow_id.trim()) {
          throw new Error("workflow_id is required");
        }
        const payload = parseJsonOrThrow(this.shuffleExec.payloadText, "shuffle payload");
        this.shuffle.execute = await this.apiCall(`/shuffle/workflows/${encodeURIComponent(this.shuffleExec.workflow_id.trim())}/execute`, {
          method: "POST",
          json: payload,
        });
      } catch (err) {
        this.setErr("Shuffle execute failed", err);
      }
    },

    async wazuhCall(mode) {
      try {
        if (mode === "auth") {
          this.wazuh.status = await this.apiCall("/wazuh/auth-test");
        } else if (mode === "manager") {
          this.wazuh.status = await this.apiCall("/wazuh/manager-info");
        } else if (mode === "version") {
          this.wazuh.status = await this.apiCall("/sync/wazuh-version");
        } else if (mode === "autosync") {
          this.wazuh.status = await this.apiCall("/wazuh/auto-sync/status");
        }
      } catch (err) {
        this.setErr("Wazuh status failed", err);
      }
    },
    async loadWazuhAgents() {
      try {
        this.wazuh.list = await this.apiCall(`/wazuh/agents?limit=${Number(this.wazuhList.limit || 50)}&offset=${Number(this.wazuhList.offset || 0)}`);
      } catch (err) {
        this.setErr("Wazuh agents failed", err);
      }
    },
    async loadWazuhAlerts() {
      try {
        const q = this.wazuhList.q ? `&q=${encodeURIComponent(this.wazuhList.q)}` : "";
        this.wazuh.list = await this.apiCall(`/wazuh/alerts?limit=${Number(this.wazuhList.limit || 50)}&offset=${Number(this.wazuhList.offset || 0)}${q}`);
      } catch (err) {
        this.setErr("Wazuh alerts failed", err);
      }
    },
    async loadWazuhManagerLogs() {
      try {
        const q = this.wazuhList.q ? `&q=${encodeURIComponent(this.wazuhList.q)}` : "";
        this.wazuh.list = await this.apiCall(`/wazuh/manager-logs?limit=${Number(this.wazuhList.limit || 50)}&offset=${Number(this.wazuhList.offset || 0)}${q}`);
      } catch (err) {
        this.setErr("Wazuh manager logs failed", err);
      }
    },
    async syncWazuhToMvp() {
      try {
        const params = new URLSearchParams({
          limit: String(Number(this.wazuhSync.limit || 50)),
          minutes: String(Number(this.wazuhSync.minutes || 120)),
          q: String(this.wazuhSync.q || "soc_mvp_test=true OR event_type:*"),
        });
        this.wazuh.sync = await this.apiCall(`/wazuh/sync-to-mvp?${params.toString()}`, {
          method: "POST",
          internal: true,
        });
      } catch (err) {
        this.setErr("Wazuh sync failed", err);
      }
    },

    async loadMvpDependencies() {
      try {
        this.mvp.status = await this.apiCall("/mvp/health/dependencies");
      } catch (err) {
        this.setErr("MVP dependencies failed", err);
      }
    },
    async loadMvpPolicy() {
      try {
        const result = await this.apiCall("/mvp/config/policies");
        this.mvp.status = result;
        const policy = result && result.data ? result.data.policy : null;
        if (policy) {
          this.mvp.policyText = JSON.stringify(policy, null, 2);
        }
      } catch (err) {
        this.setErr("MVP policy get failed", err);
      }
    },
    async updateMvpPolicy() {
      try {
        const payload = parseJsonOrThrow(this.mvp.policyText, "policy JSON");
        this.mvp.status = await this.apiCall("/mvp/config/policies", {
          method: "PUT",
          internal: true,
          json: payload,
        });
      } catch (err) {
        this.setErr("MVP policy update failed", err);
      }
    },
    async mvpIngestIncident() {
      try {
        const payload = parseJsonOrThrow(this.mvp.ingestText, "mvp ingest JSON");
        this.mvp.ingest = await this.apiCall("/mvp/incidents/ingest", {
          method: "POST",
          internal: true,
          json: payload,
        });
      } catch (err) {
        this.setErr("MVP ingest failed", err);
      }
    },
    async mvpEvaluateIoc() {
      try {
        const payload = parseJsonOrThrow(this.mvp.iocEvalText, "mvp IOC JSON");
        this.mvp.evaluate = await this.apiCall("/mvp/ioc/evaluate", {
          method: "POST",
          internal: true,
          json: payload,
        });
      } catch (err) {
        this.setErr("MVP IOC evaluate failed", err);
      }
    },
    async mvpEvaluateVpn() {
      try {
        const payload = parseJsonOrThrow(this.mvp.vpnEvalText, "mvp VPN JSON");
        this.mvp.evaluate = await this.apiCall("/mvp/vpn/evaluate", {
          method: "POST",
          internal: true,
          json: payload,
        });
      } catch (err) {
        this.setErr("MVP VPN evaluate failed", err);
      }
    },

    async loadOpenApiSpec() {
      try {
        const spec = await this.apiCall("/openapi.json");
        this.explorer.spec = spec;
        const endpoints = [];
        const paths = spec && spec.paths ? spec.paths : {};
        Object.keys(paths).forEach((path) => {
          const item = paths[path] || {};
          ["get", "post", "put", "delete", "patch"].forEach((method) => {
            if (item[method]) {
              endpoints.push({
                key: `${method.toUpperCase()} ${path}`,
                method,
                path,
                operation: item[method],
                pathItem: item,
              });
            }
          });
        });
        this.explorer.endpoints = endpoints.sort((a, b) => a.key.localeCompare(b.key));
        if (this.explorer.endpoints.length > 0 && !this.explorer.selectedKey) {
          this.explorer.selectedKey = this.explorer.endpoints[0].key;
          this.selectExplorerEndpoint();
        }
      } catch (err) {
        this.setErr("OpenAPI load failed", err);
      }
    },

    selectExplorerEndpoint() {
      const selected = this.explorer.endpoints.find((x) => x.key === this.explorer.selectedKey) || null;
      this.explorer.selected = selected;
      if (!selected) {
        return;
      }
      const params = []
        .concat(selected.pathItem.parameters || [])
        .concat(selected.operation.parameters || []);
      const pathParams = {};
      const queryParams = {};
      params.forEach((p) => {
        if (!p || !p.name) {
          return;
        }
        if (p.in === "path") {
          pathParams[p.name] = "";
        }
        if (p.in === "query") {
          queryParams[p.name] = "";
        }
      });
      this.explorer.pathParamsText = JSON.stringify(pathParams, null, 2);
      this.explorer.queryText = JSON.stringify(queryParams, null, 2);

      const hasJsonBody =
        selected.operation &&
        selected.operation.requestBody &&
        selected.operation.requestBody.content &&
        selected.operation.requestBody.content["application/json"];
      this.explorer.bodyText = hasJsonBody ? JSON.stringify({}, null, 2) : "{}";
    },

    async runExplorerRequest() {
      try {
        const selected = this.explorer.selected;
        if (!selected) {
          throw new Error("Select an endpoint first");
        }
        const pathParams = parseJsonOrThrow(this.explorer.pathParamsText, "path params");
        const queryParams = parseJsonOrThrow(this.explorer.queryText, "query params");
        const body = parseJsonOrThrow(this.explorer.bodyText, "body");

        let path = selected.path;
        Object.keys(pathParams).forEach((k) => {
          path = path.replace(`{${k}}`, encodeURIComponent(String(pathParams[k])));
        });

        const qs = new URLSearchParams();
        Object.keys(queryParams).forEach((k) => {
          const v = queryParams[k];
          if (v !== "" && v !== null && v !== undefined) {
            qs.append(k, String(v));
          }
        });
        const fullPath = qs.toString() ? `${path}?${qs.toString()}` : path;

        const needsInternal = fullPath.startsWith("/monitor/") || fullPath.startsWith("/mvp/") || fullPath.startsWith("/wazuh/sync-to-mvp");
        const withBody = ["post", "put", "patch"].includes(selected.method);
        this.explorer.result = await this.apiCall(fullPath, {
          method: selected.method.toUpperCase(),
          internal: needsInternal,
          ...(withBody ? { json: body } : {}),
        });
      } catch (err) {
        this.setErr("Explorer request failed", err);
      }
    },
  };
};
