<script setup>
import { computed, defineAsyncComponent, onBeforeUnmount, ref, watch, nextTick } from 'vue'
import MarkdownIt from 'markdown-it'
import Plyr from 'plyr'
import 'plyr/dist/plyr.css'
import { isOfficeFile, getOfficeFormat } from '../services/officeDetection'
import { hiddenTextMarkdownItPlugin } from '../utils/hiddenText.js'

const AsyncImageEditorComponent = defineAsyncComponent(() => import('./ImageEditorComponent.vue'))
const AsyncOfficePreview = defineAsyncComponent(() => import('./OfficePreview.vue'))

const VIDEO_EXTENSIONS = new Set(['mp4', 'webm', 'ogv', 'ogg', 'mov', 'm4v'])

const props = defineProps({
  node: { type: Object, default: null },
  breadcrumb: { type: Array, default: () => [] },
  rootNodes: { type: Array, default: () => [] },
  getFileIcon: { type: Function, default: () => 'file' },
  getFileBlob: { type: Function, default: () => null },
  updateFile: { type: Function, default: null },
  showSidebarToggle: { type: Boolean, default: false }
})

const emit = defineEmits(['navigate', 'toggle-sidebar'])

const md = new MarkdownIt({
  html: false,
  breaks: true,
  linkify: true
})

md.use(hiddenTextMarkdownItPlugin)

const basePreviewUrl = ref('')
const imageEditorRef = ref(null)
const imageEditorError = ref('')
const isEditingImage = ref(false)
const isSavingImage = ref(false)
const videoPreviewError = ref('')
const videoElementRef = ref(null)
const resolvedFileBlob = ref(null)
let blobRequestToken = 0
let plyrInstance = null

const isRoot = computed(() => !props.node)
const isFolder = computed(() => props.node?.type === 'folder')
const fileExt = computed(() => {
  if (!props.node || props.node.type !== 'file') return ''
  const parts = props.node.name.split('.')
  return parts.length > 1 ? parts.pop().toLowerCase() : ''
})
const isMarkdown = computed(() => ['md', 'markdown'].includes(fileExt.value))
const isImage = computed(() => {
  const mime = String(props.node?.mimeType || '')
  return mime.startsWith('image/') || ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(fileExt.value)
})
const isPdf = computed(() => {
  const mime = String(props.node?.mimeType || '')
  return mime === 'application/pdf' || fileExt.value === 'pdf'
})
const isVideo = computed(() => {
  const mime = String(props.node?.mimeType || '').toLowerCase()
  return mime.startsWith('video/') || VIDEO_EXTENSIONS.has(fileExt.value)
})
const isText = computed(() => {
  const mime = String(props.node?.mimeType || '')
  return Boolean(props.node?.content || props.node?.previewText) || mime.startsWith('text/') || mime.includes('json') || ['txt', 'json', 'js', 'jsx', 'ts', 'tsx', 'css', 'html', 'htm', 'py', 'vue', 'xml', 'yaml', 'yml', 'csv', 'log', 'sql', 'toml', 'ini', 'cfg', 'conf', 'sh', 'bat', 'ps1', 'java', 'c', 'cpp', 'h', 'hpp', 'go', 'rs'].includes(fileExt.value)
})
const isOffice = computed(() => {
  if (!props.node || props.node.type !== 'file') return false
  if (!props.node.name) return false
  return isOfficeFile({ name: props.node.name, type: props.node.mimeType })
})
const officeFormat = computed(() => {
  if (!isOffice.value) return null
  if (!props.node?.name) return null
  return getOfficeFormat({ name: props.node.name, type: props.node.mimeType })
})
const folderItems = computed(() => {
  if (!isRoot.value && !isFolder.value) return []
  return isRoot.value ? props.rootNodes : props.node.children || []
})
const fileBlob = computed(() => resolvedFileBlob.value)
const previewText = computed(() => props.node?.content || props.node?.previewText || '')
const lineCount = computed(() => {
  if (!previewText.value) return 0
  return previewText.value.split('\n').length
})
const renderedMarkdown = computed(() => md.render(previewText.value || ''))
const fileSizeLabel = computed(() => formatBytes(props.node?.size || fileBlob.value?.size || 0))
const locLabel = computed(() => {
  const count = lineCount.value
  return count ? `${count} 行` : '二进制文件'
})
const objectUrl = computed(() => basePreviewUrl.value)
const canEditImage = computed(() => isImage.value && typeof props.updateFile === 'function')

