import type {
  DecoratorContext,
  DecoratorValidatorCallbacks,
  Model,
  Namespace,
  Operation,
  Type,
} from "@typespec/compiler";

export interface AdditionalInfo {
  readonly [key: string]: unknown;
  readonly title?: string;
  readonly summary?: string;
  readonly version?: string;
  readonly termsOfService?: string;
  readonly contact?: Contact;
  readonly license?: License;
}

export interface TagMetadataWithName {
  readonly [key: string]: unknown;
  readonly name: string;
  readonly description?: string;
  readonly externalDocs?: ExternalDocs;
  readonly parent?: string;
  readonly summary?: string;
  readonly kind?: string;
}

export interface TagMetadata {
  readonly [key: string]: unknown;
  readonly description?: string;
  readonly externalDocs?: ExternalDocs;
  readonly parent?: string;
  readonly summary?: string;
  readonly kind?: string;
}

export interface Contact {
  readonly [key: string]: unknown;
  readonly name?: string;
  readonly url?: string;
  readonly email?: string;
}

export interface License {
  readonly [key: string]: unknown;
  readonly name: string;
  readonly url?: string;
}

export interface ExternalDocs {
  readonly [key: string]: unknown;
  readonly url: string;
  readonly description?: string;
}

/**
 * Specify the OpenAPI `operationId` property for this operation.
 *
 * @param operationId Operation id value.
 * @example
 * ```typespec
 * @operationId("download")
 * op read(): string;
 * ```
 */
export type OperationIdDecorator = (
  context: DecoratorContext,
  target: Operation,
  operationId: string,
) => DecoratorValidatorCallbacks | void;

/**
 * Attach some custom data to the OpenAPI element generated from this type.
 *
 * @param key Extension key.
 * @param value Extension value.
 * @example
 * ```typespec
 * @extension("x-custom", "My value")
 * @extension("x-pageable", #{nextLink: "x-next-link"})
 * op read(): string;
 * ```
 */
export type ExtensionDecorator = (
  context: DecoratorContext,
  target: Type,
  key: string,
  value: unknown,
) => DecoratorValidatorCallbacks | void;

/**
 * Specify that this model is to be treated as the OpenAPI `default` response.
 * This differs from the compiler built-in `@error` decorator as this does not necessarily represent an error.
 *
 * @example
 * ```typespec
 * @defaultResponse
 * model PetStoreResponse is object;
 *
 * op listPets(): Pet[] | PetStoreResponse;
 * ```
 */
export type DefaultResponseDecorator = (
  context: DecoratorContext,
  target: Model,
) => DecoratorValidatorCallbacks | void;

/**
 * Specify the OpenAPI `externalDocs` property for this type.
 *
 * @param url Url to the docs
 * @param description Description of the docs
 * @example
 * ```typespec
 * @externalDocs("https://example.com/detailed.md", "Detailed information on how to use this operation")
 * op listPets(): Pet[];
 * ```
 */
export type ExternalDocsDecorator = (
  context: DecoratorContext,
  target: Type,
  url: string,
  description?: string,
) => DecoratorValidatorCallbacks | void;

/**
 * Specify OpenAPI additional information.
 * The service `title` is already specified using `@service`.
 *
 * @param additionalInfo Additional information
 */
export type InfoDecorator = (
  context: DecoratorContext,
  target: Namespace,
  additionalInfo: AdditionalInfo,
) => DecoratorValidatorCallbacks | void;

/**
 * Specify OpenAPI tag metadata. Can be used in two forms:
 * - Inline form: specify a single tag by name with optional metadata.
 * - Array form: specify an ordered list of tags with their metadata in a single decorator call.
 *
 * @param name Tag name (inline form) or array of tags with metadata (array form).
 * @param tagMetadata Additional information for the tag. Only used in inline form.
 * @example Inline form
 * ```typespec
 * @service()
 * @tagMetadata("Tag Name", #{description: "Tag description", externalDocs: #{url: "https://example.com", description: "More info.", `x-custom`: "string"}, `x-custom`: "string"})
 * @tagMetadata("Child Tag", #{description: "Child tag description", parent: "Tag Name"})
 * namespace PetStore {}
 * ```
 * @example Array form (preserves explicit tag order)
 * ```typespec
 * @service()
 * @tagMetadata(#[
 *   #{ name: "First Tag", description: "First tag description" },
 *   #{ name: "Second Tag", description: "Second tag description" },
 * ])
 * namespace PetStore {}
 * ```
 */
export type TagMetadataDecorator = (
  context: DecoratorContext,
  target: Namespace,
  name: string | readonly TagMetadataWithName[],
  tagMetadata?: TagMetadata,
) => DecoratorValidatorCallbacks | void;

export type TypeSpecOpenAPIDecorators = {
  operationId: OperationIdDecorator;
  extension: ExtensionDecorator;
  defaultResponse: DefaultResponseDecorator;
  externalDocs: ExternalDocsDecorator;
  info: InfoDecorator;
  tagMetadata: TagMetadataDecorator;
};
