<template>
  <div class="imaging-pctypes">
    <div class="page-header">
      <h2>Imaging PC Types</h2>
    </div>
    <p class="setting-description">
      Each imaging PC type owns a GE-Enforce install manifest. Edit the draft,
      then Publish to ship it to PCs. Nothing reaches a PC until you publish.
    </p>

    <details class="help-panel">
      <summary>How this works</summary>
      <div class="help-body">
        <p><strong>A manifest is the desired state for a PC type</strong> - the
          list of what should be installed. shopdb serves it; each PC checks
          itself every cycle and re-installs anything that drifted (uninstalled,
          corrupted, or overwritten). That is the "self-heal".</p>
        <p><strong>Each entry</strong> is one action (install an app, copy a
          file, write a registry value, run a script) plus a <em>detection</em>
          rule that tells the PC whether it is already correct - if not, the
          action runs. Entry order is execution order.</p>
        <p><strong>Targeting</strong> narrows an entry: the fleet-wide
          <code>common</code> manifest uses PC types to hit a subset; a per-type
          manifest already only runs on its own type, so it shows just the niche
          gate it needs (CMM version, a bay number, a subtype).</p>
        <p>Full guide: <code>docs/GE-ENFORCE.md</code>.</p>
      </div>
    </details>

    <div v-if="error" class="error-message">{{ error }}</div>
    <div v-if="notice" class="settings-success">{{ notice }}</div>

    <!-- Export configuration -->
    <div class="section-card">
      <h3 class="section-title">Export configuration</h3>
      <div class="setting-row full-width">
        <label>
          <span>On-share export root</span>
          <input v-model="shareroot" placeholder="\\server\share\dt\shopfloor or /path" />
          <small class="input-hint">
            Used by Export to Share. During Milestone 1 the engine still reads
            these files; export is your push to the fleet.
          </small>
        </label>
      </div>
      <button class="btn btn-primary" @click="saveConfig">Save share root</button>
    </div>

    <div class="editor-grid">
      <!-- Scope list -->
      <aside class="section-card scope-panel">
        <div class="section-head">
          <h3 class="section-title">PC Types</h3>
          <button class="btn btn-sm btn-primary" @click="openNewScope">+ New PC Type</button>
        </div>
        <div class="scope-list">
          <div
            v-for="scope in scopes"
            :key="scope.scopeid"
            class="scope-row"
            :class="{ active: scope.scopeid === selectedId }"
            @click="selectScope(scope.scopeid)"
          >
            <div class="scope-name">{{ scope.scopename }}</div>
            <div class="scope-meta">
              <span class="badge" :class="scope.phase === 'preinstall' ? 'badge-warning' : 'badge-info'">
                {{ scope.phase }}
              </span>
              <span>{{ scope.entrycount }} entries</span>
              <span v-if="scope.publishedversion">v{{ scope.publishedversion }}</span>
              <span v-else class="unpublished">unpublished</span>
            </div>
          </div>
          <p v-if="!scopes.length" class="empty">No PC types yet.</p>
        </div>
      </aside>

      <!-- Selected scope -->
      <div v-if="detail" class="scope-detail">
        <!-- Identity and mapping -->
        <div class="section-card">
          <div class="section-head">
            <h3 class="section-title">Identity &amp; Mapping</h3>
            <div class="header-actions detail-actions">
              <button class="btn btn-sm btn-primary" @click="publish">Publish</button>
              <button class="btn btn-sm btn-secondary" @click="exportShare">Export to Share</button>
              <button class="btn btn-sm btn-secondary" @click="toggleVersions">Versions</button>
              <button class="btn btn-sm btn-secondary" @click="loadPreview">Preview</button>
              <button class="btn btn-sm btn-danger" @click="deleteScope">Delete</button>
            </div>
          </div>
          <p v-if="scopeSummary" class="scope-summary muted">{{ scopeSummary }}</p>
          <div class="settings-grid">
            <div class="setting-row">
              <label>
                <span>Computer Type ID</span>
                <input type="number" v-model.number="detail.computertypeid" />
              </label>
            </div>
            <div class="setting-row">
              <label>
                <span>Measuring Tool Type ID</span>
                <input type="number" v-model.number="detail.measuringtooltypeid" />
              </label>
            </div>
            <div class="setting-row">
              <label>
                <span>Description</span>
                <input type="text" v-model="detail.description" />
              </label>
            </div>
          </div>
          <button class="btn btn-primary" @click="saveScope">Save</button>
        </div>

        <!-- Versions panel -->
        <div v-if="showVersions" class="section-card">
          <h3 class="section-title">Published versions</h3>
          <div class="table-container">
            <table>
              <thead>
                <tr><th>Version</th><th>Published</th><th>Notes</th><th></th></tr>
              </thead>
              <tbody>
                <tr v-for="version in versions" :key="version.versionnumber">
                  <td>
                    v{{ version.versionnumber }}
                    <span v-if="version.iscurrent" class="badge badge-success">current</span>
                  </td>
                  <td class="muted">{{ formatDate(version.publishedat) }}</td>
                  <td class="muted">{{ version.notes }}</td>
                  <td class="actions">
                    <button
                      v-if="!version.iscurrent"
                      class="btn btn-sm btn-secondary"
                      @click="rollback(version.versionnumber)"
                    >Roll back</button>
                  </td>
                </tr>
                <tr v-if="!versions.length"><td colspan="4" class="empty">No published versions.</td></tr>
              </tbody>
            </table>
          </div>
        </div>

        <!-- Entries -->
        <div class="section-card">
          <div class="section-head">
            <h3 class="section-title">Manifest entries ({{ detail.entries.length }})</h3>
            <button class="btn btn-sm btn-primary" @click="openNewEntry">+ Add entry</button>
          </div>
          <p class="setting-description">
            Order is execution order. Put config-restore entries after their installer.
          </p>
          <div class="table-container">
            <table class="entries-table">
              <thead>
                <tr><th class="col-order">Order</th><th class="col-type">Type</th><th>Entry</th><th class="col-actions"></th></tr>
              </thead>
              <tbody>
                <tr v-for="(entry, index) in detail.entries" :key="entry.entryid">
                  <td class="order-cell">
                    <button
                      class="btn btn-sm btn-secondary"
                      :disabled="index === 0"
                      @click="moveEntry(index, -1)"
                    >Up</button>
                    <button
                      class="btn btn-sm btn-secondary"
                      :disabled="index === detail.entries.length - 1"
                      @click="moveEntry(index, 1)"
                    >Down</button>
                  </td>
                  <td><span class="badge badge-info">{{ entry.Type }}</span></td>
                  <td>
                    <div class="entry-name">{{ entry.Name }}</div>
                    <div class="entry-desc muted">{{ describeEntry(entry) }}</div>
                  </td>
                  <td class="row-actions">
                    <button class="btn btn-sm btn-secondary" @click="openEditEntry(entry)">Edit</button>
                    <button class="btn btn-sm btn-danger" @click="deleteEntry(entry)">Delete</button>
                  </td>
                </tr>
                <tr v-if="!detail.entries.length"><td colspan="4" class="empty">No entries.</td></tr>
              </tbody>
            </table>
          </div>
        </div>

        <!-- Simulator -->
        <div class="section-card">
          <h3 class="section-title">Simulate: what would a PC get?</h3>
          <div class="settings-grid">
            <div class="setting-row">
              <label><span>Subtype</span><input v-model="simulateInputs.subtype" placeholder="subtype" /></label>
            </div>
            <div class="setting-row">
              <label><span>Hostname</span><input v-model="simulateInputs.hostname" placeholder="hostname" /></label>
            </div>
            <div class="setting-row">
              <label><span>Machine #</span><input v-model="simulateInputs.machinenumber" placeholder="machine #" /></label>
            </div>
            <div class="setting-row">
              <label><span>CMM version</span><input v-model="simulateInputs.cmmversion" placeholder="CMM version" /></label>
            </div>
          </div>
          <button class="btn btn-primary" @click="runSimulate">Run</button>
          <div v-if="simulateResult" class="simulate-result">
            <div>
              <strong>Applies ({{ simulateResult.applied.length }}):</strong>
              {{ simulateResult.applied.join(', ') || 'none' }}
            </div>
            <div class="muted">
              <strong>Filtered:</strong>
              <span v-for="filtered in simulateResult.filtered" :key="filtered.name">
                {{ filtered.name }} ({{ filtered.filteredby.join(',') }});
              </span>
            </div>
          </div>
        </div>

        <!-- Compliance: install coverage for tracked-application entries -->
        <div class="section-card">
          <h3 class="section-title">Compliance: who has these installed?</h3>
          <p class="setting-description">
            Install counts for entries linked to a tracked application, from the
            computers collector. Only entries with a tracked app appear.
          </p>
          <div v-if="compliance && compliance.computersplugin === false" class="muted compliance-note">
            Install counts require the computers plugin.
          </div>
          <div class="table-container">
            <table>
              <thead>
                <tr>
                  <th>Entry</th>
                  <th>Tracked app</th>
                  <th>Expected version</th>
                  <th>Installed</th>
                  <th>On expected version</th>
                  <th>Note</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="row in complianceRows" :key="row.entryid">
                  <td>{{ row.entryname }}</td>
                  <td>{{ row.appname }}</td>
                  <td class="muted">{{ row.expectedversion || '-' }}</td>
                  <td>{{ row.installedcount === null ? '-' : row.installedcount }}</td>
                  <td>
                    <span v-if="row.versionmatchcount === null" class="muted">-</span>
                    <span v-else>{{ row.versionmatchcount }}</span>
                  </td>
                  <td class="muted">{{ row.coveragenote }}</td>
                </tr>
                <tr v-if="!complianceRows.length">
                  <td colspan="6" class="empty">No entries are linked to a tracked application yet.</td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>

      <div v-else class="section-card empty-detail">
        Select a PC type, or create one.
      </div>
    </div>

    <!-- New scope modal -->
    <div v-if="showNewScope" class="modal-overlay" @click.self="showNewScope = false">
      <div class="modal">
        <div class="modal-header">
          <h3>New imaging PC type</h3>
          <button class="modal-close" @click="showNewScope = false">x</button>
        </div>
        <div class="modal-body">
          <div class="setting-row full-width">
            <label>
              <span>Scope name</span>
              <input v-model="newScope.scopename" placeholder="e.g. gea-shopfloor-cmm" />
            </label>
          </div>
          <div class="setting-row">
            <label>
              <span>Phase</span>
              <select v-model="newScope.phase">
                <option value="runtime">runtime</option>
                <option value="preinstall">preinstall</option>
              </select>
            </label>
          </div>
        </div>
        <div class="modal-footer">
          <button class="btn btn-secondary" @click="showNewScope = false">Cancel</button>
          <button class="btn btn-primary" @click="createScope">Create</button>
        </div>
      </div>
    </div>

    <!-- Entry modal -->
    <div v-if="showEntry" class="modal-overlay" @click.self="showEntry = false">
      <div class="modal modal-entry">
        <div class="modal-header">
          <h3>{{ entryForm.entryid ? 'Edit entry' : 'New entry' }}</h3>
          <button class="modal-close" @click="showEntry = false">x</button>
        </div>
        <div class="modal-body">
          <div class="settings-grid">
            <div class="setting-row">
              <label><span>Name</span><input v-model="entryForm.Name" /></label>
            </div>
            <div class="setting-row">
              <label>
                <span>Type</span>
                <select v-model="entryForm.Type">
                  <option v-for="entryType in availableEntryTypes" :key="entryType" :value="entryType">{{ entryType }}</option>
                </select>
                <small v-if="isPreinstallScope" class="input-hint">Preinstall runs at imaging and supports MSI/EXE only.</small>
              </label>
            </div>
            <div class="setting-row">
              <label>
                <span>Tracked application (optional)</span>
                <select v-model="entryForm.appid">
                  <option :value="null">(not linked)</option>
                  <option v-for="app in applications" :key="app.appid" :value="app.appid">{{ app.appname }}</option>
                </select>
                <small class="input-hint">Link this entry to a tracked Application in the catalog (metadata; not part of the manifest).</small>
              </label>
            </div>
          </div>

          <!-- payload fields per type -->
          <div v-if="['MSI','EXE','CMD','BAT','INF'].includes(entryForm.Type)" class="settings-grid">
            <div class="setting-row">
              <label>
                <span>Installer (relative path)</span>
                <input v-model="entryForm.Installer" />
                <small class="input-hint">Path under the scope payload folder on the share, or an inline payload attached below.</small>
              </label>
            </div>
            <div class="setting-row">
              <label><span>Install args</span><input v-model="entryForm.InstallArgs" /></label>
            </div>
          </div>
          <div v-if="entryForm.Type === 'PS1'" class="settings-grid">
            <div class="setting-row">
              <label><span>Script (relative path)</span><input v-model="entryForm.Script" /></label>
            </div>
            <div class="setting-row">
              <label><span>Args</span><input v-model="entryForm.Args" /></label>
            </div>
          </div>
          <div v-if="entryForm.Type === 'File'" class="settings-grid">
            <div class="setting-row">
              <label>
                <span>Source (relative)</span>
                <input v-model="entryForm.Source" />
                <small class="input-hint">Path under the scope payload folder on the share, or an inline payload attached below.</small>
              </label>
            </div>
            <div class="setting-row">
              <label><span>Destination (absolute)</span><input v-model="entryForm.Destination" /></label>
            </div>
          </div>
          <div v-if="entryForm.Type === 'Registry'" class="settings-grid">
            <div class="setting-row">
              <label><span>Reg path</span><input v-model="entryForm.RegPath" /></label>
            </div>
            <div class="setting-row">
              <label><span>Reg name</span><input v-model="entryForm.RegName" /></label>
            </div>
            <div class="setting-row">
              <label><span>Reg value</span><input v-model="entryForm.RegValue" /></label>
            </div>
            <div class="setting-row">
              <label>
                <span>Reg type</span>
                <select v-model="entryForm.RegType">
                  <option v-for="regType in REG_TYPES" :key="regType" :value="regType">{{ regType }}</option>
                </select>
              </label>
            </div>
          </div>

          <!-- inline payload upload (only for a saved entry) -->
          <div v-if="entryForm.entryid" class="setting-group">
            <h3>Inline payload</h3>
            <p class="setting-description">
              Attach a small file (installer config, .reg, script) stored in
              shopdb and shipped with this entry.
            </p>
            <div v-if="entryForm.haspayload" class="payload-current muted">
              Current: {{ entryForm.payloadref }}
              <span v-if="entryForm.payloadsha256">(sha256 {{ entryForm.payloadsha256.slice(0, 12) }})</span>
            </div>
            <div v-else class="payload-current muted">No payload attached.</div>
            <div class="payload-upload">
              <input type="file" @change="onPayloadFileChange" />
              <button
                type="button"
                class="btn btn-sm btn-secondary"
                :disabled="!payloadFile || payloadUploading"
                @click="uploadPayload"
              >{{ payloadUploading ? 'Uploading...' : 'Upload' }}</button>
            </div>
            <small class="input-hint">Inline payloads are capped at 1 MB and stored in shopdb.</small>
          </div>

          <!-- detection -->
          <div class="setting-row">
            <label>
              <span>Detection method</span>
              <select v-model="entryForm.DetectionMethod">
                <option value="">(none: always installs)</option>
                <option v-for="method in availableDetectionMethods" :key="method" :value="method">{{ method }}</option>
              </select>
              <small v-if="detectionMethodHint" class="input-hint">{{ detectionMethodHint }}</small>
              <small v-if="isPreinstallScope" class="input-hint">Preinstall supports Registry/File detection only.</small>
            </label>
          </div>
          <div v-if="entryForm.DetectionMethod" class="settings-grid">
            <div class="setting-row">
              <label><span>Detection path</span><input v-model="entryForm.DetectionPath" /></label>
            </div>
            <div class="setting-row">
              <label><span>Detection name</span><input v-model="entryForm.DetectionName" /></label>
            </div>
            <div class="setting-row">
              <label>
                <span>Detection value</span>
                <input v-model="entryForm.DetectionValue" />
                <small class="input-hint">For FileVersion detection this is the expected version used by the Compliance panel.</small>
              </label>
            </div>
            <div v-if="entryForm.DetectionMethod === 'pnputil'" class="setting-row">
              <label><span>Detection pattern</span><input v-model="entryForm.DetectionPattern" /></label>
            </div>
          </div>

          <!-- targeting: only the gates this scope actually uses, by default -->
          <div class="setting-group">
            <div class="section-head">
              <h3>Targeting</h3>
              <label class="flag-label toggle-all">
                <input type="checkbox" v-model="showAllTargeting" /> Show all targeting options
              </label>
            </div>
            <p class="setting-description">{{ targetingHint }}</p>
            <div class="settings-grid">
              <div class="setting-row" v-if="showAllTargeting || targetingGates.pctypes">
                <label>
                  <span>PC types (comma)</span>
                  <input v-model="entryForm.PCTypes" placeholder="* or gea-shopfloor-cmm,..." />
                  <small class="input-hint">Which PC types this entry runs on. Needed on the fleet-wide common/preinstall manifests; a per-type manifest already only runs on its own type.</small>
                </label>
              </div>
              <div class="setting-row" v-if="showAllTargeting || targetingGates.cmmversion">
                <label>
                  <span>CMM version gate</span>
                  <input v-model="entryForm._CmmVersion" placeholder="2016 / 2019 / 2026" />
                  <small class="input-hint">Install only on CMM bays resolved to this PC-DMIS version.</small>
                </label>
              </div>
              <div class="setting-row" v-if="showAllTargeting || targetingGates.machinenumbers">
                <label>
                  <span>Target machine numbers (comma)</span>
                  <input v-model="entryForm.TargetMachineNumbers" />
                  <small class="input-hint">Restrict to specific bay machine numbers.</small>
                </label>
              </div>
              <div class="setting-row" v-if="showAllTargeting || targetingGates.hostnames">
                <label>
                  <span>Target hostnames (comma)</span>
                  <input v-model="entryForm.TargetHostnames" />
                  <small class="input-hint">Restrict to specific hostnames (supports * wildcards).</small>
                </label>
              </div>
            </div>
          </div>

          <div class="settings-grid">
            <div class="setting-row">
              <label><span>Wait timeout (sec)</span><input type="number" v-model.number="entryForm.WaitTimeoutSec" /></label>
            </div>
            <div class="setting-row">
              <label><span>Log file</span><input v-model="entryForm.LogFile" /></label>
            </div>
          </div>

          <div class="setting-row full-width">
            <label>
              <span>Comment</span>
              <textarea class="form-control" v-model="entryForm._comment" rows="2"></textarea>
            </label>
          </div>

          <!-- In-use handling + preinstall flags -->
          <div class="setting-group">
            <h3>In-use handling and preinstall flags</h3>
            <div class="setting-row">
              <label>
                <span>Close running app before install (InUseCheck)</span>
                <select v-model="entryForm.inuseBehavior">
                  <option value="">(none)</option>
                  <option v-for="behavior in INUSE_BEHAVIORS" :key="behavior" :value="behavior">{{ behavior }}</option>
                </select>
                <small class="input-hint">What to do when a process below is running: Defer skips this cycle, CloseAndReopen restarts it, ForceClose kills it, ScheduleForReboot defers to next reboot.</small>
              </label>
            </div>
            <div v-if="entryForm.inuseBehavior" class="process-editor">
              <div class="process-head">
                <span>Processes to close</span>
                <button type="button" class="btn btn-sm btn-secondary" @click="addProcess">+ process</button>
              </div>
              <div v-for="(process, processIndex) in entryForm.inuseProcesses" :key="processIndex" class="process-row">
                <input class="form-control" v-model="process.name" placeholder="Name (no .exe)" />
                <input class="form-control" v-model="process.exepath" placeholder="ExePath (optional)" />
                <input class="form-control" type="number" v-model.number="process.timeout" placeholder="timeout s" />
                <button type="button" class="btn btn-sm btn-danger" @click="removeProcess(processIndex)">Remove</button>
              </div>
            </div>
            <div v-if="isPreinstallScope" class="preinstall-flags">
              <span class="flags-label">Preinstall flags (only take effect at imaging):</span>
              <label class="flag-label"><input type="checkbox" v-model="entryForm.PreEnrollment" /> PreEnrollment</label>
              <label class="flag-label"><input type="checkbox" v-model="entryForm.KillAfterDetection" /> KillAfterDetection</label>
              <label class="flag-label"><input type="checkbox" v-model="entryForm.PCTypesStrict" /> PCTypesStrict</label>
            </div>
          </div>

          <!-- Apply mode / update window -->
          <div class="setting-group">
            <h3>Apply mode / update window</h3>
            <p class="input-hint">Not yet enforced by the engine.</p>
            <div class="settings-grid">
              <div class="setting-row">
                <label><span>Apply mode</span><input v-model="entryForm.ApplyMode" placeholder="Nightly / Immediate" /></label>
              </div>
              <div class="setting-row">
                <label><span>Update window</span><input v-model="entryForm.UpdateWindow" placeholder="HH:MM-HH:MM" /></label>
              </div>
            </div>
          </div>
        </div>
        <div class="modal-footer">
          <button class="btn btn-secondary" @click="showEntry = false">Cancel</button>
          <button class="btn btn-primary" @click="saveEntry">Save</button>
        </div>
      </div>
    </div>

    <!-- Preview modal -->
    <div v-if="showPreview" class="modal-overlay" @click.self="showPreview = false">
      <div class="modal modal-entry">
        <div class="modal-header">
          <h3>Draft manifest preview</h3>
          <button class="modal-close" @click="showPreview = false">x</button>
        </div>
        <div class="modal-body">
          <pre class="preview">{{ previewText }}</pre>
        </div>
        <div class="modal-footer">
          <button class="btn btn-secondary" @click="showPreview = false">Close</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import api from '@/api'
