/*
 * Copyright 2024-2026 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.cloud.ai.graph;

import com.alibaba.cloud.ai.graph.utils.TypeRef;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;

public interface HasMetadata<B extends HasMetadata.Builder<B>> {

	Optional<Map<String, Object>> metadata();

	/**
	 * return metadata value for key
	 * @param key given metadata key
	 * @return metadata value for key if any
	 */
	Optional<Object> metadata(String key);

	/**
	 * Prefix used to indicate an interrupt node.
	 */
	String INTERRUPT_PREFIX = "__NODE_INTERRUPT__";

	/**
	 * Returns a type-safe metadata value for the given key.
	 * <p>
	 * This method retrieves the metadata object and attempts to cast it to the specified
	 * type.
	 * @param <T> the type of the metadata value
	 * @param key the metadata key
	 * @param typeRef a {@link TypeRef} representing the desired type of the value
	 * @return an {@link Optional} containing the metadata value cast to the specified
	 * type, or an empty {@link Optional} if the key is not found or the value cannot be
	 * cast.
	 */
	default <T> Optional<T> metadata(String key, TypeRef<T> typeRef) {
		return metadata(key).flatMap(typeRef::cast);
	}

	/**
	 * return metadata value for key
	 * @param key given metadata key
	 * @return metadata value for key if any
	 * @deprecated use {@link #metadata(String)} instead
	 */
	@Deprecated(forRemoval = true)
	default Optional<Object> getMetadata(String key) {
		return metadata(key);
	};

	default <T> Optional<T> getMetadataAndRemove(String key, TypeRef<T> typeRef) {
		Optional<T> value = metadata(key, typeRef);
		if (value.isPresent()) {
			metadata().ifPresent(m -> m.remove(key));
		}
		return value;
	};

	/**
	 * Formats a node ID by prefixing it with the interrupt prefix. The formatted node ID
	 * follows the pattern "__INTERRUPT__(nodeId)".
	 * @param nodeId the node ID to format, cannot be null
	 * @return the formatted node ID string
	 * @throws NullPointerException if nodeId is null
	 */
	static String formatNodeId(String nodeId) {
		return format("%s(%s)", INTERRUPT_PREFIX, requireNonNull(nodeId, "nodeId cannot be null!"));
	}

	class Builder<B extends Builder<B>> {

		private Map<String, Object> metadata;

		public Map<String, Object> metadata() {
			return ofNullable(metadata).map(Map::copyOf).orElseGet(java.util.Map::of);
		}

		protected Builder() {
		}

		protected Builder(Map<String, Object> metadata) {
			if (metadata != null && !metadata.isEmpty()) {
				this.metadata = new HashMap<>(metadata);
			}
		}

		@SuppressWarnings("unchecked")
		public B addMetadata(String key, Object value) {
			if (metadata == null) {
				// Lazy initialization of metadata map
				metadata = new HashMap<>();
			}

			metadata.put(key, value);

			return (B) this;
		};

	}

}
