/**
 * Create Block Tool
 *
 * Tool for creating blocks in Roam from markdown content.
 * Uses the Roam Alpha API's fromMarkdown method to parse markdown
 * and insert blocks at a specified location.
 *
 * Supports:
 * - Standard markdown: headings, lists, code blocks, bold, italic, links, etc.
 * - Nested lists become nested blocks
 * - Insertion at specific parent with order control (first, last, or specific index)
 * - Page title as alternative to parent_uid
 * - Smart insertion using LLM to find the best location within an outline
 * - Analysis mode: when markdown_content is omitted, returns page style analysis
 *   to help generate content matching the existing format
 */

import { tool } from "@langchain/core/tools";
import { z } from "zod";

import { resolveContainerUid, evaluateOutline } from "./outlineEvaluator";
import { getBlockContentByUid, getOrderedDirectChildren } from "../../../../utils/roamAPI";
import { truncateText, findContainingPageName } from "./toolUtils";

/**
 * Format a block reference with its content for display.
 * Returns: "block content..." ((uid))
 */
function formatBlockReference(uid: string, maxContentLength: number = 60): string {
  const content = getBlockContentByUid(uid);
  if (content) {
    return `"${truncateText(content, maxContentLength)}" ((${uid}))`;
  }
  return `((${uid}))`;
}

/**
 * Get insertion context: parent content and surrounding siblings.
 * Returns context info for the UI to display where content will be inserted.
 * Includes an outline preview showing existing content around the insertion point.
 */
function getInsertionContext(parentUid: string, order: "first" | "last" | number, isPage: boolean = false): {
  parent_content: string;
  parent_uid: string;
  sibling_above?: string;
  sibling_below?: string;
  insertion_position: string;
  outline_preview: string;
} {
  const parentContent = getBlockContentByUid(parentUid) || "";
  const children = getOrderedDirectChildren(parentUid);

  let siblingAbove: string | undefined;
  let siblingBelow: string | undefined;
  let insertionPosition: string;
  let outlinePreview = "";

  // Helper to create marker line with special formatting tag
  const markerLine = (position: string) => `{{MARKER}}➤ New content here (${position}){{/MARKER}}`;

  // For page-level insertion, don't show "- " prefix since pages don't have block content
  const parentLine = isPage && !parentContent
    ? "" // No parent line for pages
    : `- ${truncateText(parentContent, 80)}\n`;

  if (!children || children.length === 0) {
    insertionPosition = isPage ? "top-level (no existing content)" : "first child (no existing children)";
    // Parent block with no children yet - marker goes right after parent
    if (isPage && !parentContent) {
      // Page with no children - just show marker at top level
      outlinePreview = markerLine(insertionPosition);
    } else {
      outlinePreview = `${parentLine}    ${markerLine(insertionPosition)}`;
    }
  } else if (order === "first") {
    insertionPosition = isPage ? "top-level (first)" : "first child";
    siblingBelow = children[0]?.string ? truncateText(children[0].string, 50) : undefined;
    // Marker goes before first child
    if (isPage && !parentContent) {
      outlinePreview = `${markerLine(insertionPosition)}${siblingBelow ? `\n- ${siblingBelow}` : ""}`;
    } else {
      outlinePreview = `${parentLine}    ${markerLine(insertionPosition)}${siblingBelow ? `\n    - ${siblingBelow}` : ""}`;
    }
  } else if (order === "last") {
    insertionPosition = isPage ? "top-level (last)" : "last child";
    const lastChild = children[children.length - 1];
    siblingAbove = lastChild?.string ? truncateText(lastChild.string, 50) : undefined;
    // Show parent with ellipsis and last child, then marker at the end
    const ellipsis = children.length > 1 ? "\n    ..." : "";
    if (isPage && !parentContent) {
      const topEllipsis = children.length > 1 ? "...\n" : "";
      outlinePreview = `${topEllipsis}${siblingAbove ? `- ${siblingAbove}\n` : ""}${markerLine(insertionPosition)}`;
    } else {
      outlinePreview = `${parentLine.trimEnd()}${ellipsis}${siblingAbove ? `\n    - ${siblingAbove}` : ""}\n    ${markerLine(insertionPosition)}`;
    }
  } else if (typeof order === "number") {
    insertionPosition = isPage ? `top-level (position ${order})` : `position ${order}`;
    if (order > 0 && children[order - 1]) {
      siblingAbove = children[order - 1].string ? truncateText(children[order - 1].string, 50) : undefined;
    }
    if (children[order]) {
      siblingBelow = children[order].string ? truncateText(children[order].string, 50) : undefined;
    }
    // Show parent with context around insertion position, marker between siblings
    const beforeEllipsis = order > 1 ? "\n    ..." : "";
    const afterEllipsis = order < children.length - 1 ? "\n    ..." : "";
    if (isPage && !parentContent) {
      const topEllipsis = order > 1 ? "...\n" : "";
      const bottomEllipsis = order < children.length - 1 ? "\n..." : "";
      outlinePreview = `${topEllipsis}${siblingAbove ? `- ${siblingAbove}\n` : ""}${markerLine(insertionPosition)}${siblingBelow ? `\n- ${siblingBelow}` : ""}${bottomEllipsis}`;
    } else {
      outlinePreview = `${parentLine.trimEnd()}${beforeEllipsis}${siblingAbove ? `\n    - ${siblingAbove}` : ""}\n    ${markerLine(insertionPosition)}${siblingBelow ? `\n    - ${siblingBelow}` : ""}${afterEllipsis}`;
    }
  } else {
    insertionPosition = isPage ? "top-level (last)" : "last child";
    if (isPage && !parentContent) {
      outlinePreview = markerLine(insertionPosition);
    } else {
      outlinePreview = `${parentLine}    ${markerLine(insertionPosition)}`;
    }
  }

  return {
    parent_content: truncateText(parentContent, 80),
    parent_uid: parentUid,
    sibling_above: siblingAbove,
    sibling_below: siblingBelow,
    insertion_position: insertionPosition,
    outline_preview: outlinePreview,
  };
}

