import { YAMLSemanticError, YAMLSyntaxError } from '../errors.js'
import { Node } from './Node.js'
import { Range } from './Range.js'

export class QuoteDouble extends Node {
  static endOfQuote(src, offset) {
    let ch = src[offset]
    while (ch && ch !== '"') {
      offset += ch === '\\' ? 2 : 1
      ch = src[offset]
    }
    return offset + 1
  }

  /**
   * @returns {string | { str: string, errors: YAMLSyntaxError[] }}
   */
  get strValue() {
    if (!this.valueRange || !this.context) return null
    const errors = []
    const { start, end } = this.valueRange
    const { indent, src } = this.context
    if (src[end - 1] !== '"')
      errors.push(new YAMLSyntaxError(this, 'Missing closing "quote'))
    // Using String#replace is too painful with escaped newlines preceded by
    // escaped backslashes; also, this should be faster.
    let str = ''
    for (let i = start + 1; i < end - 1; ++i) {
      const ch = src[i]
      if (ch === '\n') {
        if (Node.atDocumentBoundary(src, i + 1))
          errors.push(
            new YAMLSemanticError(
              this,
              'Document boundary indicators are not allowed within string values'
            )
          )
        const { fold, offset, error } = Node.foldNewline(src, i, indent)
        str += fold
        i = offset
        if (error)
          errors.push(
            new YAMLSemanticError(
              this,
              'Multi-line double-quoted string needs to be sufficiently indented'
            )
          )
      } else if (ch === '\\') {
        i += 1
        switch (src[i]) {
          case '0':
            str += '\0'
            break // null character
          case 'a':
            str += '\x07'
            break // bell character
          case 'b':
            str += '\b'
            break // backspace
          case 'e':
            str += '\x1b'
            break // escape character
          case 'f':
            str += '\f'
            break // form feed
          case 'n':
            str += '\n'
            break // line feed
          case 'r':
            str += '\r'
            break // carriage return
          case 't':
            str += '\t'
            break // horizontal tab
          case 'v':
            str += '\v'
            break // vertical tab
          case 'N':
            str += '\u0085'
            break // Unicode next line
          case '_':
            str += '\u00a0'
            break // Unicode non-breaking space
          case 'L':
            str += '\u2028'
            break // Unicode line separator
          case 'P':
            str += '\u2029'
            break // Unicode paragraph separator
          case ' ':
            str += ' '
            break
          case '"':
            str += '"'
            break
          case '/':
            str += '/'
            break
          case '\\':
            str += '\\'
            break
          case '\t':
            str += '\t'
            break
          case 'x':
            str += this.parseCharCode(i + 1, 2, errors)
            i += 2
            break
          case 'u':
            str += this.parseCharCode(i + 1, 4, errors)
            i += 4
            break
          case 'U':
            str += this.parseCharCode(i + 1, 8, errors)
            i += 8
            break
          case '\n':
            // skip escaped newlines, but still trim the following line
            while (src[i + 1] === ' ' || src[i + 1] === '\t') i += 1
            break
          default:
            errors.push(
              new YAMLSyntaxError(
                this,
                `Invalid escape sequence ${src.substr(i - 1, 2)}`
              )
            )
            str += '\\' + src[i]
        }
      } else if (ch === ' ' || ch === '\t') {
        // trim trailing whitespace
        const wsStart = i
        let next = src[i + 1]
        while (next === ' ' || next === '\t') {
          i += 1
          next = src[i + 1]
        }
        if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch
      } else {
        str += ch
      }
    }
    return errors.length > 0 ? { errors, str } : str
  }

  parseCharCode(offset, length, errors) {
    const { src } = this.context
    const cc = src.substr(offset, length)
    const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc)
    const code = ok ? parseInt(cc, 16) : NaN
    if (isNaN(code)) {
      errors.push(
        new YAMLSyntaxError(
          this,
          `Invalid escape sequence ${src.substr(offset - 2, length + 2)}`
        )
      )
      return src.substr(offset - 2, length + 2)
    }
    return String.fromCodePoint(code)
  }

  /**
   * Parses a "double quoted" value from the source
   *
   * @param {ParseContext} context
   * @param {number} start - Index of first character
   * @returns {number} - Index of the character after this scalar
   */
  parse(context, start) {
    this.context = context
    const { src } = context
    let offset = QuoteDouble.endOfQuote(src, start + 1)
    this.valueRange = new Range(start, offset)
    offset = Node.endOfWhiteSpace(src, offset)
    offset = this.parseComment(offset)
    trace: this.type,
      { valueRange: this.valueRange, comment: this.comment },
      JSON.stringify(this.rawValue)
    return offset
  }
}
