#include "TableColumnAutoFit.h"

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <vector>

#include <QAbstractItemView>
#include <QAbstractItemModel>
#include <QApplication>
#include <QEvent>
#include <QFontMetrics>
#include <QHeaderView>
#include <QModelIndex>
#include <QMouseEvent>
#include <QPointer>
#include <QSignalBlocker>
#include <QSize>
#include <QTableView>
#include <QTimer>
#include <QTreeView>
#include <QVariant>
#include <QWidget>

namespace
{
    // 属性名统一集中，避免和业务控件属性冲突。
    constexpr const char* AutoFitInstalledProperty = "_ksword_table_column_auto_fit_installed";
    constexpr const char* AutoFitScheduledProperty = "_ksword_table_column_auto_fit_scheduled";
    constexpr const char* AutoFitApplyingProperty = "_ksword_table_column_auto_fit_applying";
    constexpr const char* AutoFitUserAdjustedProperty = "_ksword_table_column_auto_fit_user_adjusted";
    constexpr const char* AutoFitDisabledProperty = "_ksword_table_column_auto_fit_disabled";
    constexpr const char* AutoFitHeaderHookedProperty = "_ksword_table_column_auto_fit_header_hooked";
    constexpr const char* AutoFitResizePendingProperty = "_ksword_table_column_auto_fit_resize_pending";
    constexpr const char* AutoFitResizePressPositionProperty = "_ksword_table_column_auto_fit_resize_press_position";
    constexpr const char* AutoFitStretchSectionsProperty = "_ksword_table_column_auto_fit_stretch_sections";

    // kViewportPadding 作用：
    // - 给视口右边预留少量像素，规避样式边框/网格线取整导致的 1px 横向滚动条；
    // - 不改变滚动条策略，只让列宽总和略小于 viewport 宽度。
    constexpr int kViewportPadding = 2;

    // kPreferredMinimumSectionWidth/kAbsoluteMinimumSectionWidth 作用：
    // - 普通情况下列至少保留 24px，避免内容完全不可见；
    // - 这两个值只是兜底下限，真正决定压缩底线的是下面的“可读下限”。
    constexpr int kPreferredMinimumSectionWidth = 24;
    constexpr int kAbsoluteMinimumSectionWidth = 8;

    // kReadableSectionWidthCap 作用：
    // - 列宽压缩的“可读下限”上限：一列被压到连列头和首个词都读不出来时，
    //   表格就只剩一排省略号，用户必须把窗口拉宽才能看内容——这不是自适应，是把问题推给用户；
    // - 每列的可读下限取 min(首选宽度, 本常量)：短列（PID、CPU）按自身首选宽度封顶，
    //   不会被这个值反向撑大；只有长内容列（路径、命令行、端点）会停在本值；
    // - 当所有列的可读下限之和仍然超过 viewport 时，自动 fit 不再继续压缩，
    //   列宽总和自然超出 viewport，由 Qt 按 ScrollBarAsNeeded 显示横向滚动条。
    //   本文件全程不修改滚动条策略，因此横向滚动始终可用。
    constexpr int kReadableSectionWidthCap = 96;

    // kHeaderHorizontalPadding/kCellHorizontalPadding 作用：
    // - 用字体度量估算列宽时补偿 item margin、网格线、排序箭头和图标留白；
    // - 只影响自动计算的默认宽度，不改变实际绘制样式。
    constexpr int kHeaderHorizontalPadding = 30;
    constexpr int kCellHorizontalPadding = 22;

    // kContentSampleRowLimit 作用：
    // - 自动列宽只抽样少量行，避免大表在 UI 线程做完整 resizeToContents 扫描；
    // - 表头和前若干可见/根行通常足以识别“PID/CPU/状态”等短列和“路径/命令行”等长列。
    constexpr int kContentSampleRowLimit = 48;

    // kMeasuredTextCharacterLimit 作用：
    // - 极长路径/命令行只取前一段估算，避免单列权重无限放大；
    // - 列仍会被识别为长列，并在压缩/分配剩余空间时获得更高权重。
    constexpr int kMeasuredTextCharacterLimit = 180;

    // kLongColumnExpansionThreshold 作用：
    // - 首选宽度达到该阈值的列才参与“填满剩余宽度”；
    // - 短文本列即使表格很宽，也不会被均分成很宽的一列。
    constexpr int kLongColumnExpansionThreshold = 120;

    // kResizeGripMargin 作用：
    // - 识别用户是否在表头列边界附近按下鼠标；
    // - 只有这类动作会被视作手动调列宽，普通点击排序不禁用自动 fit。
    constexpr int kResizeGripMargin = 6;

    // kCellWidgetHorizontalPadding 作用：
    // - 给 setCellWidget/indexWidget 创建的真实控件预留单元格边距；
    // - 避免下拉框、复选框、按钮刚进入表格时被列宽算法压缩到错误位置；
    // - 该值只作为自动列宽下限/首选宽度补偿，不改变控件自身样式。
    constexpr int kCellWidgetHorizontalPadding = 10;

    struct VisibleSection
    {
        int logicalIndex = -1;  // logicalIndex：QHeaderView 逻辑列号。
        int currentWidth = 0;   // currentWidth：自动 fit 前的当前列宽快照，保留给调试与后续扩展。
        int minimumWidth = 0;   // minimumWidth：本轮压缩不应低于的列宽，控件列会把真实控件宽度计入此值。
        int preferredWidth = 0; // preferredWidth：根据表头和抽样内容估算出的首选列宽。
        int fittedWidth = 0;    // fittedWidth：压缩/扩展后的目标列宽。
        bool prefersViewportExpansion = false; // prefersViewportExpansion：业务层原本声明为 Stretch 的填充候选列。
    };

    struct SectionWidthHints
    {
        int minimumWidth = 0;   // minimumWidth：必须保留的列宽下限。
        int preferredWidth = 0; // preferredWidth：内容舒适显示时的目标宽度。
    };

