import './styles/main.css'
import './styles/card.css'
import './styles/card-migrate.css'
import './styles/vrm.css'
import { getConfig, resolveHandle, getProfile, getPosts, getPost, describeRepo, listRecords, getRecord, getPds, getNetworks, getChatMessages, getCards, getCardAdmin, getRse, getRseAdmin, getLinks, getVrm } from './lib/api'
import { parseRoute, onRouteChange, navigate, type Route } from './lib/router'
import { login, logout, handleCallback, restoreSession, isLoggedIn, getLoggedInHandle, getLoggedInDid, deleteRecord, updatePost, updateChat, updateLinks, getAgent } from './lib/auth'
import { validateRecord } from './lib/lexicon'
import { renderHeader } from './components/header'
import { renderProfile } from './components/profile'
import { renderPostList, renderPostDetail, setupPostDetail } from './components/posts'
import { renderPostForm, setupPostForm } from './components/postform'
import { renderCollectionButtons, renderServerInfo, renderServiceList, renderCollectionList, renderRecordList, renderRecordDetail } from './components/browser'
import { renderModeTabs, renderLangSelector, setupModeTabs } from './components/mode-tabs'
import { renderFooter } from './components/footer'
import { renderChatListPage, renderChatThreadPage, renderChatEditForm } from './components/chat'
import { renderCardPage } from './components/card'
import { renderRsePage } from './components/rse'
import { renderLinkPage, renderLinkButtons } from './components/link'
import { renderVrmPage, setupVrmPage } from './components/vrm'
import { checkMigrationStatus, renderMigrationPage, setupMigrationButton } from './components/card-migrate'
import { renderNoteListPage, renderNoteDetailPage, setupNoteDetail } from './components/note'
import { showLoading, hideLoading } from './components/loading'
import { setupAsyncButton } from './lib/util'
import type { AppConfig, Profile as ProfileType, Post } from './types'

const app = document.getElementById('app')!

let currentHandle = ''
let configHandle = ''
let isFirstRender = true

// Shared context passed to route renderers
interface RenderContext {
  config: AppConfig
  did: string
  handle: string
  localOnly: boolean
  isOwner: boolean
  loggedInDid: string | null | undefined
  profile: ProfileType | null
  collections: string[]
  webUrl: string | undefined
  oauthEnabled: boolean
  posts: Post[]
  notePosts: Post[]
}

// Result from route content rendering
interface RouteContentResult {
  html: string
  langList?: string[]
  currentRecord?: { uri: string; cid: string; value: unknown } | null
  cardMigrationState?: Awaited<ReturnType<typeof checkMigrationStatus>> | null
}

// Filter collections by service domain
function filterCollectionsByService(collections: string[], service: string): string[] {
  return collections.filter(col => {
    const parts = col.split('.')
    if (parts.length >= 2) {
      const colService = `${parts[1]}.${parts[0]}`
      return colService === service
    }
    return false
  })
}

