"""MCP tools for managing API flows."""

import json
import re

from mcp.types import TextContent, Tool

from maeris_mcp.maeris.client import MaerisClient
from maeris_mcp.tools.collection_tools import _find_collection_by_name, _get_children


def _flow_id(flow: dict) -> str:
    for key in ["flow_id", "api_flow_id", "id", "_id"]:
        value = flow.get(key)
        if value:
            return str(value)
    return ""


def _flow_name(flow: dict) -> str:
    for key in ["name", "flow_name"]:
        value = flow.get(key)
        if isinstance(value, str) and value.strip():
            return value
    return ""


def _normalize_flow(flow: dict) -> dict:
    return {
        "id": _flow_id(flow),
        "name": _flow_name(flow),
        "createdAt": flow.get("created_at"),
    }


def _extract_list_payload(payload: dict | list[dict], keys: list[str]) -> list[dict]:
    if isinstance(payload, list):
        return payload
    if isinstance(payload, dict):
        for key in keys:
            value = payload.get(key)
            if isinstance(value, list):
                return value
    return []


def _validate_params_items(params: list[dict]) -> str | None:
    required = ["api_id", "param_key", "response_key"]
    for index, item in enumerate(params):
        if not isinstance(item, dict):
            return f"Error: params[{index}] must be an object"
        missing = [key for key in required if not item.get(key)]
        if missing:
            return f"Error: params[{index}] missing required fields: {', '.join(missing)}"
    return None


def _normalize_api_for_matching(api: dict) -> dict:
    return {
        "id": str(api.get("api_id") or api.get("id") or ""),
        "name": str(api.get("name") or ""),
        "method": str(api.get("type") or api.get("method") or "").upper(),
        "url": str(api.get("url") or ""),
        "collectionId": str(api.get("collection_id") or api.get("collectionId") or ""),
        "collectionName": str(api.get("collection_name") or api.get("collectionName") or ""),
    }


def _tokenize_text(text: str) -> set[str]:
    return {tok for tok in re.split(r"[^a-zA-Z0-9_]+", text.lower()) if tok}


def _extract_ordered_step_phrases(description: str) -> list[str]:
    cleaned = re.sub(r"\s+", " ", description.strip())
    if not cleaned:
        return []

    # Preserve order while splitting common sequencing connectors.
    parts = re.split(r"\s*(?:->|then|and then|after that|next|, then|;)\s*", cleaned, flags=re.IGNORECASE)
    steps = [p.strip(" .,") for p in parts if p and p.strip(" .,")]

    if len(steps) <= 1 and "," in cleaned:
        steps = [p.strip(" .,") for p in cleaned.split(",") if p.strip(" .,")]

    return steps


def _is_step_like_description(text: str) -> bool:
    if not text or not text.strip():
        return False
    lowered = text.lower()
    connectors = [" then ", " next ", " after that ", "->", ";", ", then"]
    if any(c in lowered for c in connectors):
        return True
    verbs = ["login", "get", "fetch", "create", "verify", "update", "delete"]
    return sum(1 for v in verbs if v in lowered) >= 2


def _normalize_feature_text(feature: str) -> str:
    return re.sub(r"\s+", " ", (feature or "").strip().lower())


def _feature_templates() -> dict[str, dict]:
    return {
        "order_checkout": {
            "keywords": {"order", "checkout", "purchase", "cart"},
            "steps": [
                "login user",
                "get user profile",
                "create order",
                "verify order exists",
            ],
        },
        "user_onboarding": {
            "keywords": {"onboard", "onboarding", "signup", "register", "user"},
            "steps": [
                "create user account",
                "login user",
                "get user profile",
                "verify user account exists",
            ],
        },
        "booking": {
            "keywords": {"booking", "reservation", "appointment"},
            "steps": [
                "login user",
                "get user profile",
                "create booking",
                "verify booking status",
            ],
        },
        "auth_profile": {
            "keywords": {"auth", "login", "profile", "account"},
            "steps": [
                "login user",
                "get user profile",
                "verify profile exists",
            ],
        },
        "generic": {
            "keywords": set(),
            "steps": [
                "login user",
                "get user profile",
                "create requested resource",
                "verify resource exists",
            ],
        },
    }


