<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import AgentTimeline from './components/AgentTimeline.vue'
import AppHeader from './components/AppHeader.vue'
import ComposerBar from './components/ComposerBar.vue'
import EditResumePreview from './components/EditResumePreview.vue'
import FeatureNavigation from './components/FeatureNavigation.vue'
import ResumeImportPanel from './components/ResumeImportPanel.vue'
import { useResumeAgent } from './composables/useResumeAgent'
import { useResumeDocument } from './composables/useResumeDocument'

const {
  hasLandingToken,
  sessionId,
  revision,
  stage,
  timeline,
  composer,
  missingFields,
  resumeId,
  resumeHook: agentResumeHook,
  traceId,
  initializing,
  pendingBlockId,
  sendingMessage,
  creatingResume,
  resetting,
  errorMessage,
  aiStatus,
  streamedAssistantText,
  isBusy,
  start,
  refreshTimeline,
  submitComponent,
  sendMessage,
  createResume,
  resetSession,
  clearError,
} = useResumeAgent()

const resumeDocument = useResumeDocument(sessionId)
const mobilePanel = ref<'chat' | 'resume'>('chat')
const displayedRevision = computed(() => resumeDocument.resume.value?.revision ?? revision.value)
const chatEnabled = computed(() => composer.value.mode !== 'ui_only')
const stageCode = computed(() => stage.value.toUpperCase().replaceAll('_', ' / '))
const REMOTE_REFRESH_INTERVAL_MS = 20_000
let remoteRefreshTimer: number | undefined
let remoteRefreshController: AbortController | null = null
const remoteRefreshBlocked = computed(
  () =>
    !hasLandingToken ||
    !sessionId.value ||
    isBusy.value ||
    Boolean(resumeDocument.busyEntryId.value) ||
    resumeDocument.importBusy.value ||
    resumeDocument.skillsBusy.value ||
    resumeDocument.summaryBusy.value,
)

function cancelRemoteRefresh() {
  remoteRefreshController?.abort()
  remoteRefreshController = null
}

async function refreshExternalChanges() {
  if (
    remoteRefreshBlocked.value ||
    document.visibilityState !== 'visible' ||
    remoteRefreshController
  ) return

  const activeController = new AbortController()
  remoteRefreshController = activeController
  try {
    await refreshTimeline(activeController.signal)
  } catch {
    // Background synchronization retries on the next interval or focus event.
  } finally {
    if (remoteRefreshController === activeController) remoteRefreshController = null
  }
}

function handleWindowFocus() {
  void refreshExternalChanges()
}

function handleVisibilityChange() {
  if (document.visibilityState === 'visible') {
    void refreshExternalChanges()
  } else {
    cancelRemoteRefresh()
  }
}
const stageLabels: Record<string, string> = {
  starting: '准备会话',
  PRIVACY_CONSENT: '隐私确认',
  RESUME_SOURCE_SELECT: '选择创建方式',
  RESUME_IMPORT_UPLOAD: '导入简历',
  PHONE_SELECTION: '手机号授权',
  MANUAL_PHONE_INPUT: '填写手机号',
  PERSONAL_INFO: '基本信息',
  JOB_TYPE_SELECT: '求职类型',
  TARGET_POSITION: '目标职位',
  TARGET_POSITION_MAJOR: '专业与方向',
  TARGET_POSITION_RECOMMENDATION: '职位建议',
  MINIMUM_READY: '准备创建',
  RESUME_CREATING: '正在创建',
  CREATE_FAILED: '等待重试',
  BUILDER_CONVERSATION: '简历共创',
}
const stageLabel = computed(() => stageLabels[stage.value] || '整理简历')

watch(agentResumeHook, (value) => {
  resumeDocument.resume.value = value ?? null
})

watch(sessionId, (value, previous) => {
  if (!value || value !== previous) void resumeDocument.restoreOptimizationRuns()
})

