import React, { useMemo } from "react";
import ReactMarkdown from "react-markdown";

import { MoreMinimoreWrite } from "./MoreMinimoreWrite";
import { MoreMinimoreRename } from "./MoreMinimoreRename";
import { MoreMinimoreDelete } from "./MoreMinimoreDelete";
import { MoreMinimoreAddDependency } from "./MoreMinimoreAddDependency";
import { MoreMinimoreExecuteSql } from "./MoreMinimoreExecuteSql";
import { MoreMinimoreAddIntegration } from "./MoreMinimoreAddIntegration";
import { MoreMinimoreEdit } from "./MoreMinimoreEdit";
import { MoreMinimoreSearchReplace } from "./MoreMinimoreSearchReplace";
import { MoreMinimoreCodebaseContext } from "./MoreMinimoreCodebaseContext";
import { MoreMinimoreThink } from "./MoreMinimoreThink";
import { CodeHighlight } from "./CodeHighlight";
import { useAtomValue } from "jotai";
import { isStreamingByIdAtom, selectedChatIdAtom } from "@/atoms/chatAtoms";
import { CustomTagState } from "./stateTypes";
import { MoreMinimoreOutput } from "./MoreMinimoreOutput";
import { DyadProblemSummary } from "./DyadProblemSummary";
import { IpcClient } from "@/ipc/ipc_client";
import { MoreMinimoreMcpToolCall } from "./MoreMinimoreMcpToolCall";
import { MoreMinimoreMcpToolResult } from "./MoreMinimoreMcpToolResult";
import { MoreMinimoreWebSearchResult } from "./MoreMinimoreWebSearchResult";
import { MoreMinimoreWebSearch } from "./MoreMinimoreWebSearch";
import { MoreMinimoreWebCrawl } from "./MoreMinimoreWebCrawl";
import { MoreMinimoreCodeSearchResult } from "./MoreMinimoreCodeSearchResult";
import { MoreMinimoreCodeSearch } from "./MoreMinimoreCodeSearch";
import { MoreMinimoreRead } from "./MoreMinimoreRead";
import { mapActionToButton } from "./ChatInput";
import { SuggestedAction } from "@/lib/schemas";
import { FixAllErrorsButton } from "./FixAllErrorsButton";

interface MoreMinimoreMarkdownParserProps {
  content: string;
}

type CustomTagInfo = {
  tag: string;
  attributes: Record<string, string>;
  content: string;
  fullMatch: string;
  inProgress?: boolean;
};

type ContentPiece =
  | { type: "markdown"; content: string }
  | { type: "custom-tag"; tagInfo: CustomTagInfo };

const customLink = ({
  node: _node,
  ...props
}: {
  node?: any;
  [key: string]: any;
}) => (
  <a
    {...props}
    onClick={(e) => {
      const url = props.href;
      if (url) {
        e.preventDefault();
        IpcClient.getInstance().openExternalUrl(url);
      }
    }}
  />
);

export const VanillaMarkdownParser = ({ content }: { content: string }) => {
  return (
    <ReactMarkdown
      components={{
        code: CodeHighlight,
        a: customLink,
      }}
    >
      {content}
    </ReactMarkdown>
  );
};

/**
 * Custom component to parse markdown content with MoreMinimore-specific tags
 */
