<template>
  <article class="glass-panel resource-table-panel host-groups-panel">
    <div class="section-title">
      <div>
        <span class="eyebrow">HOST GROUPS</span>
        <h3>Target collections</h3>
        <p>Manual or rule-based groups that aggregate imported and manually created hosts.</p>
      </div>
      <div class="section-actions">
        <button class="ghost-button compact" type="button" @click="$emit('sync-all')">
          <i class="mdi mdi-sync" aria-hidden="true"></i>Sync dynamic
        </button>
        <button class="primary-action compact" type="button" @click="openModal()">
          <i class="mdi mdi-plus" aria-hidden="true"></i>Add host group
        </button>
      </div>
    </div>

    <ResourceTable
      :columns="columns"
      :rows="pagedHostGroups"
      :total="filteredHostGroups.length"
      :search="groupSearch"
      :page="groupPage"
      :page-size="groupPageSize"
      :sort="groupSort"
      :direction="groupDirection"
      :page-count="groupPageCount"
      search-placeholder="Search collections, rules, machines..."
      item-label="host groups"
      empty-text="No host groups yet."
      @update:search="groupSearch = $event"
      @update:page="groupPage = $event"
      @update:pageSize="groupPageSize = $event"
      @sort="toggleGroupSort"
    >
      <template #cell-name="{ row }"><strong>{{ row.name }}</strong></template>
      <template #cell-mode="{ row }"><span :class="['status-pill', row.mode]">{{ row.mode }}</span></template>
      <template #cell-memberCount="{ row }">{{ row.memberCount || row.hostIds?.length || 0 }} host(s)</template>
      <template #cell-rules="{ row }">{{ ruleSummary(row) }}</template>
      <template #cell-lastSyncedAt="{ row }">{{ row.lastSyncedAt ? new Date(row.lastSyncedAt).toLocaleString() : '-' }}</template>
      <template #cell-actions="{ row }">
        <div class="table-actions">
          <button class="icon-button material-icon-button" type="button" title="View target collection" aria-label="View target collection" @click="openSummary(row)">
            <i class="mdi mdi-magnify" aria-hidden="true"></i>
          </button>
          <button v-if="row.mode === 'dynamic'" class="icon-button material-icon-button" type="button" title="Sync dynamic group" aria-label="Sync dynamic group" @click="$emit('sync', row.id)">
            <i class="mdi mdi-sync" aria-hidden="true"></i>
          </button>
          <button class="icon-button material-icon-button" type="button" title="Edit host group" aria-label="Edit host group" @click="openModal(row)">
            <i class="mdi mdi-pencil-outline" aria-hidden="true"></i>
          </button>
          <button class="icon-button material-icon-button danger-text" type="button" title="Delete host group" aria-label="Delete host group" @click="$emit('delete', row.id)">
            <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
          </button>
        </div>
      </template>
    </ResourceTable>

    <BaseModal
      :open="summaryOpen"
      :title="summaryGroup ? summaryGroup.name : 'Target collection'"
      eyebrow="TARGET COLLECTION"
      description="Read-only rule and membership summary for this Host Group."
      wide
      @close="summaryOpen = false"
    >
      <section v-if="summaryGroup" class="host-group-summary-modal">
        <div class="summary-metrics">
          <span><strong>{{ summaryHosts.length }}</strong><small>assigned machines</small></span>
          <span><strong>{{ osBreakdown.windows }}</strong><small>windows</small></span>
          <span><strong>{{ osBreakdown.linux }}</strong><small>linux</small></span>
          <span><strong>{{ osBreakdown.other }}</strong><small>other</small></span>
        </div>

        <div class="summary-rule-panel">
          <div>
            <span class="eyebrow">RULE SUMMARY</span>
            <h4>{{ summaryGroup.mode === 'dynamic' ? 'Dynamic rule set' : 'Manual membership' }}</h4>
            <p>{{ ruleSummary(summaryGroup) }}</p>
          </div>
          <span :class="['status-pill', summaryGroup.mode]">{{ summaryGroup.mode }}</span>
        </div>

        <div class="summary-actions">
          <button class="ghost-button compact" type="button" :disabled="!summaryHosts.length" @click="exportSummary('csv')">
            <i class="mdi mdi-file-delimited-outline" aria-hidden="true"></i>CSV
          </button>
          <button class="ghost-button compact" type="button" :disabled="!summaryHosts.length" @click="exportSummary('xls')">
            <i class="mdi mdi-file-excel-outline" aria-hidden="true"></i>XLS
          </button>
        </div>

        <div class="member-table-wrap readonly-members">
          <table class="member-table">
            <thead>
              <tr>
                <th>Hostname</th>
                <th>IP address</th>
                <th>OS</th>
                <th>Transport</th>
                <th>vCenter System</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="host in summaryHosts" :key="host.id">
                <td>
                  <strong>{{ host.fqdn || host.name }}</strong>
                  <small>{{ host.name }}</small>
                </td>
                <td><code>{{ host.address || '-' }}</code></td>
                <td><span :class="['status-pill', host.osFamily || 'other']">{{ osFamilyLabel(host.osFamily) }}</span></td>
                <td><span class="status-pill neutral">{{ host.transport || '-' }}</span></td>
                <td>{{ host.sourceConnectionName || (host.sourceType === 'vmware' ? 'Unknown VMware source' : '-') }}</td>
              </tr>
              <tr v-if="!summaryHosts.length">
                <td colspan="5" class="member-empty">No machines are currently assigned to this collection.</td>
              </tr>
            </tbody>
          </table>
        </div>

        <div class="form-actions modal-actions full">
          <button class="ghost-button" type="button" @click="summaryOpen = false">
            <i class="mdi mdi-close" aria-hidden="true"></i>Close
          </button>
        </div>
      </section>
    </BaseModal>

    <BaseModal
      :open="modalOpen"
      :title="form.id ? 'Update host group' : 'Create host group'"
      eyebrow="HOST GROUPS"
      description="Build reusable host targets for RunPlans and script tests."
      wide
      @close="modalOpen = false"
    >
      <form class="form-grid host-group-form" @submit.prevent="save">
        <label><span>Name</span><input v-model="form.name" required placeholder="Engineering" /></label>
        <label><span>Mode</span><select v-model="form.mode"><option value="manual">Manual</option><option value="dynamic">Rule based</option></select></label>
        <label><span>Visibility</span><select v-model="form.visibility"><option value="shared">Shared</option><option value="personal">Personal</option><option value="group">Group</option></select></label>
        <label v-if="form.visibility === 'group'"><span>Group</span><select v-model="form.groupId"><option value="">Choose group</option><option v-for="group in groups" :key="group.id" :value="group.id">{{ group.name }}</option></select></label>
        <label class="full"><span>Description</span><textarea v-model="form.description" rows="2" placeholder="What this collection targets"></textarea></label>

        <section v-if="form.mode === 'manual'" class="full host-member-browser">
          <div class="member-browser-header">
            <div>
              <span class="eyebrow">MANUAL MEMBERS</span>
              <h4>Host membership</h4>
              <p>Select exact hosts for this group. Imported host lists stay usable with search, filters, sorting, and pagination.</p>
            </div>
            <div class="member-selection-meter">
              <strong>{{ form.hostIds.length }}</strong>
              <span>selected</span>
            </div>
          </div>

          <div class="member-toolbar">
            <label class="member-search">
              <i class="mdi mdi-magnify" aria-hidden="true"></i>
              <input v-model="memberSearch" placeholder="Search host, FQDN, address, tags..." />
              <button v-if="memberSearch" type="button" aria-label="Clear host search" @click="memberSearch = ''">
                <i class="mdi mdi-close" aria-hidden="true"></i>
              </button>
            </label>
            <label>
              <span>OS</span>
              <select v-model="memberOsFamily">
                <option value="">All OS types</option>
                <option value="windows">Windows</option>
                <option value="linux">Linux</option>
                <option value="other">Other</option>
              </select>
            </label>
            <label>
              <span>Source</span>
              <select v-model="memberSource">
                <option value="">All sources</option>
                <option v-for="source in sourceOptions" :key="source" :value="source">{{ sourceLabel(source) }}</option>
              </select>
            </label>
            <label>
              <span>Transport</span>
              <select v-model="memberTransport">
                <option value="">All transports</option>
                <option v-for="transport in transportOptions" :key="transport" :value="transport">{{ transport }}</option>
              </select>
            </label>
            <button :class="['ghost-button compact', { active: selectedOnly }]" type="button" @click="selectedOnly = !selectedOnly">
              <i class="mdi mdi-format-list-checks" aria-hidden="true"></i>Selected
            </button>
          </div>

          <div class="member-results-bar">
            <span>{{ filteredHosts.length }} matching host(s)</span>
            <div>
              <button class="ghost-button compact" type="button" :disabled="!pagedHosts.length || pageAllSelected" @click="selectPage">
                Select page
              </button>
              <button class="ghost-button compact" type="button" :disabled="!pagedHosts.some((host) => form.hostIds.includes(host.id))" @click="clearPage">
                Clear page
              </button>
              <button class="ghost-button compact danger-text" type="button" :disabled="!form.hostIds.length" @click="form.hostIds = []">
                <i class="mdi mdi-close-circle-outline" aria-hidden="true"></i>Clear all
              </button>
            </div>
          </div>

          <div class="member-table-wrap">
            <table class="member-table">
              <thead>
                <tr>
                  <th class="select-col">
                    <input
                      type="checkbox"
                      :checked="pageAllSelected"
                      :disabled="!pagedHosts.length"
                      aria-label="Select all hosts on this page"
                      @change="$event.target.checked ? selectPage() : clearPage()"
                    />
                  </th>
                  <th><button type="button" @click="toggleMemberSort('name')">Host <i :class="memberSortIcon('name')" aria-hidden="true"></i></button></th>
                  <th><button type="button" @click="toggleMemberSort('address')">Address <i :class="memberSortIcon('address')" aria-hidden="true"></i></button></th>
                  <th><button type="button" @click="toggleMemberSort('osFamily')">OS <i :class="memberSortIcon('osFamily')" aria-hidden="true"></i></button></th>
                  <th><button type="button" @click="toggleMemberSort('transport')">Transport <i :class="memberSortIcon('transport')" aria-hidden="true"></i></button></th>
                  <th><button type="button" @click="toggleMemberSort('sourceType')">Source <i :class="memberSortIcon('sourceType')" aria-hidden="true"></i></button></th>
                  <th>Tags</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="host in pagedHosts" :key="host.id" :class="{ selected: form.hostIds.includes(host.id) }">
                  <td class="select-col">
                    <input v-model="form.hostIds" type="checkbox" :value="host.id" :aria-label="`Select ${host.name}`" />
                  </td>
                  <td>
                    <strong>{{ host.name }}</strong>
                    <small>{{ host.fqdn || 'No FQDN recorded' }}</small>
                  </td>
                  <td><code>{{ host.address || '-' }}</code></td>
                  <td><span :class="['status-pill', host.osFamily || 'other']">{{ osFamilyLabel(host.osFamily) }}</span></td>
                  <td><span class="status-pill neutral">{{ host.transport || '-' }}</span></td>
                  <td><span :class="['status-pill', host.sourceType === 'vmware' ? 'dynamic' : 'manual']">{{ sourceLabel(host.sourceType) }}</span></td>
                  <td><span class="member-tags">{{ tagSummary(host) }}</span></td>
                </tr>
                <tr v-if="!pagedHosts.length">
                  <td colspan="7" class="member-empty">No hosts match the current filters.</td>
                </tr>
              </tbody>
            </table>
          </div>

          <div class="member-pagination">
            <label>
              <span>Rows</span>
              <select v-model.number="memberPageSize">
                <option :value="8">8</option>
                <option :value="12">12</option>
                <option :value="20">20</option>
              </select>
            </label>
            <span>Page {{ Math.min(memberPage, memberPageCount) }} of {{ memberPageCount }}</span>
            <div>
              <button class="ghost-button compact" type="button" :disabled="memberPage <= 1" @click="memberPage -= 1">
                <i class="mdi mdi-chevron-left" aria-hidden="true"></i>Previous
              </button>
              <button class="ghost-button compact" type="button" :disabled="memberPage >= memberPageCount" @click="memberPage += 1">
                Next<i class="mdi mdi-chevron-right" aria-hidden="true"></i>
              </button>
            </div>
          </div>
        </section>

        <section v-else class="full rule-editor">
          <div class="list-header"><span>Dynamic rules</span><strong>{{ form.rules.length }} rule(s)</strong></div>
          <label><span>Match mode</span><select v-model="form.matchMode"><option value="all">All rules</option><option value="any">Any rule</option></select></label>
          <div v-for="(rule, index) in form.rules" :key="index" class="rule-row">
            <select v-model="rule.field">
              <option value="name">Name</option>
              <option value="fqdn">FQDN</option>
              <option value="address">Address</option>
              <option value="tags">Tags</option>
              <option value="transport">Transport</option>
              <option value="sourceType">Source</option>
              <option value="osFamily">OS family</option>
            </select>
            <select v-model="rule.operator">
              <option value="contains">contains</option>
              <option value="equals">equals</option>
              <option value="startsWith">starts with</option>
              <option value="endsWith">ends with</option>
            </select>
            <input v-model="rule.value" required placeholder="ENG" />
            <button class="ghost-button compact danger-text" type="button" @click="removeRule(index)">
              <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
            </button>
          </div>
          <button class="ghost-button compact" type="button" @click="addRule"><i class="mdi mdi-plus" aria-hidden="true"></i>Add rule</button>
        </section>

        <div class="form-actions modal-actions full">
          <button class="ghost-button" type="button" @click="modalOpen = false">Cancel</button>
          <button class="primary-action" type="submit"><i class="mdi mdi-content-save-outline" aria-hidden="true"></i>Save host group</button>
        </div>
      </form>
    </BaseModal>
  </article>
