#include "presenter.h"

#include <stddef.h>
#include <string.h>
#include "../global/global_state.h"
#include "../../key.h"

/* -------------------------------------------------------------------------
 * 枚举名：MenuNavResult
 * 功能:
 *   菜单导航操作后的刷新结果状态指示器。用于 `MenuNavigator_ProcessKey()`
 *   函数返回值，标识按键操作是否导致菜单界面需要重新绘制。
 *
 * 成员说明:
 *   MENU_NAV_NO_REFRESH (0) - 无刷新：
 *       表示当前按键操作未引起菜单焦点或层级状态的变化，
 *       或变化不足以触发界面重绘（例如 F1/F2 功能键、无效按键等）。
 *       调用方收到此值后不应执行额外的刷新操作。
 *
 *   MENU_NAV_REFRESH (1) - 需刷新：
 *       表示当前按键操作导致菜单选中项或层级发生变化，
 *       需要重建导航路径并触发界面刷新以反映新的状态。
 *
 * 使用场景:
 *   - `MenuNavigator_ProcessKey()` 根据按键类型和当前菜单状态计算是否需要刷新
 *   - `MenuPresenter_HandleInput()` 根据返回值决定是否调用路由重建和刷新接口
 *   - 返回值为 MENU_NAV_REFRESH 时，会触发：
 *       1) MenuNavigator_RebuildRoute() 重建当前导航路径 ptRoute[]
 *       2) Presenter->refresh() 执行界面增量或整页刷新
 *
 * 设计意图:
 *   - 通过返回值区分"需刷新"和"无需刷新"两种状态，避免不必要的界面重绘
 *   - 将导航计算与渲染决策分离，符合关注点分离原则
 * ------------------------------------------------------------------------- */
typedef enum
{
    MENU_NAV_NO_REFRESH = 0,  ///< 无需刷新：按键未引起菜单焦点/层级变化（如 F1/F2、无效按键）
    MENU_NAV_REFRESH = 1      ///< 需要刷新：按键导致选中项或层级改变，需重建路径并触发界面更新
} MenuNavResult;

/* -------------------------------------------------------------------------
 * 函数名：MenuNavigator_ProcessKey
 * 功能:
 *   处理菜单导航按键，根据当前选中项和输入按键更新导航状态，并返回是否需要刷新界面。
 *   本函数是菜单导航的核心逻辑，负责解析各类按键（方向键、确认键、返回键等）并执行
 *   相应的菜单层级跳转和选中项变更操作。
 *
 * 参数:
 *   menuCtrl - 菜单运行时控制上下文指针，包含当前选中项、路径数组、各级菜单节点引用等状态
 *   keyVal   - 输入按键值（来自 KEY_F1, KEY_F2, KEY_U, KEY_D, KEY_L, KEY_R, KEY_ENT, KEY_ESC 等）
 *
 * 返回值:
 *   MenuNavResult - 导航结果枚举：
 *     MENU_NAV_NO_REFRESH (0) - 无需刷新：按键未引起状态变化（如 F1/F2、未知按键）
 *     MENU_NAV_REFRESH (1)    - 需要刷新：选中项或层级发生变化，需触发界面更新
 *
 * 按键处理逻辑:
 *   KEY_F1 / KEY_F2: 
 *       功能键，当前不执行任何导航操作，仅占用一个 case 分支。
 *   
 *   KEY_U (Up - 上移):
 *       将选中项移动到前一项（ptCurrent->links.before），标记需要刷新界面。
 *   
 *   KEY_D (Down - 下移):
 *       将选中项移动到后一项（ptCurrent->links.behind），标记需要刷新界面。
 *   
 *   KEY_L (Left - 左移/返回上级):
 *       向左移动或返回上一级菜单：
 *       1) 根据当前层级从 ptRoute 数组获取对应层级的节点
 *       2) 若为 0 级菜单（byClass == 0）:
 *          - wPos == 1 时：向前跳过两个节点后下探到子级
 *          - wPos != 1 时：向前一个节点后下探到子级
 *       3) 标记需要刷新界面。
 *   
 *   KEY_R / KEY_ENT (Right/Enter - 右移/确认):
 *       向右移动或进入子菜单：
 *       a) 若当前项有子菜单（links.lower != NULL）:
 *          下探到子菜单节点，标记需要刷新。
 *       b) 否则若当前项绑定了窗口回调函数（pfnWinProc != NULL）:
 *          直接执行该回调函数（如打开弹窗、启动新界面等）。
 *   
 *   KEY_ESC (Escape - 返回/退出):
 *       返回或退出菜单：
 *       a) 若当前在 1 级菜单（byClass == 1）:
 *          重置导航状态到初始根节点，快速返回顶层。
 *       b) 否则：
 *          向上回退一级（ptRoute[byClass - 1]），标记需要刷新。
 *   
 *   default (未知按键):
 *       直接返回 MENU_NAV_NO_REFRESH，不执行任何操作。
 *
 * 边界处理:
 *   - 本函数假设 menuCtrl 指针有效，不做空指针校验（由调用方保证）。
 *   - 在访问 ptCurrent->links.before/behind/lower/higher 等指针前，依赖菜单树结构的正确性。
 *   - 对于子菜单为空或回调函数为 NULL 的情况有相应判断保护。
 *
 * 实现说明:
 *   - 使用局部变量 ptCurrent、ptHead、ptRoute 缓存 menuCtrl 中的状态引用，避免多次解引用。
 *   - needRefresh 标志用于累积本次按键操作是否需要刷新界面（默认 0，遇到有效导航操作后置为 1）。
 *   - 函数末尾统一设置 menuCtrl->ptCurrent = ptCurrent 更新选中项，并根据 needRefresh 返回结果。
 * ------------------------------------------------------------------------- */