export const MoreMinimoreMarkdownParser: React.FC<MoreMinimoreMarkdownParserProps> = ({
  content,
}) => {
  const chatId = useAtomValue(selectedChatIdAtom);
  const isStreaming = useAtomValue(isStreamingByIdAtom).get(chatId!) ?? false;
  // Extract content pieces (markdown and custom tags)
  const contentPieces = useMemo(() => {
    return parseCustomTags(content);
  }, [content]);

  // Extract error messages and track positions
  const { errorMessages, lastErrorIndex, errorCount } = useMemo(() => {
    const errors: string[] = [];
    let lastIndex = -1;
    let count = 0;

    contentPieces.forEach((piece, index) => {
      if (
        piece.type === "custom-tag" &&
        piece.tagInfo.tag === "dyad-output" &&
        piece.tagInfo.attributes.type === "error"
      ) {
        const errorMessage = piece.tagInfo.attributes.message;
        if (errorMessage?.trim()) {
          errors.push(errorMessage.trim());
          count++;
          lastIndex = index;
        }
      }
    });

    return {
      errorMessages: errors,
      lastErrorIndex: lastIndex,
      errorCount: count,
    };
  }, [contentPieces]);

  return (
    <>
      {contentPieces.map((piece, index) => (
        <React.Fragment key={index}>
          {piece.type === "markdown"
            ? piece.content && (
                <ReactMarkdown
                  components={{
                    code: CodeHighlight,
                    a: customLink,
                  }}
                >
                  {piece.content}
                </ReactMarkdown>
              )
            : renderCustomTag(piece.tagInfo, { isStreaming })}
          {index === lastErrorIndex &&
            errorCount > 1 &&
            !isStreaming &&
            chatId && (
              <div className="mt-3 w-full flex">
                <FixAllErrorsButton
                  errorMessages={errorMessages}
                  chatId={chatId}
                />
              </div>
            )}
        </React.Fragment>
      ))}
    </>
  );
};

/**
 * Pre-process content to handle unclosed custom tags
 * Adds closing tags at the end of the content for any unclosed custom tags
 * Assumes the opening tags are complete and valid
 * Returns the processed content and a map of in-progress tags
 */
function preprocessUnclosedTags(content: string): {
  processedContent: string;
  inProgressTags: Map<string, Set<number>>;
} {
  const customTagNames = [
    "dyad-write",
    "dyad-rename",
    "dyad-delete",
    "dyad-add-dependency",
    "dyad-execute-sql",
    "dyad-add-integration",
    "dyad-output",
    "dyad-problem-report",
    "dyad-chat-summary",
    "dyad-edit",
    "dyad-search-replace",
    "dyad-codebase-context",
    "dyad-web-search-result",
    "dyad-web-search",
    "dyad-web-crawl",
    "dyad-read",
    "think",
    "dyad-command",
    "dyad-mcp-tool-call",
    "dyad-mcp-tool-result",
  ];

  let processedContent = content;
  // Map to track which tags are in progress and their positions
  const inProgressTags = new Map<string, Set<number>>();

  // For each tag type, check if there are unclosed tags
  for (const tagName of customTagNames) {
    // Count opening and closing tags
    const openTagPattern = new RegExp(`<${tagName}(?:\\s[^>]*)?>`, "g");
    const closeTagPattern = new RegExp(`</${tagName}>`, "g");

    // Track the positions of opening tags
    const openingMatches: RegExpExecArray[] = [];
    let match;

    // Reset regex lastIndex to start from the beginning
    openTagPattern.lastIndex = 0;

    while ((match = openTagPattern.exec(processedContent)) !== null) {
      openingMatches.push({ ...match });
    }

    const openCount = openingMatches.length;
    const closeCount = (processedContent.match(closeTagPattern) || []).length;

    // If we have more opening than closing tags
    const missingCloseTags = openCount - closeCount;
    if (missingCloseTags > 0) {
      // Add the required number of closing tags at the end
      processedContent += Array(missingCloseTags)
        .fill(`</${tagName}>`)
        .join("");

      // Mark the last N tags as in progress where N is the number of missing closing tags
      const inProgressIndexes = new Set<number>();
      const startIndex = openCount - missingCloseTags;
      for (let i = startIndex; i < openCount; i++) {
        inProgressIndexes.add(openingMatches[i].index);
      }
      inProgressTags.set(tagName, inProgressIndexes);
    }
  }

  return { processedContent, inProgressTags };
}

/**
 * Parse the content to extract custom tags and markdown sections into a unified array
 */
