<script setup lang="ts">
/**
 * The staleness sweep: rules that assert a FACT, oldest verification first.
 *
 * Cross-cutting by nature — a rule that has gone false does not care which
 * rulebook it sits in — so this is its own pane rather than a filter on the
 * per-topic rule list. That list can only ever show one topic of one
 * rulebook, so filtering it would quietly under-report, which is the exact
 * failure this surface exists to catch.
 */
import { onMounted, ref } from "vue";
import { useRulebooksStore } from "@/stores/rulebooks";
import type { RuleTier } from "@/api/rulebooks";

const emit = defineEmits<{ "open-rule": [id: number] }>();

const store = useRulebooksStore();
const neverOnly = ref(false);
const tier = ref<RuleTier | "">("");
const busyId = ref<number | null>(null);

function reload() {
  return store.fetchRulesDue({
    neverOnly: neverOnly.value || undefined,
    tier: tier.value || undefined,
  });
}

async function verify(id: number, stillTrue: boolean) {
  busyId.value = id;
  try {
    await store.verifyRule(id, stillTrue);
  } finally {
    busyId.value = null;
  }
}

onMounted(reload);
</script>

<template>
  <section class="pane sweep">
    <header>
      <h2>Due for verification</h2>
      <p class="lede">
        Rules that assert a fact about something outside your control. Most rules are
        decisions and never appear here — they have no truth value to go stale.
      </p>
    </header>

    <div class="filters">
      <label class="filter">
        <input v-model="neverOnly" type="checkbox" @change="reload" />
        <span>Never checked only</span>
      </label>
      <label class="filter">
        <span>Tier</span>
        <select v-model="tier" @change="reload">
          <option value="">any</option>
          <option value="always_on">always on</option>
          <option value="conditional">conditional</option>
        </select>
      </label>
    </div>

    <p v-if="store.loading" class="state">Loading…</p>

    <!-- An empty sweep is GOOD NEWS, and must not read like a broken page. -->
    <p v-else-if="!store.rulesDue.length" class="state empty">
      Nothing to check.
      {{ neverOnly || tier ? "No rule matches these filters." : "No rule carries a check yet — add one to a rule that asserts a fact." }}
    </p>

    <ol v-else class="rows">
      <li v-for="r in store.rulesDue" :key="r.id" class="row">
        <div class="row-head">
          <button class="row-title" @click="emit('open-rule', r.id)">{{ r.title }}</button>
          <span v-if="r.tier === 'always_on'" class="rule-chip" title="Loaded into every session — a wrong one is wrong everywhere at once">always on</span>
          <span class="age" :class="{ unchecked: r.days_since_verified === null }">
            {{ r.days_since_verified === null
              ? "never checked"
              : `${r.days_since_verified}d ago` }}
          </span>
        </div>

        <p class="statement">{{ r.statement }}</p>

        <dl class="check">
          <dt>Check</dt>
          <dd><code>{{ r.verify_with }}</code></dd>
          <template v-if="r.expires_when">
            <dt>Ends when</dt>
            <dd>{{ r.expires_when }}</dd>
          </template>
        </dl>

        <div class="actions">
          <button :disabled="busyId === r.id" @click="verify(r.id, true)">Still true</button>
          <button :disabled="busyId === r.id" @click="verify(r.id, false)">No longer true</button>
        </div>
      </li>
    </ol>

    <p v-if="store.rulesDue.length" class="footnote">
      Record a result only after actually running the check. “No longer true” stores nothing
      on purpose — the rule is wrong rather than in a state worth recording, so it keeps its
      place here until you correct or retire it.
    </p>
  </section>
</template>

<style src="@/assets/rules-shared.css" />
<style scoped>
.sweep { display: flex; flex-direction: column; gap: var(--fs-space-3); }
.lede {
  margin: 0;
  max-width: 62ch;
  font-size: 0.85rem;
  color: var(--fs-text-secondary);
  line-height: 1.5;
}

.filters { display: flex; gap: var(--fs-space-5); align-items: center; flex-wrap: wrap; }
.filter { display: flex; align-items: center; gap: var(--fs-space-2); font-size: 0.82rem; color: var(--fs-text-secondary); }
.filter input[type="checkbox"] { accent-color: var(--fs-accent); }
.filter select {
  font: inherit; font-size: 0.82rem;
  background: var(--fs-surface-page); color: var(--fs-text-primary);
  border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md);
  padding: 0.2rem 0.4rem;
}

.state { margin: 0; font-size: 0.9rem; color: var(--fs-text-secondary); }
.state.empty { color: var(--fs-text-tertiary); }

.rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--fs-space-3); }
.row {
  background: var(--fs-surface-raised);
  border-radius: var(--fs-radius-md);
  padding: var(--fs-space-3);
}
.row-head { display: flex; align-items: baseline; gap: var(--fs-space-2); flex-wrap: wrap; }
.row-title {
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: Fraunces, serif; font-style: italic; font-size: 1.02rem;
  color: var(--fs-text-primary); text-align: left;
}
.row-title:hover { text-decoration: underline; }
/* The ORDER carries urgency — the top of this list is the most overdue thing
   in the rulebook. No red/amber ramp: it would restate the ordering and force
   an invented "stale after N days" threshold. "Never" is marked because it is
   categorically different from a date, not a worse one. */
.age { margin-left: auto; font-size: 0.78rem; color: var(--fs-text-secondary); font-variant-numeric: tabular-nums; }
.age.unchecked { font-style: italic; color: var(--fs-text-tertiary); }

.statement { margin: 0.35rem 0 0; font-size: 0.88rem; color: var(--fs-text-secondary); }

.check { display: grid; grid-template-columns: auto 1fr; gap: 0.15rem var(--fs-space-3); margin: var(--fs-space-3) 0 0; }
.check dt { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--fs-text-tertiary); }
.check dd { margin: 0; font-size: 0.82rem; color: var(--fs-text-primary); min-width: 0; }
.check code {
  font-family: var(--fs-font-mono);
  background: var(--fs-surface-code-inline);
  border-radius: var(--fs-radius-sm);
  padding: 0.05rem 0.3rem;
  overflow-wrap: anywhere;
}

.actions { display: flex; gap: var(--fs-space-2); margin-top: var(--fs-space-3); }
.actions button {
  cursor: pointer; font: inherit; font-size: 0.78rem;
  background: var(--fs-surface-page); color: var(--fs-text-primary);
  border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md);
  padding: 0.25rem 0.6rem;
}
.actions button:hover:not(:disabled) { background: var(--fs-surface-hover); }
.actions button:disabled { opacity: var(--fs-disabled-opacity); cursor: default; }

.footnote { margin: 0; max-width: 62ch; font-size: 0.78rem; color: var(--fs-text-tertiary); line-height: 1.45; }
</style>
