/**
 * useConfigActiveStatus — compute active status for jails, filters, and
 * actions by correlating runtime jail data with their config files.
 *
 * Active determination rules:
 * - Jail:   `enabled === true` in the live JailSummary list.
 * - Filter: referenced by at least one enabled jail (jail name = filter name
 *           by fail2ban convention, unless a filter field is provided).
 * - Action: referenced in the `actions` array of at least one enabled jail's
 *           JailConfig.
 */

import { useCallback, useEffect, useRef, useState } from "react";
import { fetchJails } from "../api/jails";
import { fetchJailConfigs } from "../api/config";
import { handleFetchError } from "../utils/fetchError";
import type { JailConfig } from "../types/config";
import type { JailSummary } from "../types/jail";

// ---------------------------------------------------------------------------
// Public interface
// ---------------------------------------------------------------------------

export interface UseConfigActiveStatusResult {
  /** Names of jails that are currently enabled. */
  activeJails: Set<string>;
  /** Names of filter files referenced by at least one enabled jail. */
  activeFilters: Set<string>;
  /** Names of action files referenced by at least one enabled jail. */
  activeActions: Set<string>;
  /** True while any parallel fetch is in progress. */
  loading: boolean;
  /** Error message from any failed fetch, or null. */
  error: string | null;
  /** Re-fetch all data sources. */
  refresh: () => void;
}

// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------

/**
 * Fetch jails and jail configs, then derive active-status sets for each
 * config type.  Active status is computed from live jail data; filter and
 * action files are not fetched directly because their active state is already
 * available via {@link fetchFilters} / {@link fetchActions}.
 *
 * @returns Active-status sets, loading flag, error, and refresh function.
 */
export function useConfigActiveStatus(): UseConfigActiveStatusResult {
  const [activeJails, setActiveJails] = useState<Set<string>>(new Set());
  const [activeFilters, setActiveFilters] = useState<Set<string>>(new Set());
  const [activeActions, setActiveActions] = useState<Set<string>>(new Set());
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const abortRef = useRef<AbortController | null>(null);

  const load = useCallback((): void => {
    abortRef.current?.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;

    setLoading(true);
    setError(null);

    Promise.all([
      fetchJails(),
      fetchJailConfigs(),
    ])
      .then(([jailsResp, configsResp]) => {
        if (ctrl.signal.aborted) return;

        const summaries: JailSummary[] = jailsResp.jails;
        const configs: JailConfig[] = configsResp.jails;

        // Active jails: enabled in the runtime summary list.
        const jailSet = new Set<string>(
          summaries.filter((j) => j.enabled).map((j) => j.name),
        );

        // Active filters: referenced by any enabled jail.
        // By fail2ban convention the filter name equals the jail name unless
        // the config explicitly declares a [filter] section (not surfaced in
        // JailConfig), so we use the jail name as the filter name.
        const filterSet = new Set<string>();
        for (const cfg of configs) {
          if (jailSet.has(cfg.name)) {
            filterSet.add(cfg.name);
          }
        }

        // Active actions: referenced in the actions array of any enabled jail.
        const actionSet = new Set<string>();
        for (const cfg of configs) {
          if (jailSet.has(cfg.name)) {
            for (const action of cfg.actions) {
              // Actions can be stored as "name[param=val]" — extract base name.
              const bracketIdx = action.indexOf("[");
              const baseName = (bracketIdx >= 0 ? action.slice(0, bracketIdx) : action).trim();
              actionSet.add(baseName);
            }
          }
        }

        setActiveJails(jailSet);
        setActiveFilters(filterSet);
        setActiveActions(actionSet);
        setLoading(false);
      })
      .catch((err: unknown) => {
        if (ctrl.signal.aborted) return;
        handleFetchError(err, setError, "Failed to load active status.");
        setLoading(false);
      });
  }, []);

  useEffect(() => {
    load();
    return (): void => {
      abortRef.current?.abort();
    };
  }, [load]);

  return {
    activeJails,
    activeFilters,
    activeActions,
    loading,
    error,
    refresh: load,
  };
}
