import { AmfNamespace as ns } from '../definitions/Namespace.js'
import { IApiArrayNode, IApiDataNode, IApiObjectNode, IApiScalarNode } from '../definitions/Shapes.js'
import { readTypedValue } from '../Utils.js'

type ReturnType = Record<string, unknown> | string | null | number | boolean | undefined

/**
 * Base class for all schema generators based on AMF's DataNode which includes AMF's examples.
 */
export class DataNodeBase {
  /**
   * @param node The AMF data node to turn into a schema.
   * @returns Undefined when passed non-DataNode domain element.
   */
  processNode(node: IApiDataNode): ReturnType | ReturnType[] {
    const { types } = node
    if (types.includes(ns.aml.vocabularies.data.Scalar)) {
      return this.processScalarNode(node as IApiScalarNode)
    }
    if (types.includes(ns.aml.vocabularies.data.Array)) {
      return this.processArrayNode(node as IApiArrayNode)
    }
    if (types.includes(ns.aml.vocabularies.data.Object)) {
      return this.processObjectNode(node as IApiObjectNode)
    }
    return undefined
  }

  /**
   * @param scalar The scalar node to process.
   * @returns The scalar value.
   */
  processScalarNode(scalar: IApiScalarNode): string | null | number | boolean | undefined {
    return readTypedValue(scalar.value, scalar.dataType)
  }

  /**
   * @param array The array node to process.
   * @returns Array value.
   */
  processArrayNode(array: IApiArrayNode): ReturnType[] {
    const container: ReturnType[] = []
    array.members.forEach((member) => {
      const result = this.processNode(member) as ReturnType
      if (result !== undefined) {
        container.push(result)
      }
    })
    return container
  }

  /**
   * @param object The object node to process.
   * @returns Object value.
   */
  processObjectNode(object: IApiObjectNode): Record<string, unknown> {
    const container: Record<string, unknown> = {}
    const { properties } = object
    Object.keys(properties).forEach((key) => {
      const definition = properties[key]
      const result = this.processNode(definition)
      if (typeof result !== 'undefined') {
        const name = this.normalizePropertyName(key)
        container[name] = result
      }
    })
    return container
  }

  /**
   * Normalizes a property name. It decodes URL encoded values.
   * @param name The property name to normalize
   */
  normalizePropertyName(name: string): string {
    let result = name
    try {
      result = decodeURIComponent(result)
    } catch {
      // ...
    }
    return result
  }
}