// Pure entry-form logic lives in entryForm.js (unit-tested by entryForm.spec.js);
// this component wires it to reactive state so the tests cover the shipped code.
import {
  ENTRY_TYPES, REG_TYPES, DETECTION_METHODS, INUSE_BEHAVIORS,
  blankEntry, buildEntryPayload as buildEntryPayloadPure,
  describeEntry, availableEntryTypes as availableEntryTypesFor,
  availableDetectionMethods as availableDetectionMethodsFor,
  targetingGates as targetingGatesFor, targetingHint as targetingHintFor,
  scopeSummary as scopeSummaryFor, detectionMethodHint as detectionMethodHintFor,
} from './entryForm'

const scopes = ref([])
const selectedId = ref(null)
const detail = ref(null)
const error = ref('')
const notice = ref('')
const shareroot = ref('')

// Phase-aware editing: the PREINSTALL runner implements only MSI/EXE types and
// Registry/File detection (other types/detections are silently skipped there),
// and only preinstall honors the preinstall flags. Restrict the form for a
// preinstall scope so an author cannot pick an option that would do nothing.
const isPreinstallScope = computed(() => detail.value?.phase === 'preinstall')
const availableEntryTypes = computed(() =>
  availableEntryTypesFor(isPreinstallScope.value, entryForm.value?.Type))