function revokeUrl(url = '') {
  if (!url) return
  URL.revokeObjectURL(url)
}

function clearBasePreview() {
  revokeUrl(basePreviewUrl.value)
  basePreviewUrl.value = ''
}

function assignBasePreviewUrl(url = '') {
  clearBasePreview()
  basePreviewUrl.value = url
}

watch(
  () => [props.node?.id, resolvedFileBlob.value],
  ([, blob]) => {
    clearBasePreview()
    videoPreviewError.value = ''

    if (!(blob instanceof Blob)) return

    assignBasePreviewUrl(URL.createObjectURL(blob))
  },
  { immediate: true }
)

watch(
  () => props.node,
  async (node) => {
    const requestToken = ++blobRequestToken
    resolvedFileBlob.value = null
    if (!node || node.type !== 'file') return

    try {
      const result = await props.getFileBlob(node)
      if (requestToken !== blobRequestToken) return
      resolvedFileBlob.value = result instanceof Blob ? result : null
    } catch {
      if (requestToken !== blobRequestToken) return
      resolvedFileBlob.value = null
    }
  },
  { immediate: true }
)

watch(
  () => props.node?.id,
  () => {
    isEditingImage.value = false
    isSavingImage.value = false
    imageEditorError.value = ''
  }
)

onBeforeUnmount(() => {
  blobRequestToken += 1
  clearBasePreview()
})

function formatBytes(bytes = 0) {
  if (!bytes) return '0 B'
  const units = ['B', 'KB', 'MB', 'GB', 'TB']
  let value = bytes
  let index = 0
  while (value >= 1024 && index < units.length - 1) {
    value /= 1024
    index += 1
  }
  return `${value >= 100 || index === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[index]}`
}

function formatDate(timestamp) {
  if (!timestamp) return '-'
  return new Date(timestamp).toLocaleString('zh-CN')
}

function navigateTo(id) {
  emit('navigate', id)
}

function toggleSidebar() {
  emit('toggle-sidebar')
}

function getImageExportFormat() {
  if (fileExt.value === 'jpg' || fileExt.value === 'jpeg') return 'jpeg'
  if (fileExt.value === 'webp') return 'webp'
  return 'png'
}

function startImageEditing() {
  if (!canEditImage.value || !objectUrl.value || isSavingImage.value) return
  imageEditorError.value = ''
  isEditingImage.value = true
}

function cancelImageEditing() {
  if (isSavingImage.value) return
  imageEditorError.value = ''
  isEditingImage.value = false
}

function handleImageEditorError(message) {
  imageEditorError.value = String(message || '图片编辑器加载失败')
}

async function saveImageEdits() {
  if (!props.node?.id || typeof props.updateFile !== 'function' || !imageEditorRef.value) return

  isSavingImage.value = true
  imageEditorError.value = ''

  try {
    const editedBlob = await imageEditorRef.value.exportImageBlob({
      format: getImageExportFormat(),
      quality: 0.92
    })

    const updated = await props.updateFile(props.node.id, editedBlob, {
      mimeType: editedBlob.type || props.node?.mimeType || '',
      storageKind: 'blob',
      previewText: '',
      isTruncatedPreview: false
    })

    if (updated === false) {
      throw new Error('保存图片失败')
    }

    isEditingImage.value = false
  } catch (error) {
    imageEditorError.value = error instanceof Error && error.message
      ? error.message
      : '保存图片失败'
  } finally {
    isSavingImage.value = false
  }
}

function downloadBlob(blob, fileName) {
  if (!(blob instanceof Blob)) return
  const url = URL.createObjectURL(blob)
  const anchor = document.createElement('a')
  anchor.href = url
  anchor.download = fileName
  document.body.appendChild(anchor)
  anchor.click()
  anchor.remove()
  setTimeout(() => URL.revokeObjectURL(url), 0)
}