watch(() => resumeDocument.resumeImport.value?.status, (status) => {
  if (status === 'applied') void refreshTimeline()
})

watch(remoteRefreshBlocked, (blocked) => {
  if (blocked) cancelRemoteRefresh()
}, { flush: 'sync' })

async function retryConnection() {
  clearError()
  if (!sessionId.value) {
    await start()
    return
  }
  try {
    await refreshTimeline()
  } catch {
    await start()
  }
}

async function confirmReset() {
  if (window.confirm('重新开始会清空当前简历共创记录。确定继续吗？')) await resetSession()
}

onMounted(() => {
  void start()
  remoteRefreshTimer = window.setInterval(() => {
    void refreshExternalChanges()
  }, REMOTE_REFRESH_INTERVAL_MS)
  window.addEventListener('focus', handleWindowFocus)
  document.addEventListener('visibilitychange', handleVisibilityChange)
})

onBeforeUnmount(() => {
  if (remoteRefreshTimer !== undefined) window.clearInterval(remoteRefreshTimer)
  window.removeEventListener('focus', handleWindowFocus)
  document.removeEventListener('visibilitychange', handleVisibilityChange)
  cancelRemoteRefresh()
})
</script>

<template>
  <div id="top" class="app-shell">
    <main v-if="!sessionId" class="auth-gate" aria-live="polite">
      <section class="auth-gate__card" :aria-busy="initializing">
        <div class="auth-gate__brand" aria-label="OfferPai Resume Agent">
          <span aria-hidden="true">OP</span>
          <strong>OfferPai Resume Agent</strong>
        </div>
        <p class="auth-gate__eyebrow">
          {{ initializing ? 'VERIFYING ACCESS' : 'ACCESS REQUIRED' }}
        </p>
        <h1>
          {{ initializing ? '正在验证 OfferPai 登录状态' : '需要先完成 OfferPai 鉴权' }}
        </h1>
        <p v-if="initializing" class="auth-gate__message">
          正在校验登录凭证和账号信息，验证通过后会自动进入简历服务。
        </p>
        <p v-else class="auth-gate__message" role="alert">
          {{ errorMessage || '当前登录凭证不可用，请重新从 OfferPai 进入。' }}
        </p>
        <div v-if="initializing" class="auth-gate__progress" aria-hidden="true"><i /></div>
        <button
          v-else-if="hasLandingToken"
          type="button"
          class="auth-gate__retry"
          @click="retryConnection"
        >
          重新验证
        </button>
        <p v-else class="auth-gate__hint">
          请使用 OfferPai 提供的带 <code>?token=</code> 入口重新打开本页。
        </p>
      </section>
    </main>

    <template v-else>
      <AppHeader
        :stage-label="stageLabel"
        :revision="displayedRevision"
        :session-id="sessionId"
        :resetting="resetting"
        @reset="confirmReset"
      />

      <FeatureNavigation current="builder" />

      <main class="workspace">
      <div class="mobile-view-tabs" role="tablist" aria-label="移动端工作区视图">
        <button
          type="button"
          role="tab"
          :aria-selected="mobilePanel === 'chat'"
          :class="{ 'mobile-view-tabs__tab--active': mobilePanel === 'chat' }"
          @click="mobilePanel = 'chat'"
        >对话</button>
        <button
          type="button"
          role="tab"
          :aria-selected="mobilePanel === 'resume'"
          :class="{ 'mobile-view-tabs__tab--active': mobilePanel === 'resume' }"
          @click="mobilePanel = 'resume'"
        >简历</button>
      </div>

      <section
        class="preview-panel"
        :class="{ 'mobile-panel--active': mobilePanel === 'resume' }"
        aria-label="简历预览与编辑"
      >
        <EditResumePreview :document="resumeDocument" />
      </section>

      <section
        class="conversation"
        :class="{ 'mobile-panel--active': mobilePanel === 'chat' }"
        aria-labelledby="conversation-title"
      >
        <header class="conversation-heading">
          <div>
            <p>{{ stageCode }}</p>
            <h1 id="conversation-title">把经历一步步写成简历。</h1>
          </div>
          <span class="conversation-heading__live" :class="{ 'conversation-heading__live--busy': aiStatus }">
            <i aria-hidden="true" />
            {{ aiStatus || 'AI 共创中' }}
          </span>
        </header>

        <ResumeImportPanel
          v-if="stage === 'RESUME_IMPORT_UPLOAD'"
          :document="resumeDocument"
          :disabled="!sessionId || initializing"
        />

        <AgentTimeline
          :timeline="timeline"
          :initializing="initializing"
          :pending-block-id="pendingBlockId"
          :creating-resume="creatingResume"
          :error-message="errorMessage"
          :trace-id="traceId"
          :resume-id="resumeId"
          :missing-fields="missingFields"
          :ai-status="aiStatus"
          :streamed-assistant-text="streamedAssistantText"
          @submit="submitComponent"
          @create="createResume"
          @retry="retryConnection"
          @clear-error="clearError"
        />

        <ComposerBar
          :config="composer"
          :sending="sendingMessage"
          :disabled="composer.mode !== 'ui_only' && !chatEnabled"
          @send="sendMessage"
        />
      </section>
      </main>

      <footer class="app-footer">
        <span>OfferPai Resume Agent</span>
        <span>你的内容会保留在本次简历会话中</span>
      </footer>
    </template>
  </div>
