<template>
  <v-card
    :class="['fc-post-card', expanded && 'fc-post-card--expanded']"
    variant="outlined"
    :tabindex="expanded ? -1 : 0"
    @click="onCardClick"
    @keydown.enter="onCardClick"
  >
    <div class="fc-post-card__head">
      <!-- Posts with no live subscription have source=null (alembic
           0030); show a "filesystem import" affordance instead of a
           platform chip. -->
      <v-chip size="x-small" variant="tonal">
        {{ post.source?.platform ?? 'filesystem import' }}
      </v-chip>
      <RouterLink
        :to="{ name: 'artist', params: { slug: post.artist.slug } }"
        class="fc-post-card__artist"
        @click.stop
      >{{ post.artist.name }}</RouterLink>
      <span class="fc-post-card__date" :title="absoluteDate">{{ relativeDate }}</span>
      <span v-if="expanded && images.length" class="fc-post-card__meta">
        · {{ images.length }} image{{ images.length === 1 ? '' : 's' }}
      </span>
      <span v-if="expanded && attachments.length" class="fc-post-card__meta">
        · {{ attachments.length }} attachment{{ attachments.length === 1 ? '' : 's' }}
      </span>
      <v-spacer />
      <v-btn
        v-if="post.post_url"
        :href="post.post_url" target="_blank" rel="noopener"
        icon="mdi-open-in-new" size="x-small" variant="text"
        :aria-label="`open original post on ${post.source?.platform ?? 'web'}`"
        @click.stop
      />
      <v-btn
        :icon="expanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
        size="x-small" variant="text"
        :aria-label="expanded ? 'Collapse post' : 'Expand post'"
        @click.stop="toggleExpanded"
      />
    </div>

    <!-- Compact body: collapsed card. Hero + thumb rail + truncated text. -->
    <div v-if="!expanded" class="fc-post-card__body">
      <div class="fc-post-card__media">
        <template v-if="images.length">
          <div class="fc-post-card__hero">
            <img :src="hero.thumbnail_url" :alt="`hero thumbnail`" loading="lazy" />
          </div>
          <div v-if="rail.length" class="fc-post-card__rail">
            <div v-for="t in rail" :key="t.image_id" class="fc-post-card__rail-cell">
              <img :src="t.thumbnail_url" :alt="`thumbnail`" loading="lazy" />
            </div>
            <div v-if="moreCount > 0" class="fc-post-card__rail-more">
              +{{ moreCount }}
            </div>
          </div>
        </template>
        <PostEmptyThumbs v-else />
      </div>

      <div class="fc-post-card__text">
        <h3 v-if="plainTitle" class="fc-post-card__title">
          {{ plainTitle }}
        </h3>
        <h3 v-else class="fc-post-card__title fc-post-card__title--missing">
          Post {{ post.external_post_id }}
        </h3>

        <p v-if="post.description_plain" class="fc-post-card__desc">
          {{ post.description_plain }}
        </p>
        <p v-else class="fc-post-card__desc fc-post-card__desc--missing">
          (no description)
        </p>

        <div v-if="post.attachments?.length" class="fc-post-card__atts">
          <v-icon size="small" class="fc-post-card__att-icon">mdi-paperclip</v-icon>
          {{ post.attachments.length }} attachment{{ post.attachments.length === 1 ? '' : 's' }}
        </div>
      </div>
    </div>

    <!-- Expanded body: title, full mosaic, full sanitized HTML description,
         attachments. Lazy-loaded detail via getPostFull. -->
    <div v-else class="fc-post-card__expanded">
      <h2 v-if="plainTitle" class="fc-post-card__title-full">
        {{ plainTitle }}
      </h2>
      <h2 v-else class="fc-post-card__title-full fc-post-card__title--missing">
        Post {{ post.external_post_id }}
      </h2>

      <section v-if="images.length" class="fc-post-card__sec">
        <PostImageGrid :thumbnails="images" />
        <div v-if="!detailLoaded" class="fc-post-card__loading-hint">
          Loading full image list…
        </div>
      </section>

      <section v-if="descriptionHtml" class="fc-post-card__sec">
        <div class="fc-post-card__desc-full" v-html="descriptionHtml" />
      </section>
      <section v-else-if="detailLoaded" class="fc-post-card__sec">
        <p class="fc-post-card__desc fc-post-card__desc--missing">(no description)</p>
      </section>

      <section v-if="attachments.length" class="fc-post-card__sec">
        <h3 class="fc-post-card__h3">Attachments</h3>
        <div class="fc-post-card__atts-full">
          <a
            v-for="att in attachments" :key="att.id"
            :href="att.download_url" download
            class="fc-post-card__att"
            @click.stop
          >
            <v-icon size="small" class="fc-post-card__att-icon">mdi-paperclip</v-icon>
            <span>{{ att.original_filename }}</span>
            <span class="fc-post-card__att-size">({{ formatBytes(att.size_bytes) }})</span>
          </a>
        </div>
      </section>
    </div>
  </v-card>