function handleVideoError() {
  videoPreviewError.value = '当前浏览器无法直接播放这个视频格式，请下载后使用本地播放器打开。'
}

function initPlyrPlayer() {
  if (!videoElementRef.value) return
  
  // Destroy existing instance if any
  if (plyrInstance) {
    try {
      plyrInstance.destroy()
    } catch (e) {
      console.warn('Failed to destroy Plyr instance:', e)
    }
  }

  try {
    plyrInstance = new Plyr(videoElementRef.value, {
      controls: [
        'play-large',
        'play',
        'progress',
        'current-time',
        'mute',
        'volume',
        'settings',
        'pip',
        'airplay',
        'fullscreen'
      ],
      settings: ['quality', 'speed', 'loop'],
      speed: {
        selected: 1,
        options: [0.5, 1, 1.5, 2]
      },
      autoplay: false,
      preload: 'auto',
      toggleControls: true,
      resetOnEnd: true,
      hideControls: true,
      tooltips: {
        controls: true,
        seek: false
      },
    })

    plyrInstance.on('error', () => {
      handleVideoError()
    })
  } catch (error) {
    console.error('Plyr initialization failed:', error)
    videoPreviewError.value = '播放器初始化失败'
  }
}

watch(
  () => objectUrl.value,
  (url) => {
    if (url && isVideo.value) {
      nextTick(() => {
        initPlyrPlayer()
      })
    }
  }
)

onBeforeUnmount(() => {
  if (plyrInstance) {
    try {
      plyrInstance.destroy()
      plyrInstance = null
    } catch (e) {
      console.warn('Failed to cleanup Plyr instance:', e)
    }
  }
})

async function copyText() {
  if (!previewText.value) return
  try {
    await navigator.clipboard.writeText(previewText.value)
  } catch {
    // ignore
  }
}

function openRaw() {
  if (!objectUrl.value) return
  window.open(objectUrl.value, '_blank', 'noopener,noreferrer')
}

function downloadFile() {
  const blob = fileBlob.value
  if (!blob) return
  downloadBlob(blob, props.node?.name || 'download')
}
</script>