</template>

<style scoped>
.app-shell { min-height: 100vh; }

.auth-gate {
  display: grid;
  min-height: 100vh;
  place-items: center;
  padding: 28px;
  background:
    radial-gradient(circle at 18% 12%, rgba(93, 177, 165, .17), transparent 34%),
    radial-gradient(circle at 82% 78%, rgba(146, 188, 112, .13), transparent 32%),
    #f5faf8;
}

.auth-gate__card {
  width: min(100%, 520px);
  padding: clamp(30px, 6vw, 54px);
  border: 1px solid rgba(174, 207, 201, .82);
  border-radius: 28px;
  background: rgba(255, 255, 255, .9);
  box-shadow: 0 24px 70px rgba(31, 82, 75, .12);
}

.auth-gate__brand {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--ink);
  font-size: 14px;
}

.auth-gate__brand span {
  display: grid;
  width: 38px;
  height: 38px;
  place-items: center;
  border-radius: 12px;
  color: #fff;
  background: var(--brand-dark);
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .08em;
}

.auth-gate__eyebrow {
  margin: 48px 0 0;
  color: var(--brand-dark);
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .14em;
}

.auth-gate h1 {
  margin: 12px 0 0;
  color: var(--ink);
  font-family: "Aptos Display", "MiSans", "PingFang SC", sans-serif;
  font-size: clamp(28px, 6vw, 40px);
  line-height: 1.18;
}

.auth-gate__message {
  margin: 18px 0 0;
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1.8;
}

.auth-gate__progress {
  height: 4px;
  margin-top: 34px;
  overflow: hidden;
  border-radius: 999px;
  background: #e4efec;
}

.auth-gate__progress i {
  display: block;
  width: 42%;
  height: 100%;
  border-radius: inherit;
  background: var(--brand);
  animation: auth-progress 1.15s ease-in-out infinite alternate;
}

.auth-gate__retry {
  min-height: 44px;
  margin-top: 28px;
  padding: 0 22px;
  border: 0;
  border-radius: 12px;
  color: #fff;
  background: var(--brand-dark);
  font-size: 14px;
  font-weight: 750;
  cursor: pointer;
}

.auth-gate__retry:hover { filter: brightness(.94); }
.auth-gate__retry:focus-visible { outline: 3px solid rgba(57, 139, 128, .28); outline-offset: 3px; }

