"""
MCP Server for converting Markdown to mindmaps.
"""

import asyncio
import tempfile
import os
import shutil
import sys
import argparse
from pathlib import Path
from mcp.server.fastmcp import FastMCP

# Parse command line arguments
def parse_arguments():
    parser = argparse.ArgumentParser(description='MCP Server for converting Markdown to mindmaps')
    parser.add_argument('--return-type', choices=['html', 'filePath'], default='html',
                        help='Whether to return HTML content or file path. Default: html')
    return parser.parse_args()

# Global configuration
args = parse_arguments()
RETURN_TYPE = args.return_type

# Initialize FastMCP server
mcp = FastMCP("mindmap-server")

async def create_temp_file(content: str, extension: str) -> str:
    """Create a temporary file with the given content and extension."""
    temp_dir = tempfile.mkdtemp(prefix='mindmap-')
    file_path = os.path.join(temp_dir, f"input{extension}")
    
    with open(file_path, mode='w') as f:
        f.write(content)
    
    return file_path

async def run_mindmap(input_file: str, output_file: str = None, show_toolbar: bool = True) -> str:
    """Run markmap on the input file and return the path to the output file.

    Tries multiple command candidates for better Windows/macOS/Linux compatibility:
    1) npx -y markmap-cli
    2) npx -y markmap
    3) markmap-cli (global install)
    4) markmap (global install)
    5) npm exec markmap-cli

    Args:
        input_file: Path to the markdown file.
        output_file: Optional output html path. If not provided, markmap will
            generate a file next to the input file with the same basename.
        show_toolbar: Whether to show toolbar in generated html. Defaults True.
    """
    if not output_file:
        output_file = os.path.splitext(input_file)[0] + '.html'

    def build_args(base_cmd: list[str]) -> list[str]:
        args: list[str] = [*base_cmd, input_file, '--no-open']
        if not show_toolbar:
            args.append('--no-toolbar')
        if output_file:
            args.extend(['-o', output_file])
        return args

    candidates: list[list[str]] = []
    # Prefer npx if available
    if shutil.which('npx'):
        candidates.append(['npx', '-y', 'markmap-cli'])
        candidates.append(['npx', '-y', 'markmap'])
    # Global binaries
    if shutil.which('markmap-cli'):
        candidates.append(['markmap-cli'])
    if shutil.which('markmap'):
        candidates.append(['markmap'])
    # npm exec fallback
    if shutil.which('npm'):
        candidates.append(['npm', 'exec', 'markmap-cli'])

    # If none found, surface a clear error early
    if not candidates:
        raise RuntimeError(
            "Failed to run markmap-cli: No suitable command found (npx/markmap-cli/markmap/npm). "
            "Please install Node.js (includes npm/npx) or markmap-cli globally."
        )

    last_error: str | None = None
    for base in candidates:
        try:
            process = await asyncio.create_subprocess_exec(
                *build_args(base),
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await process.communicate()
            if process.returncode == 0:
                return output_file
            last_error = (stderr.decode() if stderr else f"exit {process.returncode}")
        except FileNotFoundError as fe:
            last_error = str(fe)
            continue
        except Exception as e:
            last_error = str(e)
            continue

    raise RuntimeError(f"Failed to run markmap-cli: {last_error or 'Unknown error'}")

async def get_html_content(file_path: str) -> str:
    """Read the HTML content from the given file."""
    with open(file_path, 'r', encoding='utf-8') as f:
        return f.read()

@mcp.tool()
async def convert_markdown_to_mindmap(
    markdown_content: str | None = None,  # Markdown content to convert
    input_file_path: str | None = None,   # Optional existing markdown file path
    output_dir: str | None = None,        # Optional directory to save html
    output_filename: str | None = None,   # Optional filename without extension or with .html
    toolbar: bool = True,                 # Whether to show toolbar
    save_to_path: str | None = None,      # Force save path even in html return mode
) -> str:
    """Convert Markdown to a mindmap HTML.

    You can provide either "markdown_content" or an existing "input_file_path".
    When the server is started with `--return-type filePath`, the HTML file will
    be saved and this function returns the actual file path. You can control the
    save location with `output_dir` and `output_filename`.
    """
    try:
        # Determine input markdown file
        if input_file_path:
            if not os.path.exists(input_file_path):
                raise FileNotFoundError(f"Input file not found: {input_file_path}")
            input_file = input_file_path
            with open(input_file, 'r', encoding='utf-8') as f:
                md_content = f.read()
        else:
            if not markdown_content:
                raise ValueError("Either 'markdown_content' or 'input_file_path' must be provided.")
            input_file = await create_temp_file(markdown_content, '.md')
            md_content = markdown_content

        # Decide output path
        output_file: str | None = None

        # If caller wants saving explicitly or server mode is filePath, build output path
        want_save = RETURN_TYPE == 'filePath' or save_to_path is not None or output_dir is not None or output_filename is not None
        if want_save:
            if save_to_path:
                # honor explicit path first
                out_path = Path(save_to_path)
                if out_path.suffix.lower() != '.html':
                    out_path = out_path.with_suffix('.html')
                out_path.parent.mkdir(parents=True, exist_ok=True)
                output_file = str(out_path)
            else:
                if output_dir:
                    Path(output_dir).mkdir(parents=True, exist_ok=True)
                base_dir = output_dir if output_dir else os.path.dirname(input_file)

                if output_filename:
                    name = output_filename
                    if not name.lower().endswith('.html'):
                        name = f"{os.path.splitext(name)[0]}.html"
                else:
                    name = f"{Path(input_file).stem}.html"
                output_file = str(Path(base_dir) / name)

        # Run markmap
        try:
            final_output = await run_mindmap(input_file, output_file, show_toolbar=toolbar)
            # Validate output exists
            if not os.path.exists(final_output):
                raise RuntimeError(f"Output file was not created: {final_output}")
            # Return
            if RETURN_TYPE == 'html':
                return await get_html_content(final_output)
            return final_output
        except Exception:
            # Fallback: generate HTML via markmap-autoloader (no Node required)
            def build_autoloader_html(markdown: str, show_toolbar: bool) -> str:
                hide_toolbar_style = "\n      .mm-toolbar{display:none;}\n" if not show_toolbar else ""
                return (
                    "<!DOCTYPE html>\n"
                    "<html lang=\"en\">\n"
                    "  <head>\n"
                    "    <meta charset=\"UTF-8\" />\n"
                    "    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n"
                    "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n"
                    "    <title>Markmap</title>\n"
                    "    <style>\n"
                    "      svg.markmap { width: 100%; height: 100vh; }" + hide_toolbar_style + "\n"
                    "    </style>\n"
                    "    <script src=\"https://cdn.jsdelivr.net/npm/markmap-autoloader@0.18\"></script>\n"
                    "  </head>\n"
                    "  <body>\n"
                    "    <div class=\"markmap\">\n"
                    "      <script type=\"text/template\">\n"
                    "        ---\n"
                    "        markmap:\n"
                    "          colorFreezeLevel: 2\n"
                    "        ---\n\n"
                    + markdown + "\n"
                    "      </script>\n"
                    "    </div>\n"
                    "  </body>\n"
                    "</html>\n"
                )

            html = build_autoloader_html(md_content, toolbar)
            # If user wants a file or the server is in filePath mode, save it
            if output_file is None:
                base_dir = os.path.dirname(input_file)
                output_file = str(Path(base_dir) / f"{Path(input_file).stem}.html")
            Path(os.path.dirname(output_file) or ".").mkdir(parents=True, exist_ok=True)
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(html)
            return html if RETURN_TYPE == 'html' else output_file
    except Exception as e:
        hint = (
            "Ensure Node.js is installed and 'npx markmap-cli' is available. "
            "On Windows, you may also install 'markmap-cli' globally (npm i -g markmap-cli) "
            "or use Docker as documented."
        )
        raise RuntimeError(f"Error converting Markdown to mindmap: {str(e)} | {hint}")

def main():
    """Entry point for the mindmap-mcp-server command."""
    global args, RETURN_TYPE
    
    # Parse arguments again to ensure parameters are captured when running as an entry point
    args = parse_arguments()
    RETURN_TYPE = args.return_type
    
    print(f"Starting mindmap-mcp-server with return type: {RETURN_TYPE}", file=sys.stderr)
    
    # Initialize and run the server
    mcp.run(transport='stdio')

if __name__ == "__main__":
    main()