static MenuNavResult MenuNavigator_ProcessKey(tagMenuCtrl *menuCtrl, uint8_t keyVal)
{
    uint8_t needRefresh = 0;        /* < 刷新标志：1 表示需要刷新界面，0 表示无需刷新 */
    tagPMenuItem ptCurrent;         /* < 当前选中菜单项指针（局部副本） */
    tagPMenuItem ptHead;            /* < 菜单树根节点指针（局部副本） */
    tagPMenuItem *ptRoute;          /* < 导航路径数组指针（局部副本，用于快速访问各级菜单节点） */

    /* 缓存 menuCtrl 中的状态引用，避免多次解引用 */
    ptCurrent = menuCtrl->ptCurrent;
    ptHead = menuCtrl->ptHead;
    ptRoute = menuCtrl->ptRoute;
    switch (keyVal)
    {
        case KEY_F1:
        case KEY_F2:
            /* 功能键：当前不执行任何导航操作 */
            break;
            
        case KEY_U:
            /* Up - 向上移动选中项到前一项 */
            ptCurrent = ptCurrent->links.before;
            needRefresh = 1;
            break;
            
        case KEY_D:
            /* Down - 向下移动选中项到后一项 */
            ptCurrent = ptCurrent->links.behind;
            needRefresh = 1;
            break;
            
        case KEY_L:
            /* Left 返回上一级菜单 */
            /* 由于 ptCurrent 不能为 0 级菜单，byClass - 1 必定大于 0 */
            ptCurrent = ptRoute[ptCurrent->menuDef.byClass - 1];
            /* 若当前项为 0 级菜单，则直接下探到子级 */
            if (ptCurrent->menuDef.byClass == 0)
            {
                /*当前的逻辑是，每次都回到第一个0级菜单，然后下探到子级*/
                /* 0 级菜单的特殊处理：根据 wPos 决定向前跳过的节点数 */
                if (ptCurrent->rect.wPos == 0)
                {
                    /* 只有两个 0级菜单，跳两次又回到自己 */
                    ptCurrent = ptCurrent->links.before->links.before;
                }
                else /* 若 wPos != 1，则向前跳过一个节点 */
                {
                    ptCurrent = ptCurrent->links.before;
                }
                ptCurrent = ptCurrent->links.lower;
            }
            needRefresh = 1;
            break;
            
        case KEY_R:
        case KEY_ENT:
            /* Right/Enter - 向右移动或进入子菜单 */
            if (ptCurrent->links.lower != NULL)
            {
                /* 有子菜单：下探到子级节点 */
                ptCurrent = ptCurrent->links.lower;
                needRefresh = 1;
            }
            else if (ptCurrent->menuDef.pfnWinProc != NULL)
            {
                GlobalModel_SetMenuAction(ptCurrent->menuDef.byAttrib, ptCurrent->menuDef.byName, ptCurrent->menuDef.byTip, ptCurrent->menuDef.wPara);
                /* 无子菜单但有回调函数：执行窗口回调（如打开弹窗） */
                ptCurrent->menuDef.pfnWinProc();
            }
            break;
            
        case KEY_ESC:
            /* Escape - 返回或退出菜单 */
            if (ptCurrent->menuDef.byClass == 1)
            {
                /* 在 1 级菜单：快速重置到根节点状态 */
                /* 强制整页刷新：避免 invert 反显状态残留叠加 */
                menuCtrl->pt0Level = NULL;
                menuCtrl->ptRoute[0] = ptHead;
                menuCtrl->ptCurrent = ptHead->links.lower;
                menuCtrl->ptCurBak = menuCtrl->ptCurrent;
                menuCtrl->ptRoute[1] = menuCtrl->ptCurrent;
                return MENU_NAV_REFRESH;
            }
            /* 在其他层级：向上回退一级 */
            ptCurrent = ptRoute[ptCurrent->menuDef.byClass - 1];
            needRefresh = 1;
            break;
            
        default:
            /* 未知按键：不执行任何操作，返回无需刷新 */
            return MENU_NAV_NO_REFRESH;
    }

    /* 更新选中项状态 */
    menuCtrl->ptCurrent = ptCurrent;
    /* 根据刷新标志返回结果 */
    return needRefresh ? MENU_NAV_REFRESH : MENU_NAV_NO_REFRESH;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuNavigator_RebuildRoute
 * 功能:
 *   根据当前选中菜单项，向上回溯父链并重建当前菜单路径 `ptRoute[]`。
 *
 * 参数:
 *   menuCtrl - 菜单运行时控制上下文，内部保存当前选中项与路径数组
 *   maxItem  - 最大回溯次数上限，用于防止异常链路导致死循环
 *
 * 边界处理:
 *   - 本函数不做空指针校验，默认由调用链保证 `menuCtrl` 有效。
 *   - 若 `ptCurBak == ptCurrent`，说明当前选中项未发生变化，直接返回。
 *   - 使用 `maxItem` 作为保护上限，避免异常菜单链表造成无限回溯。
 *
 * 说明:
 *   - 重建流程如下：
 *       1) 从当前选中项 `ptCurrent` 出发
 *       2) 若当前节点没有父节点，说明到达 0 级菜单链，沿 `before` 回退到根上下文
 *       3) 若存在父节点，则沿 `higher` 向上回溯，并把对应层级节点写入 `ptRoute[level]`
 *       4) 当回溯到 `byClass == 0` 的节点时结束
 *       5) 最后把当前选中节点重新写回 `ptRoute[当前层级]`
 *   - 该函数的作用是让 `ptRoute[]` 始终准确反映“从顶层到当前焦点”的完整路径，
 *     供后续 View 刷新和层级定位使用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuNavigator_RebuildRoute(tagMenuCtrl *menuCtrl, uint32_t maxItem)
{
    tagPMenuItem ptIndex;

    /* 若当前选中项未发生变化，则直接返回 */
    if (menuCtrl->ptCurBak == menuCtrl->ptCurrent)
    {
        return;
    }

    ptIndex = menuCtrl->ptCurrent;
    for (uint32_t index = 0; index < maxItem; index++)
    {
        /* 若当前节点没有父节点，则沿 `before` 回退到根上下文 */
        if (ptIndex->links.higher == NULL)
        {
            ptIndex = ptIndex->links.before;
        }
        else /* 若存在父节点，则沿 `higher` 向上回溯，并把对应层级节点写入 `ptRoute[level]` */
        {
            ptIndex = ptIndex->links.higher;
            menuCtrl->ptRoute[ptIndex->menuDef.byClass] = ptIndex;
        }
        /* 当回溯到 `byClass == 0` 的节点时结束 */
        if (ptIndex->menuDef.byClass == 0)
        {
            break;
        }
    }
    /* 最后把当前选中节点重新写回 `ptRoute[当前层级]` */
    menuCtrl->ptRoute[menuCtrl->ptCurrent->menuDef.byClass] = menuCtrl->ptCurrent;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPresenter_RenderByState
 * 功能:
 *   根据当前菜单状态差异选择渲染策略（整页刷新 / 同层局部刷新 / 单层补绘）。
 *
 * 参数:
 *   presenter - 菜单 Presenter 实例，内部持有 menuCtrl 与 view 接口
 *
 * 边界处理:
 *   - 本函数不做空指针校验，默认由调用链保证 presenter/view 有效。
 *   - 当菜单路由发生跨 0 级变化时，强制走整页刷新，避免多层残影。
 *   - 仅在“同层且同父”场景使用局部反显更新，其他变化按层级关系降级处理。
 *
 * 说明:
 *   - 该函数是 Presenter 的渲染决策核心，不直接参与菜单导航计算。
 *   - 决策顺序如下：
 *       1) 若 pt0Level 与 route[0] 不一致：
 *          说明当前顶层上下文已切换，先同步 pt0Level/ptCurBak，再 full_refresh。
 *       2) 否则若 ptCurBak 与 ptCurrent 不一致：
 *          a) 同层 + 同父：调用 update_selection_same_level，执行“旧选中恢复 + 新选中高亮”
 *          b) 新层级 >= 旧层级：调用 show_other_level，仅补绘受影响层
 *          c) 其他（通常是回退到更高层）：调用 full_refresh，保证界面一致性
 *       3) 渲染后统一将 ptCurBak 更新为 ptCurrent，作为下一轮差异比较基准。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuPresenter_RenderByState(menu_presenter_t *presenter)
{
    tagMenuCtrl *menuCtrl = &presenter->menuCtrl;
    MenuView *view = presenter->view;

    if (menuCtrl->pt0Level != menuCtrl->ptRoute[0])
    {
        menuCtrl->pt0Level = menuCtrl->ptRoute[0];
        menuCtrl->ptCurBak = menuCtrl->ptCurrent;
        presenter->view->full_refresh(presenter->view, menuCtrl, MODE_NONE);
    }
    else if (menuCtrl->ptCurBak != menuCtrl->ptCurrent)
    {
        if ((menuCtrl->ptCurrent->menuDef.byClass == menuCtrl->ptCurBak->menuDef.byClass) &&
            (menuCtrl->ptCurrent->menuDef.byClass > 0) &&
            (menuCtrl->ptCurrent->links.higher == menuCtrl->ptCurBak->links.higher))
        {
            view->update_selection_same_level(view, menuCtrl->ptCurBak, menuCtrl->ptCurrent);
        }
        else if (menuCtrl->ptCurrent->menuDef.byClass >= menuCtrl->ptCurBak->menuDef.byClass)
        {
            view->update_selection_new_level(view, menuCtrl->ptRoute[menuCtrl->ptCurrent->menuDef.byClass - 1], menuCtrl->ptRoute[menuCtrl->ptCurrent->menuDef.byClass]);
        }
        else
        {
            presenter->view->full_refresh(presenter->view, menuCtrl, MODE_NONE);
        }
        menuCtrl->ptCurBak = menuCtrl->ptCurrent;
    }
}

