const fs = require("fs");
const path = require("node:path");
const child_process = require("child_process");

/** @type {import('eslint').Rule.RuleModule} */
const copyrightHeaderRule = {
  meta: {
    schema: [{
      type: 'object',
      properties: {
        author: {type: 'string'},
        license: {type: 'string'},
        fixedDate: {type: 'string'},
      },
      required: ['author', 'license']
    }],
    type: 'problem',
    docs: {
      description: 'Files should start with a copyright header'
    },
    messages: {
      missing: 'Missing a copyright header at the start of the file',
      invalid: 'Invalid copyright header',
      tryThisHeader: 'Use this copyright header:\n\n/*{{expected}}*/'
    },
    fixable: "code",
    hasSuggestions: true,
  },
  create: function(context) {
    const code = context.getSourceCode()
    const {author, license, fixedDate} = context.options[0]

    const year = fixedDate ? new Date(fixedDate) : getAuthorDate(context.getPhysicalFilename())
    if (!year) return {}

    const expected = license
      .replace('{{year}}', year.getFullYear().toString())
      .replace('{{author}}', author)

    const comment = code.getAllComments().find(it => it.type === 'Block')
    if (!comment) {
      context.report({
        loc: {
          line: 0,
          column: 0,
        },
        messageId: 'missing',
        suggest: [
          {
            messageId: 'tryThisHeader',
            data: {expected},
            fix(fixer) {
              return fixer.insertTextBeforeRange([0, 0], `/*${expected}*/\n`)
            }
          }
        ]
      })
      return {}
    }

    if (comment.value !== expected && comment.loc && comment.range) {
      const range = comment.range
      context.report({
        loc: comment.loc,
        messageId: 'invalid',
        suggest: [
          {
            messageId: 'tryThisHeader',
            data: {expected},
            fix(fixer) {
              return fixer.replaceTextRange(range, `/*${expected}*/`)
            }
          }
        ]
      })
    }

    return {}
  }
}

/**
 * Retrieves the last edited date of a file
 *
 * Uses git history if available, last modified date otherwise.
 *
 * @param filePath {string}
 * @return {Date | undefined}
 */
function getAuthorDate(filePath) {
  // just to be on the safe side
  const sanitizedPath = path.resolve(filePath)

  try {
    const result = child_process.execSync(`git log -1 --pretty="format:%ad" "${sanitizedPath}"`, {
      cwd: path.dirname(sanitizedPath),
      stdio: 'pipe',
    })
    const date = new Date(result.toString())
    if (!Number.isNaN(date.getTime())) {
      return date
    }
  } catch {}

  try {
    const stats = fs.statSync(sanitizedPath)
    const date = new Date(stats.mtime)
    return Number.isNaN(date.getTime()) ? undefined : date
  } catch {}

  return undefined
}

module.exports = copyrightHeaderRule
