import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import { usePage } from '@inertiajs/react'
import SeoHead from '../../components/seo/SeoHead'
import TagInput from '../../components/tags/TagInput'
import UploadWizard from '../../components/upload/UploadWizard'
import UploadDescriptionEditor from '../../components/upload/UploadDescriptionEditor'
import Checkbox from '../../Components/ui/Checkbox'
import { mapUploadErrorNotice, mapUploadResultNotice } from '../../lib/uploadNotices'
import { validateMarkdownLiteContent } from '../../utils/contentValidation'

const phases = {
  idle: 'idle',
  initializing: 'initializing',
  ready: 'ready',
  uploading: 'uploading',
  finishing: 'finishing',
  polling: 'polling',
  cancelling: 'cancelling',
  success: 'success',
  error: 'error',
}

const DEFAULT_CHUNK_REQUEST_TIMEOUT_MS = 45000
const MIN_CHUNK_SIZE_BYTES = 256 * 1024

function formatChunkSize(bytes) {
  if (!Number.isFinite(bytes) || bytes <= 0) return '0 KB'
  if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(bytes % (1024 * 1024) === 0 ? 0 : 1)} MB`
  return `${Math.max(1, Math.round(bytes / 1024))} KB`
}

function isRequestTooLarge(error) {
  return Number(error?.response?.status || 0) === 413
}

const initialState = {
  phase: phases.idle,
  sessionId: null,
  uploadToken: null,
  file: null,
  filePreviewUrl: null,
  previewUrl: null,
  progress: 0,
  pipelineStatus: null,
  failureReason: null,
  error: null,
  cancelledAt: null,
  notices: [],
  artworkId: null,
  draftId: null,
  metadata: {
    title: '',
    type: '',
    category: '',
    tags: '',
    description: '',
    isMature: false,
    licenseAccepted: false,
  },
}

const STORAGE_VERSION = 1

function storageKey(userId) {
  return `sb.upload.session.${userId}.v${STORAGE_VERSION}`
}

function readStoredSession(userId) {
  try {
    const raw = window.localStorage.getItem(storageKey(userId))
    if (!raw) return null
    const parsed = JSON.parse(raw)
    if (!parsed || typeof parsed !== 'object') return null
    return parsed
  } catch (error) {
    return null
  }
}

function writeStoredSession(userId, payload) {
  try {
    window.localStorage.setItem(storageKey(userId), JSON.stringify(payload))
  } catch (error) {
    // ignore write failures
  }
}

function clearStoredSession(userId) {
  try {
    window.localStorage.removeItem(storageKey(userId))
  } catch (error) {
    // ignore removal failures
  }
}

function reducer(state, action) {
  switch (action.type) {
    case 'SET_DRAFT':
      return { ...state, draftId: action.draftId, artworkId: action.artworkId ?? state.artworkId }
    case 'SET_FILE':
      return { ...state, file: action.file, filePreviewUrl: action.previewUrl, error: null, cancelledAt: null }
    case 'SET_METADATA':
      return { ...state, metadata: { ...state.metadata, ...action.payload } }
    case 'INIT_START':
      return { ...state, phase: phases.initializing, error: null, cancelledAt: null }
    case 'INIT_SUCCESS':
      return { ...state, phase: phases.ready, sessionId: action.sessionId, uploadToken: action.uploadToken, pipelineStatus: action.status }
    case 'RESTORE_SESSION':
      return { ...state, phase: phases.ready, sessionId: action.sessionId, uploadToken: action.uploadToken, pipelineStatus: action.status ?? state.pipelineStatus }
    case 'RESUME_SESSION':
      return { ...state, phase: phases.polling, sessionId: action.sessionId, pipelineStatus: action.status ?? state.pipelineStatus }
    case 'INIT_ERROR':
      return { ...state, phase: phases.error, error: action.error }
    case 'UPLOAD_START':
      return { ...state, phase: phases.uploading, progress: 0, error: null }
    case 'UPLOAD_PROGRESS':
      return { ...state, progress: action.progress }
    case 'UPLOAD_ERROR':
      return { ...state, phase: phases.error, error: action.error }
    case 'FINISH_START':
      return { ...state, phase: phases.finishing, error: null }
    case 'FINISH_SUCCESS':
      return { ...state, phase: phases.polling, pipelineStatus: action.status, previewUrl: action.previewUrl ?? null }
    case 'FINISH_ERROR':
      return { ...state, phase: phases.error, error: action.error }
    case 'CANCEL_START':
      return { ...state, phase: phases.cancelling, error: null }
    case 'CANCEL_SUCCESS':
      return { ...state, phase: phases.idle, cancelledAt: Date.now() }
    case 'CLEAR_CANCELLED':
      return { ...state, cancelledAt: null }
    case 'CANCEL_ERROR':
      return { ...state, phase: phases.error, error: action.error }
    case 'STATUS_UPDATE':
      return { ...state, pipelineStatus: action.status, progress: action.progress ?? state.progress, failureReason: action.failureReason ?? null }
    case 'STATUS_ERROR':
      return { ...state, phase: phases.error, error: action.error }
    case 'SUCCESS':
      return { ...state, phase: phases.success }
    case 'RESET':
      return { ...initialState, draftId: state.draftId, cancelledAt: state.cancelledAt }
    case 'PUSH_NOTICE':
      return { ...state, notices: [...state.notices, action.notice] }
    case 'REMOVE_NOTICE':
      return { ...state, notices: state.notices.filter((notice) => notice.id !== action.id) }
    default:
      return state
  }
}

const MAX_CHUNK_RETRIES = 3
const RETRY_DELAY_MS = 900

function normalizeUiTag(rawTag) {
  const raw = String(rawTag ?? '').trim().toLowerCase()
  if (!raw) return ''

  return raw
    .replace(/\s+/g, '-')
    .replace(/[^a-z0-9_-]/g, '')
    .replace(/-+/g, '-')
    .replace(/_+/g, '_')
    .replace(/^[-_]+|[-_]+$/g, '')
    .slice(0, 32)
}

function parseUiTags(csvValue) {
  return String(csvValue ?? '')
    .split(/[\n,]+/)
    .map((item) => normalizeUiTag(item))
    .filter(Boolean)
}

function getTypeKey(ct) {
  if (!ct) return 'default'
  if (ct.slug && typeof ct.slug === 'string') {
    return ct.slug.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '')
  }
  return String(ct.name || '').toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '')
}

function useUploadMachine({ draftId = null, filesCdnUrl = '', chunkSize, chunkRequestTimeoutMs, userId = null } = {}) {
  const [state, dispatch] = useReducer(reducer, { ...initialState, draftId })
  const pollRef = useRef(null)
  const adaptiveChunkSizeRef = useRef(Math.max(1, Number(chunkSize || 0)))
  const effectiveChunkRequestTimeoutMs = (() => {
    const parsed = Number(chunkRequestTimeoutMs)
    return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : DEFAULT_CHUNK_REQUEST_TIMEOUT_MS
  })()

  const extractErrorMessage = useCallback((error, fallback) => {
    const message = error?.response?.data?.message
    if (message && typeof message === 'string') return message
    const errors = error?.response?.data?.errors
    if (errors && typeof errors === 'object') {
      const firstKey = Object.keys(errors)[0]
      if (firstKey && Array.isArray(errors[firstKey]) && errors[firstKey][0]) {
        return errors[firstKey][0]
      }
    }
    return fallback
  }, [])

  const pushNotice = useCallback((type, message) => {
    const normalizedType = ['success', 'warning', 'error'].includes(String(type || '').toLowerCase())
      ? String(type).toLowerCase()
      : 'error'
    const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`
    dispatch({ type: 'PUSH_NOTICE', notice: { id, type: normalizedType, message } })
    window.setTimeout(() => {
      dispatch({ type: 'REMOVE_NOTICE', id })
    }, 4500)
  }, [])

  const pushMappedNotice = useCallback((notice) => {
    if (!notice?.message) return
    pushNotice(notice.type || 'error', notice.message)
  }, [pushNotice])

  const previewUrl = useMemo(() => {
    if (state.previewUrl) return state.previewUrl
    if (!state.filePreviewUrl) return null
    return state.filePreviewUrl
  }, [state.previewUrl, state.filePreviewUrl])

  const stopPolling = useCallback(() => {
    if (pollRef.current) {
      window.clearInterval(pollRef.current)
      pollRef.current = null
    }
  }, [])

  const startPolling = useCallback(() => {
    if (!state.sessionId) return
    stopPolling()
    pollRef.current = window.setInterval(async () => {
      try {
        const res = await window.axios.get(`/api/uploads/status/${state.sessionId}`, {
          headers: state.uploadToken ? { 'X-Upload-Token': state.uploadToken } : undefined,
          params: state.uploadToken ? { upload_token: state.uploadToken } : undefined,
        })
        const data = res.data || {}
        dispatch({
          type: 'STATUS_UPDATE',
          status: data.status,
          progress: typeof data.progress === 'number' ? data.progress : state.progress,
          failureReason: data.failure_reason,
        })

        if (data.status === 'processed') {
          dispatch({ type: 'SUCCESS' })
          stopPolling()
        }

        if (data.failure_reason) {
          dispatch({ type: 'STATUS_ERROR', error: data.failure_reason })
          stopPolling()
        }
      } catch (error) {
        dispatch({ type: 'STATUS_ERROR', error: 'Status check failed.' })
        stopPolling()
      }
    }, 2500)
  }, [state.sessionId, state.uploadToken, state.progress, stopPolling])

  useEffect(() => {
    if (draftId && !state.sessionId) {
      dispatch({ type: 'RESUME_SESSION', sessionId: draftId, status: 'resuming' })
    }
  }, [draftId, state.sessionId])

  useEffect(() => {
    if (state.phase === phases.polling) {
      startPolling()
    }

    return () => stopPolling()
  }, [state.phase, startPolling, stopPolling])

  useEffect(() => {
    return () => {
      if (state.filePreviewUrl) {
        URL.revokeObjectURL(state.filePreviewUrl)
      }
    }
  }, [state.filePreviewUrl])

  const initSession = useCallback(async () => {
    dispatch({ type: 'INIT_START' })
    try {
      const res = await window.axios.post('/api/uploads/init', { client: 'web' })
      const data = res.data || {}
      dispatch({
        type: 'INIT_SUCCESS',
        sessionId: data.session_id,
        uploadToken: data.upload_token,
        status: data.status,
      })
      if (userId && data.session_id && data.upload_token && state.file) {
        writeStoredSession(userId, {
          session_id: data.session_id,
          upload_token: data.upload_token,
          file_name: state.file.name,
          file_size: state.file.size,
          updated_at: Date.now(),
        })
      }
      return { sessionId: data.session_id, uploadToken: data.upload_token }
    } catch (error) {
      const notice = mapUploadErrorNotice(error, 'Failed to initialize upload session.')
      dispatch({ type: 'INIT_ERROR', error: notice.message })
      pushMappedNotice(notice)
      return null
    }
  }, [state.file, userId, pushMappedNotice])

  const createDraft = useCallback(async () => {
    if (state.artworkId) return state.artworkId
    try {
      const res = await window.axios.post('/api/artworks', {
        title: state.metadata.title,
        category: state.metadata.category,
        tags: state.metadata.tags,
        description: state.metadata.description,
        is_mature: state.metadata.isMature,
        license: state.metadata.licenseAccepted,
      })

      const data = res.data || {}
      const artworkId = data.artwork_id ?? data.id
      if (artworkId) {
        dispatch({ type: 'SET_DRAFT', draftId: state.draftId, artworkId })
        return artworkId
      }
      throw new Error('missing_artwork_id')
    } catch (error) {
      const notice = mapUploadErrorNotice(error, 'Unable to create draft metadata.')
      dispatch({ type: 'FINISH_ERROR', error: notice.message })
      pushMappedNotice(notice)
      return null
    }
  }, [state.artworkId, state.metadata, state.draftId, pushMappedNotice])

  const syncArtworkTags = useCallback(async (artworkId) => {
    const tags = Array.from(new Set(parseUiTags(state.metadata.tags)))
    if (tags.length === 0) {
      return true
    }

    try {
      await window.axios.put(`/api/artworks/${artworkId}/tags`, { tags })
      return true
    } catch (error) {
      const notice = mapUploadErrorNotice(error, 'Tag sync failed. Upload will continue.')
      pushMappedNotice({ ...notice, type: 'warning' })
      return false
    }
  }, [state.metadata.tags, pushMappedNotice])

  const fetchStatus = useCallback(async (sessionId, uploadToken) => {
    const res = await window.axios.get(`/api/uploads/status/${sessionId}`, {
      headers: uploadToken ? { 'X-Upload-Token': uploadToken } : undefined,
      params: uploadToken ? { upload_token: uploadToken } : undefined,
    })
    return res.data || {}
  }, [])

  const validateStoredSession = useCallback(async (file) => {
    if (!userId) return null
    const stored = readStoredSession(userId)
    if (!stored) return null

    if (stored.file_name !== file.name || stored.file_size !== file.size) {
      clearStoredSession(userId)
      return null
    }

    try {
      const data = await fetchStatus(stored.session_id, stored.upload_token)
      if (!data || !data.session_id) {
        clearStoredSession(userId)
        return null
      }
      dispatch({ type: 'RESTORE_SESSION', sessionId: stored.session_id, uploadToken: stored.upload_token, status: data.status })
      return { sessionId: stored.session_id, uploadToken: stored.upload_token }
    } catch (error) {
      clearStoredSession(userId)
      return null
    }
  }, [fetchStatus, userId])

  const uploadChunk = useCallback(async (sessionId, uploadToken, blob, offset, totalSize, attempt) => {
    const payload = new FormData()
    payload.append('session_id', sessionId)
    payload.append('offset', String(offset))
    payload.append('chunk_size', String(blob.size))
    payload.append('total_size', String(totalSize))
    payload.append('chunk', blob)
    payload.append('upload_token', uploadToken)

    try {
      const res = await window.axios.post('/api/uploads/chunk', payload, {
        timeout: effectiveChunkRequestTimeoutMs,
        headers: uploadToken ? { 'X-Upload-Token': uploadToken } : undefined,
      })

      const data = res.data || {}
      if (typeof data.progress === 'number') {
        dispatch({ type: 'UPLOAD_PROGRESS', progress: data.progress })
      }

      return data
    } catch (error) {
      if (isRequestTooLarge(error)) {
        throw error
      }
      if (attempt < MAX_CHUNK_RETRIES) {
        await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS * (attempt + 1)))
        return uploadChunk(sessionId, uploadToken, blob, offset, totalSize, attempt + 1)
      }
      throw error
    }
  }, [effectiveChunkRequestTimeoutMs])

  const uploadFile = useCallback(async (sessionId, uploadToken, file) => {
    dispatch({ type: 'UPLOAD_START' })

    let status
    try {
      status = await fetchStatus(sessionId, uploadToken)
    } catch (error) {
      const notice = mapUploadErrorNotice(error, 'Unable to resume upload.')
      dispatch({ type: 'UPLOAD_ERROR', error: notice.message })
      pushMappedNotice(notice)
      return false
    }

    let offset = Number(status.received_bytes || 0)
    const totalSize = file.size
    if (offset > totalSize) offset = 0

    while (offset < totalSize) {
      const activeChunkSize = Math.max(MIN_CHUNK_SIZE_BYTES, Number(adaptiveChunkSizeRef.current || chunkSize || MIN_CHUNK_SIZE_BYTES))
      const nextOffset = Math.min(offset + activeChunkSize, totalSize)
      const chunk = file.slice(offset, nextOffset)

      try {
        const data = await uploadChunk(sessionId, uploadToken, chunk, offset, totalSize, 0)
        offset = Number(data.received_bytes ?? nextOffset)
        if (offset < nextOffset) {
          offset = nextOffset
        }
      } catch (error) {
        if (isRequestTooLarge(error) && activeChunkSize > MIN_CHUNK_SIZE_BYTES) {
          const nextChunkSize = Math.max(MIN_CHUNK_SIZE_BYTES, Math.floor(activeChunkSize / 2))
          if (nextChunkSize < activeChunkSize) {
            adaptiveChunkSizeRef.current = nextChunkSize
            pushNotice('warning', `Server rejected ${formatChunkSize(activeChunkSize)} chunks. Retrying with ${formatChunkSize(nextChunkSize)} chunks.`)
            continue
          }
        }
        const notice = mapUploadErrorNotice(error, 'File upload failed. Please retry.')
        dispatch({ type: 'UPLOAD_ERROR', error: notice.message })
        pushMappedNotice(notice)
        return false
      }
    }

    return true
  }, [chunkSize, fetchStatus, uploadChunk, pushMappedNotice])

  const finishUpload = useCallback(async (sessionId, uploadToken, artworkId) => {
    dispatch({ type: 'FINISH_START' })
    try {
      const res = await window.axios.post(
        '/api/uploads/finish',
        {
          session_id: sessionId,
          artwork_id: artworkId,
          upload_token: uploadToken,
          file_name: String(state.file?.name || ''),
        },
        { headers: { 'X-Upload-Token': uploadToken } }
      )
      const data = res.data || {}
      const previewPath = data.preview_path
      const previewUrl = previewPath ? `${filesCdnUrl}/${previewPath}` : null
      dispatch({ type: 'FINISH_SUCCESS', status: data.status, previewUrl })

      const finishNotice = mapUploadResultNotice(data, {
        fallbackType: String(data.status || '').toLowerCase() === 'queued' ? 'warning' : 'success',
        fallbackMessage: String(data.status || '').toLowerCase() === 'queued'
          ? 'Upload received. Processing is queued.'
          : 'Upload finalized successfully.',
      })
      pushMappedNotice(finishNotice)

      if (userId) {
        clearStoredSession(userId)
      }
      return true
    } catch (error) {
      const notice = mapUploadErrorNotice(error, 'Upload finalization failed.')
      dispatch({ type: 'FINISH_ERROR', error: notice.message })
      pushMappedNotice(notice)
      return false
    }
  }, [filesCdnUrl, userId, pushMappedNotice])

  const startUpload = useCallback(async () => {
    if (!state.file) {
      const message = 'Please select a file first.'
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    if (!state.metadata.title.trim()) {
      const message = 'Title is required to start the upload.'
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    if (!state.metadata.type) {
      const message = 'Please select a Type.'
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    if (!state.metadata.category) {
      const message = 'Please select a Category.'
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    if (parseUiTags(state.metadata.tags).length === 0) {
      const message = 'Please add at least one tag.'
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    if (!state.metadata.description.trim()) {
      const message = 'Please provide a description.'
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    const descriptionErrors = validateMarkdownLiteContent(state.metadata.description)
    if (descriptionErrors.length > 0) {
      const message = descriptionErrors[0]
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    if (!state.metadata.licenseAccepted) {
      const message = 'You must confirm ownership of the artwork.'
      dispatch({ type: 'UPLOAD_ERROR', error: message })
      pushNotice('error', message)
      return
    }

    let init = null
    if (userId) {
      init = await validateStoredSession(state.file)
    }

    if (!init) {
      init = await initSession()
    }

    if (!init) return

    const artworkId = await createDraft()
    if (!artworkId) return

    await syncArtworkTags(artworkId)

    const ok = await uploadFile(init.sessionId, init.uploadToken, state.file)
    if (!ok) return

    await finishUpload(init.sessionId, init.uploadToken, artworkId)
  }, [state.file, state.metadata, initSession, createDraft, syncArtworkTags, uploadFile, finishUpload, validateStoredSession, userId, pushNotice])

  const cancelUpload = useCallback(async () => {
    dispatch({ type: 'CANCEL_START' })

    if (!state.sessionId || !state.uploadToken) {
      if (userId) {
        clearStoredSession(userId)
      }
      dispatch({ type: 'CANCEL_SUCCESS' })
      dispatch({ type: 'RESET' })
      pushNotice('warning', 'Upload cancelled.')
      return
    }

    try {
      await window.axios.post(
        '/api/uploads/cancel',
        { session_id: state.sessionId, upload_token: state.uploadToken },
        { headers: { 'X-Upload-Token': state.uploadToken } }
      )
    } catch (error) {
      // ignore error, always reset local state to avoid leaking session info
    }

    if (userId) {
      clearStoredSession(userId)
    }
    dispatch({ type: 'CANCEL_SUCCESS' })
    dispatch({ type: 'RESET' })
    pushNotice('warning', 'Upload cancelled.')
  }, [state.sessionId, state.uploadToken, userId, pushNotice])

  return {
    state,
    dispatch,
    previewUrl,
    startUpload,
    initSession,
    cancelUpload,
    pushNotice,
  }
}

export default function UploadPage({ draftId = null, filesCdnUrl = '', chunkSize, chunkRequestTimeoutMs } = {}) {
  const { props } = usePage()
  const pageTitle = 'Upload Artwork — Creator Studio'
  const pageDescription = 'Submit a new artwork, complete the required metadata, and publish it from Skinbase Creator Studio.'

  const windowFlags = window?.SKINBASE_FLAGS || {}
  const propFlagRaw = props?.feature_flags?.uploads_v2
  const windowFlagRaw = (windowFlags?.uploads && windowFlags.uploads.v2) ?? windowFlags?.uploads_v2

  const toBooleanFlag = (value) => {
    if (typeof value === 'boolean') return value
    if (typeof value === 'number') return value === 1
    if (typeof value === 'string') {
      const normalized = value.trim().toLowerCase()
      return ['1', 'true', 'yes', 'on'].includes(normalized)
    }
    return false
  }

  const uploadsV2Enabled = toBooleanFlag(propFlagRaw) || toBooleanFlag(windowFlagRaw)

  if (uploadsV2Enabled) {
    return (
      <section className="min-h-[calc(100vh-4rem)] bg-[#07111c] text-slate-100">
        <SeoHead seo={{ title: pageTitle, description: pageDescription, robots: 'noindex, nofollow' }} />
        <div className="relative isolate">
          <div className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[420px] bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.22),_transparent_32%),radial-gradient(circle_at_top_right,_rgba(251,146,60,0.16),_transparent_30%),linear-gradient(180deg,_rgba(8,17,28,0.98),_rgba(7,17,28,1))]" />
          <div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
            {/* ── Wizard ─────────────────────────────────────────────────────── */}
            <div className="rounded-[32px] border border-white/10 bg-[#08111c]/92 shadow-[0_30px_120px_rgba(2,8,23,0.38)]">
              <div className="px-4 py-5 sm:px-6 lg:px-8 lg:py-8">
                <UploadWizard
                  initialDraftId={draftId ?? null}
                  chunkSize={chunkSize}
                  chunkRequestTimeoutMs={chunkRequestTimeoutMs}
                  contentTypes={Array.isArray(props?.content_types) ? props.content_types : []}
                  suggestedTags={Array.isArray(props?.suggested_tags) ? props.suggested_tags : []}
                  eligibleWorlds={Array.isArray(props?.eligible_worlds) ? props.eligible_worlds : []}
                  groupOptions={Array.isArray(props?.group_options) ? props.group_options : []}
                  contributorOptionsByGroup={props?.contributor_options_by_group && typeof props.contributor_options_by_group === 'object' ? props.contributor_options_by_group : {}}
                  initialGroupSlug={props?.initial_group || ''}
                  currentUserId={props?.auth?.user?.id ?? null}
                />
              </div>
            </div>

            {/* ── Help / info section ─────────────────────────────────────────── */}
            <div className="mt-8 grid gap-8 lg:grid-cols-[1.45fr_0.85fr] lg:items-start lg:gap-10">
              <div>
                <p className="text-[11px] uppercase tracking-[0.28em] text-sky-200/50">Skinbase Upload Studio</p>
                <h2 className="mt-3 max-w-3xl text-xl font-semibold tracking-tight text-white/70 sm:text-2xl">
                  Upload artwork with less friction and better control.
                </h2>
                <p className="mt-3 max-w-2xl text-sm leading-7 text-slate-400">
                  The upload flow now stays focused on three steps: add the file, finish the metadata, then publish with confidence. The interface is simpler, but the secure processing pipeline stays intact.
                </p>

                <div className="mt-5 grid gap-3 sm:grid-cols-3">
                  {[
                    {
                      title: 'Fast onboarding',
                      description: 'Clearer file requirements and a friendlier first step.',
                    },
                    {
                      title: 'Safer publishing',
                      description: 'Processing state, rights, and readiness stay visible the whole time.',
                    },
                    {
                      title: 'Cleaner review',
                      description: 'Metadata and publish options are easier to scan before going live.',
                    },
                  ].map((item) => (
                    <div key={item.title} className="rounded-2xl border border-white/6 bg-white/[0.02] p-4">
                      <p className="text-sm font-semibold text-white/70">{item.title}</p>
                      <p className="mt-2 text-sm leading-6 text-slate-500">{item.description}</p>
                    </div>
                  ))}
                </div>
              </div>

              <aside className="rounded-[28px] border border-white/6 bg-white/[0.02] p-5">
                <p className="text-[11px] uppercase tracking-[0.24em] text-sky-200/50">Before you start</p>
                <div className="mt-4 space-y-4">
                  {[
                    'Choose the final file you actually want published. Replacing after upload requires a reset.',
                    'ZIP, RAR, and 7Z packs still need at least one screenshot for preview generation.',
                    'You will confirm rights and visibility before the final publish step.',
                  ].map((item, index) => (
                    <div key={item} className="flex items-start gap-3">
                      <span className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-white/10 bg-white/5 text-xs font-semibold text-slate-400">
                        {index + 1}
                      </span>
                      <p className="text-sm leading-6 text-slate-500">{item}</p>
                    </div>
                  ))}
                </div>
              </aside>
            </div>
          </div>
        </div>
      </section>
    )
  }

  const userId = props?.auth?.user?.id ?? null
  const suggestedTags = Array.isArray(props?.suggested_tags) ? props.suggested_tags : []
  const safeChunkSize = Math.max(1, Number(chunkSize || 0))
  const { state, dispatch, previewUrl, startUpload, cancelUpload } = useUploadMachine({
    draftId,
    filesCdnUrl,
    chunkSize: safeChunkSize,
    chunkRequestTimeoutMs,
    userId,
  })
  const fileInputRef = useRef(null)
  const [confirmCancel, setConfirmCancel] = useState(false)
  const [contentTypes, setContentTypes] = useState([])
  const [selectedParentCategory, setSelectedParentCategory] = useState(null)

  const availableTypes = useMemo(() => (props?.content_types && Array.isArray(props.content_types)) ? props.content_types : contentTypes, [props, contentTypes])
  const selectedType = useMemo(
    () => availableTypes.find((t) => String(t.id) === String(state.metadata.type)),
    [availableTypes, state.metadata.type]
  )
  const categoryOptions = useMemo(() => selectedType?.categories || [], [selectedType])
  const hasAtLeastOneTag = useMemo(() => parseUiTags(state.metadata.tags).length > 0, [state.metadata.tags])
  const descriptionErrors = useMemo(() => validateMarkdownLiteContent(state.metadata.description), [state.metadata.description])

  useEffect(() => {
    // Prefer server-provided props, else try fetching from API endpoints
    if (props?.content_types && Array.isArray(props.content_types)) {
      setContentTypes(props.content_types)
      return
    }

    let mounted = true
    ;(async () => {
      try {
        const res = await window.axios.get('/api/content-types')
        if (!mounted) return
        setContentTypes(res.data || [])
      } catch (e) {
        // ignore, content types optional here
      }
    })()

    return () => { mounted = false }
  }, [props])

  const onFileSelect = useCallback((file) => {
    if (!file) return
    const preview = URL.createObjectURL(file)
    dispatch({ type: 'SET_FILE', file, previewUrl: preview })
  }, [dispatch])

  const onDrop = useCallback((event) => {
    event.preventDefault()
    const file = event.dataTransfer.files?.[0]
    onFileSelect(file)
  }, [onFileSelect])

  const onBrowse = useCallback(() => {
    fileInputRef.current?.click()
  }, [])

  const onChange = useCallback((event) => {
    const file = event.target.files?.[0]
    onFileSelect(file)
  }, [onFileSelect])

  const statusLabel = state.pipelineStatus || state.phase

  useEffect(() => {
    if (!confirmCancel) return
    const timer = window.setTimeout(() => {
      setConfirmCancel(false)
    }, 5000)
    return () => window.clearTimeout(timer)
  }, [confirmCancel])

  useEffect(() => {
    if (!state.cancelledAt) return
    const timer = window.setTimeout(() => {
      dispatch({ type: 'CLEAR_CANCELLED' })
    }, 3500)
    return () => window.clearTimeout(timer)
  }, [state.cancelledAt, dispatch])

  return (
    <section className="min-h-[calc(100vh-4rem)] bg-gradient-to-b from-[#0d0f1b] via-[#111827] to-[#0b0c10] py-10 px-4">
      <div className="max-w-6xl mx-auto">
        {state.notices.length > 0 && (
          <div className="mb-6 space-y-2">
            {state.notices.map((notice) => (
              <div
                key={notice.id}
                role="alert"
                aria-live="polite"
                className={`rounded-xl border px-4 py-3 text-sm ${notice.type === 'error'
                  ? 'border-red-500/40 bg-red-500/10 text-red-100'
                  : notice.type === 'warning'
                    ? 'border-amber-400/40 bg-amber-400/10 text-amber-100'
                    : 'border-emerald-400/40 bg-emerald-400/10 text-emerald-100'}`}
              >
                {notice.message}
              </div>
            ))}
          </div>
        )}
        <div className="grid gap-8 lg:grid-cols-[1.1fr,0.9fr]">
          <div className="space-y-6">
              <SeoHead seo={{ title: pageTitle, description: pageDescription, robots: 'noindex, nofollow' }} />
            <div className="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_0_50px_rgba(59,130,246,0.15)]">
              <div className="flex items-center justify-between">
                <h1 className="text-3xl font-semibold tracking-tight">Upload artwork</h1>
                <span className="text-xs uppercase tracking-[0.25em] text-sky-300">Secure pipeline</span>
              </div>
              <p className="mt-2 text-sm text-white/70">All uploads are scanned, re-encoded, and published through the Skinbase pipeline.</p>

              <div
                className="mt-6 rounded-2xl border border-dashed border-white/30 bg-white/5 p-6 text-center transition hover:border-sky-400/60"
                onDragOver={(e) => e.preventDefault()}
                onDrop={onDrop}
              >
                <div className="mx-auto h-14 w-14 rounded-full bg-sky-500/20 grid place-items-center text-sky-200">
                  <i className="fa-solid fa-cloud-arrow-up text-xl" aria-hidden="true"></i>
                </div>
                <h2 className="mt-3 text-lg font-medium">Drag & drop your file</h2>
                <p className="text-sm text-white/60">JPG, PNG, or WebP. Up to 50MB.</p>
                <button type="button" onClick={onBrowse} className="mt-4 inline-flex items-center gap-2 rounded-full bg-sky-500 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-sky-500/30">
                  Browse files
                </button>
                <input ref={fileInputRef} type="file" accept="image/jpeg,image/png,image/webp" className="hidden" onChange={onChange} />
              </div>

              {state.file && (
                <div className="mt-6 flex items-center gap-4">
                  <div className="h-20 w-20 overflow-hidden rounded-xl border border-white/10">
                    <img src={previewUrl} alt="Preview" className="h-full w-full object-cover" />
                  </div>
                  <div>
                    <div className="text-sm font-semibold">{state.file.name}</div>
                    <div className="text-xs text-white/60">{(state.file.size / 1024 / 1024).toFixed(2)} MB</div>
                  </div>
                </div>
              )}

              <div className="mt-6 grid gap-4 sm:grid-cols-2">
                <label className="text-sm sm:col-span-2">
                  <span className="text-white/80">Title</span>
                  <input
                    value={state.metadata.title}
                    onChange={(e) => dispatch({ type: 'SET_METADATA', payload: { title: e.target.value } })}
                    className="mt-2 w-full rounded-xl border border-white/10 bg-white/10 px-3 py-2 text-white focus:border-sky-400 focus:outline-none"
                    placeholder="Name your artwork"
                  />
                </label>
              </div>

              <div className="mt-6 rounded-2xl border border-white/10 bg-gradient-to-br from-white/5 via-white/0 to-white/5 p-4 sm:p-5">
                <div className="flex flex-wrap items-center justify-between gap-2">
                  <div>
                    <div className="text-sm font-semibold text-white">Choose a type</div>
                    <div className="text-xs text-white/60">Step 1: Pick what kind of artwork this is.</div>
                  </div>
                  <span className="rounded-full border border-sky-400/40 bg-sky-400/10 px-3 py-1 text-[11px] uppercase tracking-[0.2em] text-sky-200">Type</span>
                </div>

                {availableTypes.length === 0 ? (
                  <div className="mt-4 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/60">
                    No content types available.
                  </div>
                ) : (
                  <div className="mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
                    {availableTypes.map((ct) => {
                      const active = String(ct.id) === String(state.metadata.type)
                      const iconKey = getTypeKey(ct)
                      const iconPath = ct?.mascot_url || `/gfx/mascot_${iconKey}.webp`
                      return (
                        <button
                          key={ct.id}
                          type="button"
                          onClick={() => dispatch({ type: 'SET_METADATA', payload: { type: String(ct.id), category: '' } })}
                          className={`group rounded-2xl border px-4 py-3 text-left transition ${active
                            ? 'border-emerald-400/60 bg-emerald-500/10 shadow-[0_0_25px_rgba(16,185,129,0.25)]'
                            : 'border-white/10 bg-white/5 hover:border-sky-400/40 hover:bg-white/10'}`}
                          aria-pressed={active}
                        >
                          <div className="flex items-center gap-3">
                            <div className={`h-14 w-10 rounded-xl text-sm font-semibold overflow-hidden relative ${active ? '' : ''}`}>
                              <img
                                src={iconPath}
                                alt={`${ct.name} icon`}
                                className="absolute inset-0 h-full w-full object-contain object-center z-0"
                                onLoad={(e) => {
                                  const letter = document.getElementById(`type-letter-${ct.id}`)
                                  if (letter) letter.style.display = 'none'
                                }}
                                onError={(e) => {
                                  e.currentTarget.style.display = 'none'
                                  const letter = document.getElementById(`type-letter-${ct.id}`)
                                  if (letter) letter.style.display = 'grid'
                                }}
                              />
                              <div id={`type-letter-${ct.id}`} className={`relative z-10 grid h-14 w-10 place-items-center ${active ? 'text-emerald-200' : 'text-white/70'}`}>
                                {ct.name?.slice(0, 1)?.toUpperCase() || '?'}
                              </div>
                            </div>
                            <div>
                              <div className="text-sm font-semibold text-white">{ct.name}</div>
                              <div className="text-xs text-white/60">Select to reveal categories</div>
                            </div>
                          </div>
                          <div className={`mt-3 h-1 w-full rounded-full ${active ? 'bg-emerald-400/80' : 'bg-white/10 group-hover:bg-sky-400/40'}`}></div>
                        </button>
                      )
                    })}
                  </div>
                )}
              </div>

              <div className="mt-4 rounded-2xl border border-white/10 bg-white/5 p-4 sm:p-5">
                <div className="flex flex-wrap items-center justify-between gap-2">
                  <div>
                    <div className="text-sm font-semibold text-white">Choose a category</div>
                    <div className="text-xs text-white/60">Step 2: Pick a subcategory inside the type.</div>
                  </div>
                  <span className="rounded-full border border-purple-400/40 bg-purple-400/10 px-3 py-1 text-[11px] uppercase tracking-[0.2em] text-purple-200">Category</span>
                </div>

                {!selectedType ? (
                  <div className="mt-4 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/60">
                    Select a type first to see categories.
                  </div>
                ) : categoryOptions.length === 0 ? (
                  <div className="mt-4 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/60">
                    No categories available for {selectedType.name}.
                  </div>
                ) : (
                  <div className="mt-4">
                    <div className="flex flex-wrap gap-2">
                      {categoryOptions.map((cat) => {
                        const isSelected = String(cat.id) === String(state.metadata.category)
                        const isExpanded = String(cat.id) === String(selectedParentCategory)
                        const hasChildren = Array.isArray(cat.children) && cat.children.length > 0
                        return (
                          <button
                            key={cat.id}
                            type="button"
                            onClick={() => {
                              if (hasChildren) {
                                setSelectedParentCategory(String(cat.id))
                                dispatch({ type: 'SET_METADATA', payload: { category: '' } })
                                return
                              }
                              setSelectedParentCategory(null)
                              dispatch({ type: 'SET_METADATA', payload: { category: String(cat.id) } })
                            }}
                            className={`rounded-full border px-4 py-2 text-sm transition ${isSelected || isExpanded
                              ? 'border-purple-300/60 bg-purple-400/20 text-purple-100'
                              : 'border-white/10 bg-white/5 text-white/70 hover:border-purple-300/50 hover:bg-purple-400/10'}`}
                            aria-pressed={isSelected || isExpanded}
                          >
                            {cat.name}
                          </button>
                        )
                      })}
                    </div>

                    {selectedParentCategory && (() => {
                      const parent = categoryOptions.find((c) => String(c.id) === String(selectedParentCategory))
                      if (!parent || !Array.isArray(parent.children) || parent.children.length === 0) return null
                      return (
                        <div className="mt-4 rounded-xl border border-white/10 bg-white/5 p-4">
                          <div className="flex items-center justify-between">
                            <div>
                              <div className="text-sm font-semibold text-white">Subcategories for {parent.name}</div>
                              <div className="text-xs text-white/60">Choose a subcategory below</div>
                            </div>
                            <button type="button" onClick={() => setSelectedParentCategory(null)} className="text-sm text-white/60">Back</button>
                          </div>
                          <div className="mt-3 flex flex-wrap gap-2">
                            {parent.children.map((child) => {
                              const activeChild = String(child.id) === String(state.metadata.category)
                              return (
                                <button
                                  key={child.id}
                                  type="button"
                                  onClick={() => {
                                    // Select child but keep parent expanded so the panel remains visible
                                    dispatch({ type: 'SET_METADATA', payload: { category: String(child.id) } })
                                  }}
                                  className={`rounded-full border px-4 py-2 text-sm transition ${activeChild
                                    ? 'border-purple-300/60 bg-purple-400/20 text-purple-100'
                                    : 'border-white/10 bg-white/5 text-white/70 hover:border-purple-300/50 hover:bg-purple-400/10'}`}
                                  aria-pressed={activeChild}
                                >
                                  {child.name}
                                </button>
                              )
                            })}
                          </div>
                        </div>
                      )
                    })()}
                  </div>
                )}
              </div>

              <div className="mt-4">
                <span className="text-sm text-white/80">Tags</span>
                <div className="mt-2">
                  <TagInput
                    value={state.metadata.tags}
                    onChange={(nextTags) => {
                      dispatch({ type: 'SET_METADATA', payload: { tags: nextTags.join(', ') } })
                    }}
                    suggestedTags={suggestedTags}
                    minLength={2}
                    maxLength={32}
                    searchEndpoint="/api/tags/search"
                    popularEndpoint="/api/tags/popular"
                    placeholder="Type tags (e.g. cyberpunk, city)"
                  />
                </div>
              </div>

              <label className="mt-4 block text-sm">
                <span className="text-white/80">Description</span>
                <div className="mt-2">
                  <UploadDescriptionEditor
                    id="legacy-upload-description"
                    value={state.metadata.description}
                    onChange={(value) => dispatch({ type: 'SET_METADATA', payload: { description: value } })}
                    placeholder="Tell the story behind this artwork."
                    error={descriptionErrors[0] || ''}
                    rows={8}
                  />
                </div>
                {descriptionErrors.length > 0 && <p className="mt-2 text-xs text-red-200">{descriptionErrors[0]}</p>}
              </label>

              <div className="mt-4">
                <Checkbox
                  checked={state.metadata.isMature}
                  onChange={(e) => dispatch({ type: 'SET_METADATA', payload: { isMature: e.target.checked } })}
                  size={16}
                  variant="accent"
                  label="Mark this artwork as mature content."
                />
              </div>

              <div className="mt-4">
                <Checkbox
                  checked={state.metadata.licenseAccepted}
                  onChange={(e) => dispatch({ type: 'SET_METADATA', payload: { licenseAccepted: e.target.checked } })}
                  size={16}
                  variant="emerald"
                  label="I confirm I own the rights to this artwork."
                />
              </div>

              {state.cancelledAt && (
                <div role="alert" aria-live="polite" className="mt-4 rounded-xl border border-amber-400/40 bg-amber-400/10 px-4 py-3 text-sm text-amber-100">
                  Upload cancelled.
                </div>
              )}

              {state.error && (
                <div className="mt-4 rounded-xl border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm text-red-100">
                  {state.error}
                </div>
              )}

              <div className="mt-6 flex flex-wrap items-center gap-3">
                <button
                  type="button"
                  onClick={startUpload}
                  disabled={
                    !state.file ||
                    !state.metadata.title.trim() ||
                    !state.metadata.type ||
                    !state.metadata.category ||
                    !hasAtLeastOneTag ||
                    !state.metadata.description.trim() ||
                    descriptionErrors.length > 0 ||
                    !state.metadata.licenseAccepted
                  }
                  className={`inline-flex items-center gap-2 rounded-full px-5 py-2 text-sm font-semibold text-white ${(!state.file || !state.metadata.title.trim() || !state.metadata.type || !state.metadata.category || !hasAtLeastOneTag || !state.metadata.description.trim() || descriptionErrors.length > 0 || !state.metadata.licenseAccepted) ? 'bg-white/10 cursor-not-allowed' : 'bg-emerald-500 shadow-lg shadow-emerald-500/30'}`}
                >
                  <i className="fa-solid fa-rocket" aria-hidden="true"></i>
                  Start upload
                </button>
                <button
                  type="button"
                  onClick={() => {
                    if (!confirmCancel) {
                      setConfirmCancel(true)
                      return
                    }
                    setConfirmCancel(false)
                    cancelUpload()
                  }}
                  className="inline-flex items-center gap-2 rounded-full border border-red-400/40 px-4 py-2 text-sm text-red-100"
                  disabled={!state.sessionId || state.phase === phases.success}
                  aria-pressed={confirmCancel}
                >
                  <i className="fa-solid fa-ban" aria-hidden="true"></i>
                  {confirmCancel ? 'Confirm cancel' : 'Cancel'}
                </button>
                <button
                  type="button"
                  onClick={() => {
                    if (userId) {
                      clearStoredSession(userId)
                    }
                    dispatch({ type: 'RESET' })
                  }}
                  className="inline-flex items-center gap-2 rounded-full border border-white/20 px-4 py-2 text-sm text-white/70"
                >
                  Reset
                </button>
              </div>
            </div>
          </div>

          <div className="space-y-6">
            <div className="rounded-2xl border border-white/10 bg-white/5 p-6">
              <h3 className="text-lg font-semibold">Pipeline status</h3>
              <p className="mt-2 text-sm text-white/60">Stage: <span className="text-white">{statusLabel}</span></p>

              <div className="mt-4 h-2 w-full overflow-hidden rounded-full bg-white/10">
                <div className="h-full bg-sky-400 transition-all" style={{ width: `${state.progress}%` }}></div>
              </div>

              {state.failureReason && (
                <div className="mt-3 text-sm text-red-200">Failure: {state.failureReason}</div>
              )}

              {state.previewUrl && state.phase === phases.success && (
                <div className="mt-6">
                  <h4 className="text-sm font-semibold text-white/80">CDN preview</h4>
                  <div className="mt-2 overflow-hidden rounded-xl border border-white/10">
                    <img src={state.previewUrl} alt="CDN preview" className="h-56 w-full object-cover" />
                  </div>
                </div>
              )}

              <div className="mt-6 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-xs text-white/60">
                Session: {state.sessionId ?? '—'}
              </div>
            </div>

            <div className="rounded-2xl border border-white/10 bg-white/5 p-6">
              <h3 className="text-lg font-semibold">Draft resume</h3>
              <p className="mt-2 text-sm text-white/60">Use the draft link to resume an interrupted upload.</p>
              {draftId ? (
                <div className="mt-4 text-sm text-white/80">Draft ID: {draftId}</div>
              ) : (
                <div className="mt-4 text-sm text-white/50">No draft loaded.</div>
              )}
            </div>
          </div>
        </div>
      </div>
    </section>
  )
}