const availableDetectionMethods = computed(() =>
  availableDetectionMethodsFor(isPreinstallScope.value, entryForm.value?.DetectionMethod))
const detectionMethodHint = computed(() =>
  detectionMethodHintFor(entryForm.value?.DetectionMethod))
const scopeSummary = computed(() => scopeSummaryFor(detail.value))
const showAllTargeting = ref(false)
const targetingGates = computed(() => targetingGatesFor(detail.value))
const targetingHint = computed(() => targetingHintFor(detail.value))

const showNewScope = ref(false)
const newScope = ref({ scopename: '', phase: 'runtime' })

const showEntry = ref(false)
const entryForm = ref({})
const applications = ref([])

const showVersions = ref(false)
const versions = ref([])

const showPreview = ref(false)
const previewText = ref('')

const simulateInputs = ref({ subtype: '', hostname: '', machinenumber: '', cmmversion: '' })
const simulateResult = ref(null)

const compliance = ref(null)
const complianceRows = computed(() => compliance.value?.rows || [])

const payloadFile = ref(null)
const payloadUploading = ref(false)

function payload(response) { return response.data.data }

function flash(message) { notice.value = message; setTimeout(() => { notice.value = '' }, 4000) }

async function loadScopes() {
  try {
    scopes.value = payload(await api.get('/geenforce/scopes'))
  } catch (e) { error.value = 'Failed to load PC types' }
}
async function loadConfig() {
  try { shareroot.value = payload(await api.get('/geenforce/config')).shareroot } catch (e) { /* optional */ }
}
async function saveConfig() {
  try {
    await api.put('/geenforce/config', { shareroot: shareroot.value })
    flash('Share root saved.')
  } catch (e) { error.value = 'Failed to save share root' }
}
async function exportShare() {
  try {
    const result = payload(await api.post(`/geenforce/scopes/${detail.value.scopeid}/export-share`))
    flash(`Exported to ${result.path}`)
  } catch (e) { error.value = e.response?.data?.message || 'Export failed' }
}

