/**
 * Feishu context reader — the real implementation behind `feishuContextTool`.
 *
 * ADR-0003: Hub stores only anchors, reads Feishu context on demand. The tool
 * (`feishu_read_context`) is already chat-authorized by `tools.ts` (chat must
 * match the project binding). Here we actually call lark's `im.v1.message`:
 *
 *  - `trigger_message` / `reply`: `message.get` by message_id.
 *  - `status_card`: the run's status card message — same `message.get` by id.
 *  - `thread`: lark's thread replies. The SDK exposes `message.list` with a
 *    `parent_message_id` filter; we map "thread" to that.
 *
 * The lark SDK's `im.v1.message` methods are dynamic at runtime (weak types);
 * we cast through a known request/response shape and return a compact JSON for
 * the model. All calls use the chat-authorized `client` from the runtime — no
 * separate credential path, so the ADR-0003 invariant holds end-to-end.
 */
import type { FeishuRuntime } from "./client.js";
import type { FeishuContextArgs } from "../agent/tools.js";
import type { ToolContext } from "../agent/tools.js";

/** Pragmatic shape of the lark message object we consume. */
interface LarkMessage {
  message_id?: string;
  msg_type?: string;
  content?: string;
  body?: { content?: string };
  create_time?: string;
  sender?: { id?: string; sender_type?: string };
  chat_id?: string;
  parent_id?: string;
  parent_message_id?: string;
  root_id?: string;
  thread_id?: string;
}

interface ImV1Message {
  get: (p: { path: { message_id: string } }) => Promise<{ data?: { items?: LarkMessage[]; message?: LarkMessage } }>;
  list: (p: {
    params: { container_id_type: string; container_id: string; page_size?: number };
  }) => Promise<{ data?: { items?: LarkMessage[] } }>;
}

function im(rt: FeishuRuntime): ImV1Message {
  return (rt.client as unknown as { im: { v1: { message: ImV1Message } } }).im.v1.message;
}

/** Read on-demand Feishu context for the current run. Entry point for the tool. */
export async function readFeishuContext(
  args: FeishuContextArgs,
  ctx: ToolContext,
  rt: unknown,
): Promise<string> {
  const runtime = rt as FeishuRuntime;
  const api = im(runtime);
  try {
    if (args.chat_id !== ctx.boundChatId) {
      throw new Error("refused Feishu context read outside the current project's bound chat");
    }
    switch (args.anchor) {
      case "trigger_message":
      case "status_card":
      case "reply": {
        // All three are single-message reads by id.
        const res = await api.get({ path: { message_id: args.id } });
        const msg = res.data?.message ?? res.data?.items?.[0];
        if (msg === undefined) return JSON.stringify({ error: "message not found", id: args.id });
        assertBoundChat(msg, ctx.boundChatId);
        return JSON.stringify(compact(msg));
      }
      case "thread": {
        // Thread = replies to a parent message. `container_id` is the parent's
        // message_id; container_id_type=message_id scopes the list to that thread.
        const res = await api.list({
          params: {
            container_id_type: "message_id",
            container_id: args.id,
            page_size: 50,
          },
        });
        const items = res.data?.items ?? [];
        for (const item of items) assertBoundChat(item, ctx.boundChatId);
        return JSON.stringify(items.map(compact));
      }
      default:
        return JSON.stringify({ error: `unknown anchor type: ${args.anchor as string}` });
    }
  } catch (e) {
    runtime.logger.warn(
      {
        runId: ctx.runId,
        projectId: ctx.projectId,
        anchor: args.anchor,
        anchorId: args.id,
        err: e instanceof Error ? e.message : String(e),
      },
      "Feishu context read refused or failed",
    );
    return JSON.stringify({ error: e instanceof Error ? e.message : String(e) });
  }
}

function assertBoundChat(message: LarkMessage, boundChatId: string): void {
  if (message.chat_id !== boundChatId) {
    throw new Error("refused Feishu context result outside the current project's bound chat");
  }
}

/** Project a lark message down to the fields the model actually needs. */
function compact(m: LarkMessage): Record<string, unknown> {
  const parentId = m.parent_id ?? m.parent_message_id;
  return {
    message_id: m.message_id,
    msg_type: m.msg_type,
    content: m.body?.content ?? m.content,
    create_time: m.create_time,
    chat_id: m.chat_id,
    sender_id: m.sender?.id,
    parent_id: parentId,
    parent_message_id: parentId,
    root_id: m.root_id,
    thread_id: m.thread_id,
  };
}