.auth-gate__hint {
  margin: 24px 0 0;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  color: var(--ink-faint);
  font-size: 12px;
  line-height: 1.7;
}

.auth-gate__hint code {
  padding: 2px 5px;
  border-radius: 5px;
  color: var(--brand-dark);
  background: #eaf4f1;
}

.workspace {
  display: grid;
  width: min(1280px, calc(100% - 40px));
  grid-template-columns: minmax(330px, .88fr) minmax(0, 1.35fr);
  align-items: start;
  gap: clamp(28px, 4vw, 64px);
  margin: 0 auto;
  padding: 38px 0 0;
}

.preview-panel {
  position: sticky;
  top: 92px;
  display: flex;
  min-width: 0;
  height: calc(100vh - 116px);
  align-items: center;
}
.preview-panel :deep(.edit-preview) {
  position: static;
  width: 100%;
  max-height: 100%;
}
.conversation { min-width: 0; }
.mobile-view-tabs { display: none; }

.conversation-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  margin: 0 0 30px;
  padding-bottom: 20px;
  border-bottom: 1px solid rgba(190, 217, 213, .78);
}

.conversation-heading p {
  margin: 0;
  color: var(--brand-dark);
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .08em;
}

.conversation-heading h1 {
  max-width: 22ch;
  margin: 8px 0 0;
  color: var(--ink);
  font-family: "Aptos Display", "MiSans", "PingFang SC", sans-serif;
  font-size: 30px;
  font-weight: 780;
  letter-spacing: 0;
  line-height: 1.18;
}

.conversation-heading__live {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 6px;
  max-width: 180px;
  padding: 7px 10px;
  border: 1px solid #cfe4d0;
  border-radius: 999px;
  color: #4b7551;
  background: rgba(243, 250, 239, .86);
  font-size: 10px;
  font-weight: 750;
}

.conversation-heading__live i {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: #7db75f;
  box-shadow: 0 0 0 3px rgba(125, 183, 95, .14);
}

.conversation-heading__live--busy { color: var(--brand-dark); border-color: #a9d3cd; background: #eff9f7; }
.conversation-heading__live--busy i { background: var(--brand); animation: live-pulse 1s ease-in-out infinite; }

.app-footer {
  display: flex;
  width: min(1280px, calc(100% - 40px));
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin: 28px auto 0;
  padding: 24px 0 28px;
  border-top: 1px solid rgba(194, 217, 214, .66);
  color: var(--ink-faint);
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 8px;
  letter-spacing: .04em;
}

@keyframes live-pulse { 50% { opacity: .4; } }
@keyframes auth-progress { from { transform: translateX(-10%); } to { transform: translateX(150%); } }

@media (max-width: 850px) {
  .workspace { width: min(100% - 28px, 760px); grid-template-columns: 1fr; gap: 22px; padding-top: 22px; }
  .mobile-view-tabs { display: grid; grid-column: 1; grid-template-columns: 1fr 1fr; width: 100%; border-bottom: 1px solid var(--line); }
  .mobile-view-tabs button { min-height: 42px; border: 0; border-bottom: 2px solid transparent; color: var(--ink-faint); background: transparent; font-size: 13px; font-weight: 700; }
  .mobile-view-tabs__tab--active { border-bottom-color: var(--brand) !important; color: var(--ink) !important; }
  .preview-panel, .conversation { display: none; grid-column: 1; }
  .preview-panel { position: static; height: auto; align-items: stretch; }
  .preview-panel :deep(.edit-preview) { max-height: none; }
  .mobile-panel--active { display: block; }
  .conversation-heading { margin-bottom: 22px; }
  .app-footer { width: calc(100% - 28px); align-items: flex-start; flex-direction: column; gap: 5px; }
}

@media (max-width: 520px) {
  .workspace { width: min(100% - 20px, 760px); }
  .conversation-heading h1 { font-size: 26px; }
  .conversation-heading__live { max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
</style>