async function selectScope(id) {
  selectedId.value = id
  showVersions.value = false
  simulateResult.value = null
  compliance.value = null
  try {
    detail.value = payload(await api.get(`/geenforce/scopes/${id}`))
    loadCompliance(id)
  } catch (e) { error.value = 'Failed to load PC type' }
}
async function loadCompliance(id) {
  try {
    compliance.value = payload(await api.get(`/geenforce/scopes/${id}/compliance`))
  } catch (e) { compliance.value = null }
}

function openNewScope() {
  newScope.value = { scopename: '', phase: 'runtime' }
  showNewScope.value = true
}
async function createScope() {
  try {
    const created = payload(await api.post('/geenforce/scopes', newScope.value))
    showNewScope.value = false
    await loadScopes()
    selectScope(created.scopeid)
  } catch (e) { error.value = e.response?.data?.message || 'Create failed' }
}
async function saveScope() {
  try {
    await api.put(`/geenforce/scopes/${detail.value.scopeid}`, {
      description: detail.value.description,
      computertypeid: detail.value.computertypeid,
      measuringtooltypeid: detail.value.measuringtooltypeid,
    })
    await loadScopes()
  } catch (e) { error.value = 'Save failed' }
}
async function deleteScope() {
  if (!confirm(`Delete ${detail.value.scopename}? This removes its manifest.`)) return
  try {
    await api.delete(`/geenforce/scopes/${detail.value.scopeid}`)
    detail.value = null
    selectedId.value = null
    await loadScopes()
  } catch (e) { error.value = 'Delete failed' }
}

