import styled from "@emotion/styled"
import { toast } from "react-hot-toast"
import invariant from "tiny-invariant"
import { produce } from "immer"
import { ApolloError, makeVar } from "@apollo/client"
import { useEvent } from "@aet/hooks"
import { gql } from "~/generated/graphql"
import { currentState, useDispatch } from "../store"
import { changeDirty } from "../store/actions"
import { FILE_FRAGMENT, SECTION_FRAGMENT } from "../fragments"
import { client } from "~/shared/apolloClient"
import type { ErrorExtension } from "~/background/graphql/resolvers/SectionResolver"

const SAVE_FILE = gql<"SetFile">`
  ${FILE_FRAGMENT}
  ${SECTION_FRAGMENT}
  mutation SetFile($id: ID!, $file: PartialFileInput!) {
    setFile(id: $id, input: $file) {
      ...FileFragment
      sections {
        ...SectionFragment
        compiledCode(cache: false)
      }
    }
  }
`

/**
 * Saves a file to backend.
 * @param id File ID
 * @param newCode New code to save. If not provided, the current code in Redux state will be used.
 */
async function saveFile(id: string, newCode?: string) {
  const draft = currentState.tabs.find(x => x.id === id)
  invariant(draft != null, "Style not found")

  let { ...file } = draft
  if (newCode != null) {
    file = produce(file, draft => {
      draft.sections[0].code = newCode
    })
  }

  const { errors } = await client.mutate({
    mutation: SAVE_FILE,
    variables: { id, file },
  })

  if (errors?.length) {
    throw errors[0]
  }
}

interface CompilationError {
  span: ErrorExtension["span"]
  message: string
}

export const compilerErrorVars = makeVar<CompilationError[]>([])

export function useFileSaver() {
  const dispatch = useDispatch()

  return useEvent(async (fileId: string, code?: string) => {
    try {
      await saveFile(fileId, code)
      dispatch(changeDirty(fileId, false))
      compilerErrorVars([])
    } catch (e) {
      if (e instanceof ApolloError) {
        const ext = e.graphQLErrors[0]?.extensions as ErrorExtension
        if (ext?.code === "BAD_USER_INPUT") {
          compilerErrorVars([{ span: ext.span, message: e.message }])
        }
      }
      toast.error(<Pre>{e.message}</Pre>)
    }
  })
}

const Pre = styled.pre`
  margin: 0;
`
