package org.jetbrains.dokka.pages

import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.SourceSetData
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> {
    val dci: DCI
    val sourceSets: Set<SourceSetData>
    val style: Set<Style>

    fun hasAnyContent(): Boolean
}

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

    override fun hasAnyContent(): Boolean = !text.isBlank()
}

// TODO: Remove
data class ContentBreakLine(
    override val sourceSets: Set<SourceSetData>,
    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>): ContentNode = copy(extra = newExtras)

    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<SourceSetData>,
    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)
}

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

/** 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<SourceSetData>,
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentLink {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentDRILink = copy(extra = newExtras)
}

/** 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<SourceSetData>,
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentLink {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentResolvedLink =
        copy(extra = newExtras)
}

/** 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<SourceSetData>,
    override val style: Set<Style> = emptySet(),
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentLink {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentEmbeddedResource =
        copy(extra = newExtras)
}

/** Logical grouping of [ContentNode]s  */
interface ContentComposite : ContentNode {
    val children: List<ContentNode>

    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<SourceSetData>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentComposite {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentTable = copy(extra = newExtras)
}

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

/** 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<SourceSetData>,
    override val style: Set<Style>,
    override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) : ContentComposite {
    override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentGroup = copy(extra = newExtras)
}

/**
 * @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<SourceSetData>
        get() = children.flatMap { it.sourceSets }.distinct().toSet()

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

/** 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<SourceSetData>,
    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)
}

data class PlatformHintedContent(
    val inner: ContentNode,
    override val sourceSets: Set<SourceSetData>
) : 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")
}

interface Style
interface Kind

enum class ContentKind : Kind {

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

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

        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
}

object CommentTable: Style

object MultimoduleTable: Style

fun ContentNode.dfs(predicate: (ContentNode) -> Boolean): ContentNode? = if (predicate(this)) {
    this
} else {
    if (this is ContentComposite) {
        this.children.asSequence().mapNotNull { it.dfs(predicate) }.firstOrNull()
    } else {
        null
    }
}

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