package notion

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

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
}

// describedObjectSchema is objectSchema with the upstream description
// attached, mirroring the `description` option of the upstream s.object
// helper.
func describedObjectSchema(description string, properties map[string]any, required []string) map[string]any {
	schema := objectSchema(properties, required)
	schema["description"] = description
	return schema
}

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

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

func rawObjectArraySchema(description string) map[string]any {
	return map[string]any{
		"type":        "array",
		"items":       rawObjectSchema("A Notion API object."),
		"description": description,
	}
}

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

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

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

func stringSchema(description string) map[string]any {
	return map[string]any{"type": "string", "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 booleanSchema(description string) map[string]any {
	return map[string]any{"type": "boolean", "description": description}
}

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

var (
	optionalPageSizeSchema = boundedIntegerSchema(
		"The number of results per page.", 1, 100)
	optionalStartCursorSchema = stringSchema("The cursor for pagination.")
)

// Output schemas below mirror the upstream actions.ts declarations verbatim:
// the Notion list envelope keeps the provider's own field names (object,
// results, next_cursor, has_more), and the page/block/user/data source item
// schemas are loose objects — every declared property optional and
// additionalProperties true — exactly as s.looseObject builds them.

// notionRecordSchema is the upstream notionObject: a record of unconstrained
// Notion values.
func notionRecordSchema(description string) map[string]any {
	return map[string]any{
		"$schema":              schemaDialect,
		"type":                 "object",
		"description":          description,
		"additionalProperties": map[string]any{"description": "A Notion API field value."},
	}
}

// looseObjectSchema is the upstream s.looseObject: declared properties are all
// optional and unknown Notion fields pass through. An empty description is
// omitted, matching the upstream helper's optional description.
func looseObjectSchema(properties map[string]any, description string) map[string]any {
	schema := map[string]any{
		"$schema":              schemaDialect,
		"type":                 "object",
		"additionalProperties": true,
		"properties":           properties,
	}
	if description != "" {
		schema["description"] = description
	}
	return schema
}

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

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

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

func notionRecordArraySchema(description string) map[string]any {
	return map[string]any{
		"type":        "array",
		"items":       notionRecordSchema("A Notion API object."),
		"description": description,
	}
}

// pageOutputSchema is the upstream page loose object.
func pageOutputSchema() map[string]any {
	return looseObjectSchema(map[string]any{
		"object":           literalStringSchema("page", "The Notion object type."),
		"id":               stringSchema("The page ID."),
		"created_time":     dateTimeSchema("The time when the page was created."),
		"last_edited_time": dateTimeSchema("The time when the page was last edited."),
		"parent":           notionRecordSchema("The official Notion parent object."),
		"properties":       notionRecordSchema("Notion properties keyed by property name."),
		"url":              urlSchema("The canonical Notion URL for the page."),
		"archived":         booleanSchema("Whether the page is archived."),
		"in_trash":         booleanSchema("Whether the page is in the trash."),
	}, "A Notion page object.")
}

// blockOutputSchema is the upstream block loose object.
func blockOutputSchema() map[string]any {
	return looseObjectSchema(map[string]any{
		"object":       literalStringSchema("block", "The Notion object type."),
		"id":           stringSchema("The block ID."),
		"parent":       notionRecordSchema("The official Notion parent object."),
		"type":         stringSchema("The block type."),
		"has_children": booleanSchema("Whether this block has child blocks."),
		"in_trash":     booleanSchema("Whether the block is in the trash."),
	}, "A Notion block object.")
}

// userOutputSchema is the upstream user loose object.
func userOutputSchema() map[string]any {
	return looseObjectSchema(map[string]any{
		"object":     literalStringSchema("user", "The Notion object type."),
		"id":         stringSchema("The Notion user ID."),
		"name":       nullableStringSchema("The user's display name."),
		"avatar_url": nullableStringSchema("The user's avatar URL."),
		"type":       stringEnumSchema([]any{"person", "bot"}, "The user type."),
		"person": looseObjectSchema(map[string]any{
			"email": map[string]any{"type": "string", "format": "email",
				"description": "The person's email address."},
		}, ""),
		"bot": notionRecordSchema("A Notion API object."),
	}, "A Notion user object.")
}

// dataSourceOutputSchema is the upstream data source loose object.
func dataSourceOutputSchema() map[string]any {
	return looseObjectSchema(map[string]any{
		"object":     literalStringSchema("data_source", "The Notion object type."),
		"id":         stringSchema("The data source ID."),
		"title":      notionRecordArraySchema("Notion rich text objects."),
		"properties": notionRecordSchema("Notion properties keyed by property name."),
		"parent":     notionRecordSchema("The official Notion parent object."),
		"url":        urlSchema("The canonical Notion URL for the data source."),
		"in_trash":   booleanSchema("Whether the data source is in the trash."),
	}, "A Notion data source object.")
}

// listOutputSchema is the upstream listOutput envelope over one item schema:
// the Notion list object itself, with next_cursor optional (null when no
// further page exists) and unknown provider fields preserved.
func listOutputSchema(items map[string]any, description string) map[string]any {
	return map[string]any{
		"$schema": schemaDialect,
		"type":    "object",
		"properties": map[string]any{
			"object": literalStringSchema("list", "The Notion object type."),
			"results": map[string]any{
				"type":        "array",
				"items":       items,
				"description": "Returned Notion objects.",
			},
			"next_cursor": nullableStringSchema("Cursor for the next page."),
			"has_more":    booleanSchema("Whether more results are available."),
		},
		"required":             []any{"object", "results", "has_more"},
		"additionalProperties": true,
		"description":          description,
	}
}

// pageParentSchema ports the upstream oneOf parent object: a page parent, a
// data source parent, or the workspace-level private area.
func pageParentSchema() map[string]any {
	return map[string]any{
		"anyOf": []any{
			objectSchema(map[string]any{
				"page_id": nonEmptyStringSchema("The parent page ID."),
				"type":    literalStringSchema("page_id", "Always page_id."),
			}, []string{"page_id"}),
			objectSchema(map[string]any{
				"data_source_id": nonEmptyStringSchema("The parent data source ID."),
				"type":           literalStringSchema("data_source_id", "Always data_source_id."),
			}, []string{"data_source_id"}),
			objectSchema(map[string]any{
				"workspace": literalTrueSchema(),
			}, []string{"workspace"}),
		},
		"description": "The official Notion parent object.",
	}
}

func searchInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"query":       stringSchema("The search query text."),
		"filter":      rawObjectSchema("The filter object to narrow results."),
		"sort":        rawObjectSchema("The sort object to order results."),
		"pageSize":    optionalPageSizeSchema,
		"startCursor": optionalStartCursorSchema,
	}, []string{"query"})
}

func createPageInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"parent":     pageParentSchema(),
		"parentId":   stringSchema("Simple parent page ID."),
		"title":      stringSchema("Simple page title used with parentId."),
		"properties": rawObjectSchema("Notion properties keyed by property name."),
		"children":   rawObjectArraySchema("Child blocks to create with the page."),
		"markdown":   stringSchema("Enhanced Markdown content for the page."),
		"icon":       rawObjectSchema("The Notion page icon object."),
		"cover":      rawObjectSchema("The Notion page cover object."),
		"template":   rawObjectSchema("The Notion template object."),
	})
}

func updatePageInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"pageId":        nonEmptyStringSchema("The page ID to update."),
		"title":         stringSchema("The new page title."),
		"properties":    rawObjectSchema("Notion properties keyed by property name."),
		"icon":          rawObjectSchema("The Notion page icon object."),
		"cover":         rawObjectSchema("The Notion page cover object."),
		"template":      rawObjectSchema("The Notion template object."),
		"in_trash":      booleanSchema("Whether the page is in the trash."),
		"is_locked":     booleanSchema("Whether the page is locked."),
		"erase_content": booleanSchema("Whether to erase page content."),
	}, []string{"pageId"})
}

func retrievePageMarkdownInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"pageId":            nonEmptyStringSchema("The page or block ID."),
		"includeTranscript": booleanSchema("Whether to include meeting note transcripts."),
	}, []string{"pageId"})
}

func listUsersInputSchema() map[string]any {
	return optionalObjectSchema(map[string]any{
		"pageSize":    optionalPageSizeSchema,
		"startCursor": optionalStartCursorSchema,
	})
}
