package org.allaymc.api.utils.identifier;

import org.allaymc.api.plugin.Plugin;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;

import static org.allaymc.api.utils.AllayStringUtils.fastTwoPartSplit;
import static org.allaymc.api.utils.identifier.IdentifierUtils.isNamespaceValid;
import static org.allaymc.api.utils.identifier.IdentifierUtils.isPathValid;

/**
 * Represents an identifier consisting of a namespace and a path.
 * <p>
 * This class is used to uniquely identify resources or components within a specific namespace.
 * It ensures that both the namespace and path conform to specific validation rules.
 * </p>
 * <p>
 * The identifier can be constructed using various constructors, including:
 * <ul>
 *     <li>A namespace and path directly.</li>
 *     <li>A plugin instance and a path, where the namespace is derived from the plugin.</li>
 *     <li>A single string in the format "namespace:path".</li>
 * </ul>
 * </p>
 * <p>
 * Validation rules:
 * <ul>
 *     <li>The namespace must only contain characters matching [A-Za-z0-9_.-].</li>
 *     <li>The path must only contain characters matching [A-Za-z0-9/._-].</li>
 * </ul>
 * </p>
 * <p>
 * This class is immutable and provides methods for equality checks, cloning, and string representation.
 * </p>
 * <p>
 * Example usage:
 * <pre>
 *     Identifier id = new Identifier("minecraft", "cake");
 *     System.out.println(id); // Outputs: minecraft:cake
 * </pre>
 * </p>
 *
 * @author daoge_cmd
 */
public record Identifier(String namespace, String path) implements Cloneable {

    /**
     * The spectator used between namespace and path.
     */
    public static final String NAMESPACE_SEPARATOR = ":";
    /**
     * The default namespace.
     */
    public static final String DEFAULT_NAMESPACE = "minecraft";

    /**
     * Constructs an Identifier using a plugin instance and a path.
     * The namespace is derived from the plugin's name.
     *
     * @param plugin the plugin instance
     * @param path   the path of the identifier
     */
    public Identifier(Plugin plugin, String path) {
        this(plugin.getPluginContainer().descriptor().getName(), path);
    }

    /**
     * Constructs an Identifier using an array of strings.
     * The first element is the namespace, and the second is the path.
     *
     * @param id an array containing the namespace and path
     */
    public Identifier(String[] id) {
        this(id[0], id[1]);
    }

    /**
     * Constructs an Identifier from a single string in the format "namespace:path".
     * If the namespace is missing, the default namespace is used.
     *
     * @param id the identifier string
     */
    public Identifier(String id) {
        this(fastTwoPartSplit(id, NAMESPACE_SEPARATOR, DEFAULT_NAMESPACE));
    }

    /**
     * Constructs an Identifier using a namespace and a path.
     * Validates the namespace and path against specific rules.
     *
     * @param namespace the namespace of the identifier
     * @param path      the path of the identifier
     *
     * @throws InvalidIdentifierException if the namespace or path is invalid
     */
    public Identifier(String namespace, String path) {
        this.namespace = namespace.isEmpty() ? DEFAULT_NAMESPACE : namespace;
        this.path = path;
        if (!isNamespaceValid(this.namespace)) {
            throw new InvalidIdentifierException("Non [A-Za-z0-9_.-] character in namespace of location: " + this.namespace + NAMESPACE_SEPARATOR + this.path);
        }
        if (!isPathValid(this.path)) {
            throw new InvalidIdentifierException("Non [A-Za-z0-9/._-] character in path of location: " + this.namespace + NAMESPACE_SEPARATOR + this.path);
        }
    }

    /**
     * Returns the string representation of the identifier in the format "namespace:path".
     *
     * @return the string representation of the identifier
     */
    @Override
    public String toString() {
        return this.namespace + NAMESPACE_SEPARATOR + this.path;
    }

    /**
     * Checks if this identifier is equal to another object.
     * Two identifiers are equal if their namespace and path are equal.
     *
     * @param o the object to compare with
     *
     * @return true if the objects are equal, false otherwise
     */
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Identifier(String namespace1, String path1))) {
            return false;
        }

        return Objects.equals(path, path1) && Objects.equals(namespace, namespace1);
    }

    /**
     * Computes the hash code for this identifier based on its namespace and path.
     *
     * @return the hash code of the identifier
     */
    @Override
    public int hashCode() {
        return Objects.hash(namespace, path);
    }

    /**
     * Creates and returns a copy of this identifier.
     *
     * @return a clone of this identifier
     */
    @SuppressWarnings("MethodDoesntCallSuperMethod")
    @Override
    public Identifier clone() {
        return new Identifier(namespace, path);
    }

    /**
     * Annotation used to mark the identifier of a component in the component system
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Component {
    }
}