def _select_feature_template(feature: str) -> tuple[str, list[str], float]:
    normalized = _normalize_feature_text(feature)
    tokens = _tokenize_text(normalized)
    templates = _feature_templates()
    best_name = "generic"
    best_score = 0.0
    for name, template in templates.items():
        keywords = template["keywords"]
        if not keywords:
            continue
        overlap = len(tokens & keywords)
        if overlap <= 0:
            continue
        score = overlap / max(1, len(keywords))
        if score > best_score:
            best_score = score
            best_name = name
    chosen = templates[best_name]
    confidence = best_score if best_name != "generic" else (0.25 if tokens else 0.0)
    return best_name, list(chosen["steps"]), confidence


def _generate_steps_from_feature(feature: str) -> list[str]:
    _name, steps, _confidence = _select_feature_template(feature)
    return steps


def _infer_binding_aliases() -> dict[str, set[str]]:
    return {
        "token": {"token", "auth", "authorization", "bearer", "jwt", "access_token", "auth_token"},
        "userid": {"userid", "user_id", "id", "profileid", "accountid", "customerid"},
        "orderid": {"orderid", "order_id", "id", "purchaseid", "bookingid"},
        "sessionid": {"sessionid", "session_id", "cartid", "cart_id"},
    }


def _canonical_key(raw: str, aliases: dict[str, set[str]]) -> str:
    key = re.sub(r"[^a-zA-Z0-9]", "", (raw or "").lower())
    for canonical, group in aliases.items():
        group_norm = {re.sub(r"[^a-zA-Z0-9]", "", g.lower()) for g in group}
        if key in group_norm:
            return canonical
    return key


def _score_api_match(step_text: str, api: dict, feature_text: str | None = None) -> float:
    step_tokens = _tokenize_text(step_text)
    if not step_tokens:
        return 0.0

    api_name_tokens = _tokenize_text(api["name"])
    api_url_tokens = _tokenize_text(api["url"])
    overlap_name = len(step_tokens & api_name_tokens)
    overlap_url = len(step_tokens & api_url_tokens)

    score = overlap_name * 3.0 + overlap_url * 1.5

    text = step_text.lower()
    name = api["name"].lower()
    url = api["url"].lower()

    boosts = [
        ("login", {"login", "auth", "signin", "sign-in", "token"}),
        ("profile", {"profile", "user", "me", "account"}),
        ("order", {"order", "purchase", "checkout"}),
        ("verify", {"verify", "exists", "status", "check"}),
        ("create", {"create", "add", "new", "post"}),
        ("get", {"get", "fetch", "read", "list"}),
        ("booking", {"booking", "reservation"}),
        ("cart", {"cart", "checkout", "order"}),
    ]

    for cue, keywords in boosts:
        if cue in text and any(k in name or k in url for k in keywords):
            score += 4.0

    if feature_text:
        feature_tokens = _tokenize_text(feature_text)
        score += 0.8 * len(feature_tokens & (api_name_tokens | api_url_tokens))

    method = api.get("method", "")
    if "create" in text and method == "POST":
        score += 1.5
    if "get" in text and method == "GET":
        score += 1.0

    return score


def _rank_candidates_for_step(step: str, apis: list[dict], feature: str | None) -> list[dict]:
    ranked: list[dict] = []
    for api in apis:
        score = _score_api_match(step, api, feature)
        if score <= 0:
            continue
        ranked.append(
            {
                "api_id": api["id"],
                "name": api["name"],
                "method": api["method"],
                "url": api["url"],
                "score": round(score, 4),
            }
        )
    ranked.sort(key=lambda x: x["score"], reverse=True)
    return ranked


