package todoist

// Input and output JSON Schema builders for the Todoist catalog. Schemas are
// plain map literals mirroring the ported OpenConnector action contracts.

import "math"

const schemaDialect = "https://json-schema.org/draft/2020-12/schema"

func objectSchema(properties map[string]any, required []string) map[string]any {
	schema := map[string]any{
		"$schema":              schemaDialect,
		"type":                 "object",
		"additionalProperties": false,
		"properties":           properties,
	}
	if len(required) > 0 {
		anyRequired := make([]any, 0, len(required))
		for _, name := range required {
			anyRequired = append(anyRequired, name)
		}
		schema["required"] = anyRequired
	}
	return schema
}

func optionalObjectSchema(properties map[string]any) map[string]any {
	schema := objectSchema(properties, nil)
	return schema
}

func noInputSchema() map[string]any {
	schema := objectSchema(map[string]any{}, nil)
	return schema
}

func rawObjectSchema(description string) map[string]any {
	return map[string]any{
		"$schema":              schemaDialect,
		"type":                 "object",
		"description":          description,
		"additionalProperties": true,
	}
}

func literalTrueSchema() map[string]any {
	return map[string]any{"const": true}
}

func nonEmptyStringSchema(description string) map[string]any {
	return map[string]any{
		"type":        "string",
		"minLength":   1,
		"maxLength":   2048,
		"description": description,
	}
}

func nullableStringSchema(description string) map[string]any {
	return map[string]any{
		"anyOf": []any{
			map[string]any{"type": "null"},
			map[string]any{"type": "string"},
		},
		"description": description,
	}
}

func stringSchema(description string) map[string]any {
	return map[string]any{"type": "string", "description": description}
}

func integerSchema(description string) map[string]any {
	return map[string]any{"type": "integer", "description": description}
}

func nullableIntegerSchema(description string) map[string]any {
	return map[string]any{
		"anyOf": []any{
			map[string]any{"type": "null"},
			map[string]any{"type": "integer"},
		},
		"description": description,
	}
}

// integerLikeSchema mirrors the integerLike decode contract: a JSON integer
// or a string of 1-18 digits, normalized losslessly to int64. The schema and
// the handler reject the same inputs.
func integerLikeSchema(description string) map[string]any {
	return map[string]any{
		"anyOf": []any{
			map[string]any{
				"type":    "integer",
				"minimum": 0,
				"maximum": int64(math.MaxInt64),
			},
			map[string]any{
				"type":    "string",
				"pattern": "^[0-9]{1,18}$",
			},
		},
		"description": description,
	}
}

func booleanSchema(description string) map[string]any {
	return map[string]any{"type": "boolean", "description": description}
}

func nullableBooleanSchema(description string) map[string]any {
	return map[string]any{
		"anyOf": []any{
			map[string]any{"type": "null"},
			map[string]any{"type": "boolean"},
		},
		"description": description,
	}
}

func stringArraySchema(description string, minItems int) map[string]any {
	schema := map[string]any{
		"type":        "array",
		"items":       map[string]any{"type": "string", "minLength": 1},
		"description": description,
	}
	if minItems > 0 {
		schema["minItems"] = minItems
	}
	return schema
}

func stringEnumSchema(description string, values ...string) map[string]any {
	anyValues := make([]any, 0, len(values))
	for _, value := range values {
		anyValues = append(anyValues, value)
	}
	return map[string]any{
		"type":        "string",
		"enum":        anyValues,
		"description": description,
	}
}

func nullableStringEnumSchema(description string, values ...string) map[string]any {
	return map[string]any{
		"anyOf": []any{
			map[string]any{"type": "null"},
			stringEnumSchema(description, values...),
		},
		"description": description,
	}
}

func dateSchema(description string) map[string]any {
	return map[string]any{
		"type":        "string",
		"format":      "date",
		"description": description,
	}
}

func dateTimeSchema(description string) map[string]any {
	return map[string]any{
		"type":        "string",
		"format":      "date-time",
		"description": description,
	}
}