</template>

<script setup>
import { computed, ref } from 'vue'
import { RouterLink } from 'vue-router'

import { usePostsStore } from '../../stores/posts.js'
import { sanitizeHtml, toPlainText } from '../../utils/htmlSanitize.js'
import PostEmptyThumbs from './PostEmptyThumbs.vue'
import PostImageGrid from './PostImageGrid.vue'

const props = defineProps({
  post: { type: Object, required: true },
})

const postsStore = usePostsStore()

// Per-card expand state. No global modal — each PostCard owns its own
// view-mode and lazy-loaded detail.
const expanded = ref(false)
const detail = ref(null)
const detailLoaded = ref(false)
const detailError = ref(null)

// When expanded + detail loaded, prefer the uncapped detail thumbnails +
// full description. Falls back to feed shape if detail fetch is in flight
// or failed.
const merged = computed(() => detail.value || props.post)
const images = computed(() => merged.value.thumbnails || [])
const attachments = computed(() => merged.value.attachments || [])

// Titles can arrive as stored HTML (e.g. "<strong>…</strong>"); render as
// plain text — the CSS makes the title bold.
const plainTitle = computed(() => toPlainText(props.post.post_title))

// Compact-view hero+rail derived from the feed-shape (capped 6).
const hero = computed(() => props.post.thumbnails?.[0])
const rail = computed(() => (props.post.thumbnails || []).slice(1, 4))
const moreCount = computed(() => {
  const more = props.post.thumbnails_more || 0
  const railLen = rail.value.length
  const extraShown = Math.max(0, (props.post.thumbnails?.length || 0) - 1 - railLen)
  return more + extraShown
})