</template>

<script setup>
import { computed, reactive, ref, watch } from 'vue';
import BaseModal from '../ui/BaseModal.vue';
import ResourceTable from '../ui/ResourceTable.vue';

const props = defineProps({
  hostGroups: { type: Array, default: () => [] },
  hosts: { type: Array, default: () => [] },
  groups: { type: Array, default: () => [] }
});

const emit = defineEmits(['save', 'delete', 'sync', 'sync-all']);
const modalOpen = ref(false);
const summaryOpen = ref(false);
const summaryGroup = ref(null);
const groupSearch = ref('');
const groupSort = ref('name');
const groupDirection = ref('asc');
const groupPage = ref(1);
const groupPageSize = ref(8);
const memberSearch = ref('');
const memberOsFamily = ref('');
const memberSource = ref('');
const memberTransport = ref('');
const selectedOnly = ref(false);
const memberSort = ref('name');
const memberDirection = ref('asc');
const memberPage = ref(1);
const memberPageSize = ref(8);
const form = reactive(defaultForm());
const columns = [
  { key: 'name', label: 'Name' },
  { key: 'mode', label: 'Mode' },
  { key: 'memberCount', label: 'Members' },
  { key: 'rules', label: 'Rules', sortable: false },
  { key: 'lastSyncedAt', label: 'Last sync' },
  { key: 'actions', label: 'Actions', sortable: false }
];