function parseCustomTags(content: string): ContentPiece[] {
  const { processedContent, inProgressTags } = preprocessUnclosedTags(content);

  const customTagNames = [
    "dyad-write",
    "dyad-rename",
    "dyad-delete",
    "dyad-add-dependency",
    "dyad-execute-sql",
    "dyad-add-integration",
    "dyad-output",
    "dyad-problem-report",
    "dyad-chat-summary",
    "dyad-edit",
    "dyad-search-replace",
    "dyad-codebase-context",
    "dyad-web-search-result",
    "dyad-web-search",
    "dyad-web-crawl",
    "dyad-code-search-result",
    "dyad-code-search",
    "dyad-read",
    "think",
    "dyad-command",
    "dyad-mcp-tool-call",
    "dyad-mcp-tool-result",
  ];

  const tagPattern = new RegExp(
    `<(${customTagNames.join("|")})\\s*([^>]*)>(.*?)<\\/\\1>`,
    "gs",
  );

  const contentPieces: ContentPiece[] = [];
  let lastIndex = 0;
  let match;

  // Find all custom tags
  while ((match = tagPattern.exec(processedContent)) !== null) {
    const [fullMatch, tag, attributesStr, tagContent] = match;
    const startIndex = match.index;

    // Add the markdown content before this tag
    if (startIndex > lastIndex) {
      contentPieces.push({
        type: "markdown",
        content: processedContent.substring(lastIndex, startIndex),
      });
    }

    // Parse attributes
    const attributes: Record<string, string> = {};
    const attrPattern = /(\w+)="([^"]*)"/g;
    let attrMatch;
    while ((attrMatch = attrPattern.exec(attributesStr)) !== null) {
      attributes[attrMatch[1]] = attrMatch[2];
    }

    // Check if this tag was marked as in progress
    const tagInProgressSet = inProgressTags.get(tag);
    const isInProgress = tagInProgressSet?.has(startIndex);

    // Add the tag info
    contentPieces.push({
      type: "custom-tag",
      tagInfo: {
        tag,
        attributes,
        content: tagContent,
        fullMatch,
        inProgress: isInProgress || false,
      },
    });

    lastIndex = startIndex + fullMatch.length;
  }

  // Add the remaining markdown content
  if (lastIndex < processedContent.length) {
    contentPieces.push({
      type: "markdown",
      content: processedContent.substring(lastIndex),
    });
  }

  return contentPieces;
}

function getState({
  isStreaming,
  inProgress,
}: {
  isStreaming?: boolean;
  inProgress?: boolean;
}): CustomTagState {
  if (!inProgress) {
    return "finished";
  }
  return isStreaming ? "pending" : "aborted";
}

/**
 * Render a custom tag based on its type
 */