def _dedupe_and_repair_resolved_steps(
    resolved_steps: list[dict],
    candidates_by_step: dict[int, list[dict]],
    policy: str = "dedupe",
) -> tuple[list[dict], list[dict]]:
    if policy != "dedupe":
        return resolved_steps, []

    used: set[str] = set()
    repaired: list[dict] = []
    issues: list[dict] = []

    for step in resolved_steps:
        step_idx = step["stepIndex"]
        api_id = step["api_id"]
        if api_id not in used:
            repaired.append(step)
            used.add(api_id)
            continue

        replacement = None
        for candidate in candidates_by_step.get(step_idx, []):
            if candidate["api_id"] not in used:
                replacement = {
                    "stepIndex": step_idx,
                    "step": step["step"],
                    "api_id": candidate["api_id"],
                    "name": candidate["name"],
                    "method": candidate["method"],
                    "url": candidate["url"],
                    "score": candidate["score"],
                }
                break

        if replacement:
            repaired.append(replacement)
            used.add(replacement["api_id"])
        else:
            issues.append(
                {
                    "reason": "duplicate_api_unresolved",
                    "stepIndex": step_idx,
                    "step": step["step"],
                    "api_id": api_id,
                }
            )

    return repaired, issues


def _choose_step_apis(
    steps: list[str],
    candidate_apis: list[dict],
    feature_text: str | None,
    min_confidence: float = 3.0,
) -> tuple[list[dict], list[dict], list[dict], list[dict], dict[int, list[dict]]]:
    resolved: list[dict] = []
    ambiguities: list[dict] = []
    unmatched: list[dict] = []
    low_confidence: list[dict] = []
    candidates_by_step: dict[int, list[dict]] = {}

    for index, step in enumerate(steps):
        ranked = _rank_candidates_for_step(step, candidate_apis, feature_text)
        candidates_by_step[index] = ranked

        if not ranked:
            unmatched.append({"stepIndex": index, "step": step})
            continue

        top = ranked[0]
        second_score = ranked[1]["score"] if len(ranked) > 1 else -1.0

        if top["score"] < min_confidence:
            low_confidence.append(
                {
                    "stepIndex": index,
                    "step": step,
                    "topScore": top["score"],
                    "candidates": ranked[:3],
                }
            )
            continue

        if len(ranked) > 1 and (top["score"] - second_score) < 2.0:
            ambiguities.append(
                {
                    "stepIndex": index,
                    "step": step,
                    "candidates": ranked[:3],
                }
            )
            continue

        resolved.append(
            {
                "stepIndex": index,
                "step": step,
                "api_id": top["api_id"],
                "name": top["name"],
                "method": top["method"],
                "url": top["url"],
                "score": top["score"],
            }
        )

    return resolved, ambiguities, unmatched, low_confidence, candidates_by_step


def _build_resolution_error_payload(
    *,
    mode: str,
    generated_steps: list[str],
    unmatched: list[dict],
    ambiguous: list[dict],
    low_confidence: list[dict],
    duplicate_issues: list[dict],
) -> dict:
    return {
        "reason": "resolution_failed",
        "mode": mode,
        "generated_steps": generated_steps,
        "unmatched": unmatched,
        "ambiguous": ambiguous,
        "low_confidence": low_confidence,
        "duplicate_issues": duplicate_issues,
    }


def _collect_nested_keys(obj: object, out: set[str]) -> None:
    if isinstance(obj, dict):
        for k, v in obj.items():
            if isinstance(k, str):
                out.add(k)
            _collect_nested_keys(v, out)
    elif isinstance(obj, list):
        for item in obj:
            _collect_nested_keys(item, out)