const sourceOptions = computed(() => uniqueOptions(props.hosts.map((host) => host.sourceType || 'manual')));
const transportOptions = computed(() => uniqueOptions(props.hosts.map((host) => host.transport).filter(Boolean)));
const filteredHostGroups = computed(() => {
  const term = groupSearch.value.trim().toLowerCase();
  const rows = props.hostGroups.filter((group) => {
    if (!term) return true;
    return [
      group.name,
      group.description,
      group.mode,
      group.visibility,
      group.groupName,
      ruleSummary(group)
    ].filter(Boolean).some((value) => String(value).toLowerCase().includes(term));
  });
  return rows.sort((a, b) => {
    const left = sortableGroupValue(a, groupSort.value);
    const right = sortableGroupValue(b, groupSort.value);
    const result = left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' });
    return groupDirection.value === 'asc' ? result : -result;
  });
});
const groupPageCount = computed(() => Math.max(1, Math.ceil(filteredHostGroups.value.length / groupPageSize.value)));
const pagedHostGroups = computed(() => {
  const start = (groupPage.value - 1) * groupPageSize.value;
  return filteredHostGroups.value.slice(start, start + groupPageSize.value);
});
const filteredHosts = computed(() => {
  const term = memberSearch.value.trim().toLowerCase();
  const selected = new Set(form.hostIds);
  return props.hosts.filter((host) => {
    if (selectedOnly.value && !selected.has(host.id)) return false;
    if (memberOsFamily.value && (host.osFamily || 'other') !== memberOsFamily.value) return false;
    if (memberSource.value && (host.sourceType || 'manual') !== memberSource.value) return false;
    if (memberTransport.value && host.transport !== memberTransport.value) return false;
    if (!term) return true;
    return [
      host.name,
      host.fqdn,
      host.address,
      host.osFamily,
      host.transport,
      host.sourceType,
      ...(host.tags || [])
    ].filter(Boolean).some((value) => String(value).toLowerCase().includes(term));
  }).sort((a, b) => {
    const left = sortableHostValue(a, memberSort.value);
    const right = sortableHostValue(b, memberSort.value);
    const result = left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' });
    return memberDirection.value === 'asc' ? result : -result;
  });
});
const memberPageCount = computed(() => Math.max(1, Math.ceil(filteredHosts.value.length / memberPageSize.value)));
const pagedHosts = computed(() => {
  const start = (memberPage.value - 1) * memberPageSize.value;
  return filteredHosts.value.slice(start, start + memberPageSize.value);
});
const pageAllSelected = computed(() => Boolean(pagedHosts.value.length) && pagedHosts.value.every((host) => form.hostIds.includes(host.id)));
const summaryHosts = computed(() => {
  const ids = new Set(summaryGroup.value?.hostIds || []);
  return props.hosts
    .filter((host) => ids.has(host.id))
    .sort((a, b) => String(a.fqdn || a.name).localeCompare(String(b.fqdn || b.name), undefined, { numeric: true, sensitivity: 'base' }));
});
const osBreakdown = computed(() => summaryHosts.value.reduce((counts, host) => {
  const key = ['windows', 'linux'].includes(host.osFamily) ? host.osFamily : 'other';
  counts[key] += 1;
  return counts;
}, { windows: 0, linux: 0, other: 0 }));