function renderCustomTag(
  tagInfo: CustomTagInfo,
  { isStreaming }: { isStreaming: boolean },
): React.ReactNode {
  const { tag, attributes, content, inProgress } = tagInfo;

  switch (tag) {
    case "dyad-read":
      return (
        <MoreMinimoreRead
          node={{
            properties: {
              path: attributes.path || "",
            },
          }}
        >
          {content}
        </MoreMinimoreRead>
      );
    case "dyad-web-search":
      return (
        <MoreMinimoreWebSearch
          node={{
            properties: {},
          }}
        >
          {content}
        </MoreMinimoreWebSearch>
      );
    case "dyad-web-crawl":
      return (
        <MoreMinimoreWebCrawl
          node={{
            properties: {},
          }}
        >
          {content}
        </MoreMinimoreWebCrawl>
      );
    case "dyad-code-search":
      return (
        <MoreMinimoreCodeSearch
          node={{
            properties: {},
          }}
        >
          {content}
        </MoreMinimoreCodeSearch>
      );
    case "dyad-code-search-result":
      return (
        <MoreMinimoreCodeSearchResult
          node={{
            properties: {},
          }}
        >
          {content}
        </MoreMinimoreCodeSearchResult>
      );
    case "dyad-web-search-result":
      return (
        <MoreMinimoreWebSearchResult
          node={{
            properties: {
              state: getState({ isStreaming, inProgress }),
            },
          }}
        >
          {content}
        </MoreMinimoreWebSearchResult>
      );
    case "think":
      return (
        <MoreMinimoreThink
          node={{
            properties: {
              state: getState({ isStreaming, inProgress }),
            },
          }}
        >
          {content}
        </MoreMinimoreThink>
      );
    case "dyad-write":
      return (
        <MoreMinimoreWrite
          node={{
            properties: {
              path: attributes.path || "",
              description: attributes.description || "",
              state: getState({ isStreaming, inProgress }),
            },
          }}
        >
          {content}
        </MoreMinimoreWrite>
      );

    case "dyad-rename":
      return (
        <MoreMinimoreRename
          node={{
            properties: {
              from: attributes.from || "",
              to: attributes.to || "",
            },
          }}
        >
          {content}
        </MoreMinimoreRename>
      );

    case "dyad-delete":
      return (
        <MoreMinimoreDelete
          node={{
            properties: {
              path: attributes.path || "",
            },
          }}
        >
          {content}
        </MoreMinimoreDelete>
      );

    case "dyad-add-dependency":
      return (
        <MoreMinimoreAddDependency
          node={{
            properties: {
              packages: attributes.packages || "",
            },
          }}
        >
          {content}
        </MoreMinimoreAddDependency>
      );

    case "dyad-execute-sql":
      return (
        <MoreMinimoreExecuteSql
          node={{
            properties: {
              state: getState({ isStreaming, inProgress }),
              description: attributes.description || "",
            },
          }}
        >
          {content}
        </MoreMinimoreExecuteSql>
      );

    case "dyad-add-integration":
      return (
        <MoreMinimoreAddIntegration
          node={{
            properties: {
              provider: attributes.provider || "",
            },
          }}
        >
          {content}
        </MoreMinimoreAddIntegration>
      );

    case "dyad-edit":
      return (
        <MoreMinimoreEdit
          node={{
            properties: {
              path: attributes.path || "",
              description: attributes.description || "",
              state: getState({ isStreaming, inProgress }),
            },
          }}
        >
          {content}
        </MoreMinimoreEdit>
      );

    case "dyad-search-replace":
      return (
        <MoreMinimoreSearchReplace
          node={{
            properties: {
              path: attributes.path || "",
              description: attributes.description || "",
              state: getState({ isStreaming, inProgress }),
            },
          }}
        >
          {content}
        </MoreMinimoreSearchReplace>
      );

    case "dyad-codebase-context":
      return (
        <MoreMinimoreCodebaseContext
          node={{
            properties: {
              files: attributes.files || "",
              state: getState({ isStreaming, inProgress }),
            },
          }}
        >
          {content}
        </MoreMinimoreCodebaseContext>
      );

    case "dyad-mcp-tool-call":
      return (
        <MoreMinimoreMcpToolCall
          node={{
            properties: {
              serverName: attributes.server || "",
              toolName: attributes.tool || "",
            },
          }}
        >
          {content}
        </MoreMinimoreMcpToolCall>
      );

    case "dyad-mcp-tool-result":
      return (
        <MoreMinimoreMcpToolResult
          node={{
            properties: {
              serverName: attributes.server || "",
              toolName: attributes.tool || "",
            },
          }}
        >
          {content}
        </MoreMinimoreMcpToolResult>
      );

    case "dyad-output":
      return (
        <MoreMinimoreOutput
          type={attributes.type as "warning" | "error"}
          message={attributes.message}
        >
          {content}
        </MoreMinimoreOutput>
      );

    case "dyad-problem-report":
      return (
        <DyadProblemSummary summary={attributes.summary}>
          {content}
        </DyadProblemSummary>
      );

    case "dyad-chat-summary":
      // Don't render anything for dyad-chat-summary
      return null;

    case "dyad-command":
      if (attributes.type) {
        const action = {
          id: attributes.type,
        } as SuggestedAction;
        return <>{mapActionToButton(action)}</>;
      }
      return null;

    default:
      return null;
  }
}