/* -------------------------------------------------------------------------
 * 函数名：MenuPresenter_Refresh
 * 功能:
 *   Presenter 层的对外刷新接口，根据是否为首帧渲染选择不同的刷新策略。
 *   本函数作为 menu_presenter_t::refresh 回调被调用，负责协调 View 层完成界面更新。
 *
 * 参数:
 *   presenter    - 菜单 Presenter 实例指针，内部持有 menuCtrl 上下文和 view 接口引用
 *   isFirstFrame - 是否为首帧标志：
 *                   1 - 首帧渲染：需要完整初始化并刷新整个菜单界面
 *                   0 - 非首帧渲染：根据当前状态差异进行增量或局部刷新
 *
 * 刷新策略:
 *   首帧模式 (isFirstFrame == 1):
 *     - 调用 View 层的 full_refresh() 接口，完整重绘整个菜单界面。
 *     - 此时 menuCtrl 中的导航路径、选中项等状态已完成初始化，可安全进行全量渲染。
 *     - MODE_NONE 表示不进行特殊模式过滤（如调试模式、特定层级过滤等）。
 *   
 *   非首帧模式 (isFirstFrame == 0):
 *     - 调用 MenuPresenter_RenderByState() 进行智能状态对比刷新：
 *       1) 比较 pt0Level 与 ptRoute[0]，判断顶层上下文是否切换
 *       2) 比较 ptCurBak 与 ptCurrent，判断选中项是否变化
 *       3) 根据层级关系和父节点关联选择最优渲染策略：
 *          - 同层同父：局部反显更新（旧选中恢复 + 新选中高亮）
 *          - 新层级 >= 旧层级：补绘受影响层级
 *          - 回退到更高层：整页刷新保证一致性
 *     - 此策略可避免不必要的重绘，提升界面响应性能。
 *
 * 调用关系:
 *   - 被 MenuPresenter_HandleInput() 在导航状态变化后调用（isFirstFrame = 0）
 *   - 被外部初始化完成后首次调用（isFirstFrame = 1）
 *   - 通过 presenter->refresh 函数指针绑定，符合 Presenter 的接口抽象。
 *
 * 边界处理:
 *   - 本函数假设 presenter 指针有效，不做空指针校验（由调用方保证）。
 *   - view 接口的 full_refresh() 和内部渲染逻辑负责具体的绘制安全校验。
 * ------------------------------------------------------------------------- */