def _extract_target_requirements(detail: dict) -> list[dict]:
    api = detail.get("api", detail)
    url = str(api.get("url") or "")
    requirements: list[dict] = []

    param_headers = detail.get("param_headers", [])
    for ph in param_headers:
        key = str(ph.get("field_name") or "").strip()
        if not key:
            continue
        ph_type = str(ph.get("type") or "").upper()
        location = "header" if ph_type == "HEADERS" else "url"
        static_value = str(ph.get("field_value") or "")
        requirements.append({"param_key": key, "location": location, "static_value": static_value})

    for match in re.findall(r"\{+([^}]+?)\}+", url):
        key = match.strip()
        if key:
            requirements.append({"param_key": key, "location": "url", "static_value": f"{{{{{key}}}}}"})

    payload = api.get("payload")
    payload_keys: set[str] = set()
    if isinstance(payload, dict):
        _collect_nested_keys(payload, payload_keys)
    elif isinstance(payload, str):
        try:
            parsed = json.loads(payload)
            _collect_nested_keys(parsed, payload_keys)
        except Exception:
            pass

    for key in payload_keys:
        requirements.append({"param_key": key, "location": "body", "static_value": ""})

    # de-dup preserving order
    seen = set()
    result: list[dict] = []
    for req in requirements:
        marker = (req["param_key"].lower(), req["location"])
        if marker in seen:
            continue
        seen.add(marker)
        result.append(req)
    return result


def _infer_step_outputs(step: dict, aliases: dict[str, set[str]]) -> set[str]:
    tokens = _tokenize_text(" ".join([step.get("step", ""), step.get("name", ""), step.get("url", "")]))
    outputs: set[str] = set()

    if {"login", "auth", "signin", "token"} & tokens:
        outputs.add("token")
    if {"profile", "user", "account", "me"} & tokens:
        outputs.add("userid")
    if {"order", "purchase", "checkout", "cart", "booking"} & tokens:
        outputs.add("orderid")
    if {"session", "cart"} & tokens:
        outputs.add("sessionid")

    # Include all canonical aliases present in tokens.
    for tok in tokens:
        outputs.add(_canonical_key(tok, aliases))

    return outputs


def _response_key_for_canonical(canonical: str) -> str:
    if canonical == "token":
        return "token"
    if canonical == "userid":
        return "userId"
    if canonical == "orderid":
        return "orderId"
    if canonical == "sessionid":
        return "sessionId"
    return canonical


def _infer_param_bindings(
    resolved_steps: list[dict],
    detail_by_api_id: dict[str, dict],
    strict: bool,
) -> tuple[list[dict], dict[str, list[dict]], list[dict]]:
    aliases = _infer_binding_aliases()
    outputs_by_step: list[set[str]] = [_infer_step_outputs(step, aliases) for step in resolved_steps]

    all_params: list[dict] = []
    params_by_api: dict[str, list[dict]] = {}
    unresolved: list[dict] = []

    for idx, step in enumerate(resolved_steps):
        api_id = step["api_id"]
        detail = detail_by_api_id.get(api_id, {})
        requirements = _extract_target_requirements(detail)
        params_for_step: list[dict] = []

        for req in requirements:
            key = req["param_key"]
            canonical = _canonical_key(key, aliases)
            source_step_index = None

            for prev in range(idx - 1, -1, -1):
                if canonical in outputs_by_step[prev]:
                    source_step_index = prev
                    break

            if source_step_index is None:
                static_value = req.get("static_value", "")
                if static_value:
                    # Use the static value saved on the API (header value, {{VAR}} placeholder, etc.)
                    param_type = "HEADERS" if req["location"] == "header" else (
                        "BODY" if req["location"] == "body" else "URL"
                    )
                    params_for_step.append(
                        {
                            "api_id": api_id,
                            "param_key": key,
                            "response_api_id": None,
                            "response_key": static_value,
                            "type": param_type,
                        }
                    )
                elif (not strict) and canonical == "token":
                    params_for_step.append(
                        {
                            "api_id": api_id,
                            "param_key": key,
                            "response_api_id": None,
                            "response_key": "{{auth_token}}",
                            "type": "HEADERS" if req["location"] == "header" else "URL",
                        }
                    )
                elif idx > 0:
                    unresolved.append(
                        {
                            "stepIndex": idx,
                            "step": step["step"],
                            "api_id": api_id,
                            "param_key": key,
                            "reason": "no_source_step_found",
                        }
                    )
                continue

            source_api_id = resolved_steps[source_step_index]["api_id"]
            param = {
                "api_id": api_id,
                "param_key": key,
                "response_api_id": source_api_id,
                "response_key": _response_key_for_canonical(canonical),
            }

            if req["location"] == "header":
                param["type"] = "HEADERS"
            elif req["location"] == "url":
                param["type"] = "URL"
            elif req["location"] == "body":
                param["type"] = "BODY"

            params_for_step.append(param)

        params_by_api[api_id] = params_for_step
        all_params.extend(params_for_step)

    return all_params, params_by_api, unresolved


