<script setup lang="ts">
import type { WorldStarter } from '@dng/shared'

type Stage = 'seed' | 'questions' | 'generating' | 'preview' | 'confirming'
interface Message { id: string; role: 'coauthor' | 'player' | 'status'; body: string; label?: string }
interface DynamicQuestion { id: string; label: string; options: string[] }
interface RespondResult { readyToGenerate: boolean; question?: DynamicQuestion }

const { api } = useDngApi()
const stage = ref<Stage>('seed')
const busy = ref(false)
const sessionId = ref<string | null>(null)
const seed = ref('A science-fiction mystery about a dead relay sending messages from the future.')
const answers = reactive<Record<string, string>>({})
const currentQuestion = ref<DynamicQuestion | null>(null)
const answerCount = ref(0)
const draft = ref<WorldStarter | null>(null)
const retryAction = ref<'begin' | 'respond' | 'generate'>('begin')
const pendingAnswer = ref<{ key: string; value: string } | null>(null)
const messages = ref<Message[]>([
  { id: 'welcome', role: 'coauthor', body: 'Give me the first impossible sentence. I’ll ask a few focused questions, then turn it into a private world your party can enter tonight.' },
])

const displayedQuestion = computed(() => currentQuestion.value ? { key: currentQuestion.value.id, label: currentQuestion.value.label, options: currentQuestion.value.options } : null)
const progress = computed(() => stage.value === 'preview' || stage.value === 'confirming' ? 100 : stage.value === 'generating' ? 85 : stage.value === 'seed' ? 10 : 20 + Math.round((Math.min(answerCount.value, 5) / 5) * 55))

function addMessage(role: Message['role'], body: string, label?: string) {
  messages.value.push({ id: `${Date.now()}-${messages.value.length}`, role, body, label })
}

async function begin() {
  const prompt = seed.value.trim()
  if (!prompt || stage.value !== 'seed' || busy.value) return
  busy.value = true
  retryAction.value = 'begin'
  try {
    const result = await api<{ session: { id: string } }>('/api/v1/coauthor/sessions', { method: 'POST', body: { message: prompt } })
    sessionId.value = result.session.id
    addMessage('player', prompt)
    stage.value = 'questions'
    retryAction.value = 'respond'
    await requestNextQuestion()
  } catch (error) {
    addMessage('status', errorText(error) || 'Could not start a private coauthor session. Sign in and try again.', 'ERROR')
  } finally {
    busy.value = false
  }
}

async function answerQuestion(key: string, value: string) {
  if (stage.value !== 'questions' || currentQuestion.value?.id !== key || busy.value || !sessionId.value) return
  busy.value = true
  pendingAnswer.value = { key, value }
  try {
    await api(`/api/v1/coauthor/sessions/${sessionId.value}/messages`, {
      method: 'POST', body: { content: value },
    })
    answers[key] = value
    addMessage('player', value)
    currentQuestion.value = null
    answerCount.value = Math.min(answerCount.value + 1, 5)
    pendingAnswer.value = null
    retryAction.value = 'respond'
    await requestNextQuestion()
  } catch (error) {
    addMessage('status', errorText(error) || 'That answer could not be saved. Try again.', 'ERROR')
  } finally {
    busy.value = false
  }
}

async function requestNextQuestion() {
  if (!sessionId.value) return
  const result = await api<RespondResult>(`/api/v1/coauthor/sessions/${sessionId.value}/respond`, { method: 'POST' })
  if (result.readyToGenerate || answerCount.value >= 5) {
    currentQuestion.value = null
    await generate()
    return
  }
  if (!result.question) throw new Error('The coauthor did not return its next question.')
  currentQuestion.value = result.question
  addMessage('coauthor', result.question.label, `COAUTHOR · QUESTION ${Math.min(answerCount.value + 1, 5)} OF UP TO 5`)
}

function errorText(error: unknown) {
  if (error && typeof error === 'object') {
    const value = error as { data?: { statusMessage?: string; message?: string }; statusMessage?: string; message?: string }
    return value.data?.statusMessage || value.data?.message || value.statusMessage || value.message
  }
  return undefined
}