static void MenuPresenter_Refresh(menu_presenter_t *presenter, uint8_t isFirstFrame)
{
    if (isFirstFrame)
    {
        /* 首帧模式：完整重绘整个菜单界面 */
        presenter->view->full_refresh(presenter->view, &presenter->menuCtrl, MODE_NONE);
    }
    else
    {
        /* 非首帧模式：根据状态差异进行智能增量刷新 */
        MenuPresenter_RenderByState(presenter);
    }
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPresenter_HandleInput
 * 功能:
 *   处理菜单页输入按键，驱动导航状态更新，并在需要时触发路由重建与界面刷新。
 *
 * 参数:
 *   presenter - 菜单 Presenter 实例，内部持有导航控制状态与 Model/View 依赖
 *   keyVal    - 当前输入按键值
 *
 * 边界处理:
 *   - 若 `presenter == NULL`，则直接返回。
 *   - 若 `keyVal == KEY_NONE`，则直接返回。
 *   - 若按键不引起状态变化，`MenuNavigator_ProcessKey()` 会返回
 *     `MENU_NAV_NO_REFRESH`，本函数直接结束，不触发额外刷新。
 *
 * 说明:
 *   - 处理流程如下：
 *       1) 调用 `MenuNavigator_ProcessKey()`，根据按键更新 `menuCtrl` 中的当前选中项
 *          与必要的路径状态
 *       2) 若返回值为 `MENU_NAV_REFRESH`，说明本次输入导致菜单焦点或层级发生变化
 *       3) 调用 `MenuNavigator_RebuildRoute()` 重建当前路径 `ptRoute[]`
 *       4) 调用 Presenter 对外刷新接口 `refresh(..., 0)`，执行增量或整页刷新判定
 *   - 本函数本身不直接绘制界面，而是负责把“输入 -> 导航状态变化 -> 刷新请求”
 *     串联起来。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuPresenter_HandleInput(menu_presenter_t *presenter, uint8_t keyVal)
{
    MenuNavResult navResult;

    ASSERT(presenter == NULL);
    if (presenter == NULL)
    {
        return;
    }

    if (keyVal == KEY_NONE)
    {
        return;
    }

    navResult = MenuNavigator_ProcessKey(&presenter->menuCtrl, keyVal);

    if (navResult == MENU_NAV_REFRESH)
    {
        MenuNavigator_RebuildRoute(&presenter->menuCtrl, presenter->model->maxItem);
        presenter->refresh(presenter, 0);
    }
}