// -- entries --
function openNewEntry() {
  entryForm.value = blankEntry()
  payloadFile.value = null
  showEntry.value = true
}
function openEditEntry(entry) {
  const form = { ...entry }
  form.PCTypes = (entry.PCTypes || []).join(', ')
  form.TargetHostnames = (entry.TargetHostnames || []).join(', ')
  form.TargetMachineNumbers = (entry.TargetMachineNumbers || []).join(', ')
  form.RegValue = entry.RegValue !== undefined ? String(entry.RegValue) : ''
  form.DetectionMethod = entry.DetectionMethod || ''
  form.inuseBehavior = entry.InUseCheck?.Behavior || ''
  // Structured processes so ExePath + timeout survive an edit (not just Name).
  form.inuseProcesses = (entry.InUseCheck?.Processes || []).map(process => ({
    name: process.Name, exepath: process.ExePath || '',
    timeout: process.GracefulCloseTimeoutSec ?? null }))
  form.PreEnrollment = !!entry.PreEnrollment
  form.KillAfterDetection = !!entry.KillAfterDetection
  form.PCTypesStrict = !!entry.PCTypesStrict
  entryForm.value = form
  payloadFile.value = null
  showEntry.value = true
}
function addProcess() {
  if (!Array.isArray(entryForm.value.inuseProcesses)) entryForm.value.inuseProcesses = []
  entryForm.value.inuseProcesses.push({ name: '', exepath: '', timeout: null })
}
function removeProcess(index) {
  entryForm.value.inuseProcesses.splice(index, 1)
}
function buildEntryPayload() {
  return buildEntryPayloadPure(entryForm.value)
}
async function saveEntry() {
  const body = buildEntryPayload()
  try {
    if (entryForm.value.entryid) {
      await api.put(`/geenforce/entries/${entryForm.value.entryid}`, body)
    } else {
      await api.post(`/geenforce/scopes/${detail.value.scopeid}/entries`, body)
    }
    showEntry.value = false
    await selectScope(detail.value.scopeid)
    await loadScopes()
  } catch (e) { error.value = e.response?.data?.message || 'Save entry failed' }
}
function onPayloadFileChange(event) {
  payloadFile.value = event.target.files?.[0] || null
}
async function uploadPayload() {
  if (!entryForm.value.entryid || !payloadFile.value) return
  payloadUploading.value = true
  try {
    const form = new FormData()
    form.append('file', payloadFile.value)
    const updated = payload(await api.post(
      `/geenforce/entries/${entryForm.value.entryid}/payload`, form,
      { headers: { 'Content-Type': 'multipart/form-data' } }))
    entryForm.value.payloadsource = updated.payloadsource
    entryForm.value.payloadref = updated.payloadref
    entryForm.value.payloadsha256 = updated.payloadsha256
    entryForm.value.haspayload = updated.haspayload
    payloadFile.value = null
    flash('Payload uploaded.')
    await selectScope(detail.value.scopeid)
  } catch (e) { error.value = e.response?.data?.message || 'Payload upload failed' }
  finally { payloadUploading.value = false }
}
async function deleteEntry(entry) {
  if (!confirm(`Delete entry "${entry.Name}"?`)) return
  try {
    await api.delete(`/geenforce/entries/${entry.entryid}`)
    await selectScope(detail.value.scopeid)
    await loadScopes()
  } catch (e) { error.value = 'Delete entry failed' }
}
async function moveEntry(index, direction) {
  const entries = detail.value.entries
  const target = index + direction
  if (target < 0 || target >= entries.length) return
  const order = entries.map(entry => entry.entryid)
  ;[order[index], order[target]] = [order[target], order[index]]
  try {
    await api.put(`/geenforce/scopes/${detail.value.scopeid}/entries/reorder`, { order })
    await selectScope(detail.value.scopeid)
  } catch (e) { error.value = 'Reorder failed' }
}