    // horizontalHeaderForView 作用：
    // - 从 QTableView/QTreeView 统一取得横向表头；
    // - QTableWidget/QTreeWidget 继承自这两类，因此不需要单独分支。
    // 参数 view：候选表格视图。
    // 返回值：有效横向表头；不支持的视图返回 nullptr。
    QHeaderView* horizontalHeaderForView(QAbstractItemView* view)
    {
        if (view == nullptr || qobject_cast<QHeaderView*>(view) != nullptr)
        {
            return nullptr;
        }

        if (QTableView* tableView = qobject_cast<QTableView*>(view))
        {
            return tableView->horizontalHeader();
        }

        if (QTreeView* treeView = qobject_cast<QTreeView*>(view))
        {
            return treeView->header();
        }

        return nullptr;
    }

    // viewForHorizontalHeader 作用：
    // - 从表头反向取得所属表格/树表；
    // - 用于在用户拖拽表头边界时标记该表已经由用户手动接管列宽。
    // 参数 header：横向表头。
    // 返回值：所属 QAbstractItemView；无法解析时返回 nullptr。
    QAbstractItemView* viewForHorizontalHeader(QHeaderView* header)
    {
        if (header == nullptr || header->orientation() != Qt::Horizontal)
        {
            return nullptr;
        }

        return qobject_cast<QAbstractItemView*>(header->parentWidget());
    }

    // eventObjectToHorizontalHeader 作用：
    // - 把全局事件过滤器收到的 QObject 映射为 QHeaderView；
    // - 鼠标事件通常落在 header viewport 上，因此需要检查 parentWidget。
    // 参数 watchedObject：事件来源对象。
    // 返回值：横向 QHeaderView；不相关对象返回 nullptr。
    QHeaderView* eventObjectToHorizontalHeader(QObject* watchedObject)
    {
        if (QHeaderView* header = qobject_cast<QHeaderView*>(watchedObject))
        {
            return header->orientation() == Qt::Horizontal ? header : nullptr;
        }

        QWidget* watchedWidget = qobject_cast<QWidget*>(watchedObject);
        if (watchedWidget == nullptr)
        {
            return nullptr;
        }

        QHeaderView* parentHeader = qobject_cast<QHeaderView*>(watchedWidget->parentWidget());
        if (parentHeader == nullptr || parentHeader->orientation() != Qt::Horizontal)
        {
            return nullptr;
        }
        return parentHeader;
    }

    // isSupportedTableView 作用：
    // - 判断对象是否是本功能应处理的表格/树表视图；
    // - 排除 QHeaderView 本身，因为它也继承自 QAbstractItemView。
    // 参数 view：候选视图。
    // 返回值：true=支持自动列宽；false=忽略。
    bool isSupportedTableView(QAbstractItemView* view)
    {
        if (view == nullptr || qobject_cast<QHeaderView*>(view) != nullptr)
        {
            return false;
        }
        return qobject_cast<QTableView*>(view) != nullptr || qobject_cast<QTreeView*>(view) != nullptr;
    }

    // isAutoFitEnabled 作用：
    // - 输入：候选表格/树表视图；
    // - 处理：读取业务层可设置的禁用属性，允许文件管理器等自管列宽的视图退出全局列宽逻辑；
    // - 返回：true 表示全局自动列宽可以处理该视图，false 表示必须跳过。
    bool isAutoFitEnabled(QAbstractItemView* view)
    {
        return isSupportedTableView(view) && !view->property(AutoFitDisabledProperty).toBool();
    }

    // eventObjectToSupportedView 作用：
    // - 输入：全局事件过滤器收到的 QObject；
    // - 处理：既识别 QTableView/QTreeView 本体，也识别其 viewport 子控件；
    // - 返回：需要执行列宽自适应的视图；非表格对象返回 nullptr。
    QAbstractItemView* eventObjectToSupportedView(QObject* watchedObject)
    {
        if (QAbstractItemView* directView = qobject_cast<QAbstractItemView*>(watchedObject))
        {
            return isAutoFitEnabled(directView) ? directView : nullptr;
        }

        QWidget* watchedWidget = qobject_cast<QWidget*>(watchedObject);
        if (watchedWidget == nullptr)
        {
            return nullptr;
        }

        QAbstractItemView* parentView = qobject_cast<QAbstractItemView*>(watchedWidget->parentWidget());
        if (!isAutoFitEnabled(parentView) || parentView->viewport() != watchedWidget)
        {
            return nullptr;
        }
        return parentView;
    }

    // measuredTextWidth 作用：
    // - 用指定字体度量一段显示文本所需宽度；
    // - 对换行文本取单行化后的前缀，避免超长内容把某列权重无限放大；
    // - 返回值只包含文字像素宽度，不包含 item/header padding。
    int measuredTextWidth(const QFontMetrics& fontMetrics, const QString& sourceText)
    {
        QString measuredText = sourceText.simplified();
        if (measuredText.isEmpty())
        {
            return 0;
        }

        if (measuredText.size() > kMeasuredTextCharacterLimit)
        {
            measuredText = measuredText.left(kMeasuredTextCharacterLimit);
        }

        return std::max(0, fontMetrics.horizontalAdvance(measuredText));
    }