const sortDateIso = computed(() => props.post.post_date || props.post.downloaded_at)
const absoluteDate = computed(() => new Date(sortDateIso.value).toLocaleString())
const relativeDate = computed(() => {
  const then = new Date(sortDateIso.value).getTime()
  const diff = (Date.now() - then) / 1000
  if (diff < 60) return `${Math.floor(diff)}s ago`
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
  if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`
  if (diff < 86400 * 30) return `${Math.floor(diff / 86400)}d ago`
  return new Date(sortDateIso.value).toLocaleDateString()
})

const descriptionHtml = computed(() => {
  // Detail endpoint returns description_full as plain text (the service
  // uses html_to_plain on the stored description). Render plain text in
  // <p> wrappers; sanitize defensively in case the backend ever returns
  // raw HTML.
  const raw = merged.value.description_full || merged.value.description_plain
  if (!raw) return ''
  if (/[<>]/.test(raw)) return sanitizeHtml(raw)
  const esc = raw
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
  return esc
    .split(/\n\s*\n/)
    .map((p) => `<p>${p.replace(/\n/g, '<br>')}</p>`)
    .join('')
})

async function loadDetailIfNeeded () {
  if (detailLoaded.value || detail.value) return
  try {
    detail.value = await postsStore.getPostFull(props.post.id)
    detailLoaded.value = true
  } catch (e) {
    detailError.value = e.message
    // Leave merged on feed-shape; the card still renders the truncated
    // body so the operator isn't staring at a blank panel.
  }
}

function toggleExpanded () {
  expanded.value = !expanded.value
  if (expanded.value) loadDetailIfNeeded()
}

function onCardClick (e) {
  // Inner interactive elements use @click.stop so they never reach here.
  // Whole-card click expands a collapsed card; collapsing is chevron-only
  // so a mosaic-image click on an expanded card can never accidentally
  // collapse the surrounding card.
  if (expanded.value) return
  expanded.value = true
  loadDetailIfNeeded()
}

function formatBytes (n) {
  if (!n) return '0 B'
  if (n < 1024) return `${n} B`
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`
  return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`
}
</script>

<style scoped>
.fc-post-card {
  padding: 1rem;
  margin-bottom: 1rem;
  container-type: inline-size;
  transition: border-color 0.15s ease;
}
.fc-post-card:not(.fc-post-card--expanded) {
  cursor: pointer;
}
.fc-post-card:not(.fc-post-card--expanded):hover {
  border-color: rgb(var(--v-theme-accent));
}
.fc-post-card:focus-visible {
  outline: 2px solid rgb(var(--v-theme-accent));
  outline-offset: 2px;
}
.fc-post-card--expanded {
  border-color: rgb(var(--v-theme-accent) / 0.6);
}

.fc-post-card__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.8rem;
  color: rgb(var(--v-theme-on-surface-variant));
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.fc-post-card__artist {
  color: rgb(var(--v-theme-on-surface));
  text-decoration: none;
  font-weight: 600;
}
.fc-post-card__artist:hover { color: rgb(var(--v-theme-accent)); }
.fc-post-card__date,
.fc-post-card__meta { white-space: nowrap; }

/* ---- COMPACT BODY ---- */
.fc-post-card__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
@container (min-width: 800px) {
  .fc-post-card__body {
    flex-direction: row;
    gap: 24px;
  }
  .fc-post-card__media { flex: 0 0 50%; }
  .fc-post-card__text  { flex: 1 1 0; min-width: 0; }
}

.fc-post-card__hero {
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 6px;
}
.fc-post-card__hero img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}

.fc-post-card__rail {
  display: flex; gap: 6px; margin-top: 6px;
}
.fc-post-card__rail-cell {
  width: 80px; height: 80px;
  overflow: hidden; border-radius: 4px;
}
.fc-post-card__rail-cell img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}
.fc-post-card__rail-more {
  width: 80px; height: 80px;
  display: flex; align-items: center; justify-content: center;
  border: 1px dashed rgb(var(--v-theme-on-surface-variant));
  border-radius: 4px;
  color: rgb(var(--v-theme-on-surface-variant));
  font-size: 0.85rem;
}

.fc-post-card__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 18px; font-weight: 700;
  margin: 0 0 8px 0;
  color: rgb(var(--v-theme-on-surface));
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fc-post-card__title--missing {
  font-style: italic;
  color: rgb(var(--v-theme-on-surface-variant));
}
@container (min-width: 800px) {
  .fc-post-card__title {
    font-size: 20px;
    -webkit-line-clamp: 1;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
}

.fc-post-card__desc {
  font-size: 0.9rem;
  line-height: 1.5;
  color: rgb(var(--v-theme-on-surface));
  margin: 0 0 12px 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fc-post-card__desc--missing {
  font-style: italic;
  color: rgb(var(--v-theme-on-surface-variant));
}
@container (min-width: 800px) {
  .fc-post-card__desc { -webkit-line-clamp: 5; }
}

.fc-post-card__atts {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  color: rgb(var(--v-theme-on-surface-variant));
}
.fc-post-card__att-icon { color: rgb(var(--v-theme-on-surface-variant)); }

/* ---- EXPANDED BODY ---- */
.fc-post-card__expanded {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.fc-post-card__title-full {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 22px;
  font-weight: 700;
  margin: 0;
  color: rgb(var(--v-theme-on-surface));
}
@container (min-width: 800px) {
  .fc-post-card__title-full { font-size: 26px; }
}
.fc-post-card__sec { margin: 0; }
.fc-post-card__h3 {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 16px;
  font-weight: 500;
  margin: 0 0 8px 0;
  color: rgb(var(--v-theme-on-surface));
}
.fc-post-card__loading-hint {
  margin-top: 8px;
  font-size: 0.8rem;
  color: rgb(var(--v-theme-on-surface-variant));
}
.fc-post-card__desc-full {
  font-size: 0.95rem;
  line-height: 1.55;
  color: rgb(var(--v-theme-on-surface));
}
.fc-post-card__desc-full :deep(p) { margin: 0 0 12px 0; }
.fc-post-card__desc-full :deep(a) { color: rgb(var(--v-theme-accent)); }
.fc-post-card__atts-full {
  display: flex; flex-wrap: wrap; gap: 8px;
}
.fc-post-card__att {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid rgb(var(--v-theme-on-surface-variant));
  border-radius: 999px;
  color: rgb(var(--v-theme-on-surface));
  text-decoration: none;
  font-size: 0.85rem;
}
.fc-post-card__att:hover {
  color: rgb(var(--v-theme-accent));
  border-color: rgb(var(--v-theme-accent));
}
.fc-post-card__att-size { color: rgb(var(--v-theme-on-surface-variant)); }
</style>