// -- publish / versions --
async function publish() {
  const notes = prompt('Publish notes (optional):') ?? ''
  try {
    await api.post(`/geenforce/scopes/${detail.value.scopeid}/publish`, { notes })
    await loadScopes()
    await selectScope(detail.value.scopeid)
    if (showVersions.value) loadVersions()
  } catch (e) { error.value = e.response?.data?.message || 'Publish failed' }
}
async function toggleVersions() {
  showVersions.value = !showVersions.value
  if (showVersions.value) await loadVersions()
}
async function loadVersions() {
  try {
    versions.value = payload(await api.get(`/geenforce/scopes/${detail.value.scopeid}/versions`))
  } catch (e) { error.value = 'Failed to load versions' }
}
async function rollback(versionnumber) {
  if (!confirm(`Roll back to v${versionnumber}? PCs get this on their next cycle.`)) return
  try {
    await api.post(`/geenforce/scopes/${detail.value.scopeid}/rollback`, { versionnumber })
    await loadVersions()
    await loadScopes()
  } catch (e) { error.value = 'Rollback failed' }
}

// -- simulate / preview --
async function runSimulate() {
  try {
    const params = { ...simulateInputs.value }
    simulateResult.value = payload(await api.get(
      `/geenforce/scopes/${detail.value.scopeid}/simulate`, { params }))
  } catch (e) { error.value = 'Simulate failed' }
}
async function loadPreview() {
  try {
    const data = payload(await api.get(`/geenforce/scopes/${detail.value.scopeid}/preview`))
    previewText.value = JSON.stringify(data.manifest, null, 2)
    showPreview.value = true
  } catch (e) { error.value = 'Preview failed' }
}

