<!--
  @component Main filter component; handles filter object on sub-component changes before dispatching it

  Properties:
  - `menuText String?`: Optional text to show in the dropdown menu [Default: null]
  - `filterPresets Object?`: Optional predefined filter values [Default: {}]
  - `disableClusterSelection Bool?`: Is the selection disabled [Default: false]
  - `startTimeQuickSelect Bool?`: Render startTime quick selections [Default: false]
  - `shortJobQuickSelect Bool?`: Render short job quick selections [Default: false]
  - `shortJobCutoff Int?`: Time in seconds for jobs to be considered short [Default: null]
  - `matchedJobs Number?`: Number of jobs matching the filter [Default: -2]
  - `showFilter Func`: If the filter component should be rendered in addition to total count info [Default: true]
  - `applyFilters Func`: The callback function to apply current filter selection
  
  Functions:
  - `void updateFilters (additionalFilters: Object, force: Bool)`:
    Handles new filters from nested components, triggers upstream update event. 
    'additionalFilters' usually added to existing selection, but can be forced to overwrite instead.
-->

<script>
  import {
    DropdownItem,
    DropdownMenu,
    DropdownToggle,
    Button,
    ButtonGroup,
    ButtonDropdown,
    Icon,
    Tooltip
  } from "@sveltestrap/sveltestrap";
  import Info from "./filters/InfoBox.svelte";
  import Cluster from "./filters/Cluster.svelte";
  import JobStates, { allJobStates, mapSharedStates } from "./filters/JobStates.svelte";
  import StartTime, { startTimeSelectOptions } from "./filters/StartTime.svelte";
  import Duration from "./filters/Duration.svelte";
  import Tags from "./filters/Tags.svelte";
  import Tag from "./helper/Tag.svelte";
  import Resources from "./filters/Resources.svelte";
  import Energy from "./filters/Energy.svelte";
  import Statistics from "./filters/Stats.svelte";
  import { formatDurationTime } from "./units.js";

  /* Svelte 5 Props */
  let {  
    menuText = null,
    filterPresets = {},
    disableClusterSelection = false,
    startTimeQuickSelect = false,
    shortJobQuickSelect = false,
    shortJobCutoff = 0,
    matchedJobs = -2,
    showFilter = true,
    applyFilters
  } = $props();

  /* Const Init */
  const nodeMatchLabels = {
    eq: "",
    contains: " Contains",
  }
  const filterReset = {
    // Direct Filters
    dbId: [],
    jobId: "",
    jobIdMatch: "eq",
    arrayJobId: null,
    jobName: "",
    // View Filters
    project: "",
    projectMatch: "contains",
    user: "",
    userMatch: "contains",
    // Filter Modals
    cluster: null,
    subCluster: null,
    partition: null,
    states: allJobStates,
    shared: "",
    schedule: "",
    startTime: { from: null, to: null, range: ""},
    duration: {
      lessThan: null,
      moreThan: null,
      from: null,
      to: null,
    },
    tags: [],
    numNodes: { from: null, to: null },
    numHWThreads: { from: null, to: null },
    numAccelerators: { from: null, to: null },
    node: null,
    nodeMatch: "eq",
    energy: { from: null, to: null },
    stats: [],
  };

  /* State Init */
  // svelte-ignore state_referenced_locally
  let filters = $state({
    dbId: filterPresets?.dbId || [],
    jobId: filterPresets?.jobId || "",
    jobIdMatch: filterPresets?.jobIdMatch || "eq",
    arrayJobId: filterPresets?.arrayJobId || null,
    jobName: filterPresets?.jobName || "",
    project: filterPresets?.project || "",
    projectMatch: filterPresets?.projectMatch || "contains",
    user: filterPresets?.user || "",
    userMatch: filterPresets?.userMatch || "contains",
    cluster: filterPresets?.cluster || null,
    subCluster: filterPresets?.subCluster || null,
    partition: filterPresets?.partition || null,
    states:
      filterPresets?.states || filterPresets?.state
        ? [filterPresets.state].flat()
        : allJobStates,
    shared: filterPresets?.shared || "",
    schedule: filterPresets?.schedule || "",
    startTime: filterPresets?.startTime || { from: null, to: null, range: ""},
    duration: filterPresets?.duration || {
      lessThan: null,
      moreThan: null,
      from: null,
      to: null,
    },
    tags: filterPresets?.tags || [],
    numNodes: filterPresets?.numNodes || { from: null, to: null },
    numHWThreads: filterPresets?.numHWThreads || { from: null, to: null },
    numAccelerators: filterPresets?.numAccelerators || { from: null, to: null },
    node: filterPresets?.node || null,
    nodeMatch: filterPresets?.nodeMatch || "eq",
    energy: filterPresets?.energy || { from: null, to: null },
    stats: filterPresets?.stats || [],
  });

  /* Opened States */
  let isClusterOpen = $state(false)
  let isJobStatesOpen = $state(false)
  let isStartTimeOpen = $state(false)
  let isDurationOpen = $state(false)
  let isTagsOpen = $state(false)
  let isResourcesOpen = $state(false)
  let isEnergyOpen = $state(false)
  let isStatsOpen = $state(false)

  /* Functions */
  // Can be called from the outside to trigger a 'update' event from this component.
  // 'force' option empties existing filters and then applies only 'additionalFilters'
  export function updateFilters(additionalFilters = null, force = false) {
    // Empty Current Filter For Force
    if (additionalFilters != null && force) {
      filters = {...filterReset}
    }
    // Add Additional Filters
    if (additionalFilters != null) {
      for (let key in additionalFilters) filters[key] = additionalFilters[key];
    }
    // Construct New Filter
    let items = [];
    if (filters.dbId.length != 0)
      items.push({ dbId: filters.dbId });
    if (filters.cluster) items.push({ cluster: { eq: filters.cluster } });
    if (filters.subCluster) items.push({ subCluster: { eq: filters.subCluster } });
    if (filters.partition) items.push({ partition: { eq: filters.partition } });
    if (filters.states.length != allJobStates?.length)
      items.push({ state: filters.states });
    if (filters.shared) items.push({ shared: filters.shared });
    if (filters.project)
      items.push({ project: { [filters.projectMatch]: filters.project } });
    if (filters.user)
      items.push({ user: { [filters.userMatch]: filters.user } });
    if (filters.numNodes.from != null && filters.numNodes.to != null) {
      items.push({
        numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
      });
    }
    if (filters.numAccelerators.from != null && filters.numAccelerators.to != null) {
      items.push({
        numAccelerators: {
          from: filters.numAccelerators.from,
          to: filters.numAccelerators.to,
        },
      });
    }
    if (filters.numHWThreads.from != null && filters.numHWThreads.to != null) {
      items.push({
        numHWThreads: {
          from: filters.numHWThreads.from,
          to: filters.numHWThreads.to,
        },
      });
    }
    if (filters.arrayJobId != null)
      items.push({ arrayJobId: filters.arrayJobId });
    if (filters.tags.length != 0) items.push({ tags: filters.tags });
    if (filters.startTime.from || filters.startTime.to)
      items.push({
        startTime: { from: filters.startTime.from, to: filters.startTime.to },
      });
    if (filters.startTime.range)
      items.push({
        startTime: { range: filters.startTime.range },
      });
    if (filters.duration.from && filters.duration.to)
      items.push({
        duration: { from: filters.duration.from, to: filters.duration.to },
      });
    if (filters.duration.lessThan)
      items.push({ duration: { to: filters.duration.lessThan, from: 0 } });
    if (filters.duration.moreThan)
      items.push({ duration: { to: 0, from: filters.duration.moreThan } });
    if (filters.energy.from != null && filters.energy.to != null)
      items.push({
        energy: { from: filters.energy.from, to: filters.energy.to },
      });
    if (filters.jobId)
      items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } });
    if (filters.stats.length != 0) {
      const metricStats = [];
      filters.stats.forEach((st) => {
        if (st.from != null && st.to != null) 
          metricStats.push({ metricName: st.field, range: { from: st.from, to: st.to }});
      });
      if (metricStats.length != 0)
         items.push({metricStats})
    };
    if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
    if (filters.jobName) items.push({ jobName: { contains: filters.jobName } });
    if (filters.schedule) items.push({ schedule: filters.schedule });
    applyFilters({ filters: items });
    changeURL();
    return items;
  }

  function changeURL() {
    const dateToUnixEpoch = (rfc3339) => Math.floor(Date.parse(rfc3339) / 1000);
    let opts = [];

    // Direct Filters
    if (filters.dbId.length != 0) {
      for (let dbi of filters.dbId) {
        opts.push(`dbId=${dbi}`);
      }
    }
    if (filters.jobId.length != 0)
      if (filters.jobIdMatch != "in") {
        opts.push(`jobId=${filters.jobId}`);
      } else {
        for (let singleJobId of filters.jobId)
          opts.push(`jobId=${singleJobId}`);
      }
    if (filters.jobIdMatch != "eq")
      opts.push(`jobIdMatch=${filters.jobIdMatch}`); // "eq" is default-case
    if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`);
    if (filters.jobName) opts.push(`jobName=${filters.jobName}`);
    // View Filters
    if (filters.project) opts.push(`project=${filters.project}`);
    if (filters.project && filters.projectMatch != "contains") // "contains" is default-case
     opts.push(`projectMatch=${filters.projectMatch}`);
    if (filters.user) {
      if (filters.user.length != 0) {
        if (filters.userMatch != "in") {
          opts.push(`user=${filters.user}`);
        } else {
          for (let singleUser of filters.user) opts.push(`user=${singleUser}`);
        }
      }
    }
    if (filters.userMatch != "contains") // "contains" is default-case
      opts.push(`userMatch=${filters.userMatch}`);
    // Filter Modals
    if (filters.cluster) opts.push(`cluster=${filters.cluster}`);
    if (filters.subCluster) opts.push(`subCluster=${filters.subCluster}`);
    if (filters.partition) opts.push(`partition=${filters.partition}`);
    if (filters.states.length != allJobStates?.length)
      for (let state of filters.states) opts.push(`state=${state}`);
    if (filters.shared) opts.push(`shared=${filters.shared}`);
    if (filters.schedule) opts.push(`schedule=${filters.schedule}`);
    if (filters.startTime.from && filters.startTime.to)
      opts.push(
        `startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`,
      );
    if (filters.startTime.range) {
        opts.push(`startTime=${filters.startTime.range}`)
    }
    if (filters.duration.from && filters.duration.to)
      opts.push(`duration=${filters.duration.from}-${filters.duration.to}`);
    if (filters.duration.lessThan)
      opts.push(`duration=lessthan-${filters.duration.lessThan}`);
    if (filters.duration.moreThan)
      opts.push(`duration=morethan-${filters.duration.moreThan}`);
    if (filters.tags.length != 0)
      for (let tag of filters.tags) opts.push(`tag=${tag}`);
    if (filters.numNodes.from > 0 && filters.numNodes.to > 0)
      opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`);
    else if (filters.numNodes.from > 0 && filters.numNodes.to == 0)
      opts.push(`numNodes=morethan-${filters.numNodes.from}`);
    else if (filters.numNodes.from == 0 && filters.numNodes.to > 0)
      opts.push(`numNodes=lessthan-${filters.numNodes.to}`);
    if (filters.numHWThreads.from > 0 && filters.numHWThreads.to > 0)
      opts.push(`numHWThreads=${filters.numHWThreads.from}-${filters.numHWThreads.to}`);
    else if (filters.numHWThreads.from > 0 && filters.numHWThreads.to == 0)
      opts.push(`numHWThreads=morethan-${filters.numHWThreads.from}`);
    else if (filters.numHWThreads.from == 0 && filters.numHWThreads.to > 0)
      opts.push(`numHWThreads=lessthan-${filters.numHWThreads.to}`);
    if (filters.numAccelerators.from > 0 && filters.numAccelerators.to > 0)
      opts.push(`numAccelerators=${filters.numAccelerators.from}-${filters.numAccelerators.to}`);
    else if (filters.numAccelerators.from > 0 && filters.numAccelerators.to == 0)
      opts.push(`numAccelerators=morethan-${filters.numAccelerators.from}`);
    else if (filters.numAccelerators.from == 0 && filters.numAccelerators.to > 0)
      opts.push(`numAccelerators=lessthan-${filters.numAccelerators.to}`);
    if (filters.node) opts.push(`node=${filters.node}`);
    if (filters.node && filters.nodeMatch != "eq") // "eq" is default-case
      opts.push(`nodeMatch=${filters.nodeMatch}`);
    if (filters.energy.from > 0 && filters.energy.to > 0)
      opts.push(`energy=${filters.energy.from}-${filters.energy.to}`);
    else if (filters.energy.from > 0 && filters.energy.to == 0)
      opts.push(`energy=morethan-${filters.energy.from}`);
    else if (filters.energy.from == 0 && filters.energy.to > 0)
      opts.push(`energy=lessthan-${filters.energy.to}`);
    if (filters.stats.length > 0)
      for (let stat of filters.stats) {
        if (stat.from > 0 && stat.to > 0)
          opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
        else if (stat.from > 0 && stat.to == 0)
          opts.push(`stat=${stat.field}-morethan-${stat.from}`);
        else if (stat.from == 0 && stat.to > 0)
          opts.push(`stat=${stat.field}-lessthan-${stat.to}`);
      }
    // Build && Return
    if (opts.length == 0 && window.location.search.length <= 1) return;
    let newurl = `${window.location.pathname}?${opts.join("&")}`;
    window.history.replaceState(null, "", newurl);
  }
