// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: LicenseRef-MutuaL-1.2

// Package mcp implements the MCP surface for cooked-mcp.
package mcp

import (
	"context"
	"fmt"
	"strings"

	sdk "github.com/modelcontextprotocol/go-sdk/mcp"

	"git.secluded.site/cooked-mcp/internal/cooked"
)

const (
	serverName   = "Cooked"
	readToolName = "read"
)

// Backend provides the Cooked operations exposed as MCP tools.
type Backend interface {
	ReadShoppingList(ctx context.Context) (cooked.ShoppingList, error)
}

// Server exposes Cooked as an MCP server.
type Server struct {
	backend Backend
	sdk     *sdk.Server
}

// NewServer returns an MCP server for a Cooked backend.
func NewServer(backend Backend, version string) *Server {
	server := &Server{backend: backend}
	server.sdk = sdk.NewServer(&sdk.Implementation{Name: serverName, Title: serverName, Version: version}, nil)
	sdk.AddTool(server.sdk, readTool(), server.callReadTool)

	return server
}

// RunStdio serves MCP over the official SDK stdio transport.
func (s *Server) RunStdio(ctx context.Context) error {
	return s.sdk.Run(ctx, &sdk.StdioTransport{})
}

// CallReadTool invokes the read tool without asserting MCP wire presentation.
func (s *Server) CallReadTool(ctx context.Context, arguments ReadArguments) (ReadOutput, error) {
	_, output, err := s.callReadTool(ctx, nil, arguments)
	if err != nil {
		return ReadOutput{}, err
	}

	return output, nil
}

func (s *Server) callReadTool(ctx context.Context, _ *sdk.CallToolRequest, arguments ReadArguments) (*sdk.CallToolResult, ReadOutput, error) {
	if arguments.Target != "shopping_list" {
		return nil, ReadOutput{}, fmt.Errorf("unsupported read target %q in this slice", arguments.Target)
	}

	shoppingList, err := s.backend.ReadShoppingList(ctx)
	if err != nil {
		return nil, ReadOutput{}, err
	}

	output := ReadOutput{
		Aisles:  shoppingList.Aisles,
		Recipes: shoppingList.Recipes,
	}

	return &sdk.CallToolResult{Content: []sdk.Content{&sdk.TextContent{Text: formatShoppingList(shoppingList)}}}, output, nil
}

func readTool() *sdk.Tool {
	openWorld := true
	return &sdk.Tool{
		Name:        readToolName,
		Title:       "Read Cooked data",
		Description: "Read Cooked data. This slice supports target shopping_list.",
		Annotations: &sdk.ToolAnnotations{
			Title:         "Read Cooked data",
			ReadOnlyHint:  true,
			OpenWorldHint: &openWorld,
		},
	}
}

func formatShoppingList(shoppingList cooked.ShoppingList) string {
	if len(shoppingList.Aisles) == 0 {
		return "Shopping list is empty."
	}

	var builder strings.Builder
	builder.WriteString("Shopping list:\n")
	for _, aisle := range shoppingList.Aisles {
		builder.WriteString("- ")
		builder.WriteString(aisle.Name)
		builder.WriteString(":")
		if len(aisle.ProductGroups) == 0 {
			builder.WriteString(" no items\n")
			continue
		}
		builder.WriteByte('\n')

		for _, product := range aisle.ProductGroups {
			builder.WriteString("  - ")
			builder.WriteString(product.Name)
			if product.Quantity != "" {
				builder.WriteString(" — ")
				builder.WriteString(product.Quantity)
			}
			builder.WriteString(" (id: ")
			builder.WriteString(product.ID)
			builder.WriteString(")")
			if product.Selected {
				builder.WriteString(" selected")
			}
			builder.WriteByte('\n')
		}
	}

	return strings.TrimRight(builder.String(), "\n")
}

// ReadArguments contains read tool arguments.
type ReadArguments struct {
	Target string `json:"target" jsonschema:"Cooked object to read. Use shopping_list in this slice."`
}

// ReadOutput is the structured output for the current read tool slice.
type ReadOutput struct {
	Aisles  []cooked.Aisle `json:"aisles"`
	Recipes []string       `json:"recipes"`
}