<template>
  <div class="file-content">
    <div v-if="isRoot || isFolder" class="directory-shell">
      <div class="directory-card">
        <div class="directory-card-header">
          <div>
            <h3>{{ isRoot ? '文档空间' : node.name }}</h3>
            <p>{{ isRoot ? '所有文件都保存在当前服务端文档空间中。' : '像 GitHub 一样浏览当前目录内容。' }}</p>
          </div>
          <div class="directory-header-meta">
            <span>{{ folderItems.length }} 项</span>
            <span>{{ isRoot ? '根目录' : '目录' }}</span>
          </div>
        </div>

        <div class="directory-table">
          <div class="directory-row directory-row-head">
            <span class="col-name">Name</span>
            <span class="col-size">Size</span>
            <span class="col-date">Updated</span>
          </div>

          <div v-if="!isRoot" class="directory-row directory-parent" @click="navigateTo(node.parentId || null)">
            <span class="col-name">..</span>
            <span class="col-size">-</span>
            <span class="col-date">返回上级</span>
          </div>

          <button
            v-for="item in folderItems"
            :key="item.id"
            class="directory-row directory-button"
            type="button"
            @click="navigateTo(item.id)"
          >
            <span class="col-name">
              <span :class="item.type === 'folder' ? 'icon-folder' : ['icon-file', `icon-${getFileIcon(item.name)}`]"></span>
              <span class="name-text">{{ item.name }}</span>
            </span>
            <span class="col-size">{{ item.type === 'folder' ? '-' : formatBytes(item.size) }}</span>
            <span class="col-date">{{ formatDate(item.updatedAt) }}</span>
          </button>

          <div v-if="folderItems.length === 0" class="directory-empty">
            这里还没有文件。你可以从左侧直接上传文件，或者新建一个目录开始整理。
          </div>
        </div>
      </div>
    </div>

    <template v-else-if="node">
      <div class="github-file-header">
        <div class="file-header-row">
          <button
            v-if="showSidebarToggle"
            class="sidebar-toggle-btn"
            type="button"
            title="切换左侧栏"
            @click="toggleSidebar"
          >
            <svg viewBox="0 0 16 16" width="15" height="15" fill="currentColor" aria-hidden="true"><path d="M2.75 2A1.75 1.75 0 001 3.75v8.5C1 13.216 1.784 14 2.75 14h10.5A1.75 1.75 0 0015 12.25v-8.5A1.75 1.75 0 0013.25 2H2.75zm0 1.5h2.5v9h-2.5a.25.25 0 01-.25-.25v-8.5a.25.25 0 01.25-.25zm4 9v-9h6.5a.25.25 0 01.25.25v8.5a.25.25 0 01-.25.25h-6.5z"/></svg>
          </button>

          <div class="file-header-main">
            <div class="file-path">
              <button type="button" class="breadcrumb-link" @click="navigateTo(null)">workspace</button>
              <template v-for="(item, index) in breadcrumb" :key="item.id || item.name">
                <span class="path-sep">/</span>
                <button
                  v-if="index < breadcrumb.length - 1"
                  type="button"
                  class="breadcrumb-link"
                  @click="navigateTo(item.id)"
                >
                  {{ item.name }}
                </button>
                <span v-else class="breadcrumb-current">{{ item.name }}</span>
              </template>
            </div>

            <div class="file-header-meta">
              <span class="file-meta">{{ fileSizeLabel }}</span>
              <span class="file-meta">{{ locLabel }}</span>
              <span v-if="node.isTruncatedPreview" class="truncated-pill">仅预览前 2MB</span>
              <span v-else-if="isVideo && videoPreviewError" class="truncated-pill">视频预览不可用</span>
            </div>
          </div>

          <div class="file-actions">
            <button
              v-if="canEditImage && !isEditingImage"
              class="action-btn"
              type="button"
              @click="startImageEditing"
              :disabled="!objectUrl"
            >
              编辑图片
            </button>
            <template v-else-if="canEditImage && isEditingImage">
              <button class="action-btn" type="button" @click="cancelImageEditing" :disabled="isSavingImage">取消</button>
              <button class="action-btn primary-btn" type="button" @click="saveImageEdits" :disabled="isSavingImage">{{ isSavingImage ? '保存中' : '保存编辑' }}</button>
            </template>
            <button class="action-btn" type="button" @click="copyText" :disabled="!previewText">复制</button>
            <button class="action-btn" type="button" @click="downloadFile" :disabled="!fileBlob">下载</button>
          </div>
        </div>
      </div>

      <div v-if="isMarkdown" class="content-markdown">
        <article class="markdown-body" v-html="renderedMarkdown"></article>
      </div>

      <div v-else-if="isText" class="content-text">
        <div class="code-container">
          <div class="line-numbers">
            <div v-for="n in Math.max(lineCount, 1)" :key="n" class="line-number">{{ n }}</div>
          </div>
          <pre class="text-content">{{ previewText }}</pre>
        </div>
      </div>

      <div v-else-if="isImage && objectUrl" class="content-preview">
        <div class="preview-surface image-surface" :class="{ 'image-editor-active': isEditingImage }">
          <div v-if="imageEditorError" class="image-inline-error">{{ imageEditorError }}</div>
          <AsyncImageEditorComponent
            v-if="isEditingImage"
            ref="imageEditorRef"
            :image-url="objectUrl"
            :image-name="node.name"
            @error="handleImageEditorError"
          />
          <img :src="objectUrl" :alt="node.name" />
        </div>
      </div>

      <div v-else-if="isVideo" class="content-preview">
        <div class="preview-surface video-surface">
          <div v-if="videoPreviewError" class="video-state-card video-state-error">
            <h3>视频预览暂时不可用</h3>
            <p>{{ videoPreviewError }}</p>
            <p>原始文件仍保存在服务端文档空间中，你可以继续下载后使用本地播放器打开。</p>
          </div>

          <video
            v-else-if="objectUrl"
            ref="videoElementRef"
            class="plyr-video video-player"
            controls
            playsinline
            preload="auto"
            :src="objectUrl"
          ></video>
        </div>
      </div>

      <div v-else-if="isPdf && objectUrl" class="content-preview">
        <iframe class="pdf-frame" :src="objectUrl" :title="node.name"></iframe>
      </div>

      <div v-else-if="isOffice && fileBlob && officeFormat" class="content-preview">
        <AsyncOfficePreview
          :key="`${node?.id || node?.name || 'office'}:${officeFormat}:${fileBlob?.size || 0}`"
          :fileBlob="fileBlob"
          :fileName="node.name"
          :format="officeFormat"
        />
      </div>

      <div v-else class="content-unsupported">
        <div class="unsupported-card">
          <h3>暂不支持在线预览此文件</h3>
          <p>文件已保存在服务端文档空间中，你仍然可以点击右上角“下载”获取原文件。</p>
          <div class="unsupported-meta">
            <span>{{ node.name }}</span>
            <span>{{ fileSizeLabel }}</span>
            <span>{{ node.mimeType || '未知类型' }}</span>
          </div>
        </div>
      </div>
    </template>
  </div>