def _build_flow_map(resolved_steps: list[dict], params_by_api: dict[str, list[dict]]) -> list[dict]:
    flow_map: list[dict] = []
    for idx, step in enumerate(resolved_steps):
        parent_id = None if idx == 0 else resolved_steps[idx - 1]["api_id"]
        flow_map.append(
            {
                "parent_id": parent_id,
                "child_id": step["api_id"],
                "flow_order": idx,
                "params": params_by_api.get(step["api_id"], []),
            }
        )
    return flow_map


async def _collection_api_ids(client: MaerisClient, collection_name: str) -> tuple[set[str] | None, str | None]:
    collection = await _find_collection_by_name(client, collection_name)
    if not collection:
        return None, f"Error: no collection found with name '{collection_name}'"

    root_id = collection["api_collection_id"]
    queue = [root_id]
    ids: set[str] = set()

    while queue:
        current = queue.pop(0)
        children = await _get_children(client, current)
        for api in children.get("apis", []):
            api_id = str(api.get("api_id") or "")
            if api_id:
                ids.add(api_id)
        for sub in children.get("api_collections", []):
            sub_id = str(sub.get("api_collection_id") or "")
            if sub_id:
                queue.append(sub_id)

    return ids, None


def create_api_flow_tool() -> Tool:
    return Tool(
        name="create_api_flow",
        description="Create a new API flow.",
        inputSchema={
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Name of the API flow."},
            },
            "required": ["name"],
        },
    )


def get_api_flows_tool() -> Tool:
    return Tool(
        name="get_api_flows",
        description="List all API flows.",
        inputSchema={"type": "object", "properties": {}, "required": []},
    )


def edit_api_flow_tool() -> Tool:
    return Tool(
        name="edit_api_flow",
        description="Update an API flow's name by flow ID.",
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
                "name": {"type": "string", "description": "New API flow name."},
            },
            "required": ["flow_id", "name"],
        },
    )


def delete_api_flow_tool() -> Tool:
    return Tool(
        name="delete_api_flow",
        description="Delete an API flow by flow ID.",
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
            },
            "required": ["flow_id"],
        },
    )


def create_api_flow_map_tool() -> Tool:
    return Tool(
        name="create_api_flow_map",
        description=(
            "Create flow map entries for an API flow. "
            "Each entry represents one API step in the flow. "
            "parent_id must be null for the first step and set to the child_id of the immediately preceding step for all subsequent steps. "
            "flow_order is 0-based. "
            "params should contain all headers (type=HEADERS), URL placeholder variables extracted from the API URL (type=URL), "
            "query params (type=URL), and body fields (type=BODY). "
            "For static values use response_api_id=null and response_key=the literal value or {{PLACEHOLDER}}. "
            "For values sourced from a previous step's response use response_api_id=that step's api_id and response_key=the response field name."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
                "flow_map": {
                    "type": "array",
                    "description": "Flow map entries to save.",
                    "items": {
                        "type": "object",
                        "properties": {
                            "parent_id": {"type": ["string", "null"]},
                            "child_id": {"type": "string"},
                            "flow_order": {"type": "integer"},
                            "params": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "api_id": {"type": "string"},
                                        "param_key": {"type": "string"},
                                        "response_api_id": {"type": ["string", "null"]},
                                        "response_key": {"type": "string"},
                                        "type": {"type": "string"},
                                    },
                                    "required": ["api_id", "param_key", "response_key"],
                                },
                            },
                        },
                        "required": ["child_id", "flow_order"],
                    },
                },
            },
            "required": ["flow_id", "flow_map"],
        },
    )


