text: /**
   * @author Yosuke ota
   * See LICENSE file in root directory for full license.
   */
  'use strict'
  
  // -----------------------------------------------------------------------------
  // Requirements
  // -----------------------------------------------------------------------------
  
  const htmlComments = require('../utils/html-comments')
  
  // ------------------------------------------------------------------------------
  // Helpers
  // ------------------------------------------------------------------------------
  
  /**
   * Normalize options.
   * @param {number|"tab"|undefined} type The type of indentation.
   * @returns { { indentChar: string, indentSize: number, indentText: string } } Normalized options.
   */
  function parseOptions(type) {
    const ret = {
      indentChar: ' ',
      indentSize: 2,
      indentText: ''
    }
  
    if (Number.isSafeInteger(type)) {
      ret.indentSize = Number(type)
    } else if (type === 'tab') {
      ret.indentChar = '\t'
      ret.indentSize = 1
    }
    ret.indentText = ret.indentChar.repeat(ret.indentSize)
  
    return ret
  }
  
  /**
   * @param {string} s
   * @param {string} [unitChar]
   */
  function toDisplay(s, unitChar) {
    if (s.length === 0 && unitChar) {
      return `0 ${toUnit(unitChar)}s`
    }
    const char = s[0]
    if (char === ' ' || char === '\t') {
      if (s.split('').every((c) => c === char)) {
        return `${s.length} ${toUnit(char)}${s.length === 1 ? '' : 's'}`
      }
    }
  
    return JSON.stringify(s)
  }
  
  /** @param {string} char */
  function toUnit(char) {
    if (char === '\t') {
      return 'tab'
    }
    if (char === ' ') {
      return 'space'
    }
    return JSON.stringify(char)
  }
  
  // ------------------------------------------------------------------------------
  // Rule Definition
  // ------------------------------------------------------------------------------
  
  module.exports = {
    meta: {
      type: 'layout',
  
      docs: {
        description: 'enforce consistent indentation in HTML comments',
        categories: undefined,
        url: 'https://eslint.vuejs.org/rules/html-comment-indent.html'
      },
      fixable: 'whitespace',
      schema: [
        {
          anyOf: [{ type: 'integer', minimum: 0 }, { enum: ['tab'] }]
        }
      ],
      messages: {
        unexpectedBaseIndentation:
          'Expected base point indentation of {{expected}}, but found {{actual}}.',
        missingBaseIndentation:
          'Expected base point indentation of {{expected}}, but not found.',
        unexpectedIndentationCharacter:
          'Expected {{expected}} character, but found {{actual}} character.',
        unexpectedIndentation:
          'Expected indentation of {{expected}} but found {{actual}}.',
        unexpectedRelativeIndentation:
          'Expected relative indentation of {{expected}} but found {{actual}}.'
      }
    },
    /** @param {RuleContext} context */
    create(context) {
      const options = parseOptions(context.options[0])
      const sourceCode = context.getSourceCode()
      return htmlComments.defineVisitor(
        context,
        null,
        (comment) => {
          const baseIndentText = getLineIndentText(comment.open.loc.start.line)
          let endLine
          if (comment.value) {
            const startLine = comment.value.loc.start.line
            endLine = comment.value.loc.end.line
  
            const checkStartLine =
              comment.open.loc.end.line === startLine ? startLine + 1 : startLine
  
            for (let line = checkStartLine; line <= endLine; line++) {
              validateIndentForLine(line, baseIndentText, 1)
            }
          } else {
            endLine = comment.open.loc.end.line
          }
  
          if (endLine < comment.close.loc.start.line) {
            // `-->`
            validateIndentForLine(comment.close.loc.start.line, baseIndentText, 0)
          }
        },
        { includeDirectives: true }
      )
  
      /**
       * Checks whether the given line is a blank line.
       * @param {number} line The number of line. Begins with 1.
       * @returns {boolean} `true` if the given line is a blank line
       */
      function isEmptyLine(line) {
        const lineText = sourceCode.getLines()[line - 1]
        return !lineText.trim()
      }
  
      /**
       * Get the actual indentation of the given line.
       * @param {number} line The number of line. Begins with 1.
       * @returns {string} The actual indentation text
       */
      function getLineIndentText(line) {
        const lineText = sourceCode.getLines()[line - 1]
        const charIndex = lineText.search(/\S/)
        // already checked
        // if (charIndex < 0) {
        //   return lineText
        // }
        return lineText.slice(0, charIndex)
      }
  
      /**
       * Define the function which fixes the problem.
       * @param {number} line The number of line.
       * @param {string} actualIndentText The actual indentation text.
       * @param {string} expectedIndentText The expected indentation text.
       * @returns { (fixer: RuleFixer) => Fix } The defined function.
       */
      function defineFix(line, actualIndentText, expectedIndentText) {
        return (fixer) => {
          const start = sourceCode.getIndexFromLoc({
            line,
            column: 0
          })
          return fixer.replaceTextRange(
            [start, start + actualIndentText.length],
            expectedIndentText
          )
        }
      }
  
      /**
       * Validate the indentation of a line.
       * @param {number} line The number of line. Begins with 1.
       * @param {string} baseIndentText The expected base indentation text.
       * @param {number} offset The number of the indentation offset.
       */
      function validateIndentForLine(line, baseIndentText, offset) {
        if (isEmptyLine(line)) {
          return
        }
        const actualIndentText = getLineIndentText(line)
  
        const expectedOffsetIndentText = options.indentText.repeat(offset)
        const expectedIndentText = baseIndentText + expectedOffsetIndentText
  
        // validate base indent
        if (
          baseIndentText &&
          (actualIndentText.length < baseIndentText.length ||
            !actualIndentText.startsWith(baseIndentText))
        ) {
          context.report({
            loc: {
              start: { line, column: 0 },
              end: { line, column: actualIndentText.length }
            },
            messageId: actualIndentText
              ? 'unexpectedBaseIndentation'
              : 'missingBaseIndentation',
            data: {
              expected: toDisplay(baseIndentText),
              actual: toDisplay(actualIndentText.slice(0, baseIndentText.length))
            },
            fix: defineFix(line, actualIndentText, expectedIndentText)
          })
          return
        }
  
        const actualOffsetIndentText = actualIndentText.slice(
          baseIndentText.length
        )
  
        // validate indent charctor
        for (let i = 0; i < actualOffsetIndentText.length; ++i) {
          if (actualOffsetIndentText[i] !== options.indentChar) {
            context.report({
              loc: {
                start: { line, column: baseIndentText.length + i },
                end: { line, column: baseIndentText.length + i + 1 }
              },
              messageId: 'unexpectedIndentationCharacter',
              data: {
                expected: toUnit(options.indentChar),
                actual: toUnit(actualOffsetIndentText[i])
              },
              fix: defineFix(line, actualIndentText, expectedIndentText)
            })
            return
          }
        }
  
        // validate indent length
        if (actualOffsetIndentText.length !== expectedOffsetIndentText.length) {
          context.report({
            loc: {
              start: { line, column: baseIndentText.length },
              end: { line, column: actualIndentText.length }
            },
            messageId: baseIndentText
              ? 'unexpectedRelativeIndentation'
              : 'unexpectedIndentation',
            data: {
              expected: toDisplay(expectedOffsetIndentText, options.indentChar),
              actual: toDisplay(actualOffsetIndentText, options.indentChar)
            },
            fix: defineFix(line, actualIndentText, expectedIndentText)
          })
        }
      }
    }
  }
  