/**
 * Docsector MCP Server — Cloudflare Pages Function
 *
 * Auto-generated by docsector build. Do not edit manually.
 *
 * This implements the MCP (Model Context Protocol) Streamable HTTP transport,
 * exposing documentation content as tools for AI assistants.
 *
 * Placeholders replaced at build time:
 *   __MCP_SERVER_NAME__    → config.mcp.serverName
 *   __MCP_SERVER_VERSION__ → config.branding.version
 *   __MCP_TOOL_SUFFIX__    → config.mcp.toolSuffix
 *   __MCP_SITE_URL__       → config.siteUrl
 */

const SERVER_NAME = '__MCP_SERVER_NAME__'
const SERVER_VERSION = '__MCP_SERVER_VERSION__'
const TOOL_SUFFIX = '__MCP_TOOL_SUFFIX__'
const SITE_URL = '__MCP_SITE_URL__'

const PROTOCOL_VERSION = '2025-03-26'

const CORS_HEADERS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept, Mcp-Session-Id',
  'Access-Control-Expose-Headers': 'Mcp-Session-Id'
}

function jsonResponse (body, status = 200) {
  return new Response(JSON.stringify(body), {
    status,
    headers: { 'Content-Type': 'application/json', ...CORS_HEADERS }
  })
}

function jsonRpcResponse (id, result) {
  return jsonResponse({ jsonrpc: '2.0', id, result })
}

function jsonRpcError (id, code, message) {
  return jsonResponse({ jsonrpc: '2.0', id, error: { code, message } })
}

// --- Tool definitions ---

const TOOLS = [
  {
    name: `search_${TOOL_SUFFIX}`,
    description: `Search the ${SERVER_NAME} documentation. Returns matching pages with titles and URLs. Use this to find relevant documentation pages by keyword.`,
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search term or phrase to find in the documentation'
        }
      },
      required: ['query']
    }
  },
  {
    name: `get_page_${TOOL_SUFFIX}`,
    description: `Get the full Markdown content of a specific ${SERVER_NAME} documentation page. Use the path returned by search_${TOOL_SUFFIX}, or provide a known documentation path.`,
    inputSchema: {
      type: 'object',
      properties: {
        path: {
          type: 'string',
          description: 'Page path (e.g. "manual/CLI/Terminal/Output/overview"). Do not include leading slash or .md extension.'
        }
      },
      required: ['path']
    }
  }
]

// --- Page index cache (per-isolate) ---

let cachedPages = null

async function getPages (env) {
  if (cachedPages) return cachedPages

  const url = new URL('/mcp-pages.json', SITE_URL || 'http://localhost')
  const res = await env.ASSETS.fetch(url.toString())

  if (!res.ok) return []

  cachedPages = await res.json()
  return cachedPages
}

// --- Tool handlers ---

async function handleSearch (args, env) {
  const query = (args.query || '').toLowerCase().trim()
  if (!query) {
    return { content: [{ type: 'text', text: 'Please provide a search query.' }], isError: true }
  }

  const pages = await getPages(env)
  const terms = query.split(/\s+/)

  // Score pages by title match
  const results = []
  for (const page of pages) {
    const title = (page.title || '').toLowerCase()
    const path = (page.path || '').toLowerCase()

    let score = 0
    for (const term of terms) {
      if (title.includes(term)) score += 2
      if (path.includes(term)) score += 1
    }

    if (score > 0) {
      results.push({ ...page, score })
    }
  }

  results.sort((a, b) => b.score - a.score)
  const top = results.slice(0, 15)

  if (top.length === 0) {
    return {
      content: [{ type: 'text', text: `No documentation pages found for "${args.query}".` }]
    }
  }

  const siteBase = (SITE_URL || '').replace(/\/+$/, '')
  const lines = top.map(r => {
    const url = siteBase ? `${siteBase}/${r.path}` : r.path
    return `- **${r.title}** — [${r.path}](${url})`
  })

  return {
    content: [{
      type: 'text',
      text: `Found ${results.length} result(s) for "${args.query}":\n\n${lines.join('\n')}\n\nUse get_page_${TOOL_SUFFIX} with the path to read the full content.`
    }]
  }
}

async function handleGetPage (args, env) {
  let path = (args.path || '').replace(/^\/+/, '').replace(/\/+$/, '').replace(/\.md$/, '')
  if (!path) {
    return { content: [{ type: 'text', text: 'Please provide a page path.' }], isError: true }
  }

  const siteBase = SITE_URL || 'http://localhost'
  const pages = await getPages(env)

  // Try the path as-is, then with /overview appended
  const attempts = [path]
  if (!path.endsWith('/overview') && !path.endsWith('/showcase') && !path.endsWith('/vs')) {
    attempts.push(`${path}/overview`)
  }

  for (const pagePath of attempts) {
    if (!pages.some(page => page.path === pagePath)) continue

    const url = new URL(`/${pagePath}.md`, siteBase)
    const res = await env.ASSETS.fetch(url.toString())

    if (res.ok) {
      const text = await res.text()
      const contentType = res.headers.get('content-type') || ''
      const looksLikeHtml = /^\s*<!doctype\s+html/i.test(text) || /^\s*<html[\s>]/i.test(text)
      if (contentType.includes('text/html') || looksLikeHtml) continue

      const siteUrl = siteBase.replace(/\/+$/, '')

      return {
        content: [{
          type: 'text',
          text: `# ${pagePath}\n\nSource: ${siteUrl}/${pagePath}\n\n---\n\n${text}`
        }]
      }
    }
  }

  return {
    content: [{
      type: 'text',
      text: `Page not found: "${path}". Use search_${TOOL_SUFFIX} to find available pages.`
    }],
    isError: true
  }
}

// --- MCP JSON-RPC dispatcher ---

async function handleJsonRpc (body, env) {
  const { jsonrpc, id, method, params } = body

  if (jsonrpc !== '2.0') {
    return jsonRpcError(id ?? null, -32600, 'Invalid JSON-RPC version')
  }

  switch (method) {
    case 'initialize':
      return jsonRpcResponse(id, {
        protocolVersion: PROTOCOL_VERSION,
        capabilities: {
          tools: { listChanged: false }
        },
        serverInfo: {
          name: SERVER_NAME,
          version: SERVER_VERSION
        }
      })

    case 'notifications/initialized':
      // Client acknowledgement — no response needed for notifications
      return new Response(null, { status: 204, headers: CORS_HEADERS })

    case 'tools/list':
      return jsonRpcResponse(id, { tools: TOOLS })

    case 'tools/call': {
      const toolName = params?.name
      const args = params?.arguments || {}

      if (toolName === `search_${TOOL_SUFFIX}`) {
        const result = await handleSearch(args, env)
        return jsonRpcResponse(id, result)
      }

      if (toolName === `get_page_${TOOL_SUFFIX}`) {
        const result = await handleGetPage(args, env)
        return jsonRpcResponse(id, result)
      }

      return jsonRpcError(id, -32602, `Unknown tool: "${toolName}"`)
    }

    default:
      return jsonRpcError(id, -32601, `Method not found: "${method}"`)
  }
}

// --- Cloudflare Pages Function handlers ---

export async function onRequestGet (context) {
  const siteBase = (SITE_URL || '').replace(/\/+$/, '')

  return jsonResponse({
    name: SERVER_NAME,
    version: SERVER_VERSION,
    protocolVersion: PROTOCOL_VERSION,
    capabilities: { tools: TOOLS.map(t => t.name) },
    documentation: siteBase || undefined,
    usage: {
      claude_desktop: {
        [SERVER_NAME]: { type: 'url', url: `${siteBase}/mcp` }
      },
      vscode: {
        [SERVER_NAME]: { type: 'http', url: `${siteBase}/mcp` }
      },
      curl: `curl -X POST ${siteBase}/mcp -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'`
    }
  })
}

export async function onRequestPost (context) {
  const { request, env } = context

  const contentType = request.headers.get('content-type') || ''
  if (!contentType.includes('application/json')) {
    return jsonRpcError(null, -32700, 'Content-Type must be application/json')
  }

  let body
  try {
    body = await request.json()
  } catch {
    return jsonRpcError(null, -32700, 'Parse error: invalid JSON')
  }

  return handleJsonRpc(body, env)
}

export async function onRequestOptions () {
  return new Response(null, { status: 204, headers: CORS_HEADERS })
}