</template>

<style scoped>
.file-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: #f6f8fa;
}

[data-theme='dark'] .file-content {
  background: #0d1117;
}

.directory-shell,
.content-markdown,
.content-text,
.content-preview,
.content-unsupported {
  flex: 1;
  min-height: 0;
  overflow: auto;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
}

.directory-shell {
  padding: 24px;
}

.directory-card {
  overflow: hidden;
  border: 1px solid var(--github-border);
  border-radius: 12px;
  background: var(--github-bg);
  box-shadow: 0 20px 40px rgba(15, 23, 42, 0.06);
}

.directory-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 24px;
  border-bottom: 1px solid var(--github-border);
}

.directory-card-header h3 {
  margin: 0;
  font-size: 1.25rem;
}

.directory-card-header p {
  margin: 6px 0 0;
  color: var(--github-text-secondary);
  font-size: 14px;
}

.directory-header-meta {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.directory-header-meta span,
.truncated-pill {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 10px;
  border: 1px solid var(--github-border);
  border-radius: 999px;
  background: var(--github-hover);
  color: var(--github-text-secondary);
  font-size: 12px;
  font-weight: 600;
}

.directory-table {
  display: flex;
  flex-direction: column;
}

.directory-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 120px 180px;
  align-items: center;
  gap: 16px;
  min-height: 48px;
  padding: 0 20px;
  border-bottom: 1px solid var(--github-border);
}

.directory-row-head {
  min-height: 40px;
  background: var(--github-hover);
  color: var(--github-text-secondary);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.directory-button {
  width: 100%;
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

.directory-button:hover,
.directory-parent:hover {
  background: var(--github-hover);
}

.directory-parent {
  cursor: pointer;
  color: var(--github-text-secondary);
}

.col-name {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.name-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.col-size,
.col-date {
  color: var(--github-text-secondary);
  font-size: 13px;
}

.directory-empty {
  padding: 40px 24px;
  color: var(--github-text-secondary);
}

.github-file-header {
  border-bottom: 1px solid var(--github-border);
  background: var(--github-bg);
}

.file-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 20px;
  flex-wrap: wrap;
}

.sidebar-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--github-border);
  border-radius: 8px;
  background: var(--github-bg);
  color: var(--github-text-secondary);
  cursor: pointer;
}

.sidebar-toggle-btn:hover {
  background: var(--github-hover);
}

.file-header-main {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
  flex: 1;
  flex-wrap: wrap;
}

.file-path {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  flex-wrap: wrap;
}

.breadcrumb-link {
  border: none;
  background: transparent;
  color: #0969da;
  cursor: pointer;
  font-size: 14px;
}

.breadcrumb-current {
  color: var(--github-text);
  font-size: 14px;
  font-weight: 600;
}

.path-sep,
.file-meta {
  color: var(--github-text-secondary);
  font-size: 13px;
}

.file-header-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.file-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-left: auto;
}

.action-btn {
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--github-border);
  border-radius: 8px;
  background: var(--github-bg);
  color: var(--github-text);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.action-btn:hover:enabled {
  background: var(--github-hover);
}

.action-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.content-markdown {
  padding: 24px;
  overflow-y: auto;
}