func nullableUnionSchema(description string, variants ...map[string]any) map[string]any {
	anyVariants := make([]any, 0, len(variants)+1)
	anyVariants = append(anyVariants, map[string]any{"type": "null"})
	for _, variant := range variants {
		anyVariants = append(anyVariants, variant)
	}
	return map[string]any{"anyOf": anyVariants, "description": description}
}

func paginatedSchema(key string, itemDescription, pageDescription string) map[string]any {
	return objectSchema(map[string]any{
		key: map[string]any{
			"type":        "array",
			"items":       rawObjectSchema(itemDescription),
			"description": pageDescription,
		},
		"nextCursor": nullableStringSchema(
			"Cursor for the next page, or null when no further page exists."),
	}, []string{key, "nextCursor"})
}

var (
	optionalCursorSchema = nonEmptyStringSchema(
		"Opaque pagination cursor returned by a previous Todoist response.")
	optionalLimitSchema = map[string]any{
		"type":        "integer",
		"minimum":     1,
		"maximum":     200,
		"description": "Maximum number of Todoist results to return in one page.",
	}
	todoistColorNames = []string{
		"berry_red", "red", "orange", "yellow", "olive_green", "lime_green",
		"green", "mint_green", "teal", "sky_blue", "light_blue", "blue",
		"grape", "violet", "lavender", "magenta", "salmon", "charcoal",
		"grey", "taupe",
	}
)

func todoistColorSchema() map[string]any {
	legacyNumeric := map[string]any{
		"type":        "integer",
		"minimum":     30,
		"maximum":     49,
		"description": "Todoist legacy numeric color code.",
	}
	named := stringEnumSchema("Todoist color name.", todoistColorNames...)
	return map[string]any{
		"anyOf":       []any{named, legacyNumeric},
		"description": "Todoist color value as a named palette entry or legacy numeric code.",
	}
}

func listProjectsInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"folderId":    integerLikeSchema("Optional Todoist folder ID to filter projects by."),
		"workspaceId": integerLikeSchema("Optional Todoist workspace ID to filter projects by."),
		"cursor":      optionalCursorSchema,
		"limit":       optionalLimitSchema,
	})
}

func createProjectInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"name":        nonEmptyStringSchema("Todoist project name."),
		"description": stringSchema("Todoist project description."),
		"parentId":    nullableStringSchema("Parent project ID, or null where the API supports clearing it."),
		"color":       todoistColorSchema(),
		"isFavorite":  booleanSchema("Whether the project is a favorite."),
		"viewStyle":   stringEnumSchema("Todoist project view style.", "list", "board", "calendar"),
		"workspaceId": integerLikeSchema("Todoist workspace ID for creating a workspace-scoped project."),
	}, []string{"name"})
}

func updateProjectInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"projectId":   nonEmptyStringSchema("Todoist project ID."),
		"name":        nullableStringSchema("Updated project name."),
		"description": nullableStringSchema("Updated project description."),
		"color":       nullableUnionSchema("Updated project color.", todoistColorSchema()),
		"isFavorite":  nullableBooleanSchema("Updated favorite flag."),
		"viewStyle":   nullableStringEnumSchema("Updated project view style.", "list", "board", "calendar"),
		"childOrder":  nullableIntegerSchema("Updated project order among sibling projects."),
		"isCollapsed": nullableBooleanSchema("Updated collapsed state of the project."),
		"folderId":    nullableIntegerSchema("Updated folder ID for a workspace project."),
	}, []string{"projectId"})
}

func listSectionsInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"projectId": nonEmptyStringSchema("Optional Todoist project ID."),
		"cursor":    optionalCursorSchema,
		"limit":     optionalLimitSchema,
	})
}

func createSectionInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"projectId": nonEmptyStringSchema("Todoist project ID that will own the section."),
		"name":      nonEmptyStringSchema("Todoist section name."),
		"order":     integerSchema("Optional section order within the project."),
	}, []string{"projectId", "name"})
}

func updateSectionInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"sectionId":    nonEmptyStringSchema("Todoist section ID."),
		"name":         nullableStringSchema("Updated section name."),
		"sectionOrder": nullableIntegerSchema("Updated section order."),
		"isCollapsed":  nullableBooleanSchema("Updated collapsed state of the section."),
	}, []string{"sectionId"})
}

func listTasksInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"projectId": nonEmptyStringSchema("Optional Todoist project ID filter."),
		"sectionId": nonEmptyStringSchema("Optional Todoist section ID filter."),
		"parentId":  nonEmptyStringSchema("Optional Todoist parent task ID filter."),
		"label":     nonEmptyStringSchema("Optional Todoist label name filter."),
		"ids":       stringArraySchema("Explicit Todoist task IDs to fetch.", 1),
		"goalId":    nonEmptyStringSchema("Optional Todoist goal ID filter."),
		"cursor":    optionalCursorSchema,
		"limit":     optionalLimitSchema,
	})
}

func taskBodyFields() map[string]any {
	return map[string]any{
		"content":      nonEmptyStringSchema("Todoist task title."),
		"description":  stringSchema("Todoist task description."),
		"projectId":    nonEmptyStringSchema("Project ID that will own the task."),
		"sectionId":    nonEmptyStringSchema("Section ID that will own the task."),
		"parentId":     nonEmptyStringSchema("Parent task ID."),
		"order":        integerSchema("Task order."),
		"labels":       stringArraySchema("Todoist label names to attach.", 1),
		"priority":     boundedIntegerSchema("Todoist priority from 1 to 4.", 1, 4),
		"assigneeId":   nullableIntegerSchema("Todoist user ID to assign, or null where supported."),
		"dueString":    nonEmptyStringSchema("Natural language due date string."),
		"dueDate":      dateSchema("Due date in YYYY-MM-DD format."),
		"dueDatetime":  dateTimeSchema("Due date-time in RFC 3339 form."),
		"dueLang":      nonEmptyStringSchema("Language code used to parse the due string."),
		"duration":     integerSchema("Duration amount."),
		"durationUnit": stringEnumSchema("Todoist duration unit.", "minute", "day"),
		"deadlineDate": dateSchema("Deadline date in YYYY-MM-DD format."),
		"childOrder":   integerSchema("Task order among siblings."),
		"isCollapsed":  booleanSchema("Whether subtasks are collapsed."),
		"dayOrder":     integerSchema("Task order in Today and Upcoming."),
	}
}

func boundedIntegerSchema(description string, minimum, maximum int) map[string]any {
	return map[string]any{
		"type":        "integer",
		"minimum":     minimum,
		"maximum":     maximum,
		"description": description,
	}
}

func createTaskInputSchema() map[string]any {
	properties := taskBodyFields()
	return objectSchema(properties, []string{"content"})
}

func updateTaskInputSchema() map[string]any {
	properties := taskBodyFields()
	properties["taskId"] = nonEmptyStringSchema("Todoist task ID.")
	return objectSchema(properties, []string{"taskId"})
}

func listCommentsInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"taskId":    nonEmptyStringSchema("Optional task ID filter."),
		"projectId": nonEmptyStringSchema("Optional project ID filter."),
		"cursor":    optionalCursorSchema,
		"limit":     optionalLimitSchema,
	})
}

func attachmentInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"fileUrl":      map[string]any{"type": "string", "format": "uri", "description": "Attachment download URL."},
		"fileName":     stringSchema("Attachment file name."),
		"fileType":     stringSchema("Attachment MIME type."),
		"resourceType": stringSchema("Attachment resource type."),
	})
}

func createCommentInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"content":      nonEmptyStringSchema("Todoist comment content."),
		"taskId":       nonEmptyStringSchema("Task ID that owns the comment."),
		"projectId":    nonEmptyStringSchema("Project ID that owns the comment."),
		"attachment":   attachmentInputSchema(),
		"uidsToNotify": integerArraySchema("Todoist user IDs to notify.", "A Todoist user ID."),
	}, []string{"content"})
}

func integerArraySchema(description, itemDescription string) map[string]any {
	return map[string]any{
		"type":        "array",
		"minItems":    1,
		"items":       map[string]any{"type": "integer", "description": itemDescription},
		"description": description,
	}
}

func listLabelsInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"cursor": optionalCursorSchema,
		"limit":  optionalLimitSchema,
	})
}
