import {
  IAmfAnyTypes,
  IAmfArrayTypes,
  IAmfFileTypes,
  IAmfNodeTypes,
  IAmfPropertyTypes,
  IAmfRecursiveTypes,
  IAmfScalarTypes,
  IAmfTupleTypes,
  IAmfUnionTypes,
  IAmfXmlSerializationTypes,
} from '../AmfTypes.js'
import { IApiDocumentation, IApiDocumentSourceMaps } from './Api.js'
import { IApiDomainProperty } from './Base.js'

export type IShapeUnion =
  | IApiScalarShape
  | IApiNodeShape
  | IApiUnionShape
  | IApiFileShape
  | IApiSchemaShape
  | IApiAnyShape
  | IApiArrayShape
  | IApiTupleShape
  | IApiRecursiveShape

export interface IApiAssociationShape {
  /**
   * This is custom property not available in AMF and used with data associations.
   *
   * Whether the target entity should be embedded under the property name.
   * When false, this association is just an information that one entity depend on another.
   * When true, it changes the definition of the schema having this association to
   * add the target schema properties inline with this property.
   *
   * **When true**
   *
   * ```javascript
   * // generated schema for `address` association
   * {
   *  "name": "example value",
   *  "address": {
   *    "city": "example value",
   *    ...
   *  }
   * }
   * ```
   *
   * **When false**
   *
   * ```javascript
   * // generated schema for `address` association
   * {
   *  "name": "example value",
   *  "address": "the key of the referenced schema"
   * }
   * ```
   */
  linked?: boolean
  /**
   * When the association has multiple targets the union type should be
   * set to describe which union this is.
   *
   * Possible values are:
   *
   * - allOf - To validate against `allOf`, the given data must be valid against
   *   all of the given sub-schemas. When generating, it's a sum of all properties.
   * - anyOf - To validate against `anyOf`, the given data must be valid against
   *   any (one or more) of the given sub-schemas. When generation a schema, it takes first union schema.
   * - oneOf - To validate against `oneOf`, the given data must be valid against
   *   exactly one of the given sub-schemas. It behaves the same as `anyOf` when generating a schema
   * - not - The `not` keyword declares that an instance validates if it doesn’t
   *   validate against the given sub-schema. It has no use when generating a schema.
   *
   * @default anyOf
   */
  unionType?: 'allOf' | 'anyOf' | 'oneOf' | 'not'
}

export interface IApiDataExample extends IApiDomainProperty {
  name?: string
  displayName?: string
  description?: string
  /**
   * This is the "raw" property of AMF
   */
  value?: string
  structuredValue?: IApiDataNodeUnion
  strict: boolean
  mediaType?: string
  location?: string
  sourceMaps?: IApiDocumentSourceMaps
}

export function xmlSerializer(id: string): IApiXmlSerializer {
  return {
    id: `xml-serializer-${id}`,
    types: IAmfXmlSerializationTypes,
    customDomainProperties: [],
  }
}

export function shape(id: string): IApiShape {
  const result: IApiShape = {
    id,
    types: [],
    values: [],
    inherits: [],
    or: [],
    and: [],
    xone: [],
    customDomainProperties: [],
  }
  return result
}

export function anyShape(id: string): IApiAnyShape {
  const result: IApiAnyShape = {
    ...shape(id),
    types: IAmfAnyTypes,
    examples: [],
    xmlSerialization: xmlSerializer(id),
  }
  return result
}

export function nodeShape(id: string): IApiNodeShape {
  const result = anyShape(id)
  result.types = IAmfNodeTypes
  result.id = `node-shape-${id}`
  return {
    ...result,
    properties: [],
    dependencies: [],
    customShapeProperties: [],
    customShapePropertyDefinitions: [],
  }
}

export function scalarShape(id: string): IApiScalarShape {
  const result = anyShape(id)
  result.id = `scalar-shape-${id}`
  result.types = IAmfScalarTypes
  return result
}

export function unionShape(id: string): IApiUnionShape {
  const result = anyShape(id)
  result.id = `union-shape-${id}`
  result.types = IAmfUnionTypes
  return { ...result, anyOf: [] }
}

export function fileShape(id: string): IApiFileShape {
  const result = anyShape(id)
  result.id = `file-shape-${id}`
  result.types = IAmfFileTypes
  return result
}

export function arrayShape(id: string): IApiArrayShape {
  const result = anyShape(id)
  result.id = `array-shape-${id}`
  result.types = IAmfArrayTypes
  return result
}

export function tupleShape(id: string): IApiTupleShape {
  const result = anyShape(id)
  result.id = `tuple-shape-${id}`
  result.types = IAmfTupleTypes
  return { ...result, items: [] }
}

export function propertyShape(id: string): IApiPropertyShape {
  const result = anyShape(id)
  result.types = IAmfPropertyTypes
  return result
}

export function recursiveShape(id: string, fixedId?: string): IApiRecursiveShape {
  const result: IApiRecursiveShape = {
    ...shape(id),
    id: `recursive-${id}`,
    types: IAmfRecursiveTypes,
    fixPoint: fixedId || '',
  }
  return result
}

export interface IApiShape extends IApiDomainProperty {
  /**
   * Enum values for this shape.
   */
  values: IApiDataNodeUnion[]
  /**
   * The list of shapes that this shape inherits from
   */
  inherits: IShapeUnion[]
  /**
   * The `anyOf` union type.
   */
  or: IShapeUnion[]
  /**
   * The `allOf` union type.
   */
  and: IShapeUnion[]
  /**
   * The `oneOf` union type.
   */
  xone: IShapeUnion[]
  /**
   * The name of the shape in the system.
   */
  name?: string
  /**
   * The display name for the shape in the UI.
   */
  displayName?: string
  /**
   * The description of the shape.
   */
  description?: string
  /**
   * The default value encoded as a string
   */
  defaultValueStr?: string
  /**
   * The description of the default value of the shape.
   */
  defaultValue?: IApiDataNodeUnion
  /**
   * Whether the shape is deprecated.
   */
  deprecated?: boolean
  /**
   * Whether the shape is a read only shape (disallow writes to the value)
   */
  readOnly?: boolean
  /**
   * Whether the shape can only be written to (disallow reading the value)
   */
  writeOnly?: boolean
  not?: IShapeUnion
  /**
   * A label that appeared on a link.
   */
  linkLabel?: string
  sourceMaps?: IApiDocumentSourceMaps
}

/**
 * A property of an API operation.
 */
export interface IApiPropertyShape<T = IShapeUnion> extends IApiShape {
  /**
   * The path of the property. Essentially, it is link generated by the AMF library
   * where the fragment part is the name of the property.
   */
  path?: string
  /**
   * The parameter shape definition
   */
  range?: T
  /**
   * When > 0 then the parameter is required.
   */
  minCount?: number
  maxCount?: number
}

export interface IApiAnyShape extends IApiShape {
  documentation?: IApiDocumentation
  /**
   * Description of how the shape should be serialized as XML.
   */
  xmlSerialization: IApiXmlSerializer
  /**
   * Examples defined for the shape.
   */
  examples: IApiDataExample[]
}

/**
 * Describes an object shape.
 */
export interface IApiNodeShape extends IApiAnyShape {
  minProperties?: number
  maxProperties?: number
  closed?: boolean
  customShapeProperties: string[]
  customShapePropertyDefinitions: string[]
  discriminator?: string
  discriminatorValue?: string
  properties: IApiPropertyShape[]
  dependencies: string[]
}

export interface IApiXmlSerializer extends IApiDomainProperty {
  /**
   * Whether this property should be represented as an attribute of the parent shape.
   *
   * ```
   * <Person name="John Doe"></Person>
   * ```
   */
  attribute?: boolean
  /**
   * When the property is an array (has the `multiple` set to true in the data model)
   * then it tells that the list of values should be wrapped with a parent
   * element:
   *
   * ```
   * <Person>
   *  <Person fullName="John Doe"></Person>
   *  <Person fullName="Other Doe"></Person>
   * </Person>
   * ```
   *
   * Use this with the combination with `name` to describe the name of the wrapped
   * element
   *
   * ```
   * <people>
   *  <Person fullName="John Doe"></Person>
   * </people>
   * ```
   *
   * Note, this is mutually exclusive with `attribute`.
   */
  wrapped?: boolean
  /**
   * The name of the attribute or a wrapped property to use when serializing the property.
   *
   * Attribute name
   * ```xml
   * <Person fullName="John Doe"></Person>
   * ```
   *
   * Wrapped name
   * ```xml
   * <people>
   *  <Person name="John Doe"></Person>
   * </people>
   * ```
   */
  name?: string
  /**
   * The XML namespace to use.
   *
   * ```xml
   * <Person ns:name="John Doe"></Person>
   * ```
   */
  namespace?: string
  /**
   * Name prefix
   *
   * ```xml
   * <ns:Person name="John Doe"></ns:Person>
   * ```
   */
  prefix?: string
  sourceMaps?: IApiDocumentSourceMaps
}

export interface IApiScalarShape extends IApiAnyShape {
  dataType?: string
  pattern?: string
  minLength?: number
  maxLength?: number
  minimum?: number
  maximum?: number
  exclusiveMinimum?: boolean
  exclusiveMaximum?: boolean
  format?: string
  multipleOf?: number
}

export interface IApiFileShape extends IApiAnyShape {
  fileTypes?: string[]
  pattern?: string
  minLength?: number
  maxLength?: number
  minimum?: number
  maximum?: number
  exclusiveMinimum?: boolean
  exclusiveMaximum?: boolean
  format?: string
  multipleOf?: number
}

export interface IApiSchemaShape extends IApiAnyShape {
  mediaType?: string
  raw?: string
}

export interface IApiUnionShape extends IApiAnyShape {
  anyOf: IShapeUnion[]
}

export interface IApiDataArrangeShape extends IApiAnyShape {
  minItems?: number
  maxItems?: number
  uniqueItems?: boolean
}

export interface IApiArrayShape<T = IShapeUnion> extends IApiDataArrangeShape {
  items?: T
}

export interface IApiTupleShape<T = IShapeUnion> extends IApiDataArrangeShape {
  items: T[]
  additionalItems?: boolean
}

export interface IApiRecursiveShape extends IApiShape {
  fixPoint: string
}

export interface IApiDataNode extends IApiDomainProperty {
  name?: string
}

export interface IApiObjectNode extends IApiDataNode {
  properties: Record<string, IApiDataNodeUnion>
}

export interface IApiScalarNode extends IApiDataNode {
  value?: string
  dataType?: string
}

export interface IApiArrayNode extends IApiDataNode {
  members: IApiDataNodeUnion[]
}

export type IApiDataNodeUnion = IApiDataNode | IApiObjectNode | IApiScalarNode | IApiArrayNode