// Get web URL for handle from networks
async function getWebUrl(handle: string): Promise<string | undefined> {
  const networks = await getNetworks()
  for (const [_domain, network] of Object.entries(networks)) {
    if (network.handleDomains) {
      for (const hd of network.handleDomains) {
        if (handle.endsWith(`.${hd}`)) {
          return network.web
        }
      }
    }
    const webDomain = network.web?.replace(/^https?:\/\//, '')
    if (webDomain && handle.endsWith(`.${webDomain}`)) {
      return network.web
    }
  }
  const firstNetwork = Object.values(networks)[0]
  return firstNetwork?.web
}

// Collect available languages from posts
function collectPostLangs(posts: Post[]): string[] {
  const langs = new Set<string>()
  for (const post of posts) {
    langs.add(post.value.langs?.[0] || 'ja')
    if (post.value.translations) {
      for (const lang of Object.keys(post.value.translations)) {
        langs.add(lang)
      }
    }
  }
  return Array.from(langs)
}

// Collect available languages from chat messages
function collectChatLangs(messages: { value: { langs?: string[]; translations?: Record<string, unknown> } }[]): string[] {
  const langs = new Set<string>()
  for (const msg of messages) {
    langs.add(msg.value.langs?.[0] || 'ja')
    if (msg.value.translations) {
      for (const lang of Object.keys(msg.value.translations)) {
        langs.add(lang)
      }
    }
  }
  return Array.from(langs)
}

// --- Route content renderers ---

async function renderRecordRoute(route: Route, ctx: RenderContext): Promise<RouteContentResult> {
  const currentRecord = await getRecord(ctx.did, route.collection!, route.rkey!)
  let html = ''
  if (currentRecord) {
    html += `<div id="content">${renderRecordDetail(currentRecord, route.collection!, ctx.isOwner)}</div>`
  } else {
    html += `<div id="content" class="error">Record not found</div>`
  }
  html += `<nav class="back-nav"><a href="/@${ctx.handle}/at/collection/${route.collection}">${route.collection}</a></nav>`
  return { html, currentRecord }
}

async function renderCollectionRoute(route: Route, ctx: RenderContext): Promise<RouteContentResult> {
  const records = await listRecords(ctx.did, route.collection!)
  let html = `<div id="content">${renderRecordList(records, ctx.handle, route.collection!)}</div>`
  const parts = route.collection!.split('.')
  const service = parts.length >= 2 ? `${parts[1]}.${parts[0]}` : ''
  html += `<nav class="back-nav"><a href="/@${ctx.handle}/at/service/${encodeURIComponent(service)}">${service}</a></nav>`
  return { html }
}

async function renderServiceRoute(route: Route, ctx: RenderContext): Promise<RouteContentResult> {
  const filtered = filterCollectionsByService(ctx.collections, route.service!)
  let html = `<div id="content">${renderCollectionList(filtered, ctx.handle, route.service!)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}/at">at</a></nav>`
  return { html }
}

async function renderAtBrowserRoute(ctx: RenderContext): Promise<RouteContentResult> {
  const pds = await getPds(ctx.did)
  let html = `<div id="browser">`
  html += renderServerInfo(ctx.did, pds)
  html += renderServiceList(ctx.collections, ctx.handle)
  html += `</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

async function renderPostRoute(route: Route, ctx: RenderContext, langList: string[]): Promise<RouteContentResult> {
  const post = await getPost(ctx.did, ctx.config.collection, route.rkey!, ctx.localOnly)
  let html = renderLangSelector(langList)
  if (post) {
    html += `<div id="content">${renderPostDetail(post, ctx.handle, ctx.config.collection, ctx.isOwner, ctx.config.siteUrl, ctx.webUrl)}</div>`
  } else {
    html += `<div id="content" class="error">Post not found</div>`
  }
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

function renderPostPageRoute(ctx: RenderContext): RouteContentResult {
  let html = `<div id="post-form">${renderPostForm(ctx.config.collection)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

async function renderCardRoute(ctx: RenderContext): Promise<RouteContentResult> {
  const cardCollection = ctx.config.cardCollection || 'ai.syui.card.user'
  const cards = await getCards(ctx.did, cardCollection)
  let html = `<div id="content">${renderCardPage(cards, ctx.handle, cardCollection)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

async function renderCardOldRoute(ctx: RenderContext): Promise<RouteContentResult> {
  const cardMigrationState = await checkMigrationStatus(ctx.did)
  let html = `<div id="content">${renderMigrationPage(cardMigrationState, ctx.handle, ctx.isOwner)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html, cardMigrationState }
}

async function renderRseRoute(ctx: RenderContext): Promise<RouteContentResult> {
  const cardCollection = ctx.config.cardCollection || 'ai.syui.card.user'
  const adminDid = ctx.config.bot?.did || ctx.config.did || ctx.did
  const [rseData, cards, adminData, rseAdminData] = await Promise.all([
    getRse(ctx.did),
    getCards(ctx.did, cardCollection),
    getCardAdmin(adminDid),
    getRseAdmin(adminDid)
  ])
  const userCards = cards?.card || []
  let html = `<div id="content">${renderRsePage(rseData, ctx.handle, userCards, adminData, rseAdminData, cardCollection)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

async function renderLinkRoute(ctx: RenderContext): Promise<RouteContentResult> {
  const links = await getLinks(ctx.did)
  let html = `<div id="content">${renderLinkPage(links, ctx.handle, ctx.isOwner)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

async function renderVrmRoute(ctx: RenderContext): Promise<RouteContentResult> {
  const vrmData = await getVrm(ctx.did)
  let html = `<div id="content">${renderVrmPage(vrmData, ctx.handle)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

function renderNoteRoute(ctx: RenderContext): RouteContentResult {
  let html = `<div id="content">${renderNoteListPage(ctx.notePosts, ctx.handle)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html }
}

async function renderNoteDetailRoute(route: Route, ctx: RenderContext): Promise<RouteContentResult> {
  const noteCollection = 'ai.syui.note.post'
  const notePost = await getPost(ctx.did, noteCollection, route.rkey!, ctx.localOnly)
  let html = ''
  if (notePost) {
    html += `<div id="content">${renderNoteDetailPage(notePost, ctx.handle, ctx.isOwner)}</div>`
  } else {
    html += `<div id="content" class="error">Note not found</div>`
  }
  html += `<nav class="back-nav"><a href="/@${ctx.handle}/at/note">note</a></nav>`
  return { html }
}

async function renderChatListRoute(ctx: RenderContext): Promise<RouteContentResult> {
  if (!ctx.config.bot) {
    return {
      html: `<div id="content" class="error">Bot not configured in config.json</div><nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
    }
  }

  const botDid = ctx.config.bot.did
  const botHandle = ctx.config.bot.handle
  const chatCollection = ctx.config.chatCollection || 'ai.syui.log.chat'

  const [logMessages, ueMessages, botProfile, pds] = await Promise.all([
    getChatMessages(ctx.did, botDid, chatCollection),
    getChatMessages(ctx.did, botDid, 'ai.syui.ue.chat'),
    getProfile(botDid, false),
    getPds(ctx.did)
  ])

  const chatCollections = [
    { collection: chatCollection, messages: logMessages },
    { collection: 'ai.syui.ue.chat', messages: ueMessages },
  ].filter(c => c.messages.length > 0)

  const langList = collectChatLangs([...logMessages, ...ueMessages])

  let html = renderLangSelector(langList)
  html += `<div id="content">${renderChatListPage(chatCollections, ctx.did, ctx.handle, botDid, botHandle, ctx.profile, botProfile, pds || undefined)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
  return { html, langList }
}

async function renderChatThreadRoute(route: Route, ctx: RenderContext): Promise<RouteContentResult> {
  if (!ctx.config.bot) {
    return {
      html: `<div id="content" class="error">Bot not configured in config.json</div><nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
    }
  }

  const botDid = ctx.config.bot.did
  const botHandle = ctx.config.bot.handle
  const chatCollection = route.service === 'ue' ? 'ai.syui.ue.chat' : (ctx.config.chatCollection || 'ai.syui.log.chat')

  const [chatMessages, botProfile, pds] = await Promise.all([
    getChatMessages(ctx.did, botDid, chatCollection),
    getProfile(botDid, false),
    getPds(ctx.did)
  ])

  const langList = collectChatLangs(chatMessages)

  let html = renderLangSelector(langList)
  html += `<div id="content">${renderChatThreadPage(chatMessages, route.rkey!, ctx.did, ctx.handle, botDid, botHandle, ctx.profile, botProfile, pds || undefined, chatCollection, ctx.loggedInDid)}</div>`
  html += `<nav class="back-nav"><a href="/@${ctx.handle}/at/chat">chat</a></nav>`
  return { html, langList }
}

async function renderChatEditRoute(route: Route, ctx: RenderContext): Promise<RouteContentResult> {
  if (!ctx.config.bot) {
    return {
      html: `<div id="content" class="error">Bot not configured in config.json</div><nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>`
    }
  }
  if (!ctx.isOwner) {
    return {
      html: `<div id="content" class="error">You can only edit your own messages</div><nav class="back-nav"><a href="/@${ctx.handle}/at/chat">chat</a></nav>`
    }
  }

  const botDid = ctx.config.bot.did
  const chatCollection = route.service === 'ue' ? 'ai.syui.ue.chat' : (ctx.config.chatCollection || 'ai.syui.log.chat')

  const chatMessages = await getChatMessages(ctx.did, botDid, chatCollection)
  const targetUri = `at://${ctx.did}/${chatCollection}/${route.rkey}`
  const message = chatMessages.find(m => m.uri === targetUri)

  let html = ''
  if (!message) {
    html += `<div id="content" class="error">Message not found</div>`
  } else {
    html += `<div id="content">${renderChatEditForm(message, chatCollection, ctx.handle)}</div>`
  }
  html += `<nav class="back-nav"><a href="/@${ctx.handle}/at/chat">chat</a></nav>`
  return { html }
}

async function renderUserHomeRoute(ctx: RenderContext, langList: string[]): Promise<RouteContentResult> {
  const links = await getLinks(ctx.did)
  let html = `<div id="browser" class="browser-row">${renderCollectionButtons(ctx.collections, ctx.handle)}${renderLinkButtons(links)}</div>`
  html += renderLangSelector(langList)
  html += `<div id="content">${renderPostList(ctx.posts, ctx.handle)}</div>`
  return { html }
}

// Dispatch route to content renderer
async function renderRouteContent(route: Route, ctx: RenderContext, langList: string[]): Promise<RouteContentResult> {
  switch (route.type) {
    case 'record':
      return renderRecordRoute(route, ctx)
    case 'collection':
      return renderCollectionRoute(route, ctx)
    case 'service':
      return renderServiceRoute(route, ctx)
    case 'atbrowser':
      return renderAtBrowserRoute(ctx)
    case 'post':
      return renderPostRoute(route, ctx, langList)
    case 'postpage':
      return renderPostPageRoute(ctx)
    case 'card':
      return renderCardRoute(ctx)
    case 'card-old':
      return renderCardOldRoute(ctx)
    case 'rse':
      return renderRseRoute(ctx)
    case 'link':
      return renderLinkRoute(ctx)
    case 'vrm':
      return renderVrmRoute(ctx)
    case 'note':
      if (!ctx.isOwner) return { html: `<div id="content" class="error">Login required</div><nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>` }
      return renderNoteRoute(ctx)
    case 'note-detail':
      if (!ctx.isOwner) return { html: `<div id="content" class="error">Login required</div><nav class="back-nav"><a href="/@${ctx.handle}">${ctx.handle}</a></nav>` }
      return renderNoteDetailRoute(route, ctx)
    case 'chat':
      return renderChatListRoute(ctx)
    case 'chat-thread':
      return renderChatThreadRoute(route, ctx)
    case 'chat-edit':
      return renderChatEditRoute(route, ctx)
    default:
      return renderUserHomeRoute(ctx, langList)
  }
}

// --- Setup functions ---

function setupEventHandlers(): void {
  const form = document.getElementById('header-form') as HTMLFormElement
  const input = document.getElementById('header-input') as HTMLInputElement

  form?.addEventListener('submit', (e) => {
    e.preventDefault()
    const handle = input.value.trim()
    if (handle) {
      navigate({ type: 'user', handle })
    }
  })

  const loginBtn = document.getElementById('login-btn')
  loginBtn?.addEventListener('click', async () => {
    const handle = input.value.trim() || currentHandle
    if (handle) {
      try {
        await login(handle)
      } catch (e) {
        console.error('Login failed:', e)
        alert('Login failed. Please check your handle.')
      }
    } else {
      alert('Please enter a handle first.')
    }
  })

  const logoutBtn = document.getElementById('logout-btn')
  logoutBtn?.addEventListener('click', async () => {
    await logout()
  })
}

function setupValidateButton(record: { value: unknown }): void {
  const validateBtn = document.getElementById('validate-btn')
  const resultDiv = document.getElementById('validate-result')
  if (!validateBtn || !resultDiv) return

  validateBtn.addEventListener('click', async () => {
    const collection = validateBtn.getAttribute('data-collection')
    if (!collection) return

    validateBtn.textContent = 'Validating...'
    ;(validateBtn as HTMLButtonElement).disabled = true
    resultDiv.innerHTML = ''

    try {
      const result = await validateRecord(collection, record.value)
      if (result.valid) {
        resultDiv.innerHTML = `<span class="validate-valid">✓ Valid</span>`
      } else {
        resultDiv.innerHTML = `<span class="validate-invalid">✗ Invalid</span><span class="validate-error">${result.error || 'Unknown error'}</span>`
      }
    } catch (err) {
      resultDiv.innerHTML = `<span class="validate-invalid">✗ Error</span><span class="validate-error">${err}</span>`
    }

    validateBtn.textContent = 'Validate'
    ;(validateBtn as HTMLButtonElement).disabled = false
  })
}

function setupRecordDelete(handle: string): void {
  setupAsyncButton('record-delete-btn', 'Deleting...', async (btn) => {
    const collection = btn.getAttribute('data-collection')
    const rkey = btn.getAttribute('data-rkey')
    if (!collection || !rkey) return
    if (!confirm('Are you sure you want to delete this record?')) {
      btn.textContent = 'Delete'
      btn.disabled = false
      return
    }
    await deleteRecord(collection, rkey)
    navigate({ type: 'collection', handle, collection })
  })
}

function setupPostEdit(collection: string): void {
  const editBtn = document.getElementById('post-edit-btn')
  const editForm = document.getElementById('post-edit-form')
  const postDisplay = document.getElementById('post-display')
  const cancelBtn = document.getElementById('post-edit-cancel')
  const titleInput = document.getElementById('post-edit-title') as HTMLInputElement
  const contentInput = document.getElementById('post-edit-content') as HTMLTextAreaElement

  if (!editBtn || !editForm) return

  editBtn.addEventListener('click', () => {
    if (postDisplay) postDisplay.style.display = 'none'
    editForm.style.display = 'block'
    editBtn.style.display = 'none'
  })

  cancelBtn?.addEventListener('click', () => {
    editForm.style.display = 'none'
    if (postDisplay) postDisplay.style.display = ''
    editBtn.style.display = ''
  })

  setupAsyncButton('post-edit-save', 'Saving...', async (btn) => {
    const rkey = btn.getAttribute('data-rkey')
    if (!rkey || !titleInput || !contentInput) return

    const title = titleInput.value.trim()
    const content = contentInput.value.trim()
    if (!title || !content) {
      alert('Title and content are required')
      btn.textContent = 'Save'
      btn.disabled = false
      return
    }

    await updatePost(collection, rkey, title, content)
    render(parseRoute())
  })
}

function setupRecordMerge(): void {
  setupAsyncButton('record-merge-btn', 'Merging...', async (btn) => {
    const collection = btn.getAttribute('data-collection') || ''
    const rkey = btn.getAttribute('data-rkey') || ''
    if (!collection || !rkey) return

    const agent = getAgent()
    if (!agent) return

    const existing = await agent.com.atproto.repo.getRecord({
      repo: agent.assertDid,
      collection,
      rkey,
    })

    const oldValue = existing.data.value as Record<string, unknown>

    const newRecord: Record<string, unknown> = {
      $type: collection,
      site: (typeof oldValue.site === 'string' && oldValue.site) || window.location.origin,
      title: (typeof oldValue.title === 'string' ? oldValue.title : '') || '',
      publishedAt: (typeof oldValue.publishedAt === 'string' && oldValue.publishedAt)
        || (typeof oldValue.createdAt === 'string' && oldValue.createdAt)
        || new Date().toISOString(),
    }

    const oldContent = oldValue.content
    if (oldContent && typeof oldContent === 'object' && (oldContent as Record<string, unknown>).$type) {
      newRecord.content = oldContent
    } else if (typeof oldContent === 'string') {
      newRecord.content = { $type: `${collection}#markdown`, text: oldContent }
    } else {
      newRecord.content = { $type: `${collection}#markdown`, text: '' }
    }

    if (oldValue.description) newRecord.description = oldValue.description
    if (oldValue.tags) newRecord.tags = oldValue.tags
    if (oldValue.langs) newRecord.langs = oldValue.langs
    if (oldValue.translations) newRecord.translations = oldValue.translations
    if (oldValue.path) newRecord.path = oldValue.path
    if (oldValue.root) newRecord.root = oldValue.root
    if (oldValue.parent) newRecord.parent = oldValue.parent
    if (oldValue.updatedAt) newRecord.updatedAt = oldValue.updatedAt

    if (!confirm(`Merge record to new format?\n\nCollection: ${collection}\nRkey: ${rkey}`)) {
      btn.textContent = 'Merge'
      btn.disabled = false
      return
    }

    await agent.com.atproto.repo.putRecord({
      repo: agent.assertDid,
      collection,
      rkey,
      record: newRecord,
    })

    btn.textContent = 'Merged!'
    setTimeout(() => { render(parseRoute()) }, 1000)
  })
}

function setupChatEdit(collection: string, handle: string): void {
  const form = document.getElementById('chat-edit-form') as HTMLFormElement
  const contentInput = document.getElementById('chat-edit-content') as HTMLTextAreaElement
  const saveBtn = document.getElementById('chat-edit-save') as HTMLButtonElement
  const statusEl = document.getElementById('chat-edit-status') as HTMLDivElement

  if (!form || !saveBtn) return

  form.addEventListener('submit', async (e) => {
    e.preventDefault()

    const rkey = saveBtn.getAttribute('data-rkey')
    if (!rkey || !contentInput) return

    const content = contentInput.value.trim()
    if (!content) {
      alert('Content is required')
      return
    }

    try {
      saveBtn.textContent = 'Saving...'
      saveBtn.disabled = true

      await updateChat(collection, rkey, content)

      statusEl.innerHTML = '<span class="chat-edit-success">Saved!</span>'
      setTimeout(() => { navigate({ type: 'chat-thread', handle, rkey }) }, 1000)
    } catch (err) {
      console.error('Update failed:', err)
      statusEl.innerHTML = `<span class="chat-edit-error">Error: ${err}</span>`
      saveBtn.textContent = 'Save'
      saveBtn.disabled = false
    }
  })
}

function setupLinkEdit(): void {
  const editBtn = document.getElementById('link-edit-btn')
  const editForm = document.getElementById('link-edit-form')
  const linkDisplay = document.getElementById('link-display')
  const editList = document.getElementById('link-edit-list')
  const cancelBtn = document.getElementById('link-edit-cancel')
  const addBtn = document.getElementById('link-add-btn')
  const addService = document.getElementById('link-add-service') as HTMLSelectElement
  const addUsername = document.getElementById('link-add-username') as HTMLInputElement
  const statusEl = document.getElementById('link-edit-status')

  if (!editBtn || !editForm || !editList) return

  let linkIndex = editList.querySelectorAll('.link-edit-item').length

  editBtn.addEventListener('click', () => {
    if (linkDisplay) linkDisplay.style.display = 'none'
    editForm.style.display = 'block'
    editBtn.style.display = 'none'
  })

  cancelBtn?.addEventListener('click', () => {
    editForm.style.display = 'none'
    if (linkDisplay) linkDisplay.style.display = ''
    editBtn.style.display = ''
  })

  addBtn?.addEventListener('click', () => {
    const service = addService.value
    const username = addUsername.value.trim()
    if (!username) return

    const serviceNames: Record<string, string> = { github: 'GitHub', x: 'X', youtube: 'YouTube' }
    const options = ['github', 'x', 'youtube'].map(s =>
      `<option value="${s}" ${s === service ? 'selected' : ''}>${serviceNames[s]}</option>`
    ).join('')

    const newItem = document.createElement('div')
    newItem.className = 'link-edit-item'
    newItem.dataset.index = String(linkIndex++)
    newItem.innerHTML = `
      <select class="link-edit-service" data-index="${newItem.dataset.index}">
        ${options}
      </select>
      <input type="text" class="link-edit-username" data-index="${newItem.dataset.index}" value="${username}" placeholder="username">
      <button type="button" class="link-edit-remove" data-index="${newItem.dataset.index}">×</button>
    `
    editList.appendChild(newItem)
    addUsername.value = ''
  })

  editList.addEventListener('click', (e) => {
    const target = e.target as HTMLElement
    if (target.classList.contains('link-edit-remove')) {
      target.closest('.link-edit-item')?.remove()
    }
  })

  setupAsyncButton('link-edit-save', 'Saving...', async () => {
    const items = editList.querySelectorAll('.link-edit-item')
    const links: { service: string; username: string }[] = []

    items.forEach(item => {
      const service = (item.querySelector('.link-edit-service') as HTMLSelectElement)?.value
      const username = (item.querySelector('.link-edit-username') as HTMLInputElement)?.value.trim()
      if (service && username) {
        links.push({ service, username })
      }
    })

    if (addService && addUsername) {
      const pendingUsername = addUsername.value.trim()
      if (pendingUsername) {
        links.push({ service: addService.value, username: pendingUsername })
      }
    }

    await updateLinks(links)

    if (statusEl) statusEl.innerHTML = '<span class="link-edit-success">Saved!</span>'
    setTimeout(() => { render(parseRoute()) }, 1000)
  })
}

// --- Post-render setup ---

async function setupRouteHandlers(route: Route, ctx: RenderContext, result: RouteContentResult, langList: string[]): Promise<void> {
  await setupModeTabs(
    () => { render(parseRoute()) },
    langList,
    () => { render(parseRoute()) }
  )

  if (route.type === 'postpage' && isLoggedIn()) {
    setupPostForm(ctx.config.collection, () => {
      navigate({ type: 'user', handle: ctx.handle })
    })
  }

  if (ctx.isOwner) {
    setupRecordDelete(ctx.handle)
    setupPostEdit(ctx.config.collection)
  }

  if (route.type === 'chat-edit' && ctx.isOwner) {
    const chatCollection = ctx.config.chatCollection || 'ai.syui.log.chat'
    setupChatEdit(chatCollection, ctx.handle)
  }

  if (route.type === 'link' && ctx.isOwner) {
    setupLinkEdit()
  }

  if (result.currentRecord) {
    setupValidateButton(result.currentRecord)
  }

  if (route.type === 'post') {
    const contentEl = document.getElementById('content')
    if (contentEl) setupPostDetail(contentEl)
  }

  if (route.type === 'record' && ctx.isOwner) {
    setupRecordMerge()
  }

  if (route.type === 'note-detail' && route.rkey) {
    const noteCollection = 'ai.syui.note.post'
    const notePost = await getPost(ctx.did, noteCollection, route.rkey, ctx.localOnly)
    if (notePost && ctx.isOwner) {
      setupNoteDetail(notePost)
    }
  }

  if (route.type === 'vrm') {
    setupVrmPage()
  }

  if (route.type === 'card-old' && result.cardMigrationState?.oldApiUser && result.cardMigrationState?.oldApiCards) {
    setupMigrationButton(
      result.cardMigrationState.oldApiUser,
      result.cardMigrationState.oldApiCards,
      () => render(parseRoute())
    )
  }
}

// --- Main render ---

async function render(route: Route): Promise<void> {
  if (!isFirstRender) {
    showLoading(app)
  }

  try {
    const config = await getConfig()
    configHandle = config.handle

    if (config.color) {
      document.documentElement.style.setProperty('--btn-color', config.color)
    }
    if (config.title) {
      document.title = config.title
    }

    const oauthEnabled = config.oauth !== false

    // Handle OAuth callback
    const searchParams = new URLSearchParams(window.location.search)
    const hashParams = window.location.hash ? new URLSearchParams(window.location.hash.slice(1)) : null
    if (oauthEnabled && (searchParams.has('code') || searchParams.has('state') || hashParams?.has('code') || hashParams?.has('state'))) {
      await handleCallback()
    }

    // Auto-login redirect
    if (oauthEnabled && searchParams.has('login')) {
      const loginHandle = searchParams.get('login')
      if (loginHandle) {
        window.history.replaceState({}, '', window.location.pathname)
        await login(loginHandle)
        return
      }
    }

    if (oauthEnabled) {
      await restoreSession()
    }

    // Redirect logged-in user from root to user page
    if (route.type === 'home' && isLoggedIn()) {
      const loggedInHandle = getLoggedInHandle()
      if (loggedInHandle) {
        navigate({ type: 'user', handle: loggedInHandle })
        return
      }
    }

    // Determine handle and data source
    let handle: string
    let localOnly: boolean
    let did: string | null

    if (route.type === 'home') {
      handle = config.handle
      localOnly = true
      did = config.did || null
    } else if (route.handle) {
      handle = route.handle
      localOnly = handle === config.handle
      did = localOnly ? (config.did || null) : null
    } else {
      handle = config.handle
      localOnly = true
      did = config.did || null
    }

    currentHandle = handle

    if (!did) {
      did = await resolveHandle(handle)
    }

    if (!did) {
      app.innerHTML = `
        ${renderHeader(handle, oauthEnabled)}
        <div class="error">Could not resolve handle: ${handle}</div>
        ${renderFooter(config.handle, config.repoUrl)}
      `
      setupEventHandlers()
      return
    }

    const [profile, webUrl, collections] = await Promise.all([
      getProfile(did, localOnly),
      getWebUrl(handle),
      describeRepo(did)
    ])

    const posts = await getPosts(did, config.collection, localOnly)
    let langList = collectPostLangs(posts)

    const noteCollection = 'ai.syui.note.post'
    const notePosts = localOnly ? await getPosts(did, noteCollection, true) : []

    const loggedInDid = getLoggedInDid()
    const isOwner = isLoggedIn() && loggedInDid === did

    // Build context
    const ctx: RenderContext = {
      config, did, handle, localOnly, isOwner, loggedInDid,
      profile, collections, webUrl, oauthEnabled, posts, notePosts
    }

    // Build page HTML
    let html = renderHeader(handle, oauthEnabled)

    const activeTab = route.type === 'postpage' ? 'post' :
      (route.type === 'chat' || route.type === 'chat-thread' || route.type === 'chat-edit') ? 'chat' :
      route.type === 'link' ? 'link' :
      (route.type === 'note' || route.type === 'note-detail') ? 'note' :
      (route.type === 'atbrowser' || route.type === 'service' || route.type === 'collection' || route.type === 'record' ? 'browser' : 'blog')
    html += renderModeTabs(handle, activeTab, localOnly, notePosts.length > 0)

    if (profile) {
      html += await renderProfile(did, profile, handle, webUrl, localOnly, collections)
    }

    // Render route content
    const result = await renderRouteContent(route, ctx, langList)
    html += result.html
    if (result.langList) {
      langList = result.langList
    }

    html += renderFooter(config.handle, config.repoUrl)

    app.innerHTML = html
    hideLoading(app)
    setupEventHandlers()

    // Post-render setup
    await setupRouteHandlers(route, ctx, result, langList)

  } catch (error) {
    console.error('Render error:', error)
    app.innerHTML = `
      ${renderHeader(currentHandle, false)}
      <div class="error">Error: ${error}</div>
      ${renderFooter(configHandle, undefined)}
    `
    setupEventHandlers()
  } finally {
    hideLoading(app)
    isFirstRender = false
  }
}

// Initial render
render(parseRoute())

// Handle route changes
onRouteChange(render)