async function generate() {
  if (!sessionId.value) return
  stage.value = 'generating'
  retryAction.value = 'generate'
  addMessage('status', 'Building premise, location, key people, factions, hidden pressure, and an opening scene…', 'GENERATING')
  try {
    await api(`/api/v1/coauthor/sessions/${sessionId.value}/generate`, { method: 'POST' })
    const deadline = Date.now() + 90_000
    while (Date.now() < deadline) {
      const result = await api<{ session: { status: string; generatedWorld?: unknown; generated_world?: unknown } }>(`/api/v1/coauthor/sessions/${sessionId.value}`)
      if (result.session.status === 'ready') {
        draft.value = (result.session.generatedWorld ?? result.session.generated_world) as WorldStarter
        break
      }
      if (result.session.status === 'failed') throw new Error('The coauthor job failed. Try generation again.')
      await new Promise(resolve => setTimeout(resolve, 1_000))
    }
    if (!draft.value) throw new Error('World generation is taking too long. Your session is saved; try again shortly.')
    messages.value.pop()
    addMessage('status', `“${draft.value.title}” is ready for your review. Every visible field remains editable.`, 'READY')
    stage.value = 'preview'
  } catch (error) {
    messages.value.pop()
    addMessage('status', errorText(error) || 'The coauthor could not generate this world. Your answers are safe—try again.', 'ERROR')
    stage.value = 'questions'
  }
}

function retry() {
  const last = messages.value.at(-1)
  if (last?.role === 'status' && last.label === 'ERROR') messages.value.pop()
  if (retryAction.value === 'begin') begin()
  else if (retryAction.value === 'generate') generate()
  else if (pendingAnswer.value && currentQuestion.value) answerQuestion(pendingAnswer.value.key, pendingAnswer.value.value)
  else requestNextQuestion().catch(error => addMessage('status', errorText(error) || 'The coauthor could not continue. Try again.', 'ERROR'))
}

function revise() {
  stage.value = 'seed'
  sessionId.value = null
  currentQuestion.value = null
  answerCount.value = 0
  pendingAnswer.value = null
  for (const key of Object.keys(answers)) delete answers[key]
  draft.value = null
  addMessage('coauthor', 'Let’s reshape it from the opening sentence. I’ll adapt the next questions to your new direction.', 'COAUTHOR · REVISION')
}

async function confirm() {
  if (!draft.value || !sessionId.value || stage.value === 'confirming') return
  stage.value = 'confirming'
  try {
    const confirmed = await api<{ worldId: string }>(`/api/v1/coauthor/sessions/${sessionId.value}/confirm`, {
      method: 'POST', body: { world: draft.value },
    })
    const created = await api<{ campaignId: string }>('/api/v1/campaigns', {
      method: 'POST', body: { worldId: confirmed.worldId, title: draft.value.title },
    })
    await navigateTo(`/campaign/${created.campaignId}`)
  } catch (error) {
    addMessage('status', errorText(error) || 'The world could not be confirmed. Your draft is still safe.', 'ERROR')
    stage.value = 'preview'
  }
}
</script>

