import Vue from 'vue'
import * as hljs from 'highlight.js'
import * as api from '@/api'
import {nameSpacer} from '@/util/helpers'
import {Feedback, FeedbackComment, Submission, SubmissionNoType} from '@/models'
import {RootState} from '@/store/store'
import {Module} from 'vuex'

export const subNotesNamespace = nameSpacer('submissionNotes/')

export const subNotesMut = Object.freeze({
  SET_SUBMISSION: 'SET_SUBMISSION',
  SET_ORIG_FEEDBACK: 'SET_ORIG_FEEDBACK',
  SET_SHOW_FEEDBACK: 'SET_SHOW_FEEDBACK',
  UPDATE_FEEDBACK_LINE: 'UPDATE_FEEDBACK_LINE',
  UPDATE_FEEDBACK_SCORE: 'UPDATE_FEEDBACK_SCORE',
  DELETE_FEEDBACK_LINE: 'DELETE_FEEDBACK_LINE',
  TOGGLE_EDITOR_ON_LINE: 'TOGGLE_EDITOR_ON_LINE',
  MARK_COMMENT_FOR_DELETION: 'MARK_COMMENT_FOR_DELETION',
  UN_MARK_COMMENT_FOR_DELETION: 'UN_MARK_COMMENT_FOR_DELETION',
  RESET_UPDATED_FEEDBACK: 'RESET_UPDATED_FEEDBACK',
  RESET_STATE: 'RESET_STATE'
})

interface SubmissionNotesState {
  submission: SubmissionNoType
  ui: {
      showEditorOnLine: {[lineNo: number]: boolean}
      selectedCommentOnLine: {[lineNo: number]: FeedbackComment}
      showFeedback: boolean
  },
  hasOrigFeedback: boolean
  origFeedback: Feedback
  updatedFeedback: Feedback
  commentsMarkedForDeletion: {[pk: string]: FeedbackComment}
}

function initialState (): SubmissionNotesState {
  return {
    submission: {
      text: '',
      pk: '',
      type: '',
      tests: []
    },
    ui: {
      showEditorOnLine: {},
      selectedCommentOnLine: {},
      showFeedback: true
    },
    hasOrigFeedback: false,
    origFeedback: {
      pk: 0,
      score: undefined,
      isFinal: false,
      feedbackLines: {}
    },
    updatedFeedback: {
      pk: 0,
      score: undefined,
      feedbackLines: {}
    },
    commentsMarkedForDeletion: {}
  }
}

const submissionNotes: Module<SubmissionNotesState, RootState> = {
  namespaced: true,
  state: initialState(),
  getters: {
    submissionType: (state, getters, rootState) => {
      return rootState.submissionTypes[state.submission.type]
    },
    // highlight the submission the reduce the string
    // submission.text into an object where the keys are the
    // line indexes starting at one and the values the corresponding submission line
    // this makes iterating over the submission much more pleasant
    submission: (state, getters) => {
      const language = getters.submissionType
        ? getters.submissionType.programmingLanguage
        : 'c'
      const highlighted = hljs.highlight(language, state.submission.text || '', true).value
      return highlighted.split('\n').reduce((acc: {[k: number]: string}, cur, index) => {
        acc[index + 1] = cur
        return acc
      }, {})
    },
    score: state => {
      return state.updatedFeedback.score !== undefined ? state.updatedFeedback.score : state.origFeedback.score
    },
    workInProgress: state => {
      const openEditor = Object.values(state.ui.showEditorOnLine).reduce((acc, curr) => acc || curr, false)
      const feedbackWritten = Object.entries(state.updatedFeedback.feedbackLines || {}).length > 0
      return openEditor || feedbackWritten
    },
    isFeedbackCreation: state => {
      return !state.origFeedback['feedbackStageForUser'] ||
        state.origFeedback['feedbackStageForUser'] === 'feedback-creation'
    }
  },
  mutations: {
    [subNotesMut.SET_SUBMISSION]: function (state, submission: SubmissionNoType) {
      state.submission = submission
    },
    [subNotesMut.SET_ORIG_FEEDBACK]: function (state, feedback: Feedback) {
      if (feedback) {
        state.origFeedback = feedback
        state.hasOrigFeedback = true
      }
    },
    [subNotesMut.SET_SHOW_FEEDBACK]: function (state, val: boolean) {
      state.ui.showFeedback = val
    },
    [subNotesMut.UPDATE_FEEDBACK_LINE]: function (state, feedback: {lineNo: number, comment: Partial<FeedbackComment>}) {
      // explicit .toString() on lineNo is necessary because of Vue.set typings
      if (state.updatedFeedback.feedbackLines) {
          Vue.set(state.updatedFeedback.feedbackLines, feedback.lineNo.toString(), feedback.comment)
      }
    },
    [subNotesMut.UPDATE_FEEDBACK_SCORE]: function (state, score) {
      state.updatedFeedback.score = score
    },
    [subNotesMut.DELETE_FEEDBACK_LINE]: function (state, lineNo) {
      if (state.updatedFeedback.feedbackLines) {
          Vue.delete(state.updatedFeedback.feedbackLines, lineNo)
      }
    },
    [subNotesMut.TOGGLE_EDITOR_ON_LINE]: function (state, {lineNo, comment}) {
      Vue.set(state.ui.selectedCommentOnLine, lineNo, comment)
      Vue.set(state.ui.showEditorOnLine, lineNo, !state.ui.showEditorOnLine[lineNo])
    },
    [subNotesMut.MARK_COMMENT_FOR_DELETION]: function (state, comment) {
      Vue.set(state.commentsMarkedForDeletion, comment.pk, comment)
    },
    [subNotesMut.UN_MARK_COMMENT_FOR_DELETION]: function (state, comment) {
      Vue.delete(state.commentsMarkedForDeletion, comment.pk)
    },
    [subNotesMut.RESET_UPDATED_FEEDBACK]: function (state) {
      state.updatedFeedback = initialState().updatedFeedback
    },
    [subNotesMut.RESET_STATE]: function (state) {
      Object.assign(state, initialState())
    }
  },
  actions: {
    deleteComments: async function ({state}) {
      return Promise.all(
        Object.values(state.commentsMarkedForDeletion).map(comment => {
          return api.deleteComment(comment)
        })
      )
    },
    submitFeedback: async function ({state, dispatch, getters}, {isFinal = false}) {
      let feedback: Partial<Feedback> = {
        isFinal: isFinal,
        ofSubmission: state.submission.pk
      }
      if (Object.keys(state.updatedFeedback.feedbackLines || {}).length > 0) {
        feedback.feedbackLines = state.updatedFeedback.feedbackLines
      }
      if (state.origFeedback.score === undefined && state.updatedFeedback.score === undefined) {
        throw new Error('You need to give a score.')
      } else if (state.updatedFeedback.score !== undefined) {
        feedback.score = state.updatedFeedback.score
      }
      await dispatch('deleteComments')
      if (!state.hasOrigFeedback) {
        return api.submitFeedbackForAssignment({feedback})
      } else {
        feedback.pk = state.origFeedback.pk
        return api.submitUpdatedFeedback(<{feedback: Feedback}> {feedback})
      }
    }
  }
}

export default submissionNotes