<!DOCTYPE html>
<html lang="en" data-bs-theme="auto">

<head>
      <%- header %>
</head>

<body id="app" v-cloak>
  <Navbar></Navbar>
  <div id="content" class="container">
    <h1 class="my-4">{{ $t('troubleshooting.troubleshooting') }}</h1>
    <!-- Virtual gamepad and Virtual HID Driver license -->
    <div class="card my-4 virtual-gamepad-card" v-if="platform === 'windows'">
      <div class="card-body">
        <header class="virtual-gamepad-hero">
          <div class="virtual-gamepad-heading-icon" aria-hidden="true">
            <gamepad-2 :size="28"></gamepad-2>
          </div>
          <div>
            <h2 id="virtualhid" class="mb-1">{{ $t('troubleshooting.virtual_gamepad') }}</h2>
            <p class="mb-0">{{ $t('troubleshooting.virtual_gamepad_desc') }}</p>
          </div>
        </header>

        <div class="virtual-gamepad-feature-grid" :aria-label="$t('troubleshooting.virtualhid_benefits_title')">
          <article class="virtual-gamepad-feature">
            <div class="virtual-gamepad-feature-icon" aria-hidden="true">
              <gamepad-2 :size="21"></gamepad-2>
            </div>
            <div>
              <h3>{{ $t('troubleshooting.virtualhid_benefit_gamepads_title') }}</h3>
              <p>{{ $t('troubleshooting.virtualhid_benefit_gamepads') }}</p>
            </div>
          </article>
          <article class="virtual-gamepad-feature">
            <div class="virtual-gamepad-feature-icon" aria-hidden="true">
              <sparkles :size="21"></sparkles>
            </div>
            <div>
              <h3>{{ $t('troubleshooting.virtualhid_benefit_features_title') }}</h3>
              <p>{{ $t('troubleshooting.virtualhid_benefit_features') }}</p>
            </div>
          </article>
          <article class="virtual-gamepad-feature">
            <div class="virtual-gamepad-feature-icon" aria-hidden="true">
              <shield-check :size="21"></shield-check>
            </div>
            <div>
              <h3>{{ $t('troubleshooting.virtualhid_benefit_maintained_title') }}</h3>
              <p>{{ $t('troubleshooting.virtualhid_benefit_maintained') }}</p>
            </div>
          </article>
        </div>

        <section class="virtual-gamepad-section" v-if="controllerEnabled">
          <div class="virtual-gamepad-section-heading">
            <div>
              <h3 class="h4 mb-1">{{ $t('troubleshooting.virtual_gamepad_drivers') }}</h3>
              <p class="mb-0">{{ $t('troubleshooting.virtual_gamepad_drivers_desc') }}</p>
            </div>
            <button class="btn btn-outline-secondary driver-refresh-button"
                    type="button"
                    @click="refreshDriverInformation">
              <refresh-cw :size="17" :class="{ 'driver-release-spinning': driverReleaseLoading }"></refresh-cw>
              {{ $t('troubleshooting.driver_refresh') }}
            </button>
          </div>

          <div class="table-responsive driver-table-shell">
            <table class="table align-middle mb-0">
              <thead>
                <tr>
                  <th scope="col">{{ $t('troubleshooting.driver_name') }}</th>
                  <th scope="col">{{ $t('troubleshooting.driver_installed_version') }}</th>
                  <th scope="col">{{ $t('troubleshooting.driver_latest_version') }}</th>
                  <th scope="col">{{ $t('troubleshooting.driver_supported_versions') }}</th>
                  <th scope="col">{{ $t('troubleshooting.driver_status') }}</th>
                  <th scope="col" class="driver-download-column">
                    <span class="visually-hidden">{{ $t('troubleshooting.driver_download') }}</span>
                  </th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <th scope="row">{{ $t('troubleshooting.virtualhid_driver') }}</th>
                  <td>{{ driverVersion(virtualhid) }}</td>
                  <td>
                    <div class="driver-release-state"
                         :class="driverReleaseStateClass(virtualhid, virtualhidRelease)"
                         :title="driverReleaseStatusText(virtualhid, virtualhidRelease)"
                         aria-live="polite">
                      <refresh-cw v-if="virtualhidRelease.loading"
                                  :size="17"
                                  class="driver-release-spinning"></refresh-cw>
                      <check-circle v-else-if="driverReleaseState(virtualhid, virtualhidRelease) === 'current'"
                                    :size="17"></check-circle>
                      <alert-triangle v-else-if="driverReleaseState(virtualhid, virtualhidRelease) === 'outdated'"
                                      :size="17"></alert-triangle>
                      <alert-circle v-else :size="17"></alert-circle>
                      <span>{{ driverLatestVersion(virtualhidRelease) }}</span>
                    </div>
                    <small class="driver-release-detail">{{ driverReleaseStatusText(virtualhid, virtualhidRelease) }}</small>
                  </td>
                  <td>{{ virtualhid.supported_versions }}</td>
                  <td>
                    <span :class="driverStatusClass(virtualhid)">
                      {{ driverStatusText(virtualhid) }}
                    </span>
                  </td>
                  <td class="driver-download-column">
                    <a class="btn btn-outline-primary driver-download-button"
                       :href="virtualhidRelease.url"
                       target="_blank"
                       rel="noopener noreferrer">
                      <download :size="17"></download>
                      {{ $t('troubleshooting.driver_download') }}
                    </a>
                  </td>
                </tr>
                <tr id="vigembus" v-if="showVigembus">
                  <th scope="row">{{ $t('troubleshooting.vigembus_driver') }}</th>
                  <td>{{ driverVersion(vigembus) }}</td>
                  <td>
                    <div class="driver-release-state"
                         :class="driverReleaseStateClass(vigembus, vigembusRelease)"
                         :title="driverReleaseStatusText(vigembus, vigembusRelease)"
                         aria-live="polite">
                      <refresh-cw v-if="vigembusRelease.loading"
                                  :size="17"
                                  class="driver-release-spinning"></refresh-cw>
                      <check-circle v-else-if="driverReleaseState(vigembus, vigembusRelease) === 'current'"
                                    :size="17"></check-circle>
                      <alert-triangle v-else-if="driverReleaseState(vigembus, vigembusRelease) === 'outdated'"
                                      :size="17"></alert-triangle>
                      <alert-circle v-else :size="17"></alert-circle>
                      <span>{{ driverLatestVersion(vigembusRelease) }}</span>
                    </div>
                    <small class="driver-release-detail">{{ driverReleaseStatusText(vigembus, vigembusRelease) }}</small>
                  </td>
                  <td>{{ vigembus.supported_versions }}</td>
                  <td>
                    <span :class="driverStatusClass(vigembus)">
                      {{ driverStatusText(vigembus) }}
                    </span>
                  </td>
                  <td class="driver-download-column">
                    <a class="btn btn-outline-primary driver-download-button"
                       :href="vigembusRelease.url"
                       target="_blank"
                       rel="noopener noreferrer">
                      <download :size="17"></download>
                      {{ $t('troubleshooting.driver_download') }}
                    </a>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </section>

        <section class="virtualhid-license-section" aria-labelledby="virtualhid-license">
          <div class="virtualhid-license-heading">
            <div>
              <h3 id="virtualhid-license" class="h4 mb-1">{{ $t('troubleshooting.virtualhid_license') }}</h3>
              <p class="mb-0">{{ $t('troubleshooting.virtualhid_license_desc') }}</p>
            </div>
            <span :class="licenseStatusClass()">{{ licenseStatusText() }}</span>
          </div>

          <div class="alert alert-danger alert-inline" role="alert" v-if="licenseError">
            <alert-circle :size="18" class="icon flex-shrink-0"></alert-circle>
            <span>{{ licenseError }}</span>
          </div>
          <div class="alert alert-warning alert-inline" role="alert" v-else-if="!virtualhidLicense.service_available">
            <alert-triangle :size="18" class="icon flex-shrink-0"></alert-triangle>
            <span>{{ virtualhidLicense.message || $t('troubleshooting.virtualhid_license_unavailable') }}</span>
          </div>

          <div class="virtualhid-license-stats">
            <dl class="virtualhid-license-stat" v-if="virtualhidLicense.plan_name">
              <dt>{{ $t('troubleshooting.virtualhid_license_plan') }}</dt>
              <dd>{{ virtualhidLicense.plan_name }}</dd>
            </dl>
            <dl class="virtualhid-license-stat">
              <dt>{{ $t('troubleshooting.virtualhid_license_activations') }}</dt>
              <dd>{{ licenseActivationText() }}</dd>
            </dl>
            <dl class="virtualhid-license-stat">
              <dt>{{ $t('troubleshooting.virtualhid_license_active_devices') }}</dt>
              <dd>{{ virtualhidLicense.active_devices }}</dd>
            </dl>
            <dl class="virtualhid-license-stat" v-if="virtualhidLicense.customer_email">
              <dt>{{ $t('troubleshooting.virtualhid_license_customer') }}</dt>
              <dd>{{ virtualhidLicense.customer_email }}</dd>
            </dl>
          </div>

          <p class="virtualhid-license-message"
             v-if="virtualhidLicense.message && virtualhidLicense.service_available">
            {{ virtualhidLicense.message }}
          </p>

          <div class="virtualhid-license-toolbar">
            <button class="btn btn-secondary"
                    type="button"
                    :disabled="licenseBusy || !virtualhidLicense.service_available"
                    @click="updateLicense('validate')">
              <refresh-cw :size="18" :class="{ 'driver-release-spinning': licenseBusy }"></refresh-cw>
              {{ $t('troubleshooting.virtualhid_license_refresh') }}
            </button>
            <a class="btn btn-outline-primary"
               v-if="virtualhidLicense.manage_account_url"
               :href="virtualhidLicense.manage_account_url"
               target="_blank"
               rel="noopener noreferrer">
              <external-link :size="18"></external-link>
              {{ $t('troubleshooting.virtualhid_license_manage') }}
            </a>
            <a class="btn btn-success"
               v-if="virtualhidLicense.purchase_url && !virtualhidLicense.licensed"
               :href="virtualhidLicense.purchase_url"
               target="_blank"
               rel="noopener noreferrer">
              {{ $t('troubleshooting.virtualhid_license_buy') }}
            </a>
            <button class="btn btn-outline-danger"
                    type="button"
                    v-if="virtualhidLicense.licensed"
                    :disabled="licenseBusy || !virtualhidLicense.service_available"
                    @click="updateLicense('deactivate')">
              {{ $t('troubleshooting.virtualhid_license_deactivate') }}
            </button>
          </div>

          <div class="virtualhid-license-activation" v-if="!virtualhidLicense.licensed">
            <label for="virtualhid-license-key" class="form-label">
              {{ $t('troubleshooting.virtualhid_license_key') }}
            </label>
            <div class="input-group virtualhid-license-input">
              <input id="virtualhid-license-key"
                     class="form-control"
                     type="password"
                     v-model="licenseKey"
                     :placeholder="$t('troubleshooting.virtualhid_license_key_placeholder')"
                     :disabled="licenseBusy || !virtualhidLicense.service_available"
                     autocomplete="off"
                     autocapitalize="off"
                     spellcheck="false"
                     @keyup.enter="updateLicense('activate')" />
              <button class="btn btn-primary"
                      type="button"
                      :disabled="licenseBusy || !virtualhidLicense.service_available || !licenseKey.trim()"
                      @click="updateLicense('activate')">
                <key-round :size="18"></key-round>
                {{ $t('troubleshooting.virtualhid_license_activate') }}
              </button>
            </div>
            <div class="form-text">{{ $t('troubleshooting.virtualhid_license_key_desc') }}</div>
          </div>

          <div class="virtualhid-license-active" v-else>
            <check-circle :size="22" aria-hidden="true"></check-circle>
            <div>
              <strong>{{ $t('troubleshooting.virtualhid_license_machine_activated') }}</strong>
              <p class="mb-0">{{ $t('troubleshooting.virtualhid_license_machine_activated_desc') }}</p>
            </div>
          </div>
        </section>
      </div>
    </div>
    <!-- Force Close App -->
    <div class="card my-4">
      <div class="card-body">
        <h2 id="close_apps">{{ $t('troubleshooting.force_close') }}</h2>
        <p>{{ $t('troubleshooting.force_close_desc') }}</p>
        <div class="alert alert-success" v-if="closeAppStatus === true">
          <check-circle :size="18" class="icon"></check-circle>
          {{ $t('troubleshooting.force_close_success') }}
        </div>
        <div class="alert alert-danger" v-if="closeAppStatus === false">
          <alert-circle :size="18" class="icon"></alert-circle>
          {{ $t('troubleshooting.force_close_error') }}
        </div>
        <div>
          <button class="btn btn-warning" :disabled="closeAppPressed" @click="closeApp">
            <x-circle :size="18" class="icon"></x-circle>
            {{ $t('troubleshooting.force_close') }}
          </button>
        </div>
      </div>
    </div>
    <!-- Restart Sunshine -->
    <div class="card my-4">
      <div class="card-body">
        <h2 id="restart">{{ $t('troubleshooting.restart_sunshine') }}</h2>
        <p>{{ $t('troubleshooting.restart_sunshine_desc') }}</p>
        <div class="alert alert-success" v-if="restartPressed === true">
          <check-circle :size="18" class="icon"></check-circle>
          {{ $t('troubleshooting.restart_sunshine_success') }}
        </div>
        <div>
          <button class="btn btn-warning" :disabled="restartPressed" @click="restart">
            <refresh-cw :size="18" class="icon"></refresh-cw>
            {{ $t('troubleshooting.restart_sunshine') }}
          </button>
        </div>
      </div>
    </div>
    <!-- Reset persistent display device settings -->
    <div class="card my-4" v-if="platform === 'windows'">
      <div class="card-body">
        <h2 id="dd_reset">{{ $t('troubleshooting.dd_reset') }}</h2>
        <p style="white-space: pre-line">{{ $t('troubleshooting.dd_reset_desc') }}</p>
        <div class="alert alert-success" v-if="ddResetStatus === true">
          <check-circle :size="18" class="icon"></check-circle>
          {{ $t('troubleshooting.dd_reset_success') }}
        </div>
        <div class="alert alert-danger" v-if="ddResetStatus === false">
          <alert-circle :size="18" class="icon"></alert-circle>
          {{ $t('troubleshooting.dd_reset_error') }}
        </div>
        <div>
          <button class="btn btn-warning" :disabled="ddResetPressed" @click="ddResetPersistence">
            <rotate-ccw :size="18" class="icon"></rotate-ccw>
            {{ $t('troubleshooting.dd_reset') }}
          </button>
        </div>
      </div>
    </div>
    <!-- Unpair Clients -->
    <div class="card my-4">
      <div class="card-body">
        <div class="d-flex justify-content-between align-items-center mb-3">
          <h2 id="unpair" class="mb-0">{{ $t('troubleshooting.unpair_title') }}</h2>
          <button class="btn btn-danger" :disabled="unpairAllPressed" @click="unpairAll">
            <trash-2 :size="18" class="icon"></trash-2>
            {{ $t('troubleshooting.unpair_all') }}
          </button>
        </div>
        <p>{{ $t('troubleshooting.unpair_desc') }}</p>
        <div class="alert alert-success d-flex align-items-center" v-if="showApplyMessage">
          <check-circle :size="18" class="icon"></check-circle>
          <div><b>{{ $t('_common.success') }}</b> {{ $t('troubleshooting.unpair_single_success') }}</div>
          <button class="btn btn-success ms-auto" @click="clickedApplyBanner">{{ $t('_common.dismiss') }}</button>
        </div>
        <div class="alert alert-success" v-if="unpairAllStatus === true">
          <check-circle :size="18" class="icon"></check-circle>
          {{ $t('troubleshooting.unpair_all_success') }}
        </div>
        <div class="alert alert-danger" v-if="unpairAllStatus === false">
          <alert-circle :size="18" class="icon"></alert-circle>
          {{ $t('troubleshooting.unpair_all_error') }}
        </div>
      </div>
      <ul class="list-group list-group-flush" v-if="clients && clients.length > 0">
        <li v-for="client in clients" :key="client.uuid" class="list-group-item d-flex align-items-center">
          <div class="flex-grow-1">
            {{ client.name !== "" ? client.name : $t('troubleshooting.unpair_single_unknown') }}
          </div>
          <div class="form-check form-switch ms-2 mb-0">
            <input class="form-check-input" type="checkbox" role="switch"
                   :id="'toggle-' + client.uuid"
                   :checked="client.enabled"
                   :aria-checked="client.enabled.toString()"
                   @change="toggleClient(client.uuid, !client.enabled)">
          </div>
          <button class="btn btn-danger btn-sm ms-2" @click="unpairSingle(client.uuid)">
            <trash-2 :size="18" class="icon"></trash-2>
          </button>
        </li>
      </ul>
      <ul v-else class="list-group list-group-flush">
        <li class="list-group-item p-3 text-center">
          <em>{{ $t('troubleshooting.unpair_single_no_devices') }}</em>
        </li>
      </ul>
    </div>
    <!-- Logs -->
    <div class="card my-4">
      <div class="card-body">
        <h2 id="logs">{{ $t('troubleshooting.logs') }}</h2>
        <div class="d-flex justify-content-between align-items-baseline py-2">
          <p>{{ $t('troubleshooting.logs_desc') }}</p>
          <div class="input-group" style="max-width: 300px">
            <span class="input-group-text">
              <search :size="18" class="icon"></search>
            </span>
            <input type="text" class="form-control" v-model="logFilter" :placeholder="$t('troubleshooting.logs_find')" />
          </div>
        </div>
        <div>
          <div class="troubleshooting-logs" ref="logsContainer">
            <div class="log-nav-overlay">
              <div class="log-nav-controls">
                <button class="log-nav-btn" @click="scrollLogsTo('top')" title="Jump to Top">
                  <chevrons-up :size="18" class="icon"></chevrons-up>
                </button>
                <button class="log-nav-btn" @click="navigateToLog('prev')" :disabled="!hasPrevLog" title="Previous Warning/Error">
                  <chevron-up :size="18" class="icon"></chevron-up>
                </button>
                <button class="log-nav-btn" @click="navigateToLog('next')" :disabled="!hasNextLog" title="Next Warning/Error">
                  <chevron-down :size="18" class="icon"></chevron-down>
                </button>
                <button class="log-nav-btn" @click="scrollLogsTo('bottom')" title="Jump to Bottom">
                  <chevrons-down :size="18" class="icon"></chevrons-down>
                </button>
                <button class="log-nav-btn" @click="copyLogs" title="Copy Logs">
                  <check :size="18" class="icon text-success" v-if="logsCopied"></check>
                  <copy :size="18" class="icon" v-else></copy>
                </button>
              </div>
            </div>
            <pre class="mb-0" v-html="highlightedLogs"></pre>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script type="module">
    import { createApp } from 'vue'
    import { initApp } from './init'
    import Navbar from './Navbar.vue'
    import { apiFetch } from './fetch_utils'
    import {
      AlertCircle,
      AlertTriangle,
      Check,
      CheckCircle,
      ChevronDown,
      ChevronUp,
      ChevronsDown,
      ChevronsUp,
      Copy,
      Download,
      ExternalLink,
      Gamepad2,
      KeyRound,
      RefreshCw,
      RotateCcw,
      Search,
      ShieldCheck,
      Sparkles,
      Trash2,
      XCircle,
    } from '@lucide/vue'

    const app = createApp({
      components: {
        Navbar,
        AlertCircle,
        AlertTriangle,
        Check,
        CheckCircle,
        ChevronDown,
        ChevronUp,
        ChevronsDown,
        ChevronsUp,
        Copy,
        Download,
        ExternalLink,
        Gamepad2,
        KeyRound,
        RefreshCw,
        RotateCcw,
        Search,
        ShieldCheck,
        Sparkles,
        Trash2,
        XCircle,
      },
      data() {
        return {
          clients: [],
          closeAppPressed: false,
          closeAppStatus: null,
          ddResetPressed: false,
          ddResetStatus: null,
          logsCopied: false,
          logs: 'Loading...',
          logFilter: null,
          logInterval: null,
          licenseBusy: false,
          licenseError: '',
          licenseKey: '',
          restartPressed: false,
          showApplyMessage: false,
          platform: "",
          controllerEnabled: false,
          virtualInputStatusLoaded: false,
          unpairAllPressed: false,
          unpairAllStatus: null,
          virtualhid: {
            installed: false,
            version: '',
            version_compatible: false,
            minimum_version: '',
            supported_versions: '',
          },
          virtualhidLicense: {
            operation_ok: false,
            service_available: false,
            state: 'unavailable',
            licensed: false,
            active_devices: 0,
            activation_limit: 0,
            activation_usage: 0,
            plan_name: '',
            customer_email: '',
            message: '',
            purchase_url: '',
            manage_account_url: '',
          },
          virtualhidRelease: {
            loading: false,
            version: '',
            url: 'https://github.com/LizardByte/libvirtualhid/releases/latest',
            error: false,
          },
          vigembus: {
            installed: false,
            version: '',
            version_compatible: false,
            minimum_version: '',
            supported_versions: '',
          },
          vigembusRelease: {
            loading: false,
            version: '',
            url: 'https://github.com/nefarius/ViGEmBus/releases/latest',
            error: false,
          },
          currentLogIndex: -1,
          logLines: [],
        };
      },
      computed: {
        actualLogs() {
          if (!this.logFilter) return this.logs;
          const filterLower = this.logFilter.toLowerCase();
          return this.logs
            .split("\n")
            .filter((x) => x.toLowerCase().includes(filterLower))
            .join("\n");
        },

        /**
         * Parse the (possibly multi-line) log output into timestamp-prefixed entries.
         * Each entry starts with: [YYYY-MM-DD HH:MM:SS.mmm]:
         */
        parsedLogEntries() {
          const text = this.actualLogs || '';

          // Match on timestamp tokens, but keep everything between them as the entry body.
          // Using a global exec loop lets us split without losing delimiters and works
          // even when entries span multiple lines.
          const tsRegex = /\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]:/g;

          const entries = [];
          const matches = Array.from(text.matchAll(tsRegex));

          // If no timestamps are found, treat everything as a single entry.
          if (matches.length === 0) {
            const raw = text.trimEnd();
            if (!raw) return [];
            return [
              {
                index: 0,
                raw,
                level: 'Info',
                cssClass: 'log-line-info',
              },
            ];
          }

          for (let i = 0; i < matches.length; i++) {
            const start = matches[i].index;
            const end = i + 1 < matches.length ? matches[i + 1].index : text.length;
            const raw = text.slice(start, end).trimEnd();
            if (!raw) continue;

            // Determine level based on the *first* level token in the entry.
            // Sunshine logs are typically: "[ts]: Level: message".
            // Some messages may contain additional embedded timestamps, but we treat
            // those as part of the entry content.
            let level = 'Info';
            let cssClass = 'log-line-info';

            if (/\]:\s*Fatal:/i.test(raw)) {
              level = 'Fatal';
              cssClass = 'log-line-fatal';
            } else if (/\]:\s*(Error|Critical):/i.test(raw)) {
              level = 'Error';
              cssClass = 'log-line-error';
            } else if (/\]:\s*Warning:/i.test(raw)) {
              level = 'Warning';
              cssClass = 'log-line-warning';
            } else if (/\]:\s*Debug:/i.test(raw)) {
              level = 'Debug';
              cssClass = 'log-line-debug';
            }

            entries.push({
              index: entries.length,
              raw,
              level,
              cssClass,
            });
          }

          return entries;
        },

        highlightedLogs() {
          const escapeHtml = (s) =>
            s
              .replaceAll('&', '&amp;')
              .replaceAll('<', '&lt;')
              .replaceAll('>', '&gt;')
              .replaceAll('"', '&quot;')
              .replaceAll("'", '&#39;');

          return this.parsedLogEntries
            .map((entry) => {
              const safe = escapeHtml(entry.raw);
              const isSelected = entry.index === this.currentLogIndex;
              const selectedClass = isSelected ? ' log-entry-selected' : '';
              return `<span data-entry-index="${entry.index}" data-log-level="${entry.cssClass}" class="${entry.cssClass}${selectedClass}">${safe}</span>`;
            })
            // Separate entries visually with a newline.
            .join("\n");
        },

        errorWarningEntries() {
          // Only navigate between warnings/errors/fatal/critical
          return this.parsedLogEntries
            .filter((e) => e.level === 'Warning' || e.level === 'Error' || e.level === 'Fatal')
            .map((e) => e.index);
        },

        hasNextLog() {
          const indices = this.errorWarningEntries;
          if (indices.length === 0) return false;
          if (this.currentLogIndex === -1) return true;
          return indices.some((i) => i > this.currentLogIndex);
        },

        hasPrevLog() {
          const indices = this.errorWarningEntries;
          if (indices.length === 0) return false;
          if (this.currentLogIndex === -1) return false;
          return indices.some((i) => i < this.currentLogIndex);
        },

        driverReleaseLoading() {
          return this.virtualhidRelease.loading || (this.showVigembus && this.vigembusRelease.loading);
        },

        showVigembus() {
          return this.virtualInputStatusLoaded && (!this.virtualhid.installed || this.vigembus.installed);
        }
      },
      created() {
        this._logsCopyTimeout = null;
        fetch("/api/config")
          .then((r) => r.json())
          .then((r) => {
            this.platform = r.platform;
            this.controllerEnabled = r.controller !== "disabled";
            // Fetch virtual input driver status only on Windows when gamepad is enabled
            if (this.platform === 'windows' && this.controllerEnabled) {
              this.refreshDriverInformation();
            }
            if (this.platform === 'windows') {
              this.refreshLicenseStatus();
            }
          });

        this.logInterval = setInterval(() => {
          this.refreshLogs();
        }, 5000);
        this.refreshLogs();
        this.refreshClients();
      },
      beforeDestroy() {
        clearInterval(this.logInterval);
      },
      methods: {
        refreshLogs() {
          fetch("./api/logs",)
            .then((r) => r.text())
            .then((r) => {
              this.logs = r;
            });
        },
        closeApp() {
          this.closeAppPressed = true;
          apiFetch("./api/apps/close", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            } })
            .then((r) => r.json())
            .then((r) => {
              this.closeAppPressed = false;
              this.closeAppStatus = r.status;
              setTimeout(() => {
                this.closeAppStatus = null;
              }, 5000);
            });
        },
        unpairAll() {
          this.unpairAllPressed = true;
          apiFetch("./api/clients/unpair-all", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            }
           })
            .then((r) => r.json())
            .then((r) => {
              this.unpairAllPressed = false;
              this.unpairAllStatus = r.status;
              setTimeout(() => {
                this.unpairAllStatus = null;
              }, 5000);
              this.refreshClients();
            });
        },
        unpairSingle(uuid) {
          apiFetch("./api/clients/unpair", {
            method: "POST",
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({ uuid })
          }).then(() => {
            this.showApplyMessage = true;
            this.refreshClients();
          });
        },
        toggleClient(uuid, enabled) {
          fetch("./api/clients/update", {
            method: "POST",
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({ uuid, enabled })
          }).then(() => {
            this.refreshClients();
          });
        },
        refreshClients() {
          fetch("./api/clients/list")
            .then((response) => response.json())
            .then((response) => {
              const clientList = document.querySelector("#client-list");
              if (response.status === true && response.named_certs && response.named_certs.length) {
                this.clients = response.named_certs.sort((a, b) => {
                  return (a.name.toLowerCase() > b.name.toLowerCase() || a.name === "" ? 1 : -1)
                });
              } else {
                this.clients = [];
              }
            });
        },
        clickedApplyBanner() {
          this.showApplyMessage = false;
        },
        copyLogs() {
          // Copy the filtered view if a filter is active.
          navigator.clipboard.writeText(this.actualLogs).then(() => {
            // Clear any existing reset timer (handles rapid successive clicks).
            if (this._logsCopyTimeout) clearTimeout(this._logsCopyTimeout);

            // Show checkmark feedback.
            this.logsCopied = true;

            // Revert icon after 2 seconds.
            this._logsCopyTimeout = setTimeout(() => {
              this.logsCopied = false;
            }, 2000);
          }).catch((err) => {
            console.error('Failed to copy logs:', err);
          });
        },
        restart() {
          this.restartPressed = true;
          setTimeout(() => {
            this.restartPressed = false;
          }, 5000);
          apiFetch("./api/restart", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            }
          });
        },
        ddResetPersistence() {
          this.ddResetPressed = true;
          apiFetch("/api/reset-display-device-persistence", {
            method: "POST",
            headers: {
              "Content-Type": "application/json"
            }
          })
            .then((r) => r.json())
            .then((r) => {
              this.ddResetPressed = false;
              this.ddResetStatus = r.status;
              setTimeout(() => {
                this.ddResetStatus = null;
              }, 5000);
            });
        },
        /**
         * @brief Refresh the installed driver details and latest stable GitHub releases.
         */
        refreshDriverInformation() {
          this.refreshVirtualInputStatus();
          this.refreshDriverReleases();
        },
        /**
         * @brief Refresh the latest stable release metadata for both Windows gamepad drivers.
         */
        refreshDriverReleases() {
          this.updateLatestRelease(
            'virtualhidRelease',
            'LizardByte/libvirtualhid',
            'https://github.com/LizardByte/libvirtualhid/releases/latest',
          );
          this.updateLatestRelease(
            'vigembusRelease',
            'nefarius/ViGEmBus',
            'https://github.com/nefarius/ViGEmBus/releases/latest',
          );
        },
        /**
         * @brief Fetch one repository's latest non-prerelease release from GitHub.
         *
         * @param {string} property Component data property that stores the release metadata.
         * @param {string} repository GitHub repository in owner/name form.
         * @param {string} fallbackUrl Releases page used if the API request fails.
         */
        updateLatestRelease(property, repository, fallbackUrl) {
          this[property] = {
            ...this[property],
            loading: true,
            error: false,
          };

          fetch(`https://api.github.com/repos/${repository}/releases/latest`, {
            headers: {
              Accept: 'application/vnd.github+json',
            },
          })
            .then((response) => {
              if (!response.ok) {
                throw new Error(`GitHub returned ${response.status}`);
              }
              return response.json();
            })
            .then((release) => {
              if (!release.tag_name || release.draft || release.prerelease) {
                throw new Error('GitHub did not return a stable release');
              }
              this[property] = {
                loading: false,
                version: release.tag_name,
                url: release.html_url || fallbackUrl,
                error: false,
              };
            })
            .catch((error) => {
              console.error(`Failed to fetch the latest ${repository} release:`, error);
              this[property] = {
                loading: false,
                version: '',
                url: fallbackUrl,
                error: true,
              };
            });
        },
        refreshVirtualInputStatus() {
          fetch("/api/virtual-input/status")
            .then((r) => r.json())
            .then((r) => {
              const virtualhid = r.virtualhid || {};
              const vigembus = r.vigembus || {};
              this.virtualhid = {
                installed: virtualhid.installed || false,
                version: virtualhid.version || '',
                version_compatible: virtualhid.version_compatible || false,
                minimum_version: virtualhid.minimum_version || '',
                supported_versions: virtualhid.supported_versions || '',
              };
              this.vigembus = {
                installed: vigembus.installed || false,
                version: vigembus.version || '',
                version_compatible: vigembus.version_compatible || false,
                minimum_version: vigembus.minimum_version || '',
                supported_versions: vigembus.supported_versions || '',
              };
              this.virtualInputStatusLoaded = true;
            })
            .catch((err) => {
              console.error("Failed to fetch virtual input driver status:", err);
            });
        },
        /**
         * @brief Parse a numeric dotted driver or release version.
         *
         * @param {string} version Version string, optionally prefixed with "v".
         * @return {number[]|null} Numeric version parts, or null for an invalid version.
         */
        parseDriverVersion(version) {
          const match = /^v?(\d+(?:\.\d+)*)/i.exec(String(version || '').trim());
          return match ? match[1].split('.').map(Number) : null;
        },
        /**
         * @brief Compare installed and latest driver versions.
         *
         * @param {string} installedVersion Installed driver version.
         * @param {string} latestVersion Latest stable release version.
         * @return {number|null} Negative if outdated, zero if equal, positive if newer, or null if invalid.
         */
        compareDriverVersions(installedVersion, latestVersion) {
          const installedParts = this.parseDriverVersion(installedVersion);
          const latestParts = this.parseDriverVersion(latestVersion);
          if (!installedParts || !latestParts) {
            return null;
          }

          const partCount = Math.max(installedParts.length, latestParts.length);
          for (let index = 0; index < partCount; ++index) {
            const installedPart = installedParts[index] || 0;
            const latestPart = latestParts[index] || 0;
            if (installedPart !== latestPart) {
              return installedPart > latestPart ? 1 : -1;
            }
          }
          return 0;
        },
        /**
         * @brief Get the availability state for one installed driver and GitHub release.
         *
         * @param {object} driver Installed driver status.
         * @param {object} release Latest GitHub release status.
         * @return {string} Release state used to select the icon and message.
         */
        driverReleaseState(driver, release) {
          if (release.loading) {
            return 'loading';
          }
          if (release.error || !release.version) {
            return 'unavailable';
          }
          if (!driver.installed) {
            return 'not-installed';
          }

          const comparison = this.compareDriverVersions(driver.version, release.version);
          if (comparison === null) {
            return 'unknown';
          }
          return comparison >= 0 ? 'current' : 'outdated';
        },
        /**
         * @brief Get the localized release comparison message for a driver.
         *
         * @param {object} driver Installed driver status.
         * @param {object} release Latest GitHub release status.
         * @return {string} Localized release comparison message.
         */
        driverReleaseStatusText(driver, release) {
          const state = this.driverReleaseState(driver, release).replace('-', '_');
          return this.$t(`troubleshooting.driver_release_${state}`);
        },
        /**
         * @brief Get a theme-aware text class for a driver release state.
         *
         * @param {object} driver Installed driver status.
         * @param {object} release Latest GitHub release status.
         * @return {string} CSS class for the release icon and version.
         */
        driverReleaseStateClass(driver, release) {
          const state = this.driverReleaseState(driver, release);
          if (state === 'current') {
            return 'driver-release-current';
          }
          if (state === 'outdated') {
            return 'driver-release-outdated';
          }
          return 'driver-release-neutral';
        },
        /**
         * @brief Format a latest release version for the drivers table.
         *
         * @param {object} release Latest GitHub release status.
         * @return {string} Release version or a localized placeholder.
         */
        driverLatestVersion(release) {
          if (release.loading) {
            return this.$t('troubleshooting.driver_release_checking');
          }
          return release.version || this.$t('troubleshooting.driver_release_unavailable');
        },
        applyLicenseStatus(status) {
          this.virtualhidLicense = {
            ...this.virtualhidLicense,
            ...status,
          };
          this.licenseError = status.error || '';
        },
        refreshLicenseStatus() {
          this.licenseBusy = true;
          this.licenseError = '';
          fetch("/api/virtual-input/license")
            .then(async (response) => {
              const status = await response.json();
              if (!response.ok) {
                throw new Error(status.error || this.$t('troubleshooting.virtualhid_license_request_failed'));
              }
              this.applyLicenseStatus(status);
            })
            .catch((error) => {
              this.licenseError = error.message;
            })
            .finally(() => {
              this.licenseBusy = false;
            });
        },
        updateLicense(action) {
          if (this.licenseBusy || (action === 'activate' && !this.licenseKey.trim())) {
            return;
          }

          this.licenseBusy = true;
          this.licenseError = '';
          const body = { action };
          if (action === 'activate') {
            body.license_key = this.licenseKey.trim();
          }

          apiFetch("/api/virtual-input/license", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(body),
          })
            .then(async (response) => {
              const status = await response.json();
              if (!response.ok) {
                throw new Error(status.error || this.$t('troubleshooting.virtualhid_license_request_failed'));
              }
              this.applyLicenseStatus(status);
              if (action === 'activate' && status.operation_ok) {
                this.licenseKey = '';
              }
            })
            .catch((error) => {
              this.licenseError = error.message;
            })
            .finally(() => {
              body.license_key = '';
              this.licenseBusy = false;
            });
        },
        licenseStatusClass() {
          if (this.virtualhidLicense.state === 'licensed') {
            return 'badge text-bg-success';
          }
          if (['expired', 'disabled', 'invalid'].includes(this.virtualhidLicense.state)) {
            return 'badge text-bg-danger';
          }
          return 'badge text-bg-secondary';
        },
        licenseStatusText() {
          const key = `troubleshooting.virtualhid_license_state_${this.virtualhidLicense.state}`;
          return this.$t(key);
        },
        licenseActivationText() {
          if (!this.virtualhidLicense.activation_limit) {
            return this.$t('troubleshooting.virtualhid_license_not_reported');
          }
          return `${this.virtualhidLicense.activation_usage} / ${this.virtualhidLicense.activation_limit}`;
        },
        driverVersion(driver) {
          if (!driver.installed) {
            return this.$t('troubleshooting.driver_not_installed');
          }
          return driver.version || this.$t('troubleshooting.driver_version_unknown');
        },
        driverStatusClass(driver) {
          if (!driver.installed) {
            return 'badge text-bg-secondary';
          }
          return driver.version_compatible ? 'badge text-bg-success' : 'badge text-bg-danger';
        },
        driverStatusText(driver) {
          if (!driver.installed) {
            return this.$t('troubleshooting.driver_status_not_installed');
          }
          if (driver.version_compatible) {
            return this.$t('troubleshooting.driver_status_compatible');
          }
          return this.$t('troubleshooting.driver_status_unsupported');
        },
        navigateToLog(direction) {
          const indices = this.errorWarningEntries;
          if (indices.length === 0) return;

          let targetIndex;

          if (direction === 'next') {
            if (this.currentLogIndex === -1) {
              targetIndex = indices[0];
            } else {
              const nextIndices = indices.filter((i) => i > this.currentLogIndex);
              if (nextIndices.length === 0) return;
              targetIndex = nextIndices[0];
            }
          } else if (direction === 'prev') {
            if (this.currentLogIndex === -1) return;
            const prevIndices = indices.filter((i) => i < this.currentLogIndex);
            if (prevIndices.length === 0) return;
            targetIndex = prevIndices[prevIndices.length - 1];
          } else {
            return;
          }

          this.currentLogIndex = targetIndex;

          this.$nextTick(() => {
            const container = this.$refs.logsContainer;
            if (!container) return;

            const el = container.querySelector(`[data-entry-index="${targetIndex}"]`);
            if (!el) return;

            // Ensure it's visible even for tall multi-line entries.
            const containerRect = container.getBoundingClientRect();
            const elRect = el.getBoundingClientRect();
            const relativeTop = elRect.top - containerRect.top;

            container.scrollTop = container.scrollTop + relativeTop - containerRect.height * 0.15;
          });
        },
        scrollLogsTo(where) {
          const container = this.$refs.logsContainer;
          if (!container) return;

          // Reset the selected error/warning index when jumping to top or bottom
          this.currentLogIndex = -1;

          if (where === 'top') {
            container.scrollTo({ top: 0, behavior: 'smooth' });
            return;
          }

          if (where === 'bottom') {
            container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
          }
        },
      },
    });

    initApp(app);
  </script>

</body>
