# -----------------------------------------------------------------------------
# Copyright (C) 2026 Iliyas Jorio.
# This file is part of GitFourchette, distributed under the GNU GPL v3.
# For full terms, see the included LICENSE file.
# -----------------------------------------------------------------------------

from __future__ import annotations

from gitfourchette import settings, colors
from gitfourchette.blameview.blamemodel import BlameModel
from gitfourchette.codeview.codegutter import CodeGutter
from gitfourchette.localization import *
from gitfourchette.porcelain import Oid
from gitfourchette.qt import *
from gitfourchette.repomodel import UC_FAKEID
from gitfourchette.toolbox import *


class BlameGutter(CodeGutter):
    model: BlameModel

    def __init__(self, parent):
        super().__init__(parent)

        font = QFontDatabase.systemFont(QFontDatabase.SystemFont.SmallestReadableFont)
        setFontFeature(font, "tnum")  # Tabular numbers
        self.setFont(font)

        self.boldFont = QFont(font)
        self.boldFont.setBold(True)

        self.model = None

        self.installEventFilter(self)

        self.columnMetrics = []
        self.preferredWidth = 0
        self.lineHeight = 12

        self.lineColor = QColor()
        self.textColor = QColor()
        self.boldTextColor = QColor()
        self.freshColor = QColor()
        self.heatColor = QColor()
        self.unknownColor = QColor()

        self.refreshMetrics()

    def syncFont(self, codeFont: QFont):
        pointSize = codeFont.pointSizeF()
        codeFont = self.font()
        codeFont.setPointSizeF(pointSize)
        self.boldFont.setPointSizeF(pointSize)
        super().syncFont(codeFont)

    def refreshMetrics(self):
        fontMetrics = self.fontMetrics()

        maxLineNumber = self.codeView.blockCount()

        dateWidth = fontMetrics.horizontalAdvance("2000-00-00 ")
        authorWidth = fontMetrics.horizontalAdvance("M" * 8)
        lnWidth = fontMetrics.horizontalAdvance(" " + "0" * len(str(maxLineNumber)))

        self.columnMetrics = []
        x = 2
        for w in (dateWidth, authorWidth, lnWidth):
            self.columnMetrics.append((x, w))
            x += w
        x += 3
        self.preferredWidth = x

        self.lineHeight = max(fontMetrics.height(), self.codeView.fontMetrics().height())

        # Cache foreground colors
        standardForegroundColor = self.palette().color(QPalette.ColorRole.Text)
        fgRgb = standardForegroundColor.getRgb()[:3]
        self.lineColor = QColor(*fgRgb, 80)
        self.textColor = QColor(*fgRgb, 160)
        self.boldTextColor = QColor(*fgRgb, 210)

        # Cache background colors (make copies so alpha can be changed)
        self.heatColor = QColor(colors.orange)
        self.freshColor = QColor(colors.aqua)
        self.freshColor.setAlphaF(.6 if isDarkTheme(self.palette()) else .8)
        self.unknownColor = QColor(colors.fuchsia)
        self.unknownColor.setAlphaF(.4 if isDarkTheme(self.palette()) else .6)

    def calcWidth(self) -> int:
        return self.preferredWidth

    def eventFilter(self, watched, event: QEvent):
        if event.type() == QEvent.Type.ToolTip:
            return self.doToolTip(event)
        return False

    def paintEvent(self, event: QPaintEvent):
        revision = self.model.currentRevision
        if revision is None:
            return

        painter = QPainter(self)
        bgColor = self.heatColor

        # Gather some metrics
        rightEdge = self.rect().width() - 1
        lh = self.lineHeight

        lc2 = QColor(self.lineColor)
        lc2.setAlphaF(lc2.alphaF()/2)
        linePen = QPen(lc2)
        textPen = QPen(self.textColor)
        boldTextPen = QPen(self.boldTextColor)
        painter.setPen(textPen)

        lastCaptionDrawnAtLine = -1
        hunkCommitId = None
        hunkStartLine = 1

        alignRight = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter

        topCommitId = revision.commitId
        topRevisionNumber = self.model.revList.revisionNumber(topCommitId)

        for block, top, bottom in self.paintBlocks(event, painter, self.lineColor):
            lineNumber = 1 + block.blockNumber()
            try:
                annotatedLine = revision.blameLines[lineNumber]
                lineCommitId = annotatedLine.commitId
            except IndexError:
                break

            if lineCommitId != hunkCommitId:
                hunkCommitId = lineCommitId
                hunkStartLine = lineNumber
                isCurrent = lineCommitId == topCommitId
                painter.setFont(self.boldFont if isCurrent else self.font())
                painter.setPen(boldTextPen if isCurrent else textPen)

                try:
                    revisionNumber = self.model.revList.revisionNumber(lineCommitId)
                except LookupError:
                    revisionNumber = -1

                # Compute heat color
                if revisionNumber == topRevisionNumber:
                    bgColor = self.freshColor
                elif revisionNumber == -1:
                    bgColor = self.unknownColor
                else:
                    heat = revisionNumber / (topRevisionNumber - 1)
                    heat = heat ** 2  # ease in cubic
                    bgColor = self.heatColor
                    bgColor.setAlphaF(lerp(.0, .6, heat))

            # Fill heat rectangle
            heatTop = top if lastCaptionDrawnAtLine >= 0 else 0
            painter.fillRect(QRect(0, heatTop, rightEdge, bottom-heatTop), bgColor)

            # Draw line number
            lineNumL, lineNumW = self.columnMetrics[-1]
            painter.drawText(lineNumL, top, lineNumW, lh, alignRight, str(lineNumber))

            # Draw caption + separator line
            if lastCaptionDrawnAtLine < hunkStartLine:
                self.drawBlameCaption(lineCommitId, painter, top, lh)

                # Hunk separator line
                if lastCaptionDrawnAtLine > 0:
                    penBackup = painter.pen()
                    painter.setPen(linePen)
                    painter.drawLine(QLine(0, top, rightEdge-1, top))
                    painter.setPen(penBackup)  # restore text pen

                lastCaptionDrawnAtLine = lineNumber

        painter.end()

    def drawBlameCaption(self, commitId: Oid, painter: QPainter, top: int, lh: int):
        alignLeft = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
        dateL, dateW = self.columnMetrics[0]
        nameL, nameW = self.columnMetrics[1]

        if commitId == UC_FAKEID:
            dateText = self.locale().toString(QDateTime.currentDateTime(), "yyyy-MM-dd")
            nameText = _("(Uncommitted)")
        else:
            commit = self.model.repo.peel_commit(commitId)
            sig = commit.author
            dateText = signatureDateFormat(sig, "yyyy-MM-dd", localTime=True)
            nameText = abbreviatePerson(sig, AuthorDisplayStyle.LastName)

        # Date
        FittedText.draw(painter, QRect(dateL, top, dateW, lh), alignLeft, dateText, bypassSetting=True)

        # Author
        FittedText.draw(painter, QRect(nameL, top, nameW, lh), alignLeft, nameText)

    def doToolTip(self, event: QHelpEvent):
        assert isinstance(event, QHelpEvent)

        pos = event.globalPos()
        editLocalPos = self.codeView.mapFromGlobal(pos)
        textCursor = self.codeView.cursorForPosition(editLocalPos)
        lineNumber = 1 + textCursor.blockNumber()

        try:
            commitId = self.model.currentRevision.blameLines[lineNumber].commitId
        except LookupError:
            return False

        try:
            revision = self.model.revList.revisionForCommit(commitId)
            revisionNumber = self.model.revList.revisionNumber(commitId)
        except LookupError:
            revision = None
            revisionNumber = -1

        text = "<table style='white-space: pre'>"

        muted = mutedToolTipColorHex()
        colon = _(":")
        def newLine(heading, caption):
            return f"<tr><td style='color:{muted}; text-align: right;'>{heading}{colon} </td><td>{caption}</td>"

        isWorkdir = commitId == UC_FAKEID
        if isWorkdir:
            text += newLine(_("commit"), _("Not Committed Yet"))
        else:
            commit = self.model.repo.peel_commit(commitId)
            text += newLine(_("commit"), shortHash(commitId))
            text += newLine(_("author"), commit.author.name)
            text += newLine(_("date"), signatureDateFormat(commit.author, settings.prefs.shortTimeFormat, localTime=False))
        text += newLine(_("file name"), revision.path if revision else _("(not available)"))
        text += newLine(_("revision"), revisionNumber if revisionNumber >= 0 else _("(not available)"))
        text += "</table>"
        if not isWorkdir:
            text += "<p>" + escape(commit.message.rstrip()).replace("\n", "<br>") + "</p>"

        QToolTip.showText(event.globalPos(), text, self)
        event.accept()
        return True
