import { CurrentUserMark, Mark } from '../types/mark.ts'
import { hexToNpub } from './nostr.ts'
import { Meta, SignedEventContent } from '../types'
import { Event } from 'nostr-tools'
import { EMPTY } from './const.ts'
import { DrawTool, MarkType } from '../types/drawing.ts'
import {
  faT,
  faSignature,
  faBriefcase,
  faIdCard,
  faClock,
  fa1,
  faCalendarDays,
  faCheckDouble,
  faCircleDot,
  faCreditCard,
  faHeading,
  faImage,
  faPaperclip,
  faPhone,
  faSquareCaretDown,
  faSquareCheck,
  faStamp,
  faTableCellsLarge
} from '@fortawesome/free-solid-svg-icons'

/**
 * Takes in an array of Marks already filtered by User.
 * Returns an array of CurrentUserMarks with correct values mix-and-matched.
 * @param marks - default Marks extracted from Meta
 * @param signedMetaMarks - signed user Marks extracted from DocSignatures
 */
const getCurrentUserMarks = (
  marks: Mark[],
  signedMetaMarks: Mark[]
): CurrentUserMark[] => {
  return marks.map((mark, index, arr) => {
    const signedMark = signedMetaMarks.find((m) => m.id === mark.id)
    return {
      mark,
      currentValue: signedMark?.value ?? EMPTY,
      id: index + 1,
      isLast: isLast(index, arr),
      isCompleted: !!signedMark?.value
    }
  })
}

/**
 * Returns next incomplete CurrentUserMark if there is one
 * @param usersMarks
 */
const findNextIncompleteCurrentUserMark = (
  usersMarks: CurrentUserMark[]
): CurrentUserMark | undefined => {
  return usersMarks.find((mark) => !mark.isCompleted)
}

/**
 * Returns Marks that are assigned to a specific user
 * @param marks
 * @param pubkey
 */
const filterMarksByPubkey = (marks: Mark[], pubkey: string): Mark[] => {
  return marks.filter((mark) => mark.npub === hexToNpub(pubkey))
}

/**
 * Takes Signed Doc Signatures part of Meta and extracts
 * all Marks into one flat array, regardless of the user.
 * @param meta
 */
const extractMarksFromSignedMeta = (meta: Meta): Mark[] => {
  return Object.values(meta.docSignatures)
    .map((val: string) => JSON.parse(val as string))
    .map((val: Event) => JSON.parse(val.content))
    .flatMap((val: SignedEventContent) => val.marks)
}

/**
 * Checks the CurrentUserMarks array that every element in that array has been
 * marked as complete.
 * @param currentUserMarks
 */
const isCurrentUserMarksComplete = (
  currentUserMarks: CurrentUserMark[]
): boolean => {
  return currentUserMarks.every((mark) => mark.isCompleted)
}

/**
 * Inserts an updated mark into an existing array of marks. Returns a copy of the
 * existing array with a new value inserted
 * @param marks
 * @param markToUpdate
 */
const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => {
  const indexToUpdate = marks.findIndex((mark) => mark.id === markToUpdate.id)
  return [
    ...marks.slice(0, indexToUpdate),
    markToUpdate,
    ...marks.slice(indexToUpdate + 1)
  ]
}

const updateCurrentUserMarks = (
  currentUserMarks: CurrentUserMark[],
  markToUpdate: CurrentUserMark
): CurrentUserMark[] => {
  const indexToUpdate = currentUserMarks.findIndex(
    (m) => m.mark.id === markToUpdate.mark.id
  )
  return [
    ...currentUserMarks.slice(0, indexToUpdate),
    markToUpdate,
    ...currentUserMarks.slice(indexToUpdate + 1)
  ]
}

const isLast = <T>(index: number, arr: T[]) => index === arr.length - 1

const isCurrentValueLast = (
  currentUserMarks: CurrentUserMark[],
  selectedMark: CurrentUserMark,
  selectedMarkValue: string
) => {
  const filteredMarks = currentUserMarks.filter(
    (mark) => mark.id !== selectedMark.id
  )
  return (
    isCurrentUserMarksComplete(filteredMarks) && selectedMarkValue.length > 0
  )
}