.markdown-body {
  max-width: 920px;
  margin: 0 auto;
  padding: 28px 32px;
  border: 1px solid var(--github-border);
  border-radius: 12px;
  background: var(--github-bg);
  line-height: 1.8;
  color: var(--github-text);
}

.markdown-body :deep(h1),
.markdown-body :deep(h2),
.markdown-body :deep(h3) {
  margin-top: 1.4em;
  margin-bottom: 0.6em;
}

.markdown-body :deep(pre) {
  overflow: auto;
  padding: 16px;
  border-radius: 10px;
  background: var(--github-code-bg);
}

.content-text {
  padding: 16px;
}

.code-container {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  min-height: 100%;
  border: 1px solid var(--github-border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--github-bg);
}

.line-numbers {
  padding: 18px 0;
  background: var(--github-hover);
  color: var(--github-text-secondary);
  text-align: right;
  user-select: none;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12px;
}

.line-number {
  padding: 0 14px 0 0;
  line-height: 1.65;
}

.text-content {
  margin: 0;
  padding: 18px 20px;
  overflow: auto;
  overflow-y: auto;
  overflow-x: hidden;
  white-space: pre;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 13px;
  line-height: 1.65;
  color: var(--github-text);
}

.content-preview {
  padding: 20px;
  overflow: auto;
}

.preview-surface {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100%;
  border: 1px solid var(--github-border);
  border-radius: 12px;
  background:
    linear-gradient(45deg, rgba(208, 215, 222, 0.35) 25%, transparent 25%, transparent 75%, rgba(208, 215, 222, 0.35) 75%),
    linear-gradient(45deg, rgba(208, 215, 222, 0.35) 25%, transparent 25%, transparent 75%, rgba(208, 215, 222, 0.35) 75%);
  background-position: 0 0, 12px 12px;
  background-size: 24px 24px;
}

.image-surface img {
  max-width: 100%;
  max-height: 76vh;
  object-fit: contain;
  border-radius: 8px;
  box-shadow: 0 18px 40px rgba(15, 23, 42, 0.12);
}

.image-surface.image-editor-active {
  display: block;
  padding: 0;
  background: transparent;
  border: none;
}

.image-surface.image-editor-active img {
  display: none;
}

