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

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

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

    fun hasAnyContent(): Boolean

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

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

/** Simple text */
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.isBlank()
}

// TODO: Remove
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 */
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 {
    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)
}

interface ContentCode : ContentComposite

/** Code blocks */
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)

}

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 */
interface ContentLink : ContentComposite

/** All links to classes, packages, etc. that have te be resolved */
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 */
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 */
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  */
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()

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

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

/** Tables */
data class ContentTable(
    val header: List<ContentGroup>,
    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 */
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. **/
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
 */
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 {
    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 })

    // TODO NOW?
    override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): ContentDivergentGroup = this
}

/** Instance of a divergent content */
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)

}

data class PlatformHintedContent(
    val inner: ContentNode,
    override val sourceSets: Set<DisplaySourceSet>
) : ContentComposite {
    override val children = 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>) =
        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)

}

interface Style
interface Kind

enum class ContentKind : Kind {

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

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

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

enum class TextStyle : Style {
    Bold, Italic, Strong, Strikethrough, Paragraph, Block, Span, Monospace, Indented, Cover, UnderCoverText, BreakableAfter, Breakable
}

enum class ContentStyle : Style {
    RowTitle, TabbedContent, WithExtraAttributes, RunnableSample, InDocumentationAnchor
}

object CommentTable : Style

object MultimoduleTable : Style

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