from mcp.server.lowlevel import Server
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import (
    ErrorData,
    TextContent,
    Tool,
    Annotations,
    Field,
    Annotated,
    INVALID_PARAMS,
)
from pydantic import BaseModel
import subprocess
import os, json
from mcp.shared.exceptions import McpError


def find_xcode_project():
    for root, dirs, files in os.walk("."):
        dirs.sort(reverse = True)
        for dir in dirs:
            if dir.endswith(".xcworkspace") or dir.endswith(".xcodeproj"):
                return os.path.join(root, dir)
    return None

def find_scheme(project_type: str, project_name: str) -> str:
    schemes_result = subprocess.run(["xcodebuild",
                                    "-list",
                                    project_type,
                                    project_name],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE,
                                    check=False).stdout.decode("utf-8")
    
    schemes_lines = schemes_result.splitlines()
    schemes = []
    in_schemes_section = False
    for line in schemes_lines:
        if "Schemes:" in line:
            in_schemes_section = True
            continue
        if in_schemes_section:
            scheme = line.strip()
            if scheme:
                schemes.append(scheme)
    
    if schemes:
        return schemes[0]
    else:
        return ""

def find_available_simulator() -> str:
    devices_result = subprocess.run(["xcrun", "simctl", "list", "devices", "--json"], stdout=subprocess.PIPE, check=False)
    devices_json = json.loads(devices_result.stdout.decode("utf-8"))
    
    for runtime_id, devices in devices_json["devices"].items():
        if "iOS" in runtime_id:
            for device in devices:
                if device["isAvailable"]:
                    return f'platform=iOS Simulator,name={device["name"]},OS={runtime_id.split(".")[-1].replace("iOS-", "").replace("-", ".")}'
    return ""
class Folder(BaseModel):
    """Parameters"""
    folder: Annotated[str, Field(description="The full path of the current folder that the iOS Xcode workspace/project sits")]

server = Server("build")

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name = "build",
            description = "Build the iOS Xcode workspace/project in the folder",
            inputSchema = Folder.model_json_schema(),
        ),
        Tool(
            name="test",
            description="Run test for the iOS Xcode workspace/project in the folder",
            inputSchema=Folder.model_json_schema(),
        )
    ]
@server.call_tool()
async def call_tool(name, arguments: dict) -> list[TextContent]:
    try:
        args = Folder(**arguments)
    except ValueError as e:
        raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e)))
    os.chdir(args.folder)
    xcode_project_path = find_xcode_project()
    project_name = os.path.basename(xcode_project_path)
    project_type = ""
    if xcode_project_path.endswith(".xcworkspace"):
        project_type = "-workspace"
    else:
        project_type = "-project"

    scheme = find_scheme(project_type, project_name)
    destination = find_available_simulator()
    command = ["xcodebuild",
               project_type,
               project_name,
               "-scheme",
               scheme,
               "-destination",
               destination]
    if name == "test":
        command.append("test")

    result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False).stdout
    
    lines = result.decode("utf-8").splitlines()
    error_lines = [line for line in lines if "error:" or "warning:" in line.lower()]
    error_message = "\n".join(error_lines)
    if not error_message:
        error_message = "Successful"
    return [
        TextContent(type="text", text=f"Command: {' '.join(command)}"),
        TextContent(type="text", text=f"{error_message}")
        ]


async def run():
    options = server.create_initialization_options()
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            options,
            raise_exceptions=True,
        )

if __name__ == "__main__":
    import asyncio
    asyncio.run(run())
