import {validatePreviewUrl} from '@sanity/preview-url-secret'
import {type ActionFunction, type AppLoadContext, type LoaderFunction, redirect} from 'react-router'

import type {SanityContext} from '../context'
import {sanitizePerspective} from '../utils'
import {isHydrogenSession, isSanityPreviewSession} from '../utils'

function getSession(context: AppLoadContext) {
  const preview = (context.sanity as SanityContext)?.preview
  const session =
    preview && 'session' in preview && preview.session ? preview.session : context.session
  return session
}

/**
 * Route action handler for preview mode management.
 * POST/DELETE: disable preview mode, PUT: change preview perspective.
 */
export const action: ActionFunction = async ({context, request}) => {
  switch (request.method) {
    case 'POST':
    case 'DELETE': {
      try {
        const session = getSession(context)

        const headers = new Headers()
        if (isSanityPreviewSession(session)) {
          headers.set('Set-Cookie', await session.destroy())
        } else if (isHydrogenSession(session)) {
          session.unset('projectId')
          session.unset('perspective')
          headers.set('Set-Cookie', await session.commit())
        }

        return redirect('/', {
          headers,
        })
      } catch (error) {
        console.error(error)
        throw new Response(
          'Unable to disable preview mode. Please check your preview configuration',
          {
            status: 500,
          },
        )
      }
    }

    case 'PUT': {
      try {
        const sanity = context.sanity as SanityContext

        if (!sanity.preview) {
          return new Response('Preview mode is not enabled in this environment.', {status: 403})
        }

        if (!sanity.preview.token) {
          throw new Error('Enabling preview mode requires a token.')
        }

        if (!sanity.preview) {
          return new Response('Preview mode is not enabled in this environment.', {status: 403})
        }

        if (!sanity.preview.token) {
          throw new Error('Enabling preview mode requires a token.')
        }

        const session = getSession(context)
        if (!isHydrogenSession(session)) {
          throw new Error('Session is not an instance of HydrogenSession')
        }

        const projectId = sanity.client.config().projectId
        if (!projectId) {
          throw new Error('No `projectId` found in the client config.')
        }

        const sessionProjectId = session.get('projectId')
        if (sessionProjectId && sessionProjectId !== projectId) {
          return new Response('Invalid projectId', {status: 400})
        }

        const formData = await request.formData()
        const perspective = sanitizePerspective(formData.get('perspective'))
        session.set('perspective', Array.isArray(perspective) ? perspective.join(',') : perspective)

        return new Response('OK', {
          status: 200,
          headers: {
            'Set-Cookie': await session.commit(),
          },
        })
      } catch (error) {
        console.error(error)
        throw new Response(
          'Unable to enable preview mode. Please check your preview configuration',
          {
            status: 500,
          },
        )
      }
    }

    default:
      return new Response('Method not allowed', {status: 405})
  }
}

/**
 * Route loader handler for entering preview mode.
 * Validates secret token and activates preview session.
 */
export const loader: LoaderFunction = async ({context, request}) => {
  try {
    const session = getSession(context)
    const sanity = context.sanity as SanityContext

    if (!sanity.preview) {
      return new Response('Preview mode is not enabled in this environment.', {status: 403})
    }

    if (!sanity.preview.token) {
      throw new Error('Enabling preview mode requires a token.')
    }

    const projectId = sanity.client.config().projectId
    if (!projectId) {
      throw new Error('No `projectId` found in the client config.')
    }

    if (!isHydrogenSession(session)) {
      throw new Error('Session is not an instance of HydrogenSession')
    }

    const clientWithToken = sanity.client.withConfig({
      useCdn: false,
      token: sanity.preview.token,
    })

    const {
      isValid,
      redirectTo = '/',
      studioPreviewPerspective = 'drafts',
    } = await validatePreviewUrl(clientWithToken, request.url)

    if (!isValid) {
      return new Response('Invalid secret', {status: 401})
    }

    session.set('projectId', projectId)
    session.set('perspective', studioPreviewPerspective)

    return redirect(redirectTo, {
      headers: {
        'Set-Cookie': await session.commit(),
      },
    })
  } catch (error) {
    console.error(error)
    throw new Response('Unable to enable preview mode. Please check your preview configuration', {
      status: 500,
    })
  }
}