</script>

<!-- Dropdown-Button -->
<ButtonGroup>
  {#if showFilter}
    <ButtonDropdown class="cc-dropdown-on-hover mb-1" style={(matchedJobs >= -1) ? '' : 'margin-right: 0.5rem;'}>
      <DropdownToggle outline caret color="success">
        <Icon name="sliders" />
        Filters
      </DropdownToggle>
      <DropdownMenu>
        {#if menuText}
          <DropdownItem header>Note</DropdownItem>
          <DropdownItem disabled>{menuText}</DropdownItem>
          <DropdownItem divider />
        {/if}
        <DropdownItem header>Manage Filters</DropdownItem>
        <DropdownItem onclick={() => (isClusterOpen = true)}>
          <Icon name="cpu" /> Cluster/SubCluster/Partition
        </DropdownItem>
        <DropdownItem onclick={() => (isJobStatesOpen = true)}>
          <Icon name="gear-fill" /> Job States
        </DropdownItem>
        <DropdownItem onclick={() => (isStartTimeOpen = true)}>
          <Icon name="calendar-range" /> Start Time
        </DropdownItem>
        <DropdownItem onclick={() => (isDurationOpen = true)}>
          <Icon name="stopwatch" /> Duration
        </DropdownItem>
        <DropdownItem onclick={() => (isTagsOpen = true)}>
          <Icon name="tags" /> Tags
        </DropdownItem>
        <DropdownItem onclick={() => (isResourcesOpen = true)}>
          <Icon name="hdd-stack" /> Resources
        </DropdownItem>
        <DropdownItem onclick={() => (isEnergyOpen = true)}>
          <Icon name="lightning-charge-fill" /> Energy
        </DropdownItem>
        <DropdownItem onclick={() => (isStatsOpen = true)}>
          <Icon name="bar-chart" onclick={() => (isStatsOpen = true)} /> Statistics
        </DropdownItem>
        {#if shortJobQuickSelect && shortJobCutoff > 0}
          <DropdownItem divider />
          <DropdownItem header>
            Short Jobs Selection
            <Icon id="shortjobsfilter-info" style="cursor:help; padding-right: 8px;" size="sm" name="info-circle"/>
            <Tooltip target={`shortjobsfilter-info`} placement="right">
              Job duration less than {formatDurationTime(shortJobCutoff)}
            </Tooltip>
          </DropdownItem>
          <DropdownItem
            onclick={() => {
              filters.duration = { 
                moreThan: null,
                lessThan: shortJobCutoff,
                from: null,
                to: null
              }
              updateFilters();
            }}
          >
            <Icon name="stopwatch" />
            Only Short Jobs
          </DropdownItem>
          <DropdownItem
            onclick={() => {
              filters.duration = { 
                moreThan: shortJobCutoff,
                lessThan: null,
                from: null,
                to: null
              }
              updateFilters();
            }}
          >
            <Icon name="stopwatch" />
            Exclude Short Jobs
          </DropdownItem>
        {/if}
        {#if startTimeQuickSelect}
          <DropdownItem divider />
          <DropdownItem header>Start Time Quick Selection</DropdownItem>
          {#each startTimeSelectOptions.filter((stso) => stso.range !== "") as { rangeLabel, range }}
            <DropdownItem
              onclick={() => {
                filters.startTime.from = null
                filters.startTime.to = null
                filters.startTime.range = range;
                updateFilters();
              }}
            >
              <Icon name="calendar-range" />
              {rangeLabel}
            </DropdownItem>
          {/each}
        {/if}
      </DropdownMenu>
    </ButtonDropdown>
  {/if}

  {#if matchedJobs >= -1}
    <Button class="mb-1" style="margin-right: 0.25rem;" disabled outline>
      {matchedJobs == -1 ? 'Loading ...' : `${matchedJobs} jobs`}
    </Button>
  {/if}
</ButtonGroup>

{#if showFilter}
  <!-- SELECTED FILTER PILLS -->
  {#if filters.cluster}
    <Info icon="cpu" onclick={() => (isClusterOpen = true)}>
      {filters.cluster}
      {#if filters.subCluster}
        [{filters.subCluster}]
      {/if}
      {#if filters.partition}
        ({filters.partition})
      {/if}
    </Info>
  {/if}

  {#if filters.states.length != allJobStates?.length}
    <Info icon="gear-fill" onclick={() => (isJobStatesOpen = true)}>
      {filters.states.join(", ")}
      {#if filters.shared && !filters.schedule}
        ({mapSharedStates[filters.shared]})
      {:else if filters.schedule && !filters.shared}
        ({filters.schedule.charAt(0).toUpperCase() + filters.schedule?.slice(1)})
      {:else if (filters.shared && filters.schedule)}
        ({[mapSharedStates[filters.shared], (filters.schedule.charAt(0).toUpperCase() + filters.schedule.slice(1))].join(", ")})
      {/if}
    </Info>
  {:else if (filters.shared || filters.schedule)}
    <Info icon="gear-fill" onclick={() => (isJobStatesOpen = true)}>
      {#if filters.shared && !filters.schedule}
        {mapSharedStates[filters.shared]}
      {:else if filters.schedule && !filters.shared}
        {filters.schedule.charAt(0).toUpperCase() + filters.schedule?.slice(1)}
      {:else if (filters.shared && filters.schedule)}
        {[mapSharedStates[filters.shared], (filters.schedule.charAt(0).toUpperCase() + filters.schedule.slice(1))].join(", ")}
      {/if}
    </Info>
  {/if}

  {#if filters.startTime.from || filters.startTime.to}
    <Info icon="calendar-range" onclick={() => (isStartTimeOpen = true)}>
      {new Date(filters.startTime.from).toLocaleString()} - {new Date(
        filters.startTime.to,
      ).toLocaleString()}
    </Info>
  {/if}

  {#if filters.startTime.range}
    <Info icon="calendar-range" onclick={() => (isStartTimeOpen = true)}>
      Job Start: {startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel }
    </Info>
  {/if}

  {#if filters.duration.from || filters.duration.to}
    <Info icon="stopwatch" onclick={() => (isDurationOpen = true)}>
      {Math.floor(filters.duration.from / 3600)}h:{Math.floor(
        (filters.duration.from % 3600) / 60,
      )}m -
      {Math.floor(filters.duration.to / 3600)}h:{Math.floor(
        (filters.duration.to % 3600) / 60,
      )}m
    </Info>
  {/if}

  {#if filters.duration.lessThan}
    <Info icon="stopwatch" onclick={() => (isDurationOpen = true)}>
      Duration less than {Math.floor(
        filters.duration.lessThan / 3600,
      )}h:{Math.floor((filters.duration.lessThan % 3600) / 60)}m
    </Info>
  {/if}

  {#if filters.duration.moreThan}
    <Info icon="stopwatch" onclick={() => (isDurationOpen = true)}>
      Duration more than {Math.floor(
        filters.duration.moreThan / 3600,
      )}h:{Math.floor((filters.duration.moreThan % 3600) / 60)}m
    </Info>
  {/if}

  {#if filters.tags.length != 0}
    <Info icon="tags" onclick={() => (isTagsOpen = true)}>
      {#each filters.tags as tagId}
        <Tag id={tagId} clickable={false} />
      {/each}
    </Info>
  {/if}

  {#if filters.numNodes.from > 0 && filters.numNodes.to > 0}
    <Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
      Nodes: {filters.numNodes.from} - {filters.numNodes.to}
    </Info>
  {:else if filters.numNodes.from > 0 && filters.numNodes.to == 0}
    <Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
      &nbsp;&ge;&nbsp;{filters.numNodes.from} Node(s)
    </Info>
  {:else if filters.numNodes.from == 0 && filters.numNodes.to > 0}
    <Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
      &nbsp;&le;&nbsp;{filters.numNodes.to} Node(s)
    </Info>
  {/if}

  {#if filters.numHWThreads.from > 0 && filters.numHWThreads.to > 0}
    <Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
      HWThreads: {filters.numHWThreads.from} - {filters.numHWThreads.to}
    </Info>
  {:else if filters.numHWThreads.from > 0 && filters.numHWThreads.to == 0}
    <Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
      &nbsp;&ge;&nbsp;{filters.numHWThreads.from} HWThread(s)
    </Info>
  {:else if filters.numHWThreads.from == 0 && filters.numHWThreads.to > 0}
    <Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
      &nbsp;&le;&nbsp;{filters.numHWThreads.to} HWThread(s)
    </Info>
  {/if}

  {#if filters.numAccelerators.from > 0 && filters.numAccelerators.to > 0}
    <Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
      Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to}
    </Info>
  {:else if filters.numAccelerators.from > 0 && filters.numAccelerators.to == 0}
    <Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
      &nbsp;&ge;&nbsp;{filters.numAccelerators.from} Acc(s)
    </Info>
  {:else if filters.numAccelerators.from == 0 && filters.numAccelerators.to > 0}
    <Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
      &nbsp;&le;&nbsp;{filters.numAccelerators.to} Acc(s)
    </Info>
  {/if}

  {#if filters.node != null}
    <Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
      Node{nodeMatchLabels[filters.nodeMatch]}: {filters.node}
    </Info>
  {/if}

  {#if filters.energy.from > 0 && filters.energy.to > 0}
    <Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
      Total Energy: {filters.energy.from} - {filters.energy.to} kWh
    </Info>
  {:else if filters.energy.from > 0 && filters.energy.to == 0}
    <Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
      Total Energy &ge;&nbsp;{filters.energy.from} kWh
    </Info>
  {:else if filters.energy.from == 0 && filters.energy.to > 0}
    <Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
      Total Energy &le;&nbsp;{filters.energy.to} kWh
    </Info>
  {/if}

  {#if filters.stats.length > 0}
    {#each filters.stats as stat}
      {#if stat.from > 0 && stat.to > 0}
        <Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
          {stat.field}: {stat.from} - {stat.to} {stat.unit}
        </Info>&thinsp;
      {:else if stat.from > 0 && stat.to == 0}
        <Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
          {stat.field} &ge;&nbsp;{stat.from} {stat.unit}
        </Info>&thinsp;
      {:else if stat.from == 0 && stat.to > 0}
        <Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
          {stat.field} &le;&nbsp;{stat.to} {stat.unit}
        </Info>&thinsp;
      {/if}
    {/each}
  {/if}
{/if}

<Cluster
  bind:isOpen={isClusterOpen}
  presetCluster={filters.cluster}
  presetPartition={filters.partition}
  presetSubCluster={filters.subCluster}
  {disableClusterSelection}
  setFilter={(filter) => updateFilters(filter)}
/>

<JobStates
  bind:isOpen={isJobStatesOpen}
  presetStates={filters.states}
  presetShared={filters.shared}
  presetSchedule={filters.schedule}
  setFilter={(filter) => updateFilters(filter)}
/>

<StartTime
  bind:isOpen={isStartTimeOpen}
  presetStartTime={filters.startTime}
  setFilter={(filter) => updateFilters(filter)}
/>

<Duration
  bind:isOpen={isDurationOpen}
  presetDuration={filters.duration}
  setFilter={(filter) => updateFilters(filter)}
/>

<Tags
  bind:isOpen={isTagsOpen}
  presetTags={filters.tags}
  setFilter={(filter) => updateFilters(filter)}
/>

<Resources
  bind:isOpen={isResourcesOpen}
  activeCluster={filters.cluster}
  presetNumNodes={filters.numNodes}
  presetNumHWThreads={filters.numHWThreads}
  presetNumAccelerators={filters.numAccelerators}
  presetNamedNode={filters.node}
  presetNodeMatch={filters.nodeMatch}
  setFilter={(filter) => updateFilters(filter)}
/>

<Energy
  bind:isOpen={isEnergyOpen}
  presetEnergy={filters.energy}
  setFilter={(filter) => updateFilters(filter)}
/>

<Statistics
  bind:isOpen={isStatsOpen}
  presetStats={filters.stats}
  setFilter={(filter) => updateFilters(filter)}
/>

<style>
  :global(.cc-dropdown-on-hover:hover .dropdown-menu) {
    display: block;
    margin-top: 0px;
    padding-top: 0px;
    transform: none !important;
  }
</style>