export const createBlockTool = tool(
  async (
    input: {
      parent_uid?: string;
      page_title?: string;
      date?: string;
      use_main_view?: boolean;
      markdown_content?: string;
      order?: "first" | "last" | number;
      smart_insertion?: boolean;
    },
    config,
  ) => {
    const {
      parent_uid,
      page_title,
      date,
      use_main_view,
      markdown_content,
      order = "last",
      smart_insertion = false,
    } = input;

    console.log("createBlockTool input :>> ", {
      parent_uid,
      page_title,
      use_main_view,
      order,
      smart_insertion,
      markdown_content: markdown_content?.substring(0, 100),
    });

    // Get LLM from config for smart insertion
    const llm = config?.configurable?.llm;
    console.log("createBlockTool llm available :>> ", !!llm);

    // Resolve the target UID using shared helper
    // Default to main view if no location specified
    const useMainView = use_main_view || (!parent_uid && !page_title && !date);
    const resolved = await resolveContainerUid({ parent_uid, page_title, date, use_main_view: useMainView });
    if ("error" in resolved) {
      return `⚠️ ${resolved.error}`;
    }

    const { uid: targetUid, description: targetDescription } = resolved;

    // ANALYSIS MODE: If no markdown_content, return page outline and style for content generation
    if (!markdown_content || markdown_content.trim().length === 0) {
      const result = await evaluateOutline({
        containerUid: targetUid,
        llm,
        mode: "analyze_outline",
      });

      if (!result || !result.outlineContent) {
        return `📄 Analyzed ${targetDescription} (empty). Target UID: ((${targetUid})) - ready for content insertion.`;
      }

      const truncatedOutline =
        result.outlineContent.length > 3000
          ? result.outlineContent.substring(0, 3000) + "\n... (truncated)"
          : result.outlineContent;

      // The response includes the outline for the LLM, but UI will filter it to show only the first line
      return `📄 Analyzed ${targetDescription}. Target UID: ((${targetUid}))\n\n${truncatedOutline}`;
    }

    // === PRE-VALIDATION (before asking user for confirmation) ===
    // Check if the Roam API is available
    const roamAPI = (window as any).roamAlphaAPI;
    if (!roamAPI?.data?.block?.fromMarkdown) {
      return "❌ Roam Alpha API (fromMarkdown) is not available. This tool requires Roam Research with the Alpha API enabled.";
    }

    // Verify the target block/page exists
    const targetExists = roamAPI.data.pull("[:block/uid]", [
      ":block/uid",
      targetUid,
    ]);
    if (!targetExists) {
      return `⚠️ Target with UID "${targetUid}" was not found. Please verify the UID exists in your graph.`;
    }

    // === CONFIRMATION FLOW FOR INSERTION MODE ===
    // When markdown_content is provided, we need user confirmation before inserting
    const toolConfirmationCallback = config?.configurable?.toolConfirmationCallback;
    const alwaysApprovedTools = config?.configurable?.alwaysApprovedTools as Set<string> | undefined;

    // Check if this tool has been "always approved" for this session
    const isAlwaysApproved = alwaysApprovedTools?.has("create_block");

    if (toolConfirmationCallback && !isAlwaysApproved) {
      // Generate a unique tool call ID for this confirmation
      const toolCallId = `create_block_${Date.now()}`;

      // Get insertion context for UI display
      const insertionContext = getInsertionContext(targetUid, order, resolved.isPage);

      // Determine the containing page for any target (walk up if needed)
      const containingPageName = resolved.pageName || (!resolved.isPage ? findContainingPageName(targetUid) : undefined);

      // Request confirmation from the user with rich context
      const confirmationResult = await toolConfirmationCallback({
        toolName: "create_block",
        toolCallId,
        args: {
          // For page targets, show page name; for blocks, show content + uid
          is_page: resolved.isPage,
          page_name: resolved.pageName,
          containing_page_name: containingPageName,
          // Insertion context
          ...insertionContext,
          // Content to insert
          markdown_content,
          smart_insertion,
        },
      });

      if (!confirmationResult.approved) {
        // User declined - return a message that the LLM can process
        const reason = confirmationResult.declineReason
          ? `\n\nUser's feedback: "${confirmationResult.declineReason}"`
          : "";
        return `⚠️ Block insertion was declined by the user.${reason}\n\nPlease ask the user what changes they'd like you to make to the content, or if they'd like to proceed differently.`;
      }

      // Note: If alwaysApprove was set, the UI layer will update alwaysApprovedTools
      // for subsequent calls in this session
    }

    try {

      // Determine final insertion location
      let finalParentUid = targetUid;
      let finalOrder: "first" | "last" | number = order;
      let smartInsertionFailed = false;

      // Smart insertion: use LLM to find best location
      if (smart_insertion) {
        if (!llm) {
          return "⚠️ Smart insertion requested but no LLM is available. Please use explicit parent_uid and order instead, or ensure the chat agent has access to an LLM.";
        }

        const evalResult = await evaluateOutline({
          containerUid: targetUid,
          llm,
          mode: "find_insertion_location",
          contentToInsert: markdown_content,
        });

        console.log("evalResult :>> ", evalResult);

        if (evalResult?.location) {
          finalParentUid = evalResult.location.parentUid;
          finalOrder = evalResult.location.order;
        } else {
          // Fallback to direct insertion at target
          smartInsertionFailed = true;
          console.warn(
            "Smart insertion failed to determine location, using target directly",
          );
        }
      }

      // Snapshot children count before insertion to verify success
      const childrenBefore = roamAPI.data.pull("[:block/children]", [
        ":block/uid",
        finalParentUid,
      ]);
      const childCountBefore = childrenBefore?.[":block/children"]?.length || 0;

      // Create blocks from markdown
      const result = await roamAPI.data.block.fromMarkdown({
        location: { "parent-uid": finalParentUid, order: finalOrder },
        "markdown-string": markdown_content,
      });

      console.log("result :>> ", result);
      // Handle different return formats from the API
      // The API may return an array of UIDs, an object with uids, or just success/failure
      let topUidsArray: string[] = [];

      if (Array.isArray(result)) {
        topUidsArray = result;
      } else if (result && typeof result === "object") {
        // Could be { uids: [...] } or similar
        if (Array.isArray(result.uids)) {
          topUidsArray = result.uids;
        } else if (Array.isArray(result["block-uids"])) {
          topUidsArray = result["block-uids"];
        }
      }

      // Build location description with content context
      let locationDesc: string;
      if (page_title && finalParentUid === targetUid) {
        locationDesc = `[[${page_title}]]`;
      } else if (resolved.pageName && finalParentUid === targetUid) {
        locationDesc = `[[${resolved.pageName}]]`;
      } else {
        // Show block content for context
        locationDesc = formatBlockReference(finalParentUid, 60);
      }
      if (smart_insertion && finalParentUid !== targetUid) {
        locationDesc += " (smart insertion)";
      }

      // Smart insertion fallback note
      const smartFallbackNote = smartInsertionFailed
        ? "\n⚠️ Smart insertion could not determine the best location — content was appended at the end instead."
        : "";

      // If we couldn't get UIDs but the API didn't throw, verify via child count change
      if (topUidsArray.length === 0) {
        const childrenAfter = roamAPI.data.pull("[:block/children]", [
          ":block/uid",
          finalParentUid,
        ]);
        const childCountAfter = childrenAfter?.[":block/children"]?.length || 0;

        if (childCountAfter > childCountBefore) {
          return `✅ Blocks created as children of ${locationDesc}.${smartFallbackNote}`;
        }

        return "⚠️ No blocks were created. The markdown content may be empty or invalid.";
      }

      const createdCount = topUidsArray.length;
      const uidList =
        createdCount <= 5
          ? topUidsArray.map((uid: string) => `((${uid}))`).join(", ")
          : `${topUidsArray
              .slice(0, 3)
              .map((uid: string) => `((${uid}))`)
              .join(", ")} ... and ${createdCount - 3} more`;

      return `✅ Created ${createdCount} top-level block(s) as children of ${locationDesc}.\nBlock UIDs: ${uidList}${smartFallbackNote}`;
    } catch (error) {
      console.error("Error creating blocks from markdown:", error);
      return `❌ Failed to create blocks. ${
        error instanceof Error ? error.message : "Unknown error"
      }`;
    }
  },
  {
    name: "create_block",
    description: `Create blocks in Roam. REQUIRES TWO CALLS:

**CALL 1 - MANDATORY ANALYSIS** (omit markdown_content):
→ Returns the target's current outline so you can match its style

**CALL 2 - INSERTION** (include markdown_content):
→ Inserts your content formatted to match the existing style

⚠️ NEVER provide markdown_content on the first call. You MUST see the existing outline first to match its formatting, structure, tags, and logic.

Location options: parent_uid (block UID), page_title (page name), or date (for DNP). If no location is specified, defaults to the user's currently open page/block in the main view.
smart_insertion: Set true to auto-find best location within the outline.`,
    schema: z.object({
      parent_uid: z
        .string()
        .optional()
        .describe(
          "The UID of the parent block where new blocks will be inserted as children. Must be a valid 9-character Roam block UID. Either parent_uid or page_title must be provided.",
        ),
      page_title: z
        .string()
        .optional()
        .describe(
          "The title of a page where new blocks will be inserted as children. Use this instead of parent_uid when you know the page name. The page must exist in the graph.",
        ),
      date: z
        .string()
        .optional()
        .describe(
          "ISO date format (YYYY-MM-DD) to target a Daily Notes Page. Example: '2024-01-15' targets the DNP for January 15th, 2024. Use this instead of page_title for date-based pages.",
        ),
      use_main_view: z
        .boolean()
        .optional()
        .describe(
          "Use the currently open page/block in the main view as the target. Defaults to true if no other location (parent_uid, page_title, date) is specified.",
        ),
      markdown_content: z
        .string()
        .optional()
        .describe(
          "⚠️ DO NOT provide on first call. First call without this to see the outline. Then call again WITH this, formatted to match the existing style you observed.",
        ),
      order: z
        .union([z.enum(["first", "last"]), z.number().int().min(0)])
        .optional()
        .describe(
          'Where to insert the new blocks among siblings: "first" (at the beginning), "last" (at the end, default), or a non-negative integer for specific position (0-indexed). Ignored if smart_insertion is true.',
        ),
      smart_insertion: z
        .boolean()
        .optional()
        .describe(
          "DEFAULT TO TRUE when inserting into a page without explicit position. Uses LLM to analyze the existing outline and automatically determine the best location for the new content based on logical structure and topic relevance. Only set to false if user explicitly specifies position or provides exact parent_uid.",
        ),
    }),
  },
);