watch([memberSearch, memberOsFamily, memberSource, memberTransport, selectedOnly, memberPageSize], () => {
  memberPage.value = 1;
});

watch([groupSearch, groupPageSize], () => {
  groupPage.value = 1;
});

watch(groupPageCount, (pageCount) => {
  if (groupPage.value > pageCount) groupPage.value = pageCount;
});

watch(memberPageCount, (pageCount) => {
  if (memberPage.value > pageCount) memberPage.value = pageCount;
});

function defaultForm() {
  return {
    id: null,
    name: '',
    description: '',
    mode: 'manual',
    matchMode: 'all',
    rules: [{ field: 'name', operator: 'contains', value: 'ENG' }],
    hostIds: [],
    visibility: 'shared',
    groupId: ''
  };
}

function openModal(group = null) {
  Object.assign(form, defaultForm(), group || {});
  form.rules = (group?.rules?.length ? group.rules : defaultForm().rules).map((rule) => ({ ...rule }));
  form.hostIds = [...(group?.hostIds || [])];
  form.groupId = group?.groupId || '';
  memberSearch.value = '';
  memberSource.value = '';
  memberTransport.value = '';
  selectedOnly.value = false;
  memberSort.value = 'name';
  memberDirection.value = 'asc';
  memberPage.value = 1;
  modalOpen.value = true;
}

