/*
 * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package org.jetbrains.dokka.pages

import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.WithChildren
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.model.properties.WithExtraProperties

public data class DCI(val dri: Set<DRI>, val kind: Kind) {
    override fun toString(): String = "$dri[$kind]"
}

public interface ContentNode : WithExtraProperties<ContentNode>, WithChildren<ContentNode> {
    public val dci: DCI
    public val sourceSets: Set<DisplaySourceSet>
    public val style: Set<Style>

    public fun hasAnyContent(): Boolean

    public fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentNode

    override val children: List<ContentNode>
        get() = emptyList()
}

/** Simple text */
public data class ContentText(
    val text: String,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentNode {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentText = copy(extra = newExtras)
    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentText = copy(sourceSets = sourceSets)
    override fun hasAnyContent(): Boolean = text.isNotBlank()
}

public data class ContentBreakLine(
    override val sourceSets: Set<DisplaySourceSet>,
    override val dci: DCI = DCI(emptySet(), ContentKind.Empty),
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentNode {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentBreakLine = copy(extra = newExtras)
    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentBreakLine = copy(sourceSets = sourceSets)
    override fun hasAnyContent(): Boolean = true
}

/** Headers */
public data class ContentHeader(
    override val children: List<ContentNode>,
    val level: Int,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentComposite {
    public constructor(level: Int, c: ContentComposite) : this(c.children, level, c.dci, c.sourceSets, c.style, c.extra)

    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentHeader = copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentHeader =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentHeader =
        copy(sourceSets = sourceSets)
}

public interface ContentCode : ContentComposite

/** Code blocks */
public data class ContentCodeBlock(
    override val children: List<ContentNode>,
    val language: String,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentCode {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentCodeBlock = copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentCodeBlock =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentCodeBlock =
        copy(sourceSets = sourceSets)

}

public data class ContentCodeInline(
    override val children: List<ContentNode>,
    val language: String,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentCode {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentCodeInline = copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentCodeInline =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentCodeInline =
        copy(sourceSets = sourceSets)

}

/** Union type replacement */
public interface ContentLink : ContentComposite

/** All links to classes, packages, etc. that have te be resolved */
public data class ContentDRILink(
    override val children: List<ContentNode>,
    val address: DRI,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentLink {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentDRILink = copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentDRILink =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentDRILink =
        copy(sourceSets = sourceSets)

}

/** All links that do not need to be resolved */
public data class ContentResolvedLink(
    override val children: List<ContentNode>,
    val address: String,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentLink {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentResolvedLink =
        copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentResolvedLink =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentResolvedLink =
        copy(sourceSets = sourceSets)
}

/** Embedded resources like images */
public data class ContentEmbeddedResource(
    override val children: List<ContentNode> = emptyList(),
    val address: String,
    val altText: String?,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentLink {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentEmbeddedResource =
        copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentEmbeddedResource =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentEmbeddedResource =
        copy(sourceSets = sourceSets)
}

/** Logical grouping of [ContentNode]s  */
public interface ContentComposite : ContentNode {
    override val children: List<ContentNode> // overwrite to make it abstract once again

    override val sourceSets: Set<DisplaySourceSet> get() = children.flatMap { it.sourceSets }.toSet()

    public fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentComposite

    override fun hasAnyContent(): Boolean = children.any { it.hasAnyContent() }
}

/** Tables */
public data class ContentTable(
    val header: List<ContentGroup>,
    val caption: ContentGroup? = null,
    override val children: List<ContentGroup>,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentComposite {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentTable = copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentTable =
        copy(children = children.map(transformer).map { it as ContentGroup })

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentTable =
        copy(sourceSets = sourceSets)

}

/** Lists */
public data class ContentList(
    override val children: List<ContentNode>,
    val ordered: Boolean,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentComposite {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentList = copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentList =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentList =
        copy(sourceSets = sourceSets)
}

/** Default group, eg. for blocks of Functions, Properties, etc. **/
public data class ContentGroup(
    override val children: List<ContentNode>,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentComposite {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentGroup = copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentGroup =
        copy(children = children.map(transformer))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentGroup =
        copy(sourceSets = sourceSets)
}

/**
 * @property groupID is used for finding and copying [ContentDivergentInstance]s when merging [ContentPage]s
 */
public data class ContentDivergentGroup(
    override val children: List<ContentDivergentInstance>,
    override val dci: DCI,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode>,
    val groupID: GroupID,
    val implicitlySourceSetHinted: Boolean = true
) : ContentComposite {
    public data class GroupID(val name: String)

    override val sourceSets: Set<DisplaySourceSet>
        get() = children.flatMap { it.sourceSets }.distinct().toSet()

    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentDivergentGroup =
        copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentDivergentGroup =
        copy(children = children.map(transformer).map { it as ContentDivergentInstance })

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentDivergentGroup = this
}

/** Instance of a divergent content */
public data class ContentDivergentInstance(
    val before: ContentNode?,
    val divergent: ContentNode,
    val after: ContentNode?,
    override val dci: DCI,
    override val sourceSets: Set<DisplaySourceSet>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentComposite {
    override val children: List<ContentNode>
        get() = listOfNotNull(before, divergent, after)

    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentDivergentInstance =
        copy(extra = newExtras)

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): ContentDivergentInstance =
        copy(
            before = before?.let(transformer),
            divergent = divergent.let(transformer),
            after = after?.let(transformer)
        )

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentDivergentInstance =
        copy(sourceSets = sourceSets)

}

public data class PlatformHintedContent(
    val inner: ContentNode,
    override val sourceSets: Set<DisplaySourceSet>
) : ContentComposite {
    override val children: List<ContentNode> = listOf(inner)

    override val dci: DCI
        get() = inner.dci

    override val extra: PropertyContainer<ContentNode>
        get() = inner.extra

    override val style: Set<Style>
        get() = inner.style

    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentNode =
        throw UnsupportedOperationException("This method should not be called on this PlatformHintedContent")

    override fun transformChildren(transformer: (ContentNode) -> ContentNode): PlatformHintedContent =
        copy(inner = transformer(inner))

    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): PlatformHintedContent =
        copy(sourceSets = sourceSets)

}

public interface Style
public interface Kind

/**
 * [ContentKind] represents a grouping of content of one kind that can can be rendered
 * as part of a composite page (one tab/block within a class's page, for instance).
 */
public enum class ContentKind : Kind {

    /**
     * Marks all sorts of signatures. Can contain sub-kinds marked as [SymbolContentKind]
     *
     * Some examples:
     * - primary constructor: `data class CoroutineName(name: String) : AbstractCoroutineContextElement`
     * - constructor: `fun CoroutineName(name: String)`
     * - function: `open override fun toString(): String`
     * - property: `val name: String`
     */
    Symbol,

    Comment, Constructors, Functions, Parameters, Properties, Classlikes, Packages, Sample, Main, BriefComment,
    Empty, Source, TypeAliases, Cover, Inheritors, SourceSetDependentHint, Extensions, Annotations,

    /**
     * Deprecation details block with related information such as message/replaceWith/level.
     */
    Deprecation;

    public companion object {
        private val platformTagged =
            setOf(
                Constructors,
                Functions,
                Properties,
                Classlikes,
                Packages,
                Source,
                TypeAliases,
                Inheritors,
                Extensions
            )

        public fun shouldBePlatformTagged(kind: Kind): Boolean = kind in platformTagged
    }
}

/**
 * Content kind for [ContentKind.Symbol] content, which is essentially about signatures
 */
public enum class SymbolContentKind : Kind {
    /**
     * Marks constructor/function parameters, everything in-between parentheses.
     *
     * For function `fun foo(bar: String, baz: Int, qux: Boolean)`,
     * the parameters would be the whole of `bar: String, baz: Int, qux: Boolean`
     */
    Parameters,

    /**
     * Marks a single parameter in a function. Most likely to be a child of [Parameters].
     *
     * In function `fun foo(bar: String, baz: Int, qux: Boolean)` there would be 3 [Parameter] instances:
     * - `bar: String, `
     * - `baz: Int, `
     * - `qux: Boolean`
     */
    Parameter,
}

public enum class TokenStyle : Style {
    Keyword, Punctuation, Function, Operator, Annotation, Number, String, Boolean, Constant
}

public enum class TextStyle : Style {
    Bold, Italic, Strong, Strikethrough, Paragraph,
    Block, Span, Monospace, Indented, Cover, UnderCoverText, BreakableAfter, Breakable, InlineComment, Quotation,
    FloatingRight, Var, Underlined
}

public enum class ContentStyle : Style {
    RowTitle,
    /**
     * The style is used only for HTML. It is applied only for [ContentGroup].
     * Creating and rendering tabs is a part of a renderer.
     */
    TabbedContent,

    WithExtraAttributes, RunnableSample, InDocumentationAnchor, Caption,
    Wrapped, Indented, KDocTag, Footnote
}

public enum class ListStyle : Style {
    /**
     * Represents a list of groups of [DescriptionTerm] and [DescriptionDetails].
     * Common uses for this element are to implement a glossary or to display
     * metadata (a list of key-value pairs). Formatting example: see `<dl>` html tag.
     */
    DescriptionList,

    /**
     * If used within [DescriptionList] context, specifies a term in a description
     * or definition list, usually followed by [DescriptionDetails] for one or more
     * terms. Formatting example: see `<dt>` html tag
     */
    DescriptionTerm,

    /**
     * If used within [DescriptionList] context, provides the definition or other
     * related text associated with [DescriptionTerm]. Formatting example: see `<dd>` html tag
     */
    DescriptionDetails
}

public object CommentTable : Style

public object MultimoduleTable : Style

public fun ContentNode.hasStyle(style: Style): Boolean = this.style.contains(style)