use crate::context::RequestContext;
use crate::error::WebError;
use crate::hermes_tools::{ensure_write_authorized, ToolCallInput};
use crate::routes::onlyoffice_bridge::{self, BridgeResultWire, BridgeRunError};
use axum::http::StatusCode;
use serde_json::{json, Value};
use std::collections::HashSet;

const DEFAULT_TIMEOUT_MS: u64 = 25_000;
const MAX_SHEET_BATCH_CELLS: u64 = 5_000;
const MAX_DOCUMENT_TABLE_CELLS: u64 = 1_000;
const MAX_PRESENTATION_TABLE_CELLS: u64 = 200;

pub async fn session_current(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let session = current_session(context, input)?;
    Ok(json!({
        "schema": "mnote.onlyoffice.session.v1",
        "session": session
    }))
}

pub async fn capabilities(
    _context: &RequestContext,
    _input: &ToolCallInput,
) -> Result<Value, WebError> {
    Ok(onlyoffice_bridge::capability_payload())
}

pub async fn selection_get(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    run_read_action(
        context,
        input,
        "selection.get",
        json!({
            "options": input.arg_value("options").unwrap_or_else(|| json!({}))
        }),
    )
    .await
}

pub async fn sheet_get_range(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    run_read_action(
        context,
        input,
        "sheet.get_range",
        json!({
            "address": input.arg_string("address").unwrap_or_default()
        }),
    )
    .await
}

pub async fn document_export(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let format = input
        .arg_string("format")
        .unwrap_or_else(|| "markdown".into())
        .to_ascii_lowercase();
    if !matches!(format.as_str(), "markdown" | "html") {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_export_format_invalid",
            "format 仅支持 markdown 或 html",
        )
        .with_context(context));
    }
    run_read_action(
        context,
        input,
        "document.export",
        json!({ "format": format }),
    )
    .await
}

pub async fn document_search_replace(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let search = required_string(context, input, "search")?;
    if search.is_empty() {
        return Err(
            WebError::bad_request_code("mnote_onlyoffice_search_empty", "search 不能为空")
                .with_context(context),
        );
    }
    run_write_action(
        context,
        input,
        "document.search_replace",
        json!({
            "search": search,
            "replace": input.arg_string("replace").unwrap_or_default(),
            "matchCase": arg_bool(input, "matchCase").unwrap_or(false)
        }),
    )
    .await
}

pub async fn document_insert_table(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let data = input.arg_value("data").unwrap_or_else(|| json!([]));
    let rows_from_data = data.as_array().map(|rows| rows.len() as u64).unwrap_or(0);
    let cols_from_data = data
        .as_array()
        .and_then(|rows| {
            rows.iter()
                .map(|row| row.as_array().map(|cells| cells.len() as u64).unwrap_or(1))
                .max()
        })
        .unwrap_or(0);
    let rows = arg_u64(input, "rows").unwrap_or(rows_from_data).max(1);
    let cols = arg_u64(input, "cols")
        .or_else(|| arg_u64(input, "columns"))
        .unwrap_or(cols_from_data)
        .max(1);
    if rows.saturating_mul(cols) > MAX_DOCUMENT_TABLE_CELLS {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_table_too_large",
            format!("单次 Word 表格最多支持 {MAX_DOCUMENT_TABLE_CELLS} 个单元格"),
        )
        .with_context(context));
    }
    run_write_action(
        context,
        input,
        "document.insert_table",
        json!({
            "rows": rows,
            "cols": cols,
            "data": data
        }),
    )
    .await
}

pub async fn document_get_comments(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    run_read_action(context, input, "document.get_comments", json!({})).await
}

pub async fn document_add_comment(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let text = required_string(context, input, "text")?;
    run_write_action(
        context,
        input,
        "document.add_comment",
        json!({
            "text": text,
            "author": input.arg_string("author").unwrap_or_else(|| "MNote AI".into()),
            "target": input.arg_string("target").unwrap_or_else(|| "selection".into())
        }),
    )
    .await
}

pub async fn sheet_get_sheets(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    run_read_action(context, input, "sheet.get_sheets", json!({})).await
}

pub async fn sheet_add_sheet(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let name = required_string(context, input, "name")?;
    run_write_action(context, input, "sheet.add_sheet", json!({ "name": name })).await
}

pub async fn sheet_rename_sheet(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let name = required_string(context, input, "name")?;
    run_write_action(
        context,
        input,
        "sheet.rename_sheet",
        json!({
            "name": name,
            "sheetIndex": arg_u64(input, "sheetIndex"),
            "sheetName": input.arg_string("sheetName").unwrap_or_default()
        }),
    )
    .await
}

pub async fn sheet_get_range_values(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let address = input
        .arg_string("address")
        .or_else(|| input.arg_string("range"))
        .ok_or_else(|| {
            WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address")
                .with_context(context)
        })?;
    let range = parse_a1_range(context, &address)?;
    ensure_sheet_cell_limit(context, range.row_count, range.col_count)?;
    run_read_action(
        context,
        input,
        "sheet.get_range_values",
        json!({ "address": address }),
    )
    .await
}

pub async fn sheet_get_values(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let start_row = arg_u64(input, "startRow").unwrap_or(0);
    let start_col = arg_u64(input, "startCol").unwrap_or(0);
    let row_count = required_u64(context, input, "rowCount")?;
    let col_count = required_u64(context, input, "colCount")?;
    ensure_sheet_cell_limit(context, row_count, col_count)?;
    run_read_action(
        context,
        input,
        "sheet.get_values",
        json!({
            "startRow": start_row,
            "startCol": start_col,
            "rowCount": row_count,
            "colCount": col_count
        }),
    )
    .await
}

pub async fn presentation_get_slides(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    run_read_action(context, input, "presentation.get_slides", json!({})).await
}

pub async fn presentation_get_slide_texts(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let mut payload = json!({});
    if let Some(slide_index) = arg_u64(input, "slideIndex") {
        payload["slideIndex"] = json!(slide_index);
    }
    run_read_action(context, input, "presentation.get_slide_texts", payload).await
}

pub async fn presentation_get_shapes(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let mut payload = json!({});
    if let Some(slide_index) = arg_u64(input, "slideIndex") {
        payload["slideIndex"] = json!(slide_index);
    }
    run_read_action(context, input, "presentation.get_shapes", payload).await
}

pub async fn document_insert_text(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let text = required_string(context, input, "text")?;
    run_write_action(
        context,
        input,
        "document.insert_text",
        json!({ "text": text }),
    )
    .await
}

pub async fn document_replace_selection(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let text = required_string(context, input, "text")?;
    run_write_action(
        context,
        input,
        "document.replace_selection",
        json!({ "text": text }),
    )
    .await
}

pub async fn document_insert_html(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let html = required_string(context, input, "html")?;
    run_write_action(
        context,
        input,
        "document.insert_html",
        json!({ "html": html }),
    )
    .await
}

pub async fn sheet_set_value(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let value = input.arg_value("value").ok_or_else(|| {
        WebError::bad_request_code("mnote_onlyoffice_value_required", "缺少 value")
            .with_context(context)
    })?;
    run_write_action(
        context,
        input,
        "sheet.set_value",
        json!({
            "address": input.arg_string("address").unwrap_or_default(),
            "value": value
        }),
    )
    .await
}

pub async fn sheet_set_formula(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let formula = required_string(context, input, "formula")?;
    run_write_action(
        context,
        input,
        "sheet.set_formula",
        json!({
            "address": input.arg_string("address").unwrap_or_default(),
            "formula": formula
        }),
    )
    .await
}

pub async fn sheet_batch_set_values(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let values = input.arg_value("values").ok_or_else(|| {
        WebError::bad_request_code("mnote_onlyoffice_values_required", "缺少 values")
            .with_context(context)
    })?;
    let rows = values.as_array().ok_or_else(|| {
        WebError::bad_request_code("mnote_onlyoffice_values_invalid", "values 必须是二维数组")
            .with_context(context)
    })?;
    let row_count = rows.len() as u64;
    let col_count = rows
        .iter()
        .map(|row| row.as_array().map(|cells| cells.len()).unwrap_or(1))
        .max()
        .unwrap_or(0) as u64;
    if row_count == 0 || col_count == 0 {
        return Err(
            WebError::bad_request_code("mnote_onlyoffice_values_empty", "values 不能为空")
                .with_context(context),
        );
    }
    ensure_sheet_cell_limit(context, row_count, col_count)?;
    run_write_action(
        context,
        input,
        "sheet.batch_set_values",
        json!({
            "startRow": arg_u64(input, "startRow").unwrap_or(0),
            "startCol": arg_u64(input, "startCol").unwrap_or(0),
            "values": values
        }),
    )
    .await
}

pub async fn sheet_set_range_values(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let address = input
        .arg_string("address")
        .or_else(|| input.arg_string("range"))
        .ok_or_else(|| {
            WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address")
                .with_context(context)
        })?;
    let range = parse_a1_range(context, &address)?;
    ensure_sheet_cell_limit(context, range.row_count, range.col_count)?;
    let values = input.arg_value("values").ok_or_else(|| {
        WebError::bad_request_code("mnote_onlyoffice_values_required", "缺少 values")
            .with_context(context)
    })?;
    let rows = values.as_array().ok_or_else(|| {
        WebError::bad_request_code("mnote_onlyoffice_values_invalid", "values 必须是二维数组")
            .with_context(context)
    })?;
    let row_count = rows.len() as u64;
    let col_count = rows
        .iter()
        .map(|row| row.as_array().map(|cells| cells.len()).unwrap_or(1))
        .max()
        .unwrap_or(0) as u64;
    if row_count == 0 || col_count == 0 {
        return Err(
            WebError::bad_request_code("mnote_onlyoffice_values_empty", "values 不能为空")
                .with_context(context),
        );
    }
    ensure_sheet_cell_limit(context, row_count, col_count)?;
    run_write_action(
        context,
        input,
        "sheet.set_range_values",
        json!({
            "address": address,
            "values": values
        }),
    )
    .await
}

pub async fn sheet_format_range(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let address = input
        .arg_string("address")
        .or_else(|| input.arg_string("range"))
        .ok_or_else(|| {
            WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address")
                .with_context(context)
        })?;
    let range = parse_a1_range(context, &address)?;
    ensure_sheet_cell_limit(context, range.row_count, range.col_count)?;
    run_write_action(
        context,
        input,
        "sheet.format_range",
        json!({
            "address": address,
            "bold": arg_bool(input, "bold"),
            "italic": arg_bool(input, "italic"),
            "underline": arg_bool(input, "underline"),
            "fillColor": input.arg_string("fillColor").or_else(|| input.arg_string("fill_color")).unwrap_or_default(),
            "fontColor": input.arg_string("fontColor").or_else(|| input.arg_string("font_color")).unwrap_or_default(),
            "fontSize": arg_f64(input, "fontSize"),
            "fontName": input.arg_string("fontName").or_else(|| input.arg_string("font_name")).unwrap_or_default(),
            "horizontalAlign": input.arg_string("horizontalAlign").or_else(|| input.arg_string("horizontal_align")).unwrap_or_default(),
            "verticalAlign": input.arg_string("verticalAlign").or_else(|| input.arg_string("vertical_align")).unwrap_or_default(),
            "numberFormat": input.arg_string("numberFormat").or_else(|| input.arg_string("number_format")).unwrap_or_default()
        }),
    )
    .await
}

pub async fn sheet_set_dimensions(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let column_index = arg_u64(input, "columnIndex");
    let column_width = arg_f64(input, "columnWidth");
    let row_index = arg_u64(input, "rowIndex");
    let row_height = arg_f64(input, "rowHeight");
    let has_column = column_index.is_some() && column_width.is_some();
    let has_row = row_index.is_some() && row_height.is_some();
    if !has_column && !has_row {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_dimensions_required",
            "缺少 columnIndex/columnWidth 或 rowIndex/rowHeight",
        )
        .with_context(context));
    }
    run_write_action(
        context,
        input,
        "sheet.set_dimensions",
        json!({
            "columnIndex": column_index,
            "columnWidth": column_width,
            "rowIndex": row_index,
            "rowHeight": row_height
        }),
    )
    .await
}

pub async fn sheet_sort_range(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let address = input
        .arg_string("address")
        .or_else(|| input.arg_string("range"))
        .ok_or_else(|| {
            WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address")
                .with_context(context)
        })?;
    let range = parse_a1_range(context, &address)?;
    ensure_sheet_cell_limit(context, range.row_count, range.col_count)?;
    let key_column = arg_u64(input, "keyColumn").unwrap_or(1).max(1);
    if key_column > range.col_count {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_sort_key_out_of_range",
            "keyColumn 必须落在排序 range 内，且从 1 开始",
        )
        .with_context(context));
    }
    let order = input
        .arg_string("order")
        .unwrap_or_else(|| "ascending".into())
        .to_ascii_lowercase();
    if !matches!(
        order.as_str(),
        "ascending" | "descending" | "asc" | "desc" | "xlascending" | "xldescending"
    ) {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_sort_order_invalid",
            "order 仅支持 ascending/descending",
        )
        .with_context(context));
    }
    run_write_action(
        context,
        input,
        "sheet.sort_range",
        json!({
            "address": address,
            "keyColumn": key_column,
            "order": order,
            "header": arg_bool(input, "header").unwrap_or(true)
        }),
    )
    .await
}

pub async fn sheet_add_chart(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let address = input
        .arg_string("address")
        .or_else(|| input.arg_string("range"))
        .ok_or_else(|| {
            WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address")
                .with_context(context)
        })?;
    let range = parse_a1_range(context, &address)?;
    ensure_sheet_cell_limit(context, range.row_count, range.col_count)?;
    let chart_type = input
        .arg_string("chartType")
        .or_else(|| input.arg_string("chart_type"))
        .unwrap_or_else(|| "bar".into());
    run_write_action(
        context,
        input,
        "sheet.add_chart",
        json!({
            "address": address,
            "chartType": chart_type,
            "inColumns": arg_bool(input, "inColumns").unwrap_or(true),
            "style": arg_u64(input, "style").unwrap_or(2),
            "widthMm": arg_f64(input, "widthMm").unwrap_or(120.0),
            "heightMm": arg_f64(input, "heightMm").unwrap_or(80.0),
            "fromCol": arg_u64(input, "fromCol").unwrap_or(4),
            "xOffsetMm": arg_f64(input, "xOffsetMm").unwrap_or(0.0),
            "fromRow": arg_u64(input, "fromRow").unwrap_or(1),
            "yOffsetMm": arg_f64(input, "yOffsetMm").unwrap_or(0.0)
        }),
    )
    .await
}

pub async fn presentation_add_text_slide(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    run_write_action(
        context,
        input,
        "presentation.add_text_slide",
        json!({
            "title": input.arg_string("title").unwrap_or_default(),
            "body": input.arg_string("body").unwrap_or_default()
        }),
    )
    .await
}

pub async fn presentation_add_table(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let data = input.arg_value("data").unwrap_or_else(|| json!([]));
    let rows_from_data = data.as_array().map(|rows| rows.len() as u64).unwrap_or(0);
    let cols_from_data = data
        .as_array()
        .and_then(|rows| {
            rows.iter()
                .map(|row| row.as_array().map(|cells| cells.len() as u64).unwrap_or(1))
                .max()
        })
        .unwrap_or(0);
    let rows = arg_u64(input, "rows").unwrap_or(rows_from_data).max(1);
    let cols = arg_u64(input, "cols")
        .or_else(|| arg_u64(input, "columns"))
        .unwrap_or(cols_from_data)
        .max(1);
    if rows.saturating_mul(cols) > MAX_PRESENTATION_TABLE_CELLS {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_presentation_table_too_large",
            format!("单次 PPT 表格最多支持 {MAX_PRESENTATION_TABLE_CELLS} 个单元格"),
        )
        .with_context(context));
    }
    let mut payload = json!({
        "rows": rows,
        "cols": cols,
        "data": data,
        "widthMm": arg_f64(input, "widthMm").unwrap_or(190.0),
        "heightMm": arg_f64(input, "heightMm").unwrap_or(90.0)
    });
    if let Some(slide_index) = arg_u64(input, "slideIndex") {
        payload["slideIndex"] = json!(slide_index);
    }
    if let Some(x_mm) = arg_f64(input, "xMm") {
        payload["xMm"] = json!(x_mm);
    }
    if let Some(y_mm) = arg_f64(input, "yMm") {
        payload["yMm"] = json!(y_mm);
    }
    run_write_action(context, input, "presentation.add_table", payload).await
}

pub async fn presentation_clear_slide(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let slide_index = required_u64(context, input, "slideIndex")?;
    run_write_action(
        context,
        input,
        "presentation.clear_slide",
        json!({ "slideIndex": slide_index }),
    )
    .await
}

pub async fn presentation_add_shape(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let shape_type = input
        .arg_string("shapeType")
        .or_else(|| input.arg_string("shape_type"))
        .unwrap_or_else(|| "rect".into());
    let allowed_shapes = [
        "rect",
        "roundRect",
        "ellipse",
        "triangle",
        "diamond",
        "cube",
        "cloud",
        "flowChartMagneticTape",
    ];
    if !allowed_shapes.contains(&shape_type.as_str()) {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_shape_type_invalid",
            "shapeType 不在允许列表中",
        )
        .with_context(context));
    }
    let mut payload = json!({
        "shapeType": shape_type,
        "text": input.arg_string("text").unwrap_or_default(),
        "fillColor": input.arg_string("fillColor").or_else(|| input.arg_string("fill_color")).unwrap_or_else(|| "#4F81BD".into()),
        "strokeColor": input.arg_string("strokeColor").or_else(|| input.arg_string("stroke_color")).unwrap_or_default(),
        "strokeWidthMm": arg_f64(input, "strokeWidthMm").unwrap_or(0.0),
        "xMm": arg_f64(input, "xMm").unwrap_or(20.0),
        "yMm": arg_f64(input, "yMm").unwrap_or(35.0),
        "widthMm": arg_f64(input, "widthMm").unwrap_or(160.0),
        "heightMm": arg_f64(input, "heightMm").unwrap_or(70.0)
    });
    if let Some(slide_index) = arg_u64(input, "slideIndex") {
        payload["slideIndex"] = json!(slide_index);
    }
    run_write_action(context, input, "presentation.add_shape", payload).await
}

pub async fn presentation_replace_text(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let search = required_string(context, input, "search")?;
    if search.is_empty() {
        return Err(
            WebError::bad_request_code("mnote_onlyoffice_search_empty", "search 不能为空")
                .with_context(context),
        );
    }
    let mut payload = json!({
        "search": search,
        "replace": input.arg_string("replace").unwrap_or_default(),
        "matchCase": arg_bool(input, "matchCase").unwrap_or(false),
        "replaceAll": arg_bool(input, "replaceAll").unwrap_or(true)
    });
    if let Some(slide_index) = arg_u64(input, "slideIndex") {
        payload["slideIndex"] = json!(slide_index);
    }
    run_write_action(context, input, "presentation.replace_text", payload).await
}

pub async fn presentation_set_shape_text(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let slide_index = required_u64(context, input, "slideIndex")?;
    let text = required_string(context, input, "text")?;
    let shape_index = arg_u64(input, "shapeIndex");
    let shape_id = input.arg_string("shapeId");
    if shape_index.is_none() && shape_id.as_deref().unwrap_or_default().trim().is_empty() {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_shape_locator_required",
            "缺少 shapeIndex 或 shapeId",
        )
        .with_context(context));
    }
    run_write_action(
        context,
        input,
        "presentation.set_shape_text",
        json!({
            "slideIndex": slide_index,
            "shapeIndex": shape_index,
            "shapeId": shape_id.unwrap_or_default(),
            "text": text
        }),
    )
    .await
}

pub async fn presentation_delete_slide(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<Value, WebError> {
    let slide_index = required_u64(context, input, "slideIndex")?;
    run_write_action(
        context,
        input,
        "presentation.delete_slide",
        json!({ "slideIndex": slide_index }),
    )
    .await
}

async fn run_read_action(
    context: &RequestContext,
    input: &ToolCallInput,
    action: &'static str,
    payload: Value,
) -> Result<Value, WebError> {
    let session_id = resolve_explicit_session_id(context, input)?;
    ensure_onlyoffice_resource_scope_allowed(context, input, &session_id)?;
    let result = run_bridge(context, input, &session_id, action, payload).await?;
    Ok(action_result(action, &session_id, result))
}

async fn run_write_action(
    context: &RequestContext,
    input: &ToolCallInput,
    action: &'static str,
    payload: Value,
) -> Result<Value, WebError> {
    ensure_write_authorized(context, input)?;
    let session_id = resolve_explicit_session_id(context, input)?;
    ensure_onlyoffice_resource_scope_allowed(context, input, &session_id)?;
    if input.dry_run.unwrap_or(false) {
        return Ok(json!({
            "schema": "mnote.onlyoffice.action_plan.v1",
            "sessionId": session_id,
            "action": action,
            "payload": payload,
            "dryRun": true,
            "wouldWrite": true
        }));
    }
    let result = run_bridge(context, input, &session_id, action, payload).await?;
    Ok(action_result(action, &session_id, result))
}

async fn run_bridge(
    context: &RequestContext,
    input: &ToolCallInput,
    session_id: &str,
    action: &'static str,
    payload: Value,
) -> Result<BridgeResultWire, WebError> {
    let timeout_ms = input
        .arg_value("timeoutMs")
        .or_else(|| input.arg_value("timeout_ms"))
        .and_then(|value| value.as_u64())
        .unwrap_or(DEFAULT_TIMEOUT_MS);
    let result = onlyoffice_bridge::run_bridge_command(session_id, action, payload, timeout_ms)
        .await
        .map_err(|error| bridge_run_error(context, error))?;
    if !result.ok {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_bridge_failed",
            result
                .error
                .clone()
                .unwrap_or_else(|| "ONLYOFFICE bridge 命令执行失败".into()),
        )
        .with_context(context));
    }
    Ok(result)
}

fn action_result(action: &str, session_id: &str, result: BridgeResultWire) -> Value {
    json!({
        "schema": "mnote.onlyoffice.action_result.v1",
        "sessionId": session_id,
        "action": action,
        "commandId": result.id,
        "result": result.result
    })
}

fn resolve_explicit_session_id(
    context: &RequestContext,
    input: &ToolCallInput,
) -> Result<String, WebError> {
    input
        .arg_string("onlyofficeSessionId")
        .or_else(|| input.arg_string("bridgeSessionId"))
        .ok_or_else(|| {
            WebError::bad_request_code(
                "mnote_onlyoffice_session_explicit_required",
                "ONLYOFFICE 工具必须显式携带 onlyofficeSessionId",
            )
            .with_context(context)
        })
}

fn ensure_onlyoffice_resource_scope_allowed(
    context: &RequestContext,
    input: &ToolCallInput,
    session_id: &str,
) -> Result<(), WebError> {
    let Some(scope) = input.arg_value("aiAccessScope") else {
        return Err(WebError::new(
            StatusCode::FORBIDDEN,
            "mnote_onlyoffice_resource_scope_required",
            "ONLYOFFICE 工具必须携带 aiAccessScope.allowedResourceIds",
        )
        .with_context(context));
    };
    let allowed = scope
        .get("allowedResourceIds")
        .or_else(|| scope.get("allowed_resource_ids"))
        .and_then(Value::as_array)
        .map(|values| {
            values
                .iter()
                .filter_map(Value::as_str)
                .map(str::trim)
                .filter(|value| !value.is_empty())
                .map(ToOwned::to_owned)
                .collect::<HashSet<_>>()
        })
        .unwrap_or_default();
    if allowed.is_empty() {
        return Err(WebError::new(
            StatusCode::FORBIDDEN,
            "mnote_onlyoffice_resource_scope_required",
            "ONLYOFFICE 工具必须携带非空 aiAccessScope.allowedResourceIds",
        )
        .with_context(context));
    }
    let Some(session) = onlyoffice_bridge::session_info(session_id) else {
        return Err(WebError::new(
            StatusCode::FORBIDDEN,
            "mnote_onlyoffice_resource_scope_forbidden",
            "ONLYOFFICE session 未注册，无法校验资源授权",
        )
        .with_context(context));
    };
    let mut candidates = HashSet::new();
    candidates.insert(session.session_id);
    let document_id = session.document_id;
    let asset_id = session.asset_id;
    if let Some(document_id) = document_id.as_deref() {
        candidates.insert(document_id.to_string());
    }
    if let Some(asset_id) = asset_id.as_deref() {
        candidates.insert(asset_id.to_string());
    }
    if let (Some(document_id), Some(asset_id)) = (document_id.as_deref(), asset_id.as_deref()) {
        candidates.insert(format!("resource:office:{document_id}:{asset_id}"));
        candidates.insert(format!("resource:onlyoffice:{document_id}:{asset_id}"));
    }
    if candidates
        .iter()
        .any(|candidate| allowed.contains(candidate))
    {
        return Ok(());
    }
    Err(WebError::new(
        StatusCode::FORBIDDEN,
        "mnote_onlyoffice_resource_scope_forbidden",
        "ONLYOFFICE session 不在当前 AI resource 授权范围内",
    )
    .with_context(context))
}

fn current_session(context: &RequestContext, input: &ToolCallInput) -> Result<Value, WebError> {
    let session_id = resolve_explicit_session_id(context, input)?;
    ensure_onlyoffice_resource_scope_allowed(context, input, &session_id)?;
    let session = onlyoffice_bridge::session_info(&session_id).ok_or_else(|| {
        WebError::new(
            StatusCode::NOT_FOUND,
            "mnote_onlyoffice_session_not_found",
            "指定的 ONLYOFFICE bridge session 不存在，请先打开 ONLYOFFICE 文档",
        )
        .with_context(context)
    })?;
    Ok(json!({
        "sessionId": session.session_id,
        "editorType": session.editor_type.unwrap_or_default(),
        "documentId": session.document_id.unwrap_or_default(),
        "assetId": session.asset_id.unwrap_or_default(),
        "fileType": session.file_type.unwrap_or_default(),
        "lastSeenMillis": session.last_seen_millis,
        "pendingCommands": session.pending_commands,
        "pendingResults": session.pending_results
    }))
}

fn required_string(
    context: &RequestContext,
    input: &ToolCallInput,
    key: &'static str,
) -> Result<String, WebError> {
    input.arg_string(key).ok_or_else(|| {
        WebError::bad_request_code("mnote_onlyoffice_argument_required", format!("缺少 {key}"))
            .with_context(context)
    })
}

fn required_u64(
    context: &RequestContext,
    input: &ToolCallInput,
    key: &'static str,
) -> Result<u64, WebError> {
    arg_u64(input, key).ok_or_else(|| {
        WebError::bad_request_code("mnote_onlyoffice_argument_required", format!("缺少 {key}"))
            .with_context(context)
    })
}

fn arg_u64(input: &ToolCallInput, key: &'static str) -> Option<u64> {
    input
        .arg_value(key)
        .or_else(|| match key {
            "startRow" => input.arg_value("start_row"),
            "startCol" => input.arg_value("start_col"),
            "rowCount" => input.arg_value("row_count"),
            "colCount" => input.arg_value("col_count"),
            "slideIndex" => input.arg_value("slide_index"),
            "sheetIndex" => input.arg_value("sheet_index"),
            "shapeIndex" => input.arg_value("shape_index"),
            "columnIndex" => input.arg_value("column_index"),
            "rowIndex" => input.arg_value("row_index"),
            "keyColumn" => input.arg_value("key_column"),
            "fromCol" => input.arg_value("from_col"),
            "fromRow" => input.arg_value("from_row"),
            "cols" => input.arg_value("columns"),
            _ => None,
        })
        .and_then(|value| value.as_u64())
}

fn arg_f64(input: &ToolCallInput, key: &'static str) -> Option<f64> {
    input
        .arg_value(key)
        .or_else(|| match key {
            "columnWidth" => input.arg_value("column_width"),
            "rowHeight" => input.arg_value("row_height"),
            "fontSize" => input.arg_value("font_size"),
            "widthMm" => input.arg_value("width_mm"),
            "heightMm" => input.arg_value("height_mm"),
            "xOffsetMm" => input.arg_value("x_offset_mm"),
            "yOffsetMm" => input.arg_value("y_offset_mm"),
            "xMm" => input.arg_value("x_mm"),
            "yMm" => input.arg_value("y_mm"),
            "strokeWidthMm" => input.arg_value("stroke_width_mm"),
            _ => None,
        })
        .and_then(|value| value.as_f64())
        .filter(|value| value.is_finite() && *value >= 0.0)
}

fn arg_bool(input: &ToolCallInput, key: &'static str) -> Option<bool> {
    input
        .arg_value(key)
        .or_else(|| match key {
            "matchCase" => input.arg_value("match_case"),
            "replaceAll" => input.arg_value("replace_all"),
            "inColumns" => input.arg_value("in_columns"),
            _ => None,
        })
        .and_then(|value| value.as_bool())
}

fn ensure_sheet_cell_limit(
    context: &RequestContext,
    row_count: u64,
    col_count: u64,
) -> Result<(), WebError> {
    if row_count == 0 || col_count == 0 {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_sheet_range_empty",
            "rowCount 和 colCount 必须大于 0",
        )
        .with_context(context));
    }
    if row_count.saturating_mul(col_count) > MAX_SHEET_BATCH_CELLS {
        return Err(WebError::bad_request_code(
            "mnote_onlyoffice_sheet_range_too_large",
            format!("单次表格批量操作最多支持 {MAX_SHEET_BATCH_CELLS} 个单元格"),
        )
        .with_context(context));
    }
    Ok(())
}

#[derive(Debug)]
struct A1Range {
    row_count: u64,
    col_count: u64,
}

fn parse_a1_range(context: &RequestContext, address: &str) -> Result<A1Range, WebError> {
    let value = address.trim();
    let mut parts = value.split(':');
    let start = parts.next().unwrap_or_default();
    let end = parts.next().unwrap_or(start);
    if parts.next().is_some() {
        return Err(invalid_a1_range(context));
    }
    let (start_row, start_col) = parse_a1_cell(start).ok_or_else(|| invalid_a1_range(context))?;
    let (end_row, end_col) = parse_a1_cell(end).ok_or_else(|| invalid_a1_range(context))?;
    Ok(A1Range {
        row_count: start_row.abs_diff(end_row) + 1,
        col_count: start_col.abs_diff(end_col) + 1,
    })
}

fn parse_a1_cell(value: &str) -> Option<(u64, u64)> {
    let value = value.trim();
    let split_at = value
        .char_indices()
        .find_map(|(index, ch)| ch.is_ascii_digit().then_some(index))?;
    let (letters, digits) = value.split_at(split_at);
    if letters.is_empty()
        || digits.is_empty()
        || !letters.chars().all(|ch| ch.is_ascii_alphabetic())
        || !digits.chars().all(|ch| ch.is_ascii_digit())
    {
        return None;
    }
    let mut col = 0_u64;
    for ch in letters.chars() {
        col = col
            .saturating_mul(26)
            .saturating_add((ch.to_ascii_uppercase() as u8 - b'A' + 1) as u64);
    }
    let row = digits.parse::<u64>().ok()?;
    if row == 0 || col == 0 {
        return None;
    }
    Some((row - 1, col - 1))
}

fn invalid_a1_range(context: &RequestContext) -> WebError {
    WebError::bad_request_code(
        "mnote_onlyoffice_range_invalid",
        "address 必须是 A1 或 A1:B2",
    )
    .with_context(context)
}

fn bridge_run_error(context: &RequestContext, error: BridgeRunError) -> WebError {
    match error {
        BridgeRunError::BadRequest(message) => {
            WebError::bad_request_code("mnote_onlyoffice_bridge_bad_request", message)
        }
        BridgeRunError::Timeout {
            session_id,
            command_id,
        } => WebError::new(
            StatusCode::GATEWAY_TIMEOUT,
            "mnote_onlyoffice_bridge_timeout",
            format!("ONLYOFFICE bridge 命令超时，sessionId={session_id}, commandId={command_id}"),
        ),
    }
    .with_context(context)
}