function openSummary(group) {
  summaryGroup.value = group;
  summaryOpen.value = true;
}

function addRule() {
  form.rules.push({ field: 'name', operator: 'contains', value: '' });
}

function removeRule(index) {
  form.rules.splice(index, 1);
  if (!form.rules.length) addRule();
}

function ruleSummary(group) {
  if (group.mode === 'manual') return 'Manual membership';
  return (group.rules || []).map((rule) => `${rule.field} ${rule.operator} "${rule.value}"`).join(` ${group.matchMode || 'all'} `) || 'No rules';
}

function uniqueOptions(values) {
  return [...new Set(values.filter(Boolean))].sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
}

function sortableHostValue(host, key) {
  if (key === 'osFamily') return host.osFamily || 'other';
  if (key === 'sourceType') return host.sourceType || 'manual';
  return String(host[key] || '');
}

function sortableGroupValue(group, key) {
  if (key === 'memberCount') return String(group.memberCount || group.hostIds?.length || 0).padStart(8, '0');
  if (key === 'lastSyncedAt') return group.lastSyncedAt || '';
  if (key === 'rules') return ruleSummary(group);
  return String(group[key] || '');
}

function osFamilyLabel(value = 'other') {
  if (value === 'windows') return 'Windows';
  if (value === 'linux') return 'Linux';
  return 'Other';
}