def get_api_flow_map_tool() -> Tool:
    return Tool(
        name="get_api_flow_map",
        description="Get flow map for an API flow.",
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
            },
            "required": ["flow_id"],
        },
    )


def add_api_flow_params_tool() -> Tool:
    return Tool(
        name="add_api_flow_params",
        description="Add API flow params for an API flow.",
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
                "params": {
                    "type": "array",
                    "description": "Params mappings to add.",
                    "items": {
                        "type": "object",
                        "properties": {
                            "api_id": {"type": "string"},
                            "param_key": {"type": "string"},
                            "response_api_id": {"type": ["string", "null"]},
                            "response_key": {"type": "string"},
                            "type": {"type": "string"},
                        },
                        "required": ["api_id", "param_key", "response_key"],
                    },
                },
            },
            "required": ["flow_id", "params"],
        },
    )


def get_api_flow_params_tool() -> Tool:
    return Tool(
        name="get_api_flow_params",
        description="Get API flow params for an API flow.",
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
            },
            "required": ["flow_id"],
        },
    )


def edit_api_flow_params_tool() -> Tool:
    return Tool(
        name="edit_api_flow_params",
        description="Replace API flow params for an API flow.",
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
                "params": {
                    "type": "array",
                    "description": "Complete params mappings to save.",
                    "items": {
                        "type": "object",
                        "properties": {
                            "api_id": {"type": "string"},
                            "param_key": {"type": "string"},
                            "response_api_id": {"type": ["string", "null"]},
                            "response_key": {"type": "string"},
                            "type": {"type": "string"},
                        },
                        "required": ["api_id", "param_key", "response_key"],
                    },
                },
            },
            "required": ["flow_id", "params"],
        },
    )


def delete_api_flow_params_tool() -> Tool:
    return Tool(
        name="delete_api_flow_params",
        description="Delete all API flow params for an API flow.",
        inputSchema={
            "type": "object",
            "properties": {
                "flow_id": {"type": "string", "description": "API flow ID."},
            },
            "required": ["flow_id"],
        },
    )


def create_flow_tool() -> Tool:
    return Tool(
        name="create_flow",
        description=(
            "Create an API flow from natural language by inferring step order and dependencies "
            "across stored APIs."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Flow name to create."},
                "description": {
                    "type": "string",
                    "description": "Natural language flow description with ordered steps.",
                },
                "feature": {
                    "type": "string",
                    "description": "Feature intent, e.g. 'order checkout' or 'user onboarding'.",
                },
                "collection_name": {
                    "type": "string",
                    "description": "Optional collection name to limit API search scope.",
                },
                "strict": {
                    "type": "boolean",
                    "description": "Fail when dependencies cannot be fully resolved. Default true.",
                    "default": True,
                },
            },
            "required": ["name"],
        },
    )