    // headerPreferredWidth 作用：
    // - 根据模型横向表头文本估算某列表头所需宽度；
    // - 若该列显示排序箭头，则额外补偿箭头占位；
    // - 返回值包含表头 padding，可直接参与列宽首选值计算。
    int headerPreferredWidth(
        QAbstractItemView* view,
        QHeaderView* header,
        const int logicalIndex)
    {
        if (view == nullptr || header == nullptr || logicalIndex < 0)
        {
            return 0;
        }

        QAbstractItemModel* model = view->model();
        const QVariant headerValue = model != nullptr
            ? model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole)
            : QVariant();
        const int textWidth = measuredTextWidth(header->fontMetrics(), headerValue.toString());
        const int sortIndicatorReserve = header->isSortIndicatorShown()
            && header->sortIndicatorSection() == logicalIndex
            ? 18
            : 0;
        return textWidth + kHeaderHorizontalPadding + sortIndicatorReserve;
    }

    // widgetWidthHint 作用：
    // - 输入：单元格中通过 setCellWidget/indexWidget 放入的真实控件；
    // - 处理：读取 minimumWidth、minimumSizeHint 和可选 sizeHint，得到控件不可压缩/舒适显示宽度；
    // - 返回：控件宽度加上单元格边距；无控件或控件无有效宽度时返回 0。
    int widgetWidthHint(QWidget* cellWidget, const bool includePreferredSize)
    {
        if (cellWidget == nullptr)
        {
            return 0;
        }

        int widthValue = std::max(0, cellWidget->minimumWidth());
        const QSize minimumHint = cellWidget->minimumSizeHint();
        if (minimumHint.isValid() && minimumHint.width() > 0)
        {
            widthValue = std::max(widthValue, minimumHint.width());
        }
        if (includePreferredSize)
        {
            const QSize preferredHint = cellWidget->sizeHint();
            if (preferredHint.isValid() && preferredHint.width() > 0)
            {
                widthValue = std::max(widthValue, preferredHint.width());
            }
        }

        return widthValue > 0 ? widthValue + kCellWidgetHorizontalPadding : 0;
    }

    // sampleIndexWidthHints 作用：
    // - 输入：目标视图、模型索引、字体度量、树缩进和本轮基础最小宽度；
    // - 处理：综合 DisplayRole、DecorationRole、CheckStateRole、SizeHintRole 与 indexWidget 控件尺寸；
    // - 返回：该单元格贡献给列的最小/首选宽度，控件列不会再被压缩到控件自身宽度以下。
    SectionWidthHints sampleIndexWidthHints(
        QAbstractItemView* view,
        const QModelIndex& index,
        const QFontMetrics& fontMetrics,
        const int treeIndentWidth,
        const int minimumWidth)
    {
        SectionWidthHints hints;
        hints.minimumWidth = std::max(0, minimumWidth);
        hints.preferredWidth = hints.minimumWidth;

        if (!index.isValid())
        {
            return hints;
        }

        int widthValue = measuredTextWidth(fontMetrics, index.data(Qt::DisplayRole).toString());
        if (index.data(Qt::DecorationRole).isValid())
        {
            widthValue += 20;
        }
        if (index.data(Qt::CheckStateRole).isValid())
        {
            widthValue += 18;
        }

        if (widthValue > 0)
        {
            hints.preferredWidth = std::max(
                hints.preferredWidth,
                widthValue + treeIndentWidth + kCellHorizontalPadding);
        }

        const QSize itemSizeHint = index.data(Qt::SizeHintRole).toSize();
        if (itemSizeHint.isValid() && itemSizeHint.width() > 0)
        {
            const int itemHintWidth = itemSizeHint.width() + treeIndentWidth;
            hints.minimumWidth = std::max(hints.minimumWidth, itemHintWidth);
            hints.preferredWidth = std::max(hints.preferredWidth, itemHintWidth);
        }

        QWidget* cellWidget = view == nullptr ? nullptr : view->indexWidget(index);
        const int widgetMinimumWidth = widgetWidthHint(cellWidget, false);
        if (widgetMinimumWidth > 0)
        {
            hints.minimumWidth = std::max(
                hints.minimumWidth,
                widgetMinimumWidth + treeIndentWidth);
        }

        const int widgetPreferredWidth = widgetWidthHint(cellWidget, true);
        if (widgetPreferredWidth > 0)
        {
            hints.preferredWidth = std::max(
                hints.preferredWidth,
                widgetPreferredWidth + treeIndentWidth);
        }

        hints.preferredWidth = std::max(hints.preferredWidth, hints.minimumWidth);
        return hints;
    }

    // contentWidthHints 作用：
    // - 输入：目标视图、逻辑列号和本轮基础最小宽度；
    // - 处理：抽样模型前若干行，同时考虑真实单元格控件的 minimumSizeHint/sizeHint；
    // - 返回：该列内容贡献的最小/首选宽度；无可采样内容时返回基础最小宽度。
    SectionWidthHints contentWidthHints(
        QAbstractItemView* view,
        const int logicalIndex,
        const int minimumWidth)
    {
        SectionWidthHints hints;
        hints.minimumWidth = std::max(0, minimumWidth);
        hints.preferredWidth = hints.minimumWidth;

        if (view == nullptr || logicalIndex < 0 || view->model() == nullptr)
        {
            return hints;
        }

        QAbstractItemModel* model = view->model();
        QTreeView* treeView = qobject_cast<QTreeView*>(view);
        const QFontMetrics cellFontMetrics(view->font());
        const QModelIndex rootIndex = view->rootIndex();
        int sampledRowCount = 0;

        // sampleParent 作用：
        // - 从 parentIndex 下按顺序采样若干行；
        // - 对展开的树节点递归采样，直到达到全局抽样上限；
        // - 返回行为：无返回值，通过 sampledRowCount/hints 累积结果。
        auto sampleParent =
            [&](const QModelIndex& parentIndex, const int depthValue, auto&& sampleParentRef) -> void
            {
                if (sampledRowCount >= kContentSampleRowLimit)
                {
                    return;
                }

                const int rowCount = model->rowCount(parentIndex);
                for (int rowIndex = 0;
                    rowIndex < rowCount && sampledRowCount < kContentSampleRowLimit;
                    ++rowIndex)
                {
                    const QModelIndex cellIndex = model->index(rowIndex, logicalIndex, parentIndex);
                    if (cellIndex.isValid())
                    {
                        const int treeIndentWidth =
                            treeView != nullptr && logicalIndex == 0
                            ? std::max(0, depthValue) * treeView->indentation()
                            : 0;
                        const SectionWidthHints cellHints = sampleIndexWidthHints(
                            view,
                            cellIndex,
                            cellFontMetrics,
                            treeIndentWidth,
                            minimumWidth);
                        hints.minimumWidth = std::max(hints.minimumWidth, cellHints.minimumWidth);
                        hints.preferredWidth = std::max(hints.preferredWidth, cellHints.preferredWidth);
                        ++sampledRowCount;
                    }

                    if (treeView == nullptr || sampledRowCount >= kContentSampleRowLimit)
                    {
                        continue;
                    }

                    const QModelIndex treeIndex = model->index(rowIndex, 0, parentIndex);
                    if (treeIndex.isValid() && treeView->isExpanded(treeIndex))
                    {
                        sampleParentRef(treeIndex, depthValue + 1, sampleParentRef);
                    }
                }
            };

        sampleParent(rootIndex, 0, sampleParent);
        hints.preferredWidth = std::max(hints.preferredWidth, hints.minimumWidth);
        return hints;
    }

    // sectionWidthHints 作用：
    // - 输入：目标视图、表头、逻辑列号和本轮基础最小宽度；
    // - 处理：综合表头文本、抽样内容、SizeHintRole、真实单元格控件和 Fixed 列当前宽度；
    // - 返回：本列的最小/首选宽度，后续压缩不会低于 minimumWidth。
    SectionWidthHints sectionWidthHints(
        QAbstractItemView* view,
        QHeaderView* header,
        const int logicalIndex,
        const int minimumWidth)
    {
        SectionWidthHints hints = contentWidthHints(view, logicalIndex, minimumWidth);
        hints.minimumWidth = std::max(hints.minimumWidth, minimumWidth);

        if (header != nullptr &&
            logicalIndex >= 0 &&
            logicalIndex < header->count() &&
            header->sectionResizeMode(logicalIndex) == QHeaderView::Fixed)
        {
            hints.minimumWidth = std::max(
                hints.minimumWidth,
                std::max(0, header->sectionSize(logicalIndex)));
        }

        hints.preferredWidth = std::max(
            hints.minimumWidth,
            std::max(headerPreferredWidth(view, header, logicalIndex), hints.preferredWidth));
        return hints;
    }

    // sectionHasRememberedStretchIntent 作用：
    // - 判断某个逻辑列是否曾经由业务代码设置为 QHeaderView::Stretch；
    // - 全局自动 fit 后会把列模式改成 Interactive，以便用户可拖拽，因此必须用属性保留原始 Stretch 意图；
    // - 只读取当前 header 的属性，不改变列宽或 resize mode。
    // 参数 header：目标横向表头；logicalIndex：待检查的逻辑列号。
    // 返回值：true=该列应作为“无长内容时填满剩余宽度”的优先候选；false=普通列。
    bool sectionHasRememberedStretchIntent(QHeaderView* header, const int logicalIndex)
    {
        if (header == nullptr || logicalIndex < 0 || logicalIndex >= header->count())
        {
            return false;
        }

        const QVariantList stretchSections =
            header->property(AutoFitStretchSectionsProperty).toList();
        for (const QVariant& stretchSection : stretchSections)
        {
            if (stretchSection.toInt() == logicalIndex)
            {
                return true;
            }
        }
        return false;
    }

    // rememberStretchIntentIfNeeded 作用：
    // - 在自动 fit 接管列模式前记录业务层声明为 QHeaderView::Stretch 的列；
    // - 后续即使列模式被改为 Interactive，仍能按业务意图把剩余宽度分配给这些列；
    // - 重复调用会去重，不会让属性列表无限增长。
    // 参数 header：目标横向表头；logicalIndex：待记录的逻辑列号。
    // 返回值：无；记录结果保存在 header 的 QObject 属性中。
    void rememberStretchIntentIfNeeded(QHeaderView* header, const int logicalIndex)
    {
        if (header == nullptr ||
            logicalIndex < 0 ||
            logicalIndex >= header->count() ||
            header->sectionResizeMode(logicalIndex) != QHeaderView::Stretch ||
            sectionHasRememberedStretchIntent(header, logicalIndex))
        {
            return;
        }

        QVariantList stretchSections =
            header->property(AutoFitStretchSectionsProperty).toList();
        stretchSections.append(logicalIndex);
        header->setProperty(AutoFitStretchSectionsProperty, stretchSections);
    }

    // sectionPrefersViewportExpansion 作用：
    // - 综合“当前仍是 Stretch”和“历史曾是 Stretch”两种情况，判断列是否应优先吃掉剩余 viewport 宽度；
    // - 当前仍是 Stretch 时顺手记忆，避免第一次自动 fit 后丢失业务层布局意图；
    // - 不直接调整任何列宽，真正分配在 fitWidthsToAvailableSpace 中完成。
    // 参数 header：目标横向表头；logicalIndex：待检查逻辑列号。
    // 返回值：true=无长内容列时优先填充该列；false=普通列。
    bool sectionPrefersViewportExpansion(QHeaderView* header, const int logicalIndex)
    {
        rememberStretchIntentIfNeeded(header, logicalIndex);
        return sectionHasRememberedStretchIntent(header, logicalIndex);
    }

    // collectVisibleSections 作用：
    // - 按当前视觉顺序收集没有隐藏的列；
    // - 同时读取当前列宽与内容感知首选宽度，后续再整体压入 viewport。
    // 参数 view：目标表格/树表视图。
    // 参数 header：目标横向表头。
    // 参数 minimumWidth：本轮 fit 使用的最小列宽。
    // 返回值：可见列集合；无列时返回空数组。
    std::vector<VisibleSection> collectVisibleSections(
        QAbstractItemView* view,
        QHeaderView* header,
        const int minimumWidth)
    {
        std::vector<VisibleSection> visibleSections;
        if (header == nullptr)
        {
            return visibleSections;
        }

        const int sectionCount = header->count();
        visibleSections.reserve(static_cast<std::size_t>(std::max(0, sectionCount)));
        for (int visualIndex = 0; visualIndex < sectionCount; ++visualIndex)
        {
            const int logicalIndex = header->logicalIndex(visualIndex);
            if (logicalIndex < 0 || header->isSectionHidden(logicalIndex))
            {
                continue;
            }

            const int sectionWidth = std::max(header->sectionSize(logicalIndex), minimumWidth);
            const SectionWidthHints widthHints = sectionWidthHints(
                view,
                header,
                logicalIndex,
                minimumWidth);
            visibleSections.push_back({
                logicalIndex,
                sectionWidth,
                widthHints.minimumWidth,
                widthHints.preferredWidth,
                widthHints.preferredWidth,
                sectionPrefersViewportExpansion(header, logicalIndex) });
        }

        return visibleSections;
    }

    // countVisibleSections 作用：
    // - 只统计当前没有隐藏的横向列；
    // - 用于根据“实际可见列数”计算本轮最小列宽，避免进程表这类隐藏列很多的表格被过度压缩。
    // 参数 header：目标横向表头。
    // 返回值：可见列数量；无有效表头时返回 0。
    int countVisibleSections(QHeaderView* header)
    {
        if (header == nullptr)
        {
            return 0;
        }

        int visibleSectionCount = 0;
        const int sectionCount = header->count();
        for (int visualIndex = 0; visualIndex < sectionCount; ++visualIndex)
        {
            const int logicalIndex = header->logicalIndex(visualIndex);
            if (logicalIndex >= 0 && !header->isSectionHidden(logicalIndex))
            {
                ++visibleSectionCount;
            }
        }
        return visibleSectionCount;
    }

    // fitWidthsToAvailableSpace 作用：
    // - 先按内容感知 preferredWidth 排列列宽；
    // - 若总宽超过 viewport，则按“首选宽度 - 每列最小宽度”的权重压缩到 availableWidth 内；
    // - 压缩不会突破每列的可读下限；可读下限之和都放不下时保持原宽，让横向滚动条出现；
    // - 若总宽小于 viewport，则只把剩余空间分给长内容列，短字段列不会被强行均分放大；
    // - 控件列的 minimumWidth 来自真实控件尺寸，宁可出现横向滚动条也不把控件挤坏；
    // - 不隐藏列，不改变滚动条策略。
    // 参数 sections：可见列集合，会原地写入 fittedWidth。
    // 参数 availableWidth：当前 viewport 可用宽度。
    // 返回值：无。
    void fitWidthsToAvailableSpace(
        std::vector<VisibleSection>& sections,
        const int availableWidth)
    {
        if (sections.empty() || availableWidth <= 0)
        {
            return;
        }

        const int sectionCount = static_cast<int>(sections.size());
        for (VisibleSection& section : sections)
        {
            section.minimumWidth = std::max(1, section.minimumWidth);
            // 可读下限：短列按自身首选宽度封顶，长内容列停在 kReadableSectionWidthCap。
            // preferredWidth 在 sectionWidthHints 里已保证不小于 minimumWidth，
            // 因此这里抬高后的 minimumWidth 仍不会超过 preferredWidth，宽表格分支不受影响。
            section.minimumWidth = std::max(
                section.minimumWidth,
                std::min(section.preferredWidth, kReadableSectionWidthCap));
            section.fittedWidth = std::max(section.minimumWidth, section.preferredWidth);
        }

        const auto totalFittedWidth =
            [&sections]() -> int
            {
                int totalWidth = 0;
                for (const VisibleSection& section : sections)
                {
                    totalWidth += section.fittedWidth;
                }
                return totalWidth;
            };

        int fittedTotalWidth = totalFittedWidth();
        if (fittedTotalWidth == availableWidth)
        {
            return;
        }

        if (fittedTotalWidth < availableWidth)
        {
            int remainingWidth = availableWidth - fittedTotalWidth;
            std::vector<int> expansionIndexes;
            std::vector<int> expansionWeights;

            // addExpansionTarget 作用：
            // - 记录本轮可获得剩余宽度的目标列和权重；
            // - 使用逻辑上限外的 vector 下标前先检查范围；
            // - 返回值：无，结果写入 expansionIndexes/expansionWeights。
            const auto addExpansionTarget =
                [&](const int sectionIndex, const int expansionWeight) -> void
                {
                    if (sectionIndex < 0 || sectionIndex >= sectionCount)
                    {
                        return;
                    }
                    expansionIndexes.push_back(sectionIndex);
                    expansionWeights.push_back(std::max(1, expansionWeight));
                };

            // 第一优先级：仍按原策略把空间分给明显长内容列，路径/命令行/日志内容这类列会获得更多空间。
            for (int sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex)
            {
                const VisibleSection& section = sections[static_cast<std::size_t>(sectionIndex)];
                if (section.preferredWidth >= kLongColumnExpansionThreshold)
                {
                    addExpansionTarget(
                        sectionIndex,
                        section.preferredWidth - kLongColumnExpansionThreshold);
                }
            }

            // 第二优先级：没有明显长内容列时，尊重业务层原本设置为 Stretch 的列来填满 viewport。
            if (expansionIndexes.empty())
            {
                for (int sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex)
                {
                    const VisibleSection& section = sections[static_cast<std::size_t>(sectionIndex)];
                    if (section.prefersViewportExpansion)
                    {
                        addExpansionTarget(sectionIndex, section.preferredWidth);
                    }
                }
            }

            // 第三优先级：没有 Stretch 意图时，把剩余空间给当前首选宽度最大的可见列。
            if (expansionIndexes.empty())
            {
                const auto largestPreferredIt = std::max_element(
                    sections.begin(),
                    sections.end(),
                    [](const VisibleSection& leftSection, const VisibleSection& rightSection)
                    {
                        return leftSection.preferredWidth < rightSection.preferredWidth;
                    });
                if (largestPreferredIt != sections.end())
                {
                    addExpansionTarget(
                        static_cast<int>(std::distance(sections.begin(), largestPreferredIt)),
                        largestPreferredIt->preferredWidth);
                }
            }

            // 兜底：理论上 sections 非空时不会走到这里；仍保留最后可见列，确保默认总宽尽量贴合 viewport。
            if (expansionIndexes.empty())
            {
                addExpansionTarget(sectionCount - 1, 1);
            }

            int totalExpansionWeight = 0;
            for (const int expansionWeight : expansionWeights)
            {
                totalExpansionWeight += std::max(1, expansionWeight);
            }
            if (totalExpansionWeight <= 0)
            {
                return;
            }

            for (std::size_t targetIndex = 0; targetIndex < expansionIndexes.size(); ++targetIndex)
            {
                const int sectionIndex = expansionIndexes[targetIndex];
                const int extraWidth =
                    remainingWidth * expansionWeights[targetIndex] / totalExpansionWeight;
                sections[static_cast<std::size_t>(sectionIndex)].fittedWidth += extraWidth;
            }

            int finalTotalWidth = totalFittedWidth();
            std::size_t roundRobinIndex = 0;
            while (finalTotalWidth < availableWidth && !expansionIndexes.empty())
            {
                const int sectionIndex =
                    expansionIndexes[roundRobinIndex % expansionIndexes.size()];
                ++sections[static_cast<std::size_t>(sectionIndex)].fittedWidth;
                ++finalTotalWidth;
                ++roundRobinIndex;
            }
            return;
        }

        int minimumTotalWidth = 0;
        for (const VisibleSection& section : sections)
        {
            minimumTotalWidth += section.minimumWidth;
        }
        if (minimumTotalWidth >= availableWidth)
        {
            for (VisibleSection& section : sections)
            {
                section.fittedWidth = section.minimumWidth;
            }
            return;
        }

        int remainingAssignableWidth = availableWidth - minimumTotalWidth;
        int totalContentWeight = 0;
        for (const VisibleSection& section : sections)
        {
            totalContentWeight += std::max(0, section.preferredWidth - section.minimumWidth);
        }

        for (VisibleSection& section : sections)
        {
            section.fittedWidth = section.minimumWidth;
        }

        if (totalContentWeight <= 0)
        {
            for (VisibleSection& section : sections)
            {
                if (remainingAssignableWidth <= 0)
                {
                    break;
                }
                ++section.fittedWidth;
                --remainingAssignableWidth;
            }
            return;
        }

        for (VisibleSection& section : sections)
        {
            const int contentWeight = std::max(0, section.preferredWidth - section.minimumWidth);
            const int extraWidth =
                remainingAssignableWidth * contentWeight / totalContentWeight;
            section.fittedWidth += extraWidth;
        }

        int finalTotalWidth = totalFittedWidth();
        while (finalTotalWidth < availableWidth)
        {
            auto targetIt = std::max_element(
                sections.begin(),
                sections.end(),
                [](const VisibleSection& leftSection, const VisibleSection& rightSection)
                {
                    return (leftSection.preferredWidth - leftSection.fittedWidth)
                        < (rightSection.preferredWidth - rightSection.fittedWidth);
                });
            if (targetIt == sections.end())
            {
                break;
            }
            ++targetIt->fittedWidth;
            ++finalTotalWidth;
        }

        while (finalTotalWidth > availableWidth)
        {
            auto targetIt = std::max_element(
                sections.begin(),
                sections.end(),
                [](const VisibleSection& leftSection, const VisibleSection& rightSection)
                {
                    return leftSection.fittedWidth < rightSection.fittedWidth;
                });
            if (targetIt == sections.end() || targetIt->fittedWidth <= targetIt->minimumWidth)
            {
                break;
            }
            --targetIt->fittedWidth;
            --finalTotalWidth;
        }
    }

    // refreshViewGeometryAfterFit 作用：
    // - 输入：已经完成列宽调整的表格/树表和横向表头；
    // - 处理：触发 QAbstractItemView 重新布局 indexWidget/setCellWidget 控件几何，并刷新 viewport/header；
    // - 返回：无。该函数只修复显示几何，不修改模型数据或选择状态。
    void refreshViewGeometryAfterFit(QAbstractItemView* view, QHeaderView* header)
    {
        if (view == nullptr)
        {
            return;
        }

        view->doItemsLayout();
        view->updateGeometry();
        if (view->viewport() != nullptr)
        {
            view->viewport()->updateGeometry();
            view->viewport()->update();
        }
        if (header != nullptr)
        {
            header->updateGeometry();
            if (header->viewport() != nullptr)
            {
                header->viewport()->update();
            }
        }
    }

    // fitViewColumnsToViewport 作用：
    // - 将目标表格当前可见列一次性调整到 viewport 宽度以内；
    // - 统一把列 resize mode 设为 Interactive，保证用户后续可以拖拽调整；
    // - 不设置/隐藏任何滚动条。
    // 参数 view：目标表格/树表视图。
    // 返回值：true=执行过有效 fit；false=当前视图暂不可处理。
    bool fitViewColumnsToViewport(QAbstractItemView* view)
    {
        if (!isAutoFitEnabled(view) ||
            !view->isVisible() ||
            view->property(AutoFitUserAdjustedProperty).toBool())
        {
            return false;
        }

        QHeaderView* header = horizontalHeaderForView(view);
        if (header == nullptr || header->count() <= 0 || view->viewport() == nullptr)
        {
            return false;
        }
        if (!view->viewport()->isVisible())
        {
            return false;
        }

        const int availableWidth = view->viewport()->width() - kViewportPadding;
        if (availableWidth <= 0)
        {
            return false;
        }

        const int visibleSectionCount = countVisibleSections(header);
        if (visibleSectionCount <= 0)
        {
            return false;
        }

        const int dynamicMinimumWidth = std::max(
            kAbsoluteMinimumSectionWidth,
            std::min(kPreferredMinimumSectionWidth, availableWidth / visibleSectionCount));
        bool geometryRefreshRequired = false;
        if (header->minimumSectionSize() > dynamicMinimumWidth)
        {
            header->setMinimumSectionSize(dynamicMinimumWidth);
            geometryRefreshRequired = true;
        }

        std::vector<VisibleSection> visibleSections =
            collectVisibleSections(view, header, dynamicMinimumWidth);
        if (visibleSections.empty())
        {
            return false;
        }

        fitWidthsToAvailableSpace(visibleSections, availableWidth);

        geometryRefreshRequired = geometryRefreshRequired || header->stretchLastSection();
        for (const VisibleSection& section : visibleSections)
        {
            if (header->sectionResizeMode(section.logicalIndex) != QHeaderView::Interactive ||
                header->sectionSize(section.logicalIndex) != section.fittedWidth)
            {
                geometryRefreshRequired = true;
                break;
            }
        }

        // LayoutRequest 会由 doItemsLayout()/updateGeometry() 异步回送到全局过滤器。
        // 当前列宽已经吻合时不能再次刷新几何，否则会形成 singleShot(0) 的空闲重排循环。
        if (!geometryRefreshRequired)
        {
            return true;
        }

        view->setProperty(AutoFitApplyingProperty, true);

        {
            const QSignalBlocker headerSignalBlocker(header);
            header->setStretchLastSection(false);
            for (const VisibleSection& section : visibleSections)
            {
                header->setSectionResizeMode(section.logicalIndex, QHeaderView::Interactive);
            }
            for (const VisibleSection& section : visibleSections)
            {
                header->resizeSection(section.logicalIndex, section.fittedWidth);
            }
        }

        refreshViewGeometryAfterFit(view, header);
        view->setProperty(AutoFitApplyingProperty, false);
        return true;
    }

    // scheduleColumnFit 作用：
    // - 把多次 show/resize/layout/viewport resize 事件合并为一次队列尾部 fit；
    // - viewport resize 事件发生时尺寸已经更新，singleShot(0) 只用于等当前 Qt 事件处理完成；
    // - 用户拖拽列宽后会由 AutoFitUserAdjustedProperty 跳过，避免覆盖用户布局。
    // 参数 view：目标表格/树表视图。
    // 返回值：无。
    void scheduleColumnFit(QAbstractItemView* view)
    {
        if (!isAutoFitEnabled(view) ||
            view->property(AutoFitApplyingProperty).toBool() ||
            view->property(AutoFitScheduledProperty).toBool() ||
            view->property(AutoFitUserAdjustedProperty).toBool())
        {
            return;
        }

        view->setProperty(AutoFitScheduledProperty, true);
        const QPointer<QAbstractItemView> guardedView(view);
        QTimer::singleShot(0, view, [guardedView]()
            {
                if (guardedView.isNull())
                {
                    return;
                }

                guardedView->setProperty(AutoFitScheduledProperty, false);
                fitViewColumnsToViewport(guardedView.data());
            });
    }

    // installHeaderAutoFitHooks 作用：
    // - 给表格横向表头安装一次轻量信号钩子；
    // - 业务代码在 show 后继续 setColumnWidth、隐藏/显示列、替换模型导致列结构变化时，也能再触发一次 fit；
    // - 用户已经手动拖拽过列宽的表格会被 AutoFitUserAdjustedProperty 排除，不会被钩子重新压缩。
    // 参数 view：目标表格/树表视图。
    // 返回值：无。重复调用会由属性去重。
    void installHeaderAutoFitHooks(QAbstractItemView* view)
    {
        if (!isAutoFitEnabled(view))
        {
            return;
        }

        QHeaderView* header = horizontalHeaderForView(view);
        if (header == nullptr || header->property(AutoFitHeaderHookedProperty).toBool())
        {
            return;
        }

        header->setProperty(AutoFitHeaderHookedProperty, true);
        const QPointer<QAbstractItemView> guardedView(view);

        // sectionResized 覆盖业务 setColumnWidth/resizeSection，也覆盖模型列宽重算。
        QObject::connect(
            header,
            &QHeaderView::sectionResized,
            header,
            [guardedView](int, int, int)
            {
                if (!guardedView.isNull())
                {
                    scheduleColumnFit(guardedView.data());
                }
            });

        // sectionCountChanged 覆盖模型替换、列新增/删除等结构变化。
        QObject::connect(
            header,
            &QHeaderView::sectionCountChanged,
            header,
            [guardedView](int, int)
            {
                if (!guardedView.isNull())
                {
                    scheduleColumnFit(guardedView.data());
                }
            });

        // geometriesChanged 覆盖隐藏列、显示列、表头布局刷新等无 sectionResized 的情况。
        QObject::connect(
            header,
            &QHeaderView::geometriesChanged,
            header,
            [guardedView]()
            {
                if (!guardedView.isNull())
                {
                    scheduleColumnFit(guardedView.data());
                }
            });
    }

    // isNearHeaderResizeBoundary 作用：
    // - 判断鼠标位置是否靠近任意可见列边界；
    // - 仅这种按压会被视为“用户准备拖拽列宽”。
    // 参数 header：目标横向表头。
    // 参数 localPosition：鼠标在 header/viewport 坐标系中的位置。
    // 返回值：true=接近列边界；false=普通表头点击。
    bool isNearHeaderResizeBoundary(QHeaderView* header, const QPoint& localPosition)
    {
        if (header == nullptr || header->count() <= 0)
        {
            return false;
        }

        const int sectionCount = header->count();
        for (int visualIndex = 0; visualIndex < sectionCount; ++visualIndex)
        {
            const int logicalIndex = header->logicalIndex(visualIndex);
            if (logicalIndex < 0 || header->isSectionHidden(logicalIndex))
            {
                continue;
            }

            const int sectionLeft = header->sectionViewportPosition(logicalIndex);
            const int sectionRight = sectionLeft + header->sectionSize(logicalIndex);
            if (std::abs(localPosition.x() - sectionRight) <= kResizeGripMargin)
            {
                return true;
            }
        }

        return false;
    }

    // updateUserColumnResizeState 作用：
    // - 捕获用户在表头列边界附近按下并拖动鼠标的动作；
    // - 只有拖动距离超过 QApplication 的拖拽阈值后，才视作用户手动调列宽；
    // - 一旦用户开始手动调列宽，该表后续不再被全局自动 fit 干预。
    // 参数 watchedObject：全局事件源对象。
    // 参数 eventObject：当前事件。
    // 返回值：无。
    void updateUserColumnResizeState(QObject* watchedObject, QEvent* eventObject)
    {
        if (eventObject == nullptr)
        {
            return;
        }

        QHeaderView* header = eventObjectToHorizontalHeader(watchedObject);
        if (header == nullptr)
        {
            return;
        }

        if (eventObject->type() == QEvent::MouseButtonPress)
        {
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(eventObject);
            if (mouseEvent->button() != Qt::LeftButton)
            {
                return;
            }

            const QPoint headerPosition = header->viewport() == watchedObject
                ? mouseEvent->position().toPoint()
                : header->mapFromGlobal(mouseEvent->globalPosition().toPoint());
            if (!isNearHeaderResizeBoundary(header, headerPosition))
            {
                return;
            }

            header->setProperty(AutoFitResizePendingProperty, true);
            header->setProperty(AutoFitResizePressPositionProperty, headerPosition);
            return;
        }

        if (eventObject->type() == QEvent::MouseMove)
        {
            if (!header->property(AutoFitResizePendingProperty).toBool())
            {
                return;
            }

            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(eventObject);
            if ((mouseEvent->buttons() & Qt::LeftButton) == 0)
            {
                header->setProperty(AutoFitResizePendingProperty, false);
                return;
            }

            const QPoint headerPosition = header->viewport() == watchedObject
                ? mouseEvent->position().toPoint()
                : header->mapFromGlobal(mouseEvent->globalPosition().toPoint());
            const QPoint pressPosition =
                header->property(AutoFitResizePressPositionProperty).toPoint();
            if ((headerPosition - pressPosition).manhattanLength() < QApplication::startDragDistance())
            {
                return;
            }

            header->setProperty(AutoFitResizePendingProperty, false);
            if (QAbstractItemView* view = viewForHorizontalHeader(header))
            {
                view->setProperty(AutoFitUserAdjustedProperty, true);
            }
            return;
        }

        if (eventObject->type() == QEvent::MouseButtonRelease)
        {
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(eventObject);
            if (mouseEvent->button() == Qt::LeftButton)
            {
                header->setProperty(AutoFitResizePendingProperty, false);
            }
        }
    }

    class GlobalTableColumnAutoFitFilter final : public QObject
    {
    public:
        // 构造函数作用：
        // - 绑定 QApplication 为父对象；
        // - 生命周期跟随应用进程，无需手动释放。
        // 参数 parentObject：通常为 QApplication。
        explicit GlobalTableColumnAutoFitFilter(QObject* parentObject)
            : QObject(parentObject)
        {
        }

    protected:
        // eventFilter 作用：
        // - 全局捕获表格 show/resize/layout 事件并延迟执行列宽 fit；
        // - 全局捕获表头边界鼠标按压并标记用户接管列宽；
        // - 返回 false，不吞掉业务事件。
        // 参数 watchedObject：事件源对象。
        // 参数 eventObject：当前事件。
        // 返回值：始终继续 Qt 默认事件分发。
        bool eventFilter(QObject* watchedObject, QEvent* eventObject) override
        {
            updateUserColumnResizeState(watchedObject, eventObject);

            QAbstractItemView* view = eventObjectToSupportedView(watchedObject);
            if (view != nullptr && eventObject != nullptr)
            {
                installHeaderAutoFitHooks(view);

                const QEvent::Type eventType = eventObject->type();
                if (eventType == QEvent::Show ||
                    eventType == QEvent::Resize ||
                    eventType == QEvent::Polish ||
                    eventType == QEvent::LayoutRequest ||
                    eventType == QEvent::StyleChange)
                {
                    scheduleColumnFit(view);
                }
            }

            return QObject::eventFilter(watchedObject, eventObject);
        }
    };
}

namespace ks::ui
{
    void InstallGlobalTableColumnAutoFit(QApplication* appInstance)
    {
        if (appInstance == nullptr ||
            appInstance->property(AutoFitInstalledProperty).toBool())
        {
            return;
        }

        auto* filter = new GlobalTableColumnAutoFitFilter(appInstance);
        appInstance->installEventFilter(filter);
        appInstance->setProperty(AutoFitInstalledProperty, true);
    }

    void RequestTableColumnAutoFit(QAbstractItemView* view)
    {
        scheduleColumnFit(view);
    }

    // SetTableColumnAutoFitEnabled 作用：
    // - 输入：目标表格/树表视图和启用状态；
    // - 处理：通过 QObject 动态属性让特定视图退出或恢复全局列宽自适应；
    // - 返回：无返回值；恢复启用时会立即排队一次单次 fit，关闭时清理待执行请求。
    void SetTableColumnAutoFitEnabled(QAbstractItemView* view, const bool enabled)
    {
        if (!isSupportedTableView(view))
        {
            return;
        }

        view->setProperty(AutoFitDisabledProperty, !enabled);
        if (!enabled)
        {
            view->setProperty(AutoFitScheduledProperty, false);
            return;
        }

        scheduleColumnFit(view);
    }
}
