import * as diff from 'diff'
import * as Prism from 'prismjs'
import * as q from '../../../../../backend/src/Model'
import * as React from 'react'
import DiffCount from './DiffCount'
import Gutters from './Gutters'
import { isPlottable, lineChangeStyle, trimNewlineRight } from './util'
import { JsonPropertyLocation, literalsMappedByLines } from '../../../../../backend/src/JsonAstParser'
import { selectTextWithCtrlA } from '../../../utils/handleTextSelectWithCtrlA'
import { style } from './style'
import { withStyles } from '@material-ui/core'
import 'prismjs/components/prism-json'

interface Props {
  treeNode: q.TreeNode<any>
  previous: string
  current: string
  nameOfCompareMessage: string
  language?: 'json'
  classes: any
}

interface State {}

class CodeDiff extends React.Component<Props, State> {
  private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre ~ pre' })

  constructor(props: Props) {
    super(props)
    this.state = {}
  }

  private isValidJson(str: string) {
    try {
      JSON.parse(str)
      return true
    } catch (error) {
      return false
    }
  }

  private plottableLiteralsIndexedWithLineNumbers() {
    const allLiterals = this.isValidJson(this.props.current) ? literalsMappedByLines(this.props.current) || [] : []

    return allLiterals.map((l: JsonPropertyLocation) => (isPlottable(l.value) ? l : undefined)) as Array<
      JsonPropertyLocation
    >
  }

  private renderStyledCodeLines(changes: Array<Diff.Change>) {
    const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
    let lineNumber = 0

    return changes
      .map((change, key) => {
        const hasStyledCode = change.removed !== true
        const changedLines = change.count || 0
        if (hasStyledCode && this.props.language === 'json') {
          const currentLines = styledLines.slice(lineNumber, lineNumber + changedLines)
          const lines = currentLines.map((html: string, idx: number) => {
            return (
              <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}>
                <span dangerouslySetInnerHTML={{ __html: html }} />
              </div>
            )
          })
          lineNumber += changedLines

          return [<div key={key}>{lines}</div>]
        }

        return trimNewlineRight(change.value)
          .split('\n')
          .map((line, idx) => {
            return (
              <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}>
                <span>{line}</span>
              </div>
            )
          })
      })
      .reduce((a, b) => a.concat(b), [])
  }

  public render() {
    const changes = diff.diffLines(this.props.previous, this.props.current)
    const literalPositions = this.plottableLiteralsIndexedWithLineNumbers()
    const code = this.renderStyledCodeLines(changes)

    return (
      <div>
        <div tabIndex={0} onKeyDown={this.handleCtrlA} className={this.props.classes.codeWrapper}>
          <Gutters
            className={this.props.classes.gutters}
            changes={changes}
            treeNode={this.props.treeNode}
            literalPositions={literalPositions}
          />
          <pre className={this.props.classes.codeBlock}>{code}</pre>
        </div>
        <DiffCount changes={changes} nameOfCompareMessage={this.props.nameOfCompareMessage} />
      </div>
    )
  }
}

export default withStyles(style)(CodeDiff)