/* -------------------------------------------------------------------------
 * 函数名: MenuPresenter_Init
 * 功能:
 *   初始化菜单 Presenter 实例，完成依赖绑定、对外接口挂接以及初始导航状态装配。
 *
 * 参数:
 *   presenter - 待初始化的 Presenter 实例
 *   model     - 菜单 Model 实例，提供菜单树与运行时数据
 *   view      - 菜单 View 实例，提供刷新与绘制能力
 *
 * 边界处理:
 *   - 若 `presenter`、`model` 或 `view` 任一为空，则直接返回。
 *
 * 说明:
 *   - 初始化步骤如下：
 *       1) 清零 `menuCtrl`，避免历史状态残留
 *       2) 绑定 `model` / `view` 依赖
 *       3) 绑定 Presenter 对外接口 `handle_input` / `refresh`
 *       4) 将 `ptHead` 指向菜单树根节点
 *       5) 将 `ptCurrent` 初始化为根节点的首个子菜单
 *       6) 初始化 `ptCurBak` 与 `ptRoute[0..3]`，建立首帧导航路径
 *   - 该函数只负责 Presenter 状态装配，不触发实际刷新。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void MenuPresenter_Init(menu_presenter_t *presenter, menu_model_t *model, menu_view_t *view)
{
    ASSERT((presenter == NULL) || (model == NULL) || (view == NULL));
    if ((presenter == NULL) || (model == NULL) || (view == NULL))
    {
        return;
    }

    memset(&presenter->menuCtrl, 0, sizeof(presenter->menuCtrl));
    presenter->model = model;
    presenter->view = view;
    presenter->handle_input = MenuPresenter_HandleInput;
    presenter->refresh = MenuPresenter_Refresh;


    presenter->menuCtrl.ptHead = &presenter->model->menuItems[0];
    presenter->menuCtrl.pt0Level = &presenter->model->menuItems[0];
    presenter->menuCtrl.ptCurrent = presenter->menuCtrl.ptHead->links.lower;
    presenter->menuCtrl.ptCurBak = presenter->menuCtrl.ptCurrent;

    presenter->menuCtrl.ptRoute[0] = &presenter->model->menuItems[0];
    presenter->menuCtrl.ptRoute[1] = presenter->menuCtrl.ptCurrent;
    presenter->menuCtrl.ptRoute[2] = presenter->menuCtrl.ptCurrent;
    presenter->menuCtrl.ptRoute[3] = presenter->menuCtrl.ptCurrent;
}
