<script setup lang="ts">
/**
 * A project's own code, checked against the design system it is bound to (#2432).
 *
 * This is what the design surface is FOR: a project's recorded components
 * measured against the sheet they are supposed to use. The check itself is not
 * new — `check_snippets_against_system` has taken a project id since it was
 * written, and the route has always read `?project_id=`. Nothing on this side
 * ever passed one, so the capability shipped and stayed unreachable.
 *
 * The finding that matters most is the quiet one. `local_definitions` is a
 * snippet minting its own custom property instead of reaching for the shared
 * one — the codebase re-solving a solved problem, one component at a time.
 * Nothing breaks, no test fails, and the duplication only becomes visible when
 * someone changes the shared value and half the components don't move.
 *
 * SCOPE, and it is a limit rather than an omission: this reads RECORDED code —
 * snippets — because that is the code Scribe holds. A repository's own sources
 * are checked where they live, by that project's CI.
 */
import { onMounted, ref, watch } from "vue";

import { checkSnippets, type SnippetCheck } from "@/api/designSystems";

const props = defineProps<{ projectId: number; designSystemId: number | null }>();

const check = ref<SnippetCheck | null>(null);
const loading = ref(false);
const failed = ref(false);

async function run() {
  check.value = null;
  failed.value = false;
  if (props.designSystemId === null) return;
  loading.value = true;
  try {
    check.value = await checkSnippets(props.designSystemId, props.projectId);
  } catch {
    // Said out loud rather than rendered as an empty result. "Couldn't check"
    // and "nothing to report" look identical if you let them, and that is how
    // a check comes to sit dead without anyone noticing (#2419).
    failed.value = true;
  } finally {
    loading.value = false;
  }
}

onMounted(run);
watch(() => [props.projectId, props.designSystemId], run);
</script>

<template>
  <div class="pdt">
    <div v-if="designSystemId === null" class="pdt-note">
      <strong>No design system for this project.</strong>
      <p>
        Bind one in the sidebar and this tab reports where the project's recorded
        components disagree with it — references to tokens the system doesn't
        have, literals it says to stop writing, and properties a component mints
        for itself instead of reusing.
      </p>
    </div>

    <p v-else-if="loading" class="pdt-muted">Checking this project's snippets…</p>

    <div v-else-if="failed" class="pdt-note">
      <strong>The check couldn't run.</strong>
      <p>Nothing was compared — this is a failure, not a clean result.</p>
    </div>

    <template v-else-if="check">
      <p v-if="!check.checked" class="pdt-muted">
        This project has no recorded snippets, so nothing was checked. Record the
        components you reuse and they get measured against the sheet.
      </p>

      <p v-else-if="!check.findings.length" class="pdt-clean">
        {{ check.checked }} snippet{{ check.checked === 1 ? "" : "s" }} checked —
        every reference resolves, and none mints a property of its own.
      </p>

      <template v-else>
        <p class="pdt-summary">
          <strong>{{ check.findings.length }}</strong> of {{ check.checked }}
          snippet{{ check.checked === 1 ? "" : "s" }} disagree with the sheet.
        </p>

        <ul class="pdt-list">
          <li v-for="f in check.findings" :key="f.snippet_id" class="pdt-finding">
            <router-link :to="`/snippets/${f.snippet_id}`" class="pdt-title">
              {{ f.title || "Untitled snippet" }}
            </router-link>

            <!-- Renders as nothing at all: no error, no failing test, just an
                 element that quietly isn't styled. Leads for that reason. -->
            <div v-if="f.unknown.length" class="pdt-row">
              <span class="pdt-tag unknown">no such token</span>
              <span class="pdt-detail">
                <code v-for="name in f.unknown" :key="name">{{ name }}</code>
              </span>
            </div>

            <div v-if="f.local_definitions.length" class="pdt-row">
              <span class="pdt-tag local">defines its own</span>
              <span class="pdt-detail">
                <code v-for="name in f.local_definitions" :key="name">{{ name }}</code>
              </span>
            </div>

            <div v-if="f.superseded_literals.length" class="pdt-row">
              <span class="pdt-tag superseded">write the token</span>
              <span class="pdt-detail">
                <span v-for="s in f.superseded_literals" :key="s.literal" class="pdt-swap">
                  <code>{{ s.literal }}</code> → <code>{{ s.use_instead }}</code>
                </span>
              </span>
            </div>
          </li>
        </ul>
      </template>
    </template>
  </div>
</template>

<style scoped>
.pdt {
  padding: var(--fs-space-2) 0;
}

.pdt-note {
  background: var(--fs-surface-hover);
  border: 1px solid var(--fs-border-color);
  border-left: 3px solid var(--fs-warning);
  border-radius: var(--fs-radius-sm);
  padding: var(--fs-space-3) var(--fs-space-4);
}

.pdt-note p {
  margin: var(--fs-space-2) 0 0;
  color: var(--fs-text-secondary);
  font-size: var(--fs-size-body-sm);
  line-height: var(--fs-leading-body);
  max-width: 70ch;
}

.pdt-muted,
.pdt-clean,
.pdt-summary {
  color: var(--fs-text-tertiary);
  font-size: var(--fs-size-body-sm);
  margin: 0 0 var(--fs-space-3);
  max-width: 70ch;
}

.pdt-clean {
  color: var(--fs-status-done);
}

.pdt-summary {
  color: var(--fs-text-secondary);
}

.pdt-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--fs-space-3);
}

.pdt-finding {
  border: 1px solid var(--fs-border-color);
  border-radius: var(--fs-radius-md);
  padding: var(--fs-space-3);
  min-width: 0;
}

.pdt-title {
  display: block;
  font-weight: var(--fs-weight-medium);
  color: var(--fs-text-primary);
  text-decoration: none;
  margin-bottom: var(--fs-space-2);
}
.pdt-title:hover { color: var(--fs-accent); }

.pdt-row {
  display: flex;
  align-items: baseline;
  gap: var(--fs-space-2);
  flex-wrap: wrap;
  padding: 0.15rem 0;
  min-width: 0;
}

.pdt-tag {
  font-size: var(--fs-size-tiny);
  text-transform: uppercase;
  letter-spacing: var(--fs-tracking-tiny);
  padding: 0.1rem 0.45rem;
  border-radius: var(--fs-radius-sm);
  white-space: nowrap;
  flex: none;
}

.pdt-tag.unknown {
  background: var(--fs-priority-high-bg);
  color: var(--fs-priority-high);
}

.pdt-tag.local {
  background: var(--fs-priority-medium-bg);
  color: var(--fs-priority-medium);
}

.pdt-tag.superseded {
  background: var(--fs-surface-hover);
  color: var(--fs-text-tertiary);
}

.pdt-detail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--fs-space-2);
  font-size: var(--fs-size-code);
  color: var(--fs-text-secondary);
  min-width: 0;
}

.pdt-swap {
  white-space: nowrap;
}
</style>