const getUpdatedMark = (
  selectedMark: CurrentUserMark,
  selectedMarkValue: string
): CurrentUserMark => {
  return {
    ...selectedMark,
    currentValue: selectedMarkValue,
    isCompleted: !!selectedMarkValue,
    mark: {
      ...selectedMark.mark,
      value: selectedMarkValue
    }
  }
}

const findOtherUserMarks = (marks: Mark[], pubkey: string): Mark[] => {
  return marks.filter((mark) => mark.npub !== hexToNpub(pubkey))
}

export const DEFAULT_TOOLBOX: DrawTool[] = [
  {
    identifier: MarkType.TEXT,
    icon: faT,
    label: 'Text'
  },
  {
    identifier: MarkType.SIGNATURE,
    icon: faSignature,
    label: 'Signature'
  },
  {
    identifier: MarkType.FULLNAME,
    icon: faIdCard,
    label: 'Full Name',
    isComingSoon: true
  },
  {
    identifier: MarkType.JOBTITLE,
    icon: faBriefcase,
    label: 'Job Title',
    isComingSoon: true
  },
  {
    identifier: MarkType.DATETIME,
    icon: faClock,
    label: 'Date Time',
    isComingSoon: true
  },
  {
    identifier: MarkType.NUMBER,
    icon: fa1,
    label: 'Number',
    isComingSoon: true
  },
  {
    identifier: MarkType.INITIALS,
    icon: faHeading,
    label: 'Initials',
    isHidden: true
  },
  {
    identifier: MarkType.DATE,
    icon: faCalendarDays,
    label: 'Date',
    isHidden: true
  },
  {
    identifier: MarkType.IMAGES,
    icon: faImage,
    label: 'Images',
    isHidden: true
  },
  {
    identifier: MarkType.CHECKBOX,
    icon: faSquareCheck,
    label: 'Checkbox',
    isHidden: true
  },
  {
    identifier: MarkType.MULTIPLE,
    icon: faCheckDouble,
    label: 'Multiple',
    isHidden: true
  },
  {
    identifier: MarkType.FILE,
    icon: faPaperclip,
    label: 'File',
    isHidden: true
  },
  {
    identifier: MarkType.RADIO,
    icon: faCircleDot,
    label: 'Radio',
    isHidden: true
  },
  {
    identifier: MarkType.SELECT,
    icon: faSquareCaretDown,
    label: 'Select',
    isHidden: true
  },
  {
    identifier: MarkType.CELLS,
    icon: faTableCellsLarge,
    label: 'Cells',
    isHidden: true
  },
  {
    identifier: MarkType.STAMP,
    icon: faStamp,
    label: 'Stamp',
    isHidden: true
  },
  {
    identifier: MarkType.PAYMENT,
    icon: faCreditCard,
    label: 'Payment',
    isHidden: true
  },
  {
    identifier: MarkType.PHONE,
    icon: faPhone,
    label: 'Phone',
    isHidden: true
  }
]

export const getToolboxLabelByMarkType = (markType: MarkType) => {
  return DEFAULT_TOOLBOX.find((t) => t.identifier === markType)?.label
}

export const getOptimizedPathsWithStrokeWidth = (svgString: string) => {
  const parser = new DOMParser()
  const xmlDoc = parser.parseFromString(svgString, 'image/svg+xml')
  const paths = xmlDoc.querySelectorAll('path')
  const tuples: string[][] = []
  paths.forEach((path) => {
    const d = path.getAttribute('d') ?? ''
    const strokeWidth = path.getAttribute('stroke-width') ?? ''
    tuples.push([d, strokeWidth])
  })

  return tuples
}

export {
  getCurrentUserMarks,
  filterMarksByPubkey,
  extractMarksFromSignedMeta,
  isCurrentUserMarksComplete,
  findNextIncompleteCurrentUserMark,
  updateMarks,
  updateCurrentUserMarks,
  isCurrentValueLast,
  getUpdatedMark,
  findOtherUserMarks
}