function filterSummary(entry) {
  const parts = []
  if (entry.PCTypes) parts.push(`pc:${entry.PCTypes.length}`)
  if (entry.TargetMachineNumbers) parts.push(`mach:${entry.TargetMachineNumbers.length}`)
  if (entry.TargetHostnames) parts.push(`host:${entry.TargetHostnames.length}`)
  if (entry._CmmVersion) parts.push(`cmm:${entry._CmmVersion}`)
  return parts.join(' ') || '-'
}

// Plain-English one-liner: what this entry does, when it self-heals, who it
// applies to. Turns "eDNC | MSI | Registry | -" into intent an IT tech reads.
function formatDate(value) {
  return value ? new Date(value).toLocaleString() : ''
}

async function loadApplications() {
  try { applications.value = payload(await api.get('/geenforce/applications')) } catch (e) { /* optional */ }
}

loadScopes()
loadConfig()
loadApplications()
</script>

<style scoped>
.imaging-pctypes { max-width: 1200px; }

/* Two-pane master-detail; both children min-width:0 to kill overflow. */
.editor-grid {
  display: grid;
  grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}
.editor-grid > * { min-width: 0; }
@media (max-width: 900px) {
  .editor-grid { grid-template-columns: 1fr; }
  .scope-panel { position: static; }
}

