/**
 * @prettier
 */
import React, { Component } from "react"
import PropTypes from "prop-types"
import { List } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils/url"
import classNames from "classnames"
import { getExtensions } from "../../../utils"

const braceOpen = "{"
const braceClose = "}"
const propClass = "property"

export default class ObjectModel extends Component {
  static propTypes = {
    schema: PropTypes.object.isRequired,
    getComponent: PropTypes.func.isRequired,
    getConfigs: PropTypes.func.isRequired,
    expanded: PropTypes.bool,
    onToggle: PropTypes.func,
    specSelectors: PropTypes.object.isRequired,
    name: PropTypes.string,
    displayName: PropTypes.string,
    isRef: PropTypes.bool,
    expandDepth: PropTypes.number,
    depth: PropTypes.number,
    specPath: ImPropTypes.list.isRequired,
    includeReadOnly: PropTypes.bool,
    includeWriteOnly: PropTypes.bool,
  }

  render() {
    let {
      schema,
      name,
      displayName,
      isRef,
      getComponent,
      getConfigs,
      depth,
      onToggle,
      expanded,
      specPath,
      ...otherProps
    } = this.props
    let { specSelectors, expandDepth, includeReadOnly, includeWriteOnly } =
      otherProps
    const { isOAS3 } = specSelectors
    const isEmbedded = depth > 2 || (depth === 2 && specPath.last() !== "items")

    if (!schema) {
      return null
    }

    const { showExtensions } = getConfigs()
    const extensions = showExtensions ? getExtensions(schema) : List()

    let description = schema.get("description")
    let properties = schema.get("properties")
    let additionalProperties = schema.get("additionalProperties")
    let title = schema.get("title") || displayName || name
    let requiredProperties = schema.get("required")
    let infoProperties = schema.filter(
      (v, key) =>
        ["maxProperties", "minProperties", "nullable", "example"].indexOf(
          key
        ) !== -1
    )
    let deprecated = schema.get("deprecated")
    let externalDocsUrl = schema.getIn(["externalDocs", "url"])
    let externalDocsDescription = schema.getIn(["externalDocs", "description"])

    const JumpToPath = getComponent("JumpToPath", true)
    const Markdown = getComponent("Markdown", true)
    const Model = getComponent("Model")
    const ModelCollapse = getComponent("ModelCollapse")
    const Property = getComponent("Property")
    const Link = getComponent("Link")
    const ModelExtensions = getComponent("ModelExtensions")

    const JumpToPathSection = () => {
      return (
        <span className="model-jump-to-path">
          <JumpToPath path={specPath} />
        </span>
      )
    }
    const collapsedContent = (
      <span>
        <span>{braceOpen}</span>...<span>{braceClose}</span>
        {isRef ? <JumpToPathSection /> : ""}
      </span>
    )

    const allOf = specSelectors.isOAS3() ? schema.get("allOf") : null
    const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
    const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
    const not = specSelectors.isOAS3() ? schema.get("not") : null

    const titleEl = title && (
      <span className="model-title">
        {isRef && schema.get("$$ref") && (
          <span
            className={classNames("model-hint", {
              "model-hint--embedded": isEmbedded,
            })}
          >
            {schema.get("$$ref")}
          </span>
        )}
        <span className="model-title__text">{title}</span>
      </span>
    )

    return (
      <span className="model">
        <ModelCollapse
          modelName={name}
          title={titleEl}
          onToggle={onToggle}
          expanded={expanded ? true : depth <= expandDepth}
          collapsedContent={collapsedContent}
        >
          <span className="brace-open object">{braceOpen}</span>
          {!isRef ? null : <JumpToPathSection />}
          <span className="inner-object">
            {
              <table className="model">
                <tbody>
                  {!description ? null : (
                    <tr className="description">
                      <td>description:</td>
                      <td>
                        <Markdown source={description} />
                      </td>
                    </tr>
                  )}
                  {externalDocsUrl && (
                    <tr className={"external-docs"}>
                      <td>externalDocs:</td>
                      <td>
                        <Link
                          target="_blank"
                          href={sanitizeUrl(externalDocsUrl)}
                        >
                          {externalDocsDescription || externalDocsUrl}
                        </Link>
                      </td>
                    </tr>
                  )}
                  {!deprecated ? null : (
                    <tr className={"property"}>
                      <td>deprecated:</td>
                      <td>true</td>
                    </tr>
                  )}
                  {!(properties && properties.size)
                    ? null
                    : properties
                        .entrySeq()
                        .filter(([, value]) => {
                          return (
                            (!value.get("readOnly") || includeReadOnly) &&
                            (!value.get("writeOnly") || includeWriteOnly)
                          )
                        })
                        .map(([key, value]) => {
                          let isDeprecated = isOAS3() && value.get("deprecated")
                          let isRequired =
                            List.isList(requiredProperties) &&
                            requiredProperties.contains(key)

                          let classNames = ["property-row"]

                          if (isDeprecated) {
                            classNames.push("deprecated")
                          }

                          if (isRequired) {
                            classNames.push("required")
                          }

                          return (
                            <tr key={key} className={classNames.join(" ")}>
                              <td>
                                {key}
                                {isRequired && <span className="star">*</span>}
                              </td>
                              <td>
                                <Model
                                  key={`object-${name}-${key}_${value}`}
                                  {...otherProps}
                                  required={isRequired}
                                  getComponent={getComponent}
                                  specPath={specPath.push("properties", key)}
                                  getConfigs={getConfigs}
                                  schema={value}
                                  depth={depth + 1}
                                />
                              </td>
                            </tr>
                          )
                        })
                        .toArray()}
                  {extensions.size === 0 ? null : (
                    <>
                      <tr>
                        <td>&nbsp;</td>
                      </tr>
                      <ModelExtensions
                        extensions={extensions}
                        propClass="extension"
                      />
                    </>
                  )}
                  {!additionalProperties ||
                  !additionalProperties.size ? null : (
                    <tr>
                      <td>{"< * >:"}</td>
                      <td>
                        <Model
                          {...otherProps}
                          required={false}
                          getComponent={getComponent}
                          specPath={specPath.push("additionalProperties")}
                          getConfigs={getConfigs}
                          schema={additionalProperties}
                          depth={depth + 1}
                        />
                      </td>
                    </tr>
                  )}
                  {!allOf ? null : (
                    <tr>
                      <td>{"allOf ->"}</td>
                      <td>
                        {allOf.map((schema, k) => {
                          return (
                            <div key={k}>
                              <Model
                                {...otherProps}
                                required={false}
                                getComponent={getComponent}
                                specPath={specPath.push("allOf", k)}
                                getConfigs={getConfigs}
                                schema={schema}
                                depth={depth + 1}
                              />
                            </div>
                          )
                        })}
                      </td>
                    </tr>
                  )}
                  {!anyOf ? null : (
                    <tr>
                      <td>{"anyOf ->"}</td>
                      <td>
                        {anyOf.map((schema, k) => {
                          return (
                            <div key={k}>
                              <Model
                                {...otherProps}
                                required={false}
                                getComponent={getComponent}
                                specPath={specPath.push("anyOf", k)}
                                getConfigs={getConfigs}
                                schema={schema}
                                depth={depth + 1}
                              />
                            </div>
                          )
                        })}
                      </td>
                    </tr>
                  )}
                  {!oneOf ? null : (
                    <tr>
                      <td>{"oneOf ->"}</td>
                      <td>
                        {oneOf.map((schema, k) => {
                          return (
                            <div key={k}>
                              <Model
                                {...otherProps}
                                required={false}
                                getComponent={getComponent}
                                specPath={specPath.push("oneOf", k)}
                                getConfigs={getConfigs}
                                schema={schema}
                                depth={depth + 1}
                              />
                            </div>
                          )
                        })}
                      </td>
                    </tr>
                  )}
                  {!not ? null : (
                    <tr>
                      <td>{"not ->"}</td>
                      <td>
                        <div>
                          <Model
                            {...otherProps}
                            required={false}
                            getComponent={getComponent}
                            specPath={specPath.push("not")}
                            getConfigs={getConfigs}
                            schema={not}
                            depth={depth + 1}
                          />
                        </div>
                      </td>
                    </tr>
                  )}
                </tbody>
              </table>
            }
          </span>
          <span className="brace-close">{braceClose}</span>
        </ModelCollapse>
        {infoProperties.size
          ? infoProperties
              .entrySeq()
              .map(([key, v]) => (
                <Property
                  key={`${key}-${v}`}
                  propKey={key}
                  propVal={v}
                  propClass={propClass}
                />
              ))
          : null}
      </span>
    )
  }
}