<template>
  <AppShell section="WORLD FORGE">
    <div class="forge noise">
      <aside>
        <p class="step-label">CREATION PROTOCOL</p>
        <div class="progress"><i :style="{ width: `${progress}%` }" /></div>
        <ol>
          <li :class="{ active: stage === 'seed', done: stage !== 'seed' }"><b>01</b><span>Seed idea<small>Say what cannot exist yet.</small></span></li>
          <li :class="{ active: stage === 'questions', done: answerCount >= 3 }"><b>02</b><span>Shape the signal<small>{{ answerCount }} / up to 5 answers captured.</small></span></li>
          <li :class="{ active: stage === 'generating', done: stage === 'preview' || stage === 'confirming' }"><b>03</b><span>Generate structure<small>A playable starting kit.</small></span></li>
          <li :class="{ active: stage === 'preview' || stage === 'confirming' }"><b>04</b><span>Review & launch<small>You remain the final author.</small></span></li>
        </ol>
        <div class="boundary"><span>13+ BOUNDARY</span><p>Dark themes are welcome. Explicit sexual content and extreme graphic violence are not.</p></div>
      </aside>

      <main v-if="stage !== 'preview' && stage !== 'confirming'" class="coauthor">
        <p class="kicker">COAUTHOR / SESSION 01</p>
        <h1>BUILD THE<br>IMPOSSIBLE<span>.</span></h1>
        <CoauthorConversation :messages="messages" :question="displayedQuestion" :selected-answer="currentQuestion ? answers[currentQuestion.id] : undefined" :busy="stage === 'generating' || busy" @answer="answerQuestion" @retry="retry" />
        <form v-if="stage === 'seed'" class="seed" @submit.prevent="begin">
          <textarea v-model="seed" aria-label="World idea" rows="3" maxlength="1200" autofocus />
          <button :disabled="!seed.trim() || busy">{{ busy ? 'SAVING…' : 'BEGIN' }} <span>→</span></button>
        </form>
        <div v-if="stage === 'generating'" class="scanner" aria-label="Generating world"><i /><i /><i /><span>D&G</span></div>
      </main>

      <CoauthorWorldPreview v-else-if="draft" v-model="draft" @revise="revise" @confirm="confirm" />
    </div>
  </AppShell>
</template>

<style scoped>
.forge{min-height:calc(100vh - 76px);display:grid;grid-template-columns:300px 1fr}.forge>aside{padding:50px 34px;border-right:1px solid var(--line);display:flex;flex-direction:column}.step-label,.kicker{font:500 9px var(--mono);letter-spacing:.18em;color:var(--acid)}.progress{height:3px;margin-top:22px;background:#252522}.progress i{display:block;height:100%;background:var(--acid);transition:width .35s ease}ol{list-style:none;padding:20px 0;margin:0}li{display:flex;gap:18px;padding:18px 0;color:#4d4d48}li>b{font:500 9px var(--mono)}li span{font:600 10px var(--display);letter-spacing:.04em}li small{display:block;margin-top:7px;font:400 10px/1.4 var(--body);color:#55554f}li.active{color:var(--ink)}li.active b,li.done b{color:var(--acid)}li.active small{color:var(--muted)}li.done:not(.active){color:#77776f}.boundary{margin-top:auto;padding:18px;border:1px solid var(--line)}.boundary span{font:600 8px var(--mono);color:var(--acid)}.boundary p{font-size:10px;line-height:1.5;color:var(--muted)}.coauthor{padding:clamp(44px,6vw,82px);max-width:1000px;width:100%}.coauthor h1{font:600 clamp(42px,5vw,72px)/.95 var(--display);letter-spacing:-.06em;margin:16px 0 44px}.coauthor h1 span{color:var(--acid)}.seed{display:flex;align-items:stretch;max-width:780px;margin-top:22px}.seed textarea{width:100%;resize:vertical;background:#10100f;border:1px solid var(--line);color:var(--ink);padding:18px;font:500 14px/1.6 var(--body)}.seed button{border:0;background:var(--acid);color:#080808;padding:0 24px;font:600 9px var(--mono);letter-spacing:.12em}.seed button:disabled{opacity:.4}.scanner{position:relative;width:180px;height:180px;border:1px solid var(--line);border-radius:50%;display:grid;place-items:center;margin:40px auto 0;animation:rotate 8s linear infinite}.scanner:after{content:"";position:absolute;inset:28px;border:1px dashed var(--acid-dim);border-radius:50%}.scanner i{position:absolute;width:6px;height:6px;background:var(--acid);border-radius:50%}.scanner i:nth-child(1){top:8px}.scanner i:nth-child(2){left:13px;bottom:40px}.scanner i:nth-child(3){right:3px;top:64px}.scanner span{font:600 20px var(--display);color:var(--acid)}@keyframes rotate{to{transform:rotate(360deg)}}@media(max-width:850px){.forge{grid-template-columns:1fr}.forge>aside{display:none}.coauthor{box-sizing:border-box;padding:40px 20px}}
</style>