/* Section header carrying an action button/cluster. */
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
}
.section-head .section-title {
  border-bottom: none;
  margin: 0;
  padding: 0;
}
.detail-actions { flex-wrap: wrap; }

/* Scope list rail; active row tinted like the sidebar active link. */
.scope-panel { position: sticky; top: 1rem; }
.scope-list { display: flex; flex-direction: column; gap: 0.25rem; }
.scope-row {
  padding: 0.5rem 0.6rem;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
}
.scope-row:hover { background: var(--bg); }
.scope-row.active { background: var(--primary); color: #fff; }
.scope-name { font-weight: 600; font-size: 0.9rem; }
.scope-meta {
  display: flex;
  gap: 0.4rem;
  font-size: 0.72rem;
  align-items: center;
  color: var(--text-light);
  margin-top: 0.25rem;
  flex-wrap: wrap;
}
.scope-row.active .scope-meta { color: rgba(255, 255, 255, 0.85); }
.unpublished { color: var(--warning); }

.empty-detail {
  color: var(--text-light);
  text-align: center;
  padding: 3rem 1.25rem;
}
.empty {
  color: var(--text-light);
  text-align: center;
  padding: 1rem;
}
.muted { color: var(--text-light); }

/* Simulator result */
.simulate-result {
  margin-top: 1rem;
  font-size: 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* Grid children must shrink so long paths never force page overflow. */
.settings-grid > .setting-row { min-width: 0; }
.settings-grid > .setting-row input,
.settings-grid > .setting-row select { width: 100%; box-sizing: border-box; }

/* Modals: wider entry/preview, still responsive (global max-width + width:100%). */
.modal-entry { max-width: min(1100px, 96vw); }
.toggle-all { font-weight: 400; font-size: 0.8rem; }
.entry-name { font-weight: 500; }
.entry-desc { font-size: 0.78rem; margin-top: 0.15rem; overflow-wrap: anywhere; }
.scope-summary { margin: 0 0 0.75rem 0; font-size: 0.85rem; }
/* Fixed layout so the Entry column flexes and the button columns never force
   a horizontal scroll. */
.scope-detail .entries-table { width: 100%; table-layout: fixed; }
.entries-table th.col-order, .entries-table td.order-cell { width: 58px; }
.entries-table th.col-type { width: 64px; }
.entries-table th.col-actions, .entries-table td.row-actions { width: 128px; }
.order-cell { display: flex; flex-direction: column; gap: 0.2rem; }
.order-cell .btn { padding: 0.1rem 0.3rem; }
.row-actions { display: flex; gap: 0.3rem; flex-wrap: wrap; }
.help-panel { margin: 0 0 1rem 0; border: 1px solid var(--border);
  border-radius: 6px; padding: 0.5rem 0.75rem; background: var(--bg-card); }
.help-panel summary { cursor: pointer; font-weight: 600; font-size: 0.9rem; }
.help-body { margin-top: 0.5rem; }
.help-body p { margin: 0 0 0.5rem 0; font-size: 0.85rem; color: var(--text-light); }
.help-body code { font-size: 0.8rem; }
.modal-close {
  background: transparent;
  border: none;
  color: var(--text-light);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
}
.modal-close:hover { color: var(--text); }

/* Comment textarea matches the themed input look. */
.modal-body textarea.form-control {
  background: var(--bg);
  resize: vertical;
  min-height: 2.5rem;
}

/* In-use process editor: repeatable rows, children can shrink. */
.process-editor { margin-bottom: 1rem; }
.process-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.85rem;
  color: var(--text-light);
  margin-bottom: 0.5rem;
}
.process-row {
  display: grid;
  grid-template-columns: 1fr 1.4fr 0.7fr auto;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
  align-items: center;
}
.process-row > * { min-width: 0; }
@media (max-width: 560px) {
  .process-row { grid-template-columns: 1fr; }
}

/* Preinstall flags */
.preinstall-flags { margin-top: 0.75rem; }
.flags-label {
  display: block;
  font-size: 0.85rem;
  color: var(--text-light);
  margin-bottom: 0.5rem;
}
.flag-label {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-right: 1rem;
  font-size: 0.9rem;
  color: var(--text);
}

/* JSON preview */
.preview {
  background: var(--bg);
  padding: 0.75rem;
  border-radius: 6px;
  max-height: 60vh;
  overflow: auto;
  font-size: 0.8rem;
  color: var(--text);
  white-space: pre;
}

/* Reorder / disabled buttons */
.btn:disabled { opacity: 0.4; cursor: default; }

/* Inline payload upload */
.payload-current { font-size: 0.85rem; margin-bottom: 0.5rem; }
.payload-upload {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.payload-upload input[type="file"] { min-width: 0; font-size: 0.85rem; }

/* Compliance panel */
.compliance-note { margin-bottom: 0.75rem; }
</style>
