import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'

import { SocketConnectionError } from './mcpSocketClient.js'
import type {
  ClaudeForChromeContext,
  PermissionMode,
  PermissionOverrides,
  SocketClient,
} from './types.js'
import { toLoggerDetail } from './types.js'

export const handleToolCall = async (
  context: ClaudeForChromeContext,
  socketClient: SocketClient,
  name: string,
  args: Record<string, unknown>,
  permissionOverrides?: PermissionOverrides,
): Promise<CallToolResult> => {
  // Handle permission mode changes locally (not forwarded to extension)
  if (name === 'set_permission_mode') {
    return handleSetPermissionMode(socketClient, args)
  }

  // Handle switch_browser outside the normal tool call flow (manages its own connection)
  if (name === 'switch_browser') {
    return handleSwitchBrowser(context, socketClient)
  }

  try {
    const isConnected = await socketClient.ensureConnected()

    context.logger.silly(
      `[${context.serverName}] Server is connected: ${isConnected}. Received tool call: ${name} with args: ${JSON.stringify(args)}.`,
    )

    if (isConnected) {
      return await handleToolCallConnected(
        context,
        socketClient,
        name,
        args,
        permissionOverrides,
      )
    }

    return handleToolCallDisconnected(context)
  } catch (error) {
    context.logger.info(
      `[${context.serverName}] Error calling tool:`,
      toLoggerDetail(error),
    )

    if (error instanceof SocketConnectionError) {
      return handleToolCallDisconnected(context)
    }

    return {
      content: [
        {
          type: 'text',
          text: `Error calling tool, please try again. : ${error instanceof Error ? error.message : String(error)}`,
        },
      ],
      isError: true,
    }
  }
}

async function handleToolCallConnected(
  context: ClaudeForChromeContext,
  socketClient: SocketClient,
  name: string,
  args: Record<string, unknown>,
  permissionOverrides?: PermissionOverrides,
): Promise<CallToolResult> {
  const response = await socketClient.callTool(name, args, permissionOverrides)

  context.logger.silly(
    `[${context.serverName}] Received result from socket bridge: ${JSON.stringify(response)}`,
  )

  if (response === null || response === undefined) {
    return {
      content: [{ type: 'text', text: 'Tool execution completed' }],
    }
  }

  // Response will have either result or error field
  const { result, error } = response as {
    result?: { content: unknown[] | string }
    error?: { content: unknown[] | string }
  }

  // Determine which field has the content and whether it's an error
  const contentData = error || result
  const isError = !!error

  if (!contentData) {
    return {
      content: [{ type: 'text', text: 'Tool execution completed' }],
    }
  }

  if (isError && isAuthenticationError(contentData.content)) {
    context.onAuthenticationError()
  }

  const { content } = contentData

  if (content && Array.isArray(content)) {
    if (isError) {
      return {
        content: content.map((item: unknown) => {
          if (typeof item === 'object' && item !== null && 'type' in item) {
            return item
          }

          return { type: 'text', text: String(item) }
        }),
        isError: true,
      } as CallToolResult
    }

    const convertedContent = content.map((item: unknown) => {
      if (
        typeof item === 'object' &&
        item !== null &&
        'type' in item &&
        'source' in item
      ) {
        const typedItem = item
        if (
          typedItem.type === 'image' &&
          typeof typedItem.source === 'object' &&
          typedItem.source !== null &&
          'data' in typedItem.source
        ) {
          return {
            type: 'image',
            data: typedItem.source.data,
            mimeType:
              'media_type' in typedItem.source
                ? typedItem.source.media_type || 'image/png'
                : 'image/png',
          }
        }
      }

      if (typeof item === 'object' && item !== null && 'type' in item) {
        return item
      }

      return { type: 'text', text: String(item) }
    })

    return {
      content: convertedContent,
      isError,
    } as CallToolResult
  }

  // Handle string content
  if (typeof content === 'string') {
    return {
      content: [{ type: 'text', text: content }],
      isError,
    } as CallToolResult
  }

  // Fallback for unexpected result format
  context.logger.warn(
    `[${context.serverName}] Unexpected result format from socket bridge: ${JSON.stringify(response)}`,
  )

  return {
    content: [{ type: 'text', text: JSON.stringify(response) }],
    isError,
  }
}

function handleToolCallDisconnected(
  context: ClaudeForChromeContext,
): CallToolResult {
  const text = context.onToolCallDisconnected()
  return {
    content: [{ type: 'text', text }],
  }
}

/**
 * Handle set_permission_mode tool call locally.
 * This is security-sensitive as it controls whether permission prompts are shown.
 */
async function handleSetPermissionMode(
  socketClient: SocketClient,
  args: Record<string, unknown>,
): Promise<CallToolResult> {
  // Validate permission mode at runtime
  const validModes = [
    'ask',
    'skip_all_permission_checks',
    'follow_a_plan',
  ] as const
  const mode = args.mode as string | undefined
  const permissionMode: PermissionMode =
    mode && validModes.includes(mode as PermissionMode)
      ? (mode as PermissionMode)
      : 'ask'

  if (socketClient.setPermissionMode) {
    await socketClient.setPermissionMode(
      permissionMode,
      args.allowed_domains as string[] | undefined,
    )
  }

  return {
    content: [
      { type: 'text', text: `Permission mode set to: ${permissionMode}` },
    ],
  }
}

/**
 * Handle switch_browser tool call. Broadcasts a pairing request and blocks
 * until a browser responds or timeout.
 */
async function handleSwitchBrowser(
  context: ClaudeForChromeContext,
  socketClient: SocketClient,
): Promise<CallToolResult> {
  if (!context.bridgeConfig) {
    return {
      content: [
        {
          type: 'text',
          text: 'Browser switching is only available with bridge connections.',
        },
      ],
      isError: true,
    }
  }

  const isConnected = await socketClient.ensureConnected()
  if (!isConnected) {
    return handleToolCallDisconnected(context)
  }

  const result = (await socketClient.switchBrowser?.()) ?? null

  if (result === 'no_other_browsers') {
    return {
      content: [
        {
          type: 'text',
          text: 'No other browsers available to switch to. Open Chrome with the Claude extension in another browser to switch.',
        },
      ],
      isError: true,
    }
  }

  if (result) {
    return {
      content: [
        { type: 'text', text: `Connected to browser "${result.name}".` },
      ],
    }
  }

  return {
    content: [
      {
        type: 'text',
        text: 'No browser responded within the timeout. Make sure Chrome is open with the Claude extension installed, then try again.',
      },
    ],
    isError: true,
  }
}

/**
 * Check if the error content indicates an authentication issue
 */
function isAuthenticationError(content: unknown[] | string): boolean {
  const errorText = Array.isArray(content)
    ? content
        .map(item => {
          if (typeof item === 'string') return item
          if (
            typeof item === 'object' &&
            item !== null &&
            'text' in item &&
            typeof item.text === 'string'
          ) {
            return item.text
          }
          return ''
        })
        .join(' ')
    : String(content)

  return errorText.toLowerCase().includes('re-authenticated')
}