function toggleMemberSort(key) {
  if (memberSort.value === key) {
    memberDirection.value = memberDirection.value === 'asc' ? 'desc' : 'asc';
    return;
  }
  memberSort.value = key;
  memberDirection.value = 'asc';
}

function toggleGroupSort(key) {
  if (groupSort.value === key) {
    groupDirection.value = groupDirection.value === 'asc' ? 'desc' : 'asc';
    return;
  }
  groupSort.value = key;
  groupDirection.value = 'asc';
}

function memberSortIcon(key) {
  if (memberSort.value !== key) return 'mdi mdi-swap-vertical';
  return memberDirection.value === 'asc' ? 'mdi mdi-sort-alphabetical-ascending' : 'mdi mdi-sort-alphabetical-descending';
}

function selectPage() {
  const selected = new Set(form.hostIds);
  for (const host of pagedHosts.value) selected.add(host.id);
  form.hostIds = [...selected];
}

function clearPage() {
  const pageIds = new Set(pagedHosts.value.map((host) => host.id));
  form.hostIds = form.hostIds.filter((id) => !pageIds.has(id));
}

function sourceLabel(source = 'manual') {
  return source === 'vmware' ? 'VMware' : 'Manual';
}

function tagSummary(host) {
  return host.tags?.length ? host.tags.slice(0, 3).join(', ') : '-';
}

function exportSummary(format) {
  if (!summaryGroup.value || !summaryHosts.value.length) return;
  const rows = summaryHosts.value.map((host) => ({
    Hostname: host.fqdn || host.name || '',
    'IP address': host.address || '',
    'vCenter System': host.sourceConnectionName || (host.sourceType === 'vmware' ? 'Unknown VMware source' : '')
  }));
  if (format === 'xls') {
    downloadFile(`${safeFileName(summaryGroup.value.name)}-hosts.xls`, excelHtml(rows), 'application/vnd.ms-excel;charset=utf-8');
    return;
  }
  downloadFile(`${safeFileName(summaryGroup.value.name)}-hosts.csv`, csvText(rows), 'text/csv;charset=utf-8');
}

function csvText(rows) {
  const headers = ['Hostname', 'IP address', 'vCenter System'];
  return [
    headers.join(','),
    ...rows.map((row) => headers.map((header) => csvCell(row[header])).join(','))
  ].join('\n');
}

function excelHtml(rows) {
  const headers = ['Hostname', 'IP address', 'vCenter System'];
  return `<!doctype html><html><head><meta charset="utf-8"></head><body><table><thead><tr>${headers.map((header) => `<th>${htmlEscape(header)}</th>`).join('')}</tr></thead><tbody>${rows.map((row) => `<tr>${headers.map((header) => `<td>${htmlEscape(row[header])}</td>`).join('')}</tr>`).join('')}</tbody></table></body></html>`;
}

function downloadFile(filename, content, type) {
  const blob = new Blob([content], { type });
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();
  URL.revokeObjectURL(url);
}

function csvCell(value) {
  const text = String(value ?? '');
  return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
}

function htmlEscape(value) {
  return String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

function safeFileName(value) {
  return String(value || 'host-group').trim().replace(/[^A-Za-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '') || 'host-group';
}

function save() {
  emit('save', {
    ...form,
    groupId: form.groupId || null,
    rules: form.mode === 'dynamic' ? form.rules.filter((rule) => rule.value?.trim()) : [],
    hostIds: form.mode === 'manual' ? form.hostIds : []
  });
  modalOpen.value = false;
}
</script>