.image-inline-error {
  margin-bottom: 14px;
  padding: 12px 14px;
  border: 1px solid color-mix(in srgb, #cf222e 35%, var(--github-border) 65%);
  border-radius: 10px;
  background: color-mix(in srgb, #cf222e 8%, var(--github-bg) 92%);
  color: #cf222e;
  font-size: 13px;
}

.video-surface {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 20px;
  min-height: 72vh;
  padding: 24px;
  background:
    radial-gradient(circle at top, rgba(9, 105, 218, 0.08), transparent 38%),
    linear-gradient(180deg, rgba(13, 17, 23, 0.04), rgba(13, 17, 23, 0.08));
}

.video-player {
  align-self: center;
  width: min(1100px, 100%);
  max-height: 78vh;
  border-radius: 14px;
  background: #000;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.22);
}

/* Plyr-specific overrides */
.plyr {
  --plyr-color-main: #0969da;
  border-radius: 14px;
  overflow: hidden;
}

.plyr--video .plyr__control.plyr__tab-focus {
  box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.5);
}

.plyr--video .plyr__control:hover:not(.plyr__tab-focus) {
  background: #0969da;
}

.plyr--video .plyr__control--overlaid {
  background: rgba(9, 105, 218, 0.9);
}

.plyr--full-ui.plyr--video .plyr__controls {
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
}

.plyr__menu__container .plyr__control[role="menuitemradio"][aria-checked="true"] {
  background: #0969da;
}

.plyr__volume {
  color: #0969da;
}

.primary-btn {
  border-color: #0969da;
  background: #0969da;
  color: #fff;
}

.primary-btn:hover:enabled {
  background: #0859ba;
}

.video-state-card {
  width: min(520px, 100%);
  padding: 24px;
  border: 1px solid var(--github-border);
  border-radius: 16px;
  background: color-mix(in srgb, var(--github-bg) 92%, white 8%);
  box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
  text-align: center;
}

.video-state-card h3 {
  margin: 0 0 10px;
}

.video-state-card p {
  margin: 0;
  color: var(--github-text-secondary);
  line-height: 1.7;
}

.video-state-card strong {
  display: block;
  margin-top: 12px;
  color: var(--github-text);
}

.video-state-error {
  border-color: color-mix(in srgb, #cf222e 40%, var(--github-border) 60%);
}

.pdf-frame {
  width: 100%;
  min-height: 78vh;
  height: 100%;
  border: 1px solid var(--github-border);
  border-radius: 12px;
  background: var(--github-bg);
}

.content-unsupported {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.unsupported-card {
  width: min(640px, 100%);
  padding: 28px;
  border: 1px solid var(--github-border);
  border-radius: 16px;
  background: var(--github-bg);
  text-align: center;
}

.unsupported-card h3 {
  margin: 0 0 8px;
}

.unsupported-card p {
  margin: 0;
  color: var(--github-text-secondary);
}

.unsupported-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 18px;
}

.unsupported-meta span,
.icon-folder,
.icon-file {
  display: inline-flex;
  align-items: center;
}

.unsupported-meta span {
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--github-border);
  border-radius: 999px;
  background: var(--github-hover);
  color: var(--github-text-secondary);
  font-size: 12px;
}

.icon-folder::before {
  content: '📁';
  font-size: 15px;
}

.icon-file::before {
  content: '📄';
  font-size: 14px;
}

.icon-markdown::before { content: 'Ⓜ'; font-size: 12px; font-weight: 700; color: #0969da; }
.icon-json::before { content: '{ }'; font-size: 8px; font-weight: 700; color: #8250df; }
.icon-javascript::before { content: 'JS'; font-size: 9px; font-weight: 700; color: #9a6700; }
.icon-typescript::before { content: 'TS'; font-size: 9px; font-weight: 700; color: #0969da; }
.icon-css::before { content: 'CSS'; font-size: 7px; font-weight: 700; color: #1f883d; }
.icon-html::before { content: 'HTML'; font-size: 6px; font-weight: 700; color: #bc4c00; }
.icon-python::before { content: 'PY'; font-size: 9px; font-weight: 700; color: #0969da; }
.icon-vue::before { content: 'Vue'; font-size: 8px; font-weight: 700; color: #1f883d; }
.icon-yaml::before { content: 'YML'; font-size: 8px; font-weight: 700; color: #0969da; }
.icon-xml::before { content: '</>'; font-size: 8px; font-weight: 700; color: #bc4c00; }
.icon-csv::before { content: 'CSV'; font-size: 8px; font-weight: 700; color: #1f883d; }
.icon-log::before { content: 'LOG'; font-size: 7px; font-weight: 700; color: #6e7781; }
.icon-sql::before { content: 'SQL'; font-size: 8px; font-weight: 700; color: #8250df; }
.icon-image::before { content: '🖼'; font-size: 13px; }
.icon-pdf::before { content: 'PDF'; font-size: 8px; font-weight: 700; color: #cf222e; }
.icon-video::before { content: '▶'; font-size: 11px; font-weight: 700; color: #0969da; }
.icon-word::before { content: 'DOC'; font-size: 7px; font-weight: 700; color: #0969da; }
.icon-ppt::before { content: 'PPT'; font-size: 7px; font-weight: 700; color: #bc4c00; }
.icon-excel::before { content: 'XLS'; font-size: 7px; font-weight: 700; color: #1f883d; }
.icon-zip::before { content: 'ZIP'; font-size: 7px; font-weight: 700; color: #6f42c1; }
.icon-text::before { content: 'TXT'; font-size: 8px; font-weight: 700; color: #6e7781; }

@media (max-width: 960px) {
  .file-header-row,
  .directory-card-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .file-header-main,
  .file-actions {
    width: 100%;
  }

  .file-actions {
    margin-left: 0;
  }

  .directory-row {
    grid-template-columns: minmax(0, 1fr);
    padding-top: 10px;
    padding-bottom: 10px;
  }

  .col-size,
  .col-date {
    font-size: 12px;
  }

  .code-container {
    grid-template-columns: 56px minmax(0, 1fr);
  }
}
</style>