async def handle_create_api_flow(arguments: dict) -> list[TextContent]:
    name = arguments.get("name")
    if not name:
        return [TextContent(type="text", text="Error: name is required")]

    async with MaerisClient() as client:
        created = await client.create_api_flow(name)

    result = _normalize_flow(created)
    if not result["name"]:
        result["name"] = name
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_get_api_flows(arguments: dict) -> list[TextContent]:
    async with MaerisClient() as client:
        flows = await client.get_api_flows()

    result = [_normalize_flow(flow) for flow in flows]
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_edit_api_flow(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    name = arguments.get("name")
    if not flow_id or not name:
        return [TextContent(type="text", text="Error: flow_id and name are required")]

    async with MaerisClient() as client:
        updated = await client.update_api_flow(flow_id, name)

    result = _normalize_flow(updated)
    if not result["id"]:
        result["id"] = flow_id
    if not result["name"]:
        result["name"] = name
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_delete_api_flow(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    if not flow_id:
        return [TextContent(type="text", text="Error: flow_id is required")]

    async with MaerisClient() as client:
        await client.delete_api_flow(flow_id)

    result = {"deleted": True, "id": flow_id}
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_create_api_flow_map(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    flow_map = arguments.get("flow_map") or []
    if not flow_id:
        return [TextContent(type="text", text="Error: flow_id is required")]
    if not flow_map:
        return [TextContent(type="text", text="Error: flow_map is required and must not be empty")]

    async with MaerisClient() as client:
        response = await client.create_api_flow_map(flow_id, flow_map)

    result = {"flowId": flow_id, "flowMapCount": len(flow_map), "response": response}
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_get_api_flow_map(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    if not flow_id:
        return [TextContent(type="text", text="Error: flow_id is required")]

    async with MaerisClient() as client:
        response = await client.get_api_flow_map(flow_id)

    flow_map = _extract_list_payload(response, ["flow_map", "data", "items", "results"])
    if not flow_map and isinstance(response, dict):
        flow_map = response.get("flow_map", [])
    result = {"flowId": flow_id, "flow_map": flow_map}
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_add_api_flow_params(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    params = arguments.get("params") or []
    if not flow_id:
        return [TextContent(type="text", text="Error: flow_id is required")]
    if not params:
        return [TextContent(type="text", text="Error: params is required and must not be empty")]
    error = _validate_params_items(params)
    if error:
        return [TextContent(type="text", text=error)]

    async with MaerisClient() as client:
        response = await client.add_api_flow_params(flow_id, params)

    result = {"flowId": flow_id, "paramsCount": len(params), "response": response}
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_get_api_flow_params(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    if not flow_id:
        return [TextContent(type="text", text="Error: flow_id is required")]

    async with MaerisClient() as client:
        response = await client.get_api_flow_params(flow_id)

    params = _extract_list_payload(response, ["params", "data", "items", "results"])
    if not params and isinstance(response, dict):
        params = response.get("params", [])
    result = {"flowId": flow_id, "params": params}
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_edit_api_flow_params(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    params = arguments.get("params") or []
    if not flow_id:
        return [TextContent(type="text", text="Error: flow_id is required")]
    if not params:
        return [TextContent(type="text", text="Error: params is required and must not be empty")]
    error = _validate_params_items(params)
    if error:
        return [TextContent(type="text", text=error)]

    async with MaerisClient() as client:
        response = await client.update_api_flow_params(flow_id, params)

    result = {"flowId": flow_id, "paramsCount": len(params), "response": response}
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_delete_api_flow_params(arguments: dict) -> list[TextContent]:
    flow_id = arguments.get("flow_id")
    if not flow_id:
        return [TextContent(type="text", text="Error: flow_id is required")]

    async with MaerisClient() as client:
        await client.delete_api_flow_params(flow_id)

    result = {"deleted": True, "id": flow_id}
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def handle_create_flow(arguments: dict) -> list[TextContent]:
    name = arguments.get("name")
    description = str(arguments.get("description") or "").strip()
    feature = str(arguments.get("feature") or "").strip()
    collection_name = arguments.get("collection_name")
    strict = bool(arguments.get("strict", True))

    if not name:
        return [TextContent(type="text", text="Error: name is required")]
    if not description and not feature:
        return [TextContent(type="text", text="Error: at least one of description or feature is required")]

    if description and _is_step_like_description(description):
        mode = "explicit"
        steps = _extract_ordered_step_phrases(description)
        feature_hint = _normalize_feature_text(feature) if feature else None
    else:
        mode = "feature"
        inferred_feature = feature or description
        steps = _generate_steps_from_feature(inferred_feature)
        feature_hint = _normalize_feature_text(inferred_feature)

    if not steps:
        return [TextContent(type="text", text="Error: could not infer ordered steps from input")]
    if mode == "feature" and (len(steps) < 3 or len(steps) > 6):
        return [TextContent(type="text", text="Error: generated feature steps must be between 3 and 6")]

    async with MaerisClient() as client:
        existing_flows = await client.get_api_flows()
        conflicts = [f for f in existing_flows if _flow_name(f).lower() == str(name).lower()]
        if conflicts:
            details = {
                "reason": "name_conflict",
                "name": name,
                "matches": [_normalize_flow(f) for f in conflicts],
            }
            return [TextContent(type="text", text=f"Error: {json.dumps(details, indent=2)}")]

        all_apis = [_normalize_api_for_matching(a) for a in await client.get_all_apis()]
        all_apis = [a for a in all_apis if a["id"]]

        if collection_name:
            allowed_ids, collection_error = await _collection_api_ids(client, collection_name)
            if collection_error:
                return [TextContent(type="text", text=collection_error)]
            all_apis = [a for a in all_apis if a["id"] in (allowed_ids or set())]

        if not all_apis:
            return [TextContent(type="text", text="Error: no candidate APIs available for flow creation")]

        resolved_steps, ambiguities, unmatched, low_confidence, candidates_by_step = _choose_step_apis(
            steps,
            all_apis,
            feature_text=feature_hint,
            min_confidence=3.0,
        )

        repaired_steps, duplicate_issues = _dedupe_and_repair_resolved_steps(
            resolved_steps,
            candidates_by_step,
            policy="dedupe",
        )
        resolved_steps = repaired_steps

        if ambiguities or unmatched or low_confidence or duplicate_issues or len(resolved_steps) != len(steps):
            details = _build_resolution_error_payload(
                mode=mode,
                generated_steps=steps,
                unmatched=unmatched,
                ambiguous=ambiguities,
                low_confidence=low_confidence,
                duplicate_issues=duplicate_issues,
            )
            return [TextContent(type="text", text=f"Error: {json.dumps(details, indent=2)}")]

        detail_by_api_id: dict[str, dict] = {}
        for step in resolved_steps:
            detail_by_api_id[step["api_id"]] = await client.get_api(step["api_id"])

        params, params_by_api, unresolved = _infer_param_bindings(
            resolved_steps,
            detail_by_api_id,
            strict=strict,
        )

        if strict and unresolved:
            details = {
                "reason": "unresolved_dependencies",
                "mode": mode,
                "generated_steps": steps,
                "unresolved": unresolved,
            }
            return [TextContent(type="text", text=f"Error: {json.dumps(details, indent=2)}")]

        flow_map = _build_flow_map(resolved_steps, params_by_api)

        created = await client.create_api_flow(name)
        flow_id = _flow_id(created)
        if not flow_id:
            return [TextContent(type="text", text="Error: flow created but no flow id returned")]

        try:
            await client.create_api_flow_map(flow_id, flow_map)
        except Exception as exc:
            details = {
                "reason": "persistence_failed",
                "stage": "create_api_flow_map",
                "flowId": flow_id,
                "error": str(exc),
            }
            return [TextContent(type="text", text=f"Error: {json.dumps(details, indent=2)}")]

        if params:
            try:
                await client.add_api_flow_params(flow_id, params)
            except Exception as exc:
                details = {
                    "reason": "persistence_failed",
                    "stage": "add_api_flow_params",
                    "flowId": flow_id,
                    "error": str(exc),
                }
                return [TextContent(type="text", text=f"Error: {json.dumps(details, indent=2)}")]

    result = {
        "flowId": flow_id,
        "name": name,
        "mode": mode,
        "generated_steps": steps,
        "steps": resolved_steps,
        "flow_map": flow_map,
        "params": params,
        "summary": {
            "stepCount": len(resolved_steps),
            "paramBindings": len(params),
            "strict": strict,
            "warnings": unresolved if (unresolved and not strict) else [],
        },
    }
    return [TextContent(type="text", text=json.dumps(result, indent=2))]
