Refactor OutputBuilder to decouple data extraction from structure
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
index dac5b5c..cfa3e63 100644
--- a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
@@ -67,7 +67,7 @@
 
 interface JavaLayoutHtmlTemplateService {
     fun composePage(
-            nodes: List<DocumentationNode>,
+            page: JavaLayoutHtmlFormatOutputBuilder.Page,
             tagConsumer: TagConsumer<Appendable>,
             headContent: HEAD.() -> Unit,
             bodyContent: BODY.() -> Unit
@@ -75,7 +75,7 @@
 
     class Default : JavaLayoutHtmlTemplateService {
         override fun composePage(
-                nodes: List<DocumentationNode>,
+                page: JavaLayoutHtmlFormatOutputBuilder.Page,
                 tagConsumer: TagConsumer<Appendable>,
                 headContent: HEAD.() -> Unit,
                 bodyContent: BODY.() -> Unit
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
index 1116c6c..9ea5e9e 100644
--- a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
@@ -12,19 +12,90 @@
 
 
 open class JavaLayoutHtmlFormatOutputBuilder(
-        val output: Appendable,
-        val languageService: LanguageService,
-        val uriProvider: JavaLayoutHtmlUriProvider,
-        val templateService: JavaLayoutHtmlTemplateService,
-        val logger: DokkaLogger,
-        val uri: URI
+    val output: Appendable,
+    val languageService: LanguageService,
+    val uriProvider: JavaLayoutHtmlUriProvider,
+    val templateService: JavaLayoutHtmlTemplateService,
+    val logger: DokkaLogger,
+    val uri: URI
 ) {
 
     val htmlConsumer = output.appendHTML()
 
-    val contentToHtmlBuilder = ContentToHtmlBuilder(uriProvider, uri)
 
-    open fun <T> FlowContent.summaryNodeGroup(nodes: Iterable<T>, header: String, headerAsRow: Boolean = false, row: TBODY.(T) -> Unit) {
+    private fun FlowContent.hN(
+        level: Int,
+        classes: String? = null,
+        block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit
+    ) {
+        when (level) {
+            1 -> h1(classes, block)
+            2 -> h2(classes, block)
+            3 -> h3(classes, block)
+            4 -> h4(classes, block)
+            5 -> h5(classes, block)
+            6 -> h6(classes, block)
+        }
+    }
+
+    fun FlowContent.metaMarkup(content: List<ContentNode>): Unit = content.forEach { metaMarkup(it) }
+    fun FlowContent.metaMarkup(content: ContentNode) {
+        when (content) {
+            is ContentText -> +content.text
+            is ContentSymbol -> span("symbol") { +content.text }
+            is ContentKeyword -> span("keyword") { +content.text }
+            is ContentIdentifier -> span("identifier") {
+                content.signature?.let { id = it }
+                +content.text
+            }
+
+            is ContentHeading -> hN(level = content.level) { metaMarkup(content.children) }
+
+            is ContentEntity -> +content.text
+
+            is ContentStrong -> strong { metaMarkup(content.children) }
+            is ContentStrikethrough -> del { metaMarkup(content.children) }
+            is ContentEmphasis -> em { metaMarkup(content.children) }
+
+            is ContentOrderedList -> ol { metaMarkup(content.children) }
+            is ContentUnorderedList -> ul { metaMarkup(content.children) }
+            is ContentListItem -> consumer.li {
+                (content.children.singleOrNull() as? ContentParagraph)
+                    ?.let { paragraph -> metaMarkup(paragraph.children) }
+                        ?: metaMarkup(content.children)
+            }
+
+
+            is ContentCode -> pre { code { metaMarkup(content.children) } }
+            is ContentBlockSampleCode -> pre { code {} }
+            is ContentBlockCode -> pre { code {} }
+
+
+            is ContentNonBreakingSpace -> +nbsp
+            is ContentSoftLineBreak, is ContentIndentedSoftLineBreak -> {
+            }
+
+            is ContentParagraph -> p { metaMarkup(content.children) }
+
+            is ContentNodeLink -> {
+                val href = content.node?.let { uriProvider.linkTo(it, uri) } ?: "#"
+                a(href = href) { metaMarkup(content.children) }
+            }
+            is ContentExternalLink -> {
+                a(href = content.href) { metaMarkup(content.children) }
+            }
+
+            is ContentBlock -> metaMarkup(content.children)
+        }
+    }
+
+
+    protected open fun <T> FlowContent.summaryNodeGroup(
+        nodes: Iterable<T>,
+        header: String,
+        headerAsRow: Boolean = true,
+        row: TBODY.(T) -> Unit
+    ) {
         if (nodes.none()) return
         if (!headerAsRow) {
             h2 { +header }
@@ -39,38 +110,31 @@
         }
     }
 
-    fun FlowContent.metaMarkup(content: ContentNode) = with(contentToHtmlBuilder) {
-        appendContent(content)
-    }
 
-    fun FlowContent.metaMarkup(content: List<ContentNode>) = with(contentToHtmlBuilder) {
-        appendContent(content)
-    }
-
-    open fun TBODY.classLikeRow(node: DocumentationNode) = tr {
+    protected open fun TBODY.classLikeRow(node: DocumentationNode) = tr {
         td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
         td { metaMarkup(node.summary) }
     }
 
-    fun FlowContent.modifiers(node: DocumentationNode) {
+    protected fun FlowContent.modifiers(node: DocumentationNode) {
         for (modifier in node.details(NodeKind.Modifier)) {
             renderedSignature(modifier, SUMMARY)
         }
     }
 
-    fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
+    protected fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
         val params = func.details(NodeKind.Parameter)
-                .map { languageService.render(it, FULL) }
-                .run {
-                    drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
-                        acc + ContentText(", ") + node
-                    }
+            .map { languageService.render(it, FULL) }
+            .run {
+                drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
+                    acc + ContentText(", ") + node
                 }
+            }
         metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
     }
 
 
-    open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
+    protected open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
         if (node.kind != NodeKind.Constructor) {
             td {
                 modifiers(node)
@@ -85,7 +149,7 @@
                         renderedSignature(receiver.detail(NodeKind.Type), SUMMARY)
                         +"."
                     }
-                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                    a(href = node) { +node.name }
                     shortFunctionParametersList(node)
                 }
             }
@@ -94,7 +158,7 @@
         }
     }
 
-    open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode) = tr {
+    protected open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode) = tr {
         td {
             modifiers(node)
             renderedSignature(node.detail(NodeKind.Type), SUMMARY)
@@ -102,7 +166,7 @@
         td {
             div {
                 code {
-                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                    a(href = node) { +node.name }
                 }
             }
 
@@ -110,14 +174,14 @@
         }
     }
 
-    open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
+    protected open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
         td {
             modifiers(node)
         }
         td {
             div {
                 code {
-                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                    a(href = node) { +node.name }
                 }
             }
 
@@ -125,11 +189,14 @@
         }
     }
 
-    open fun TBODY.inheritRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
+    protected open fun TBODY.inheritRow(
+        entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
+        summaryRow: TBODY.(DocumentationNode) -> Unit
+    ) = tr {
         td {
             val (from, nodes) = entry
             +"From class "
-            a(href = uriProvider.linkTo(from.owner!!, uri)) { +from.qualifiedName() }
+            a(href = from.owner!!) { +from.qualifiedName() }
             table {
                 tbody {
                     for (node in nodes) {
@@ -140,11 +207,14 @@
         }
     }
 
-    open fun TBODY.extensionRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
+    protected open fun TBODY.extensionRow(
+        entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
+        summaryRow: TBODY.(DocumentationNode) -> Unit
+    ) = tr {
         td {
             val (from, nodes) = entry
             +"From "
-            a(href = uriProvider.linkTo(from, uri)) { +from.qualifiedName() }
+            a(href = from) { +from.qualifiedName() }
             table {
                 tbody {
                     for (node in nodes) {
@@ -155,11 +225,341 @@
         }
     }
 
-    private fun FlowContent.renderedSignature(node: DocumentationNode, mode: LanguageService.RenderMode = SUMMARY) {
+    protected open fun FlowContent.a(href: DocumentationNode, classes: String? = null, block: A.() -> Unit) {
+        val hrefText = if (href.kind == NodeKind.ExternalLink)
+            href.name
+        else
+            uriProvider.linkTo(href, uri)
+        a(href = hrefText, classes = classes, block = block)
+    }
+
+    protected open fun FlowContent.renderedSignature(node: DocumentationNode, mode: LanguageService.RenderMode = SUMMARY) {
         metaMarkup(languageService.render(node, mode))
     }
 
-    open fun FlowContent.memberDocs(node: DocumentationNode) {
+    protected open fun generatePackage(page: Page.PackagePage) = templateService.composePage(
+        page,
+        htmlConsumer,
+        headContent = {
+
+        },
+        bodyContent = {
+            h1 { +page.node.name }
+            metaMarkup(page.node.content)
+            summaryNodeGroup(page.classes, "Classes", headerAsRow = false) { classLikeRow(it) }
+            summaryNodeGroup(page.exceptions, "Exceptions", headerAsRow = false) { classLikeRow(it) }
+            summaryNodeGroup(page.typeAliases, "Type-aliases", headerAsRow = false) { classLikeRow(it) }
+            summaryNodeGroup(page.annotations, "Annotations", headerAsRow = false) { classLikeRow(it) }
+            summaryNodeGroup(page.enums, "Enums", headerAsRow = false) { classLikeRow(it) }
+
+            summaryNodeGroup(
+                page.functions,
+                "Top-level functions summary",
+                headerAsRow = false
+            ) { functionLikeSummaryRow(it) }
+            summaryNodeGroup(
+                page.properties,
+                "Top-level properties summary",
+                headerAsRow = false
+            ) { propertyLikeSummaryRow(it) }
+
+
+            fullMemberDocs(page.functions, "Top-level functions")
+            fullMemberDocs(page.properties, "Top-level properties")
+        }
+    )
+
+    protected fun FlowContent.qualifiedTypeReference(node: DocumentationNode) {
+        if (node.kind in classLike) {
+            a(href = node) { +node.qualifiedName() }
+            return
+        }
+
+        val targetLink = node.links.single()
+
+        if (targetLink.kind == NodeKind.TypeParameter) {
+            +node.name
+            return
+        }
+
+        a(href = targetLink) {
+            +node.qualifiedNameFromType()
+        }
+        val typeParameters = node.details(NodeKind.Type)
+        if (typeParameters.isNotEmpty()) {
+            +"<"
+            typeParameters.forEach {
+                if (it != typeParameters.first()) {
+                    +", "
+                }
+                qualifiedTypeReference(it)
+            }
+            +">"
+        }
+    }
+
+    protected open fun FlowContent.classHierarchy(superclasses: List<DocumentationNode>) {
+        table {
+            superclasses.forEach {
+                tr {
+                    if (it != superclasses.first()) {
+                        td {
+                            +"   ↳"
+                        }
+                    }
+                    td {
+                        qualifiedTypeReference(it)
+                    }
+                }
+            }
+        }
+    }
+
+    protected open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) {
+        if (inheritors.isEmpty()) return
+        div {
+            table {
+                thead {
+                    tr {
+                        td {
+                            if (direct)
+                                +"Known Direct Subclasses"
+                            else
+                                +"Known Indirect Subclasses"
+                        }
+                    }
+                }
+                tbody {
+                    inheritors.forEach { inheritor ->
+                        tr {
+                            td {
+                                a(href = inheritor) { +inheritor.classNodeNameWithOuterClass() }
+                            }
+                            td {
+                                metaMarkup(inheritor.summary)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    protected open fun FlowContent.classLikeSummaries(page: Page.ClassPage) = with(page) {
+        summaryNodeGroup(
+            nestedClasses,
+            "Nested classes",
+            headerAsRow = true
+        ) {
+            nestedClassSummaryRow(it)
+        }
+
+        summaryNodeGroup(
+            constructors,
+            "Constructors",
+            headerAsRow = true
+        ) {
+            functionLikeSummaryRow(it)
+        }
+
+        summaryNodeGroup(functions, "Functions", headerAsRow = true) { functionLikeSummaryRow(it) }
+        summaryNodeGroup(
+            companionFunctions,
+            "Companion functions",
+            headerAsRow = true
+        ) {
+            functionLikeSummaryRow(it)
+        }
+        summaryNodeGroup(
+            inheritedFunctionsByReceiver.entries,
+            "Inherited functions",
+            headerAsRow = true
+        ) {
+            inheritRow(it) {
+                functionLikeSummaryRow(it)
+            }
+        }
+        summaryNodeGroup(
+            extensionFunctions.entries,
+            "Extension functions",
+            headerAsRow = true
+        ) {
+            extensionRow(it) {
+                functionLikeSummaryRow(it)
+            }
+        }
+        summaryNodeGroup(
+            inheritedExtensionFunctions.entries,
+            "Inherited extension functions",
+            headerAsRow = true
+        ) {
+            extensionRow(it) {
+                functionLikeSummaryRow(it)
+            }
+        }
+
+
+        summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
+        summaryNodeGroup(
+            companionProperties,
+            "Companion properties",
+            headerAsRow = true
+        ) {
+            propertyLikeSummaryRow(it)
+        }
+
+        summaryNodeGroup(
+            inheritedPropertiesByReceiver.entries,
+            "Inherited properties",
+            headerAsRow = true
+        ) {
+            inheritRow(it) {
+                propertyLikeSummaryRow(it)
+            }
+        }
+        summaryNodeGroup(
+            extensionProperties.entries,
+            "Extension properties",
+            headerAsRow = true
+        ) {
+            extensionRow(it) {
+                propertyLikeSummaryRow(it)
+            }
+        }
+        summaryNodeGroup(
+            inheritedExtensionProperties.entries,
+            "Inherited extension properties",
+            headerAsRow = true
+        ) {
+            extensionRow(it) {
+                propertyLikeSummaryRow(it)
+            }
+        }
+    }
+
+    protected open fun FlowContent.classLikeFullMemberDocs(page: Page.ClassPage) = with(page) {
+        fullMemberDocs(constructors, "Constructors")
+        fullMemberDocs(functions, "Functions")
+        fullMemberDocs(properties, "Properties")
+        if (!hasMeaningfulCompanion) {
+            fullMemberDocs(companionFunctions, "Companion functions")
+            fullMemberDocs(companionProperties, "Companion properties")
+        }
+    }
+
+    protected open fun generateClassLike(page: Page.ClassPage) = templateService.composePage(
+        page,
+        htmlConsumer,
+        headContent = {
+
+        },
+        bodyContent = {
+            val node = page.node
+            with(page) {
+                h1 { +node.name }
+                pre { renderedSignature(node, FULL) }
+                classHierarchy(page.superclasses)
+
+                subclasses(page.directInheritors, true)
+                subclasses(page.indirectInheritors, false)
+
+                metaMarkup(node.content)
+
+                h2 { +"Summary" }
+                classLikeSummaries(page)
+
+                classLikeFullMemberDocs(page)
+            }
+        }
+    )
+
+    protected open fun generateClassIndex(page: Page.ClassIndex) = templateService.composePage(
+        page,
+        htmlConsumer,
+        headContent = {
+
+        },
+        bodyContent = {
+            h1 { +"Class Index" }
+
+
+            ul {
+                page.classesByFirstLetter.forEach { (letter) ->
+                    li { a(href = "#letter_$letter") { +letter } }
+                }
+            }
+
+            page.classesByFirstLetter.forEach { (letter, classes) ->
+                h2 {
+                    id = "letter_$letter"
+                    +letter
+                }
+                table {
+                    tbody {
+                        for (node in classes) {
+                            tr {
+                                td {
+                                    a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
+                                }
+                                td {
+                                    metaMarkup(node.content)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    )
+
+    protected open fun generatePackageIndex(page: Page.PackageIndex) = templateService.composePage(
+        page,
+        htmlConsumer,
+        headContent = {
+
+        },
+        bodyContent = {
+            h1 { +"Package Index" }
+            table {
+                tbody {
+                    for (node in page.packages) {
+                        tr {
+                            td {
+                                a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                            }
+                            td {
+                                metaMarkup(node.content)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    )
+
+    fun generatePage(page: Page) {
+        when (page) {
+            is Page.PackageIndex -> generatePackageIndex(page)
+            is Page.ClassIndex -> generateClassIndex(page)
+            is Page.ClassPage -> generateClassLike(page)
+            is Page.PackagePage -> generatePackage(page)
+        }
+    }
+
+    protected fun FlowContent.fullMemberDocs(
+        nodes: List<DocumentationNode>,
+        header: String
+    ) {
+        if (nodes.none()) return
+        h2 {
+            +header
+        }
+        for (node in nodes) {
+            fullMemberDocs(node)
+        }
+    }
+
+    protected open fun FlowContent.fullMemberDocs(node: DocumentationNode) {
         div {
             id = node.signatureForAnchor(logger)
             h3 { +node.name }
@@ -183,357 +583,132 @@
         }
     }
 
-    fun appendPackage(node: DocumentationNode) = templateService.composePage(
-            listOf(node),
-            htmlConsumer,
-            headContent = {
-
-            },
-            bodyContent = {
-                h1 { +node.name }
-                metaMarkup(node.content)
-                summaryNodeGroup(node.members(NodeKind.Class), "Classes") { classLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.Exception), "Exceptions") { classLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.TypeAlias), "Type-aliases") { classLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.AnnotationClass), "Annotations") { classLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.Enum), "Enums") { classLikeRow(it) }
-
-                summaryNodeGroup(node.members(NodeKind.Function), "Top-level functions summary") { functionLikeSummaryRow(it) }
-                summaryNodeGroup(node.members(NodeKind.Property), "Top-level properties summary") { propertyLikeSummaryRow(it) }
-
-
-                fullDocs(node.members(NodeKind.Function), "Top-level functions") { memberDocs(it) }
-                fullDocs(node.members(NodeKind.Property), "Top-level properties") { memberDocs(it) }
+    sealed class Page {
+        class PackageIndex(packages: List<DocumentationNode>) : Page() {
+            init {
+                assert(packages.all { it.kind == NodeKind.Package })
             }
-    )
 
-    fun FlowContent.qualifiedTypeReference(node: DocumentationNode) {
-        if (node.kind in classLike) {
-            a(href = uriProvider.linkTo(node, uri)) {
-                +node.qualifiedName()
+            val packages = packages.sortedBy { it.name }
+        }
+
+        class ClassIndex(allTypesNode: DocumentationNode) : Page() {
+            init {
+                assert(allTypesNode.kind == NodeKind.AllTypes)
             }
-            return
-        }
 
-        val targetLink = node.links.single()
+            // Wide-collect all nested classes
+            val classes: List<DocumentationNode> =
+                generateSequence(listOf(allTypesNode)) { nodes ->
+                    nodes
+                        .flatMap { it.members.filter { it.kind in NodeKind.classLike } }
+                        .takeUnless { it.isEmpty() }
+                }.drop(1)
+                    .flatten()
+                    .sortedBy { it.classNodeNameWithOuterClass() }
+                    .toList()
 
-        if (targetLink.kind == NodeKind.TypeParameter) {
-            +node.name
-            return
-        }
 
-        val href = if (targetLink.kind == NodeKind.ExternalLink)
-            targetLink.name
-        else
-            uriProvider.linkTo(targetLink, uri)
-
-        a(href = href) {
-            +node.qualifiedNameFromType()
-        }
-        val typeParameters = node.details(NodeKind.Type)
-        if (typeParameters.isNotEmpty()) {
-            +"<"
-            typeParameters.forEach {
-                if (it != typeParameters.first()) {
-                    +", "
-                }
-                qualifiedTypeReference(it)
-            }
-            +">"
-        }
-    }
-
-    open fun FlowContent.classHierarchy(node: DocumentationNode) {
-
-        val superclasses = (sequenceOf(node) + node.superclassTypeSequence).toList().asReversed()
-        table {
-            superclasses.forEach {
-                tr {
-                    if (it != superclasses.first()) {
-                        td {
-                            +"   ↳"
-                        }
+            // Group all classes by it's first letter and sort
+            val classesByFirstLetter =
+                classes
+                    .groupBy {
+                        it.classNodeNameWithOuterClass().first().toString()
                     }
-                    td {
-                        qualifiedTypeReference(it)
-                    }
-                }
-            }
+                    .entries
+                    .sortedBy { (letter) -> letter }
         }
-    }
 
-    open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) {
-        if (inheritors.isEmpty()) return
-        div {
-            table {
-                thead {
-                    tr {
-                        td {
-                            if (direct)
-                                +"Known Direct Subclasses"
-                            else
-                                +"Known Indirect Subclasses"
-                        }
-                    }
-                }
-                tbody {
-                    inheritors.forEach {
-                        tr {
-                            td {
-                                a(href = uriProvider.linkTo(it, uri)) { +it.classNodeNameWithOuterClass() }
-                            }
-                            td {
-                                metaMarkup(it.summary)
-                            }
-                        }
-                    }
-                }
+        class ClassPage(val node: DocumentationNode) : Page() {
+
+            init {
+                assert(node.kind in NodeKind.classLike)
             }
-        }
-    }
 
-    fun appendClassLike(node: DocumentationNode) = templateService.composePage(
-            listOf(node),
-            htmlConsumer,
-            headContent = {
+            val superclasses = (sequenceOf(node) + node.superclassTypeSequence).toList().asReversed()
 
-            },
-            bodyContent = {
-                h1 { +node.name }
-                pre { renderedSignature(node, FULL) }
-                classHierarchy(node)
 
+            val directInheritors: List<DocumentationNode>
+            val indirectInheritors: List<DocumentationNode>
+
+            init {
+                // Wide-collect all inheritors
                 val inheritors = generateSequence(node.inheritors) { inheritors ->
                     inheritors
-                            .flatMap { it.inheritors }
-                            .takeUnless { it.isEmpty() }
+                        .flatMap { it.inheritors }
+                        .takeUnless { it.isEmpty() }
                 }
-                subclasses(inheritors.first(), true)
-                subclasses(inheritors.drop(1).flatten().toList(), false)
+                directInheritors = inheritors.first()
+                indirectInheritors = inheritors.drop(1).flatten().toList()
+            }
+
+            val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
+            val hasMeaningfulCompanion = !isCompanion && node.companion != null
+
+            private fun DocumentationNode.thisTypeExtension() =
+                detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
+
+            val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
+            val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
+
+            private fun DocumentationNode.isFunction() = kind == functionKind
+            private fun DocumentationNode.isProperty() = kind == propertyKind
 
 
-                metaMarkup(node.content)
+            val nestedClasses = node.members.filter { it.kind in NodeKind.classLike }
+            val constructors = node.members(NodeKind.Constructor)
+            val functions = node.members(functionKind)
+            val properties = node.members(propertyKind)
+            val inheritedFunctionsByReceiver = node.inheritedMembers(functionKind).groupBy { it.owner!! }
+            val inheritedPropertiesByReceiver = node.inheritedMembers(propertyKind).groupBy { it.owner!! }
 
-                h2 { +"Summary" }
+            val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
 
-                val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
-                val hasMeaningfulCompanion = !isCompanion && node.companion != null
+            val extensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
+            val extensionProperties: Map<DocumentationNode, List<DocumentationNode>>
+            val inheritedExtensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
+            val inheritedExtensionProperties: Map<DocumentationNode, List<DocumentationNode>>
 
-                fun DocumentationNode.thisTypeExtension() = detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
-
-                val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
-                val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
-
-                fun DocumentationNode.isFunction() = kind == functionKind
-                fun DocumentationNode.isProperty() = kind == propertyKind
-
-                val functions = node.members(functionKind)
-                val properties = node.members(propertyKind)
-                val inheritedFunctionsByReceiver = node.inheritedMembers(functionKind).groupBy { it.owner!! }
-                val inheritedPropertiesByReceiver = node.inheritedMembers(propertyKind).groupBy { it.owner!! }
-
-
-                val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
+            init {
                 val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() }
-                val extensionFunctions = extensions.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
-                val extensionProperties = extensions.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
-                val inheritedExtensionFunctions = inheritedExtensions.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
-                val inheritedExtensionProperties = inheritedExtensions.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
-
-                val companionFunctions = node.members(NodeKind.CompanionObjectFunction)
-                val companionProperties = node.members(NodeKind.CompanionObjectProperty)
-
-                summaryNodeGroup(node.members.filter { it.kind in NodeKind.classLike }, "Nested classes", headerAsRow = true) { nestedClassSummaryRow(it) }
-
-                summaryNodeGroup(node.members(NodeKind.Constructor), "Constructors", headerAsRow = true) { functionLikeSummaryRow(it) }
-
-                summaryNodeGroup(functions, "Functions", headerAsRow = true) { functionLikeSummaryRow(it) }
-                if (!isCompanion) {
-                    summaryNodeGroup(companionFunctions, "Companion functions", headerAsRow = true) { functionLikeSummaryRow(it) }
-                }
-                summaryNodeGroup(inheritedFunctionsByReceiver.entries, "Inherited functions", headerAsRow = true) { inheritRow(it) { functionLikeSummaryRow(it) } }
-                summaryNodeGroup(extensionFunctions.entries, "Extension functions", headerAsRow = true) { extensionRow(it) { functionLikeSummaryRow(it) } }
-                summaryNodeGroup(inheritedExtensionFunctions.entries, "Inherited extension functions", headerAsRow = true) { extensionRow(it) { functionLikeSummaryRow(it) } }
-
-
-                summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
-                if (!isCompanion) {
-                    summaryNodeGroup(companionProperties, "Companion properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
-                }
-                summaryNodeGroup(inheritedPropertiesByReceiver.entries, "Inherited properties", headerAsRow = true) { inheritRow(it) { propertyLikeSummaryRow(it) } }
-                summaryNodeGroup(extensionProperties.entries, "Extension properties", headerAsRow = true) { extensionRow(it) { propertyLikeSummaryRow(it) } }
-                summaryNodeGroup(inheritedExtensionProperties.entries, "Inherited extension properties", headerAsRow = true) { extensionRow(it) { propertyLikeSummaryRow(it) } }
-
-
-                fullDocs(node.members(NodeKind.Constructor), "Constructors") { memberDocs(it) }
-                fullDocs(functions, "Functions") { memberDocs(it) }
-                fullDocs(properties, "Properties") { memberDocs(it) }
-                if (!isCompanion && !hasMeaningfulCompanion) {
-                    fullDocs(companionFunctions, "Companion functions") { memberDocs(it) }
-                    fullDocs(companionProperties, "Companion properties") { memberDocs(it) }
-                }
+                extensionFunctions = extensions.filter { it.isFunction() }.groupBy { it.owner!! }
+                extensionProperties = extensions.filter { it.isProperty() }.groupBy { it.owner!! }
+                inheritedExtensionFunctions =
+                        inheritedExtensions.filter { it.isFunction() }.groupBy { it.owner!! }
+                inheritedExtensionProperties =
+                        inheritedExtensions.filter { it.isProperty() }.groupBy { it.owner!! }
             }
-    )
 
-    fun generateClassesIndex(allTypesNode: DocumentationNode) = templateService.composePage(
-            listOf(allTypesNode),
-            htmlConsumer,
-            headContent = {
+            val companionFunctions = node.members(NodeKind.CompanionObjectFunction).takeUnless { isCompanion }.orEmpty()
+            val companionProperties =
+                node.members(NodeKind.CompanionObjectProperty).takeUnless { isCompanion }.orEmpty()
 
-            },
-            bodyContent = {
-                h1 { +"Class Index" }
 
-                fun DocumentationNode.classWithNestedClasses(): List<DocumentationNode> =
-                        members.filter { it.kind in classLike }.flatMap(DocumentationNode::classWithNestedClasses) + this
-
-                val classesByFirstLetter = allTypesNode.members
-                        .filterNot { it.kind == NodeKind.ExternalClass }
-                        .flatMap(DocumentationNode::classWithNestedClasses)
-                        .groupBy {
-                            it.classNodeNameWithOuterClass().first().toString()
-                        }
-                        .entries
-                        .sortedBy { (letter) -> letter }
-
-                ul {
-                    classesByFirstLetter.forEach { (letter) ->
-                        li { a(href = "#letter_$letter") { +letter } }
-                    }
-                }
-
-                classesByFirstLetter.forEach { (letter, nodes) ->
-                    h2 {
-                        id = "letter_$letter"
-                        +letter
-                    }
-                    table {
-                        tbody {
-                            for (node in nodes.sortedBy { it.classNodeNameWithOuterClass() }) {
-                                tr {
-                                    td {
-                                        a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
-                                    }
-                                    td {
-                                        metaMarkup(node.content)
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-    )
-
-    fun generatePackageIndex(nodes: List<DocumentationNode>) = templateService.composePage(nodes,
-            htmlConsumer,
-            headContent = {
-
-            },
-            bodyContent = {
-                h1 { +"Package Index" }
-                table {
-                    tbody {
-                        for (node in nodes.sortedBy { it.name }) {
-                            tr {
-                                td {
-                                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
-                                }
-                                td {
-                                    metaMarkup(node.content)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-    )
-
-    private fun FlowContent.fullDocs(
-            nodes: List<DocumentationNode>,
-            header: String,
-            renderNode: FlowContent.(DocumentationNode) -> Unit
-    ) {
-        if (nodes.none()) return
-        h2 {
-            +header
         }
-        for (node in nodes) {
-            renderNode(node)
-        }
-    }
-}
 
-class ContentToHtmlBuilder(val uriProvider: JavaLayoutHtmlUriProvider, val uri: URI) {
-    fun FlowContent.appendContent(content: List<ContentNode>): Unit = content.forEach { appendContent(it) }
+        class PackagePage(val node: DocumentationNode) : Page() {
 
-    private fun FlowContent.hN(level: Int, classes: String? = null, block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit) {
-        when (level) {
-            1 -> h1(classes, block)
-            2 -> h2(classes, block)
-            3 -> h3(classes, block)
-            4 -> h4(classes, block)
-            5 -> h5(classes, block)
-            6 -> h6(classes, block)
-        }
-    }
-
-    fun FlowContent.appendContent(content: ContentNode) {
-        when (content) {
-            is ContentText -> +content.text
-            is ContentSymbol -> span("symbol") { +content.text }
-            is ContentKeyword -> span("keyword") { +content.text }
-            is ContentIdentifier -> span("identifier") {
-                content.signature?.let { id = it }
-                +content.text
+            init {
+                assert(node.kind == NodeKind.Package)
             }
 
-            is ContentHeading -> hN(level = content.level) { appendContent(content.children) }
+            val classes = node.members(NodeKind.Class)
+            val exceptions = node.members(NodeKind.Exception)
+            val typeAliases = node.members(NodeKind.TypeAlias)
+            val annotations = node.members(NodeKind.AnnotationClass)
+            val enums = node.members(NodeKind.Enum)
 
-            is ContentEntity -> +content.text
-
-            is ContentStrong -> strong { appendContent(content.children) }
-            is ContentStrikethrough -> del { appendContent(content.children) }
-            is ContentEmphasis -> em { appendContent(content.children) }
-
-            is ContentOrderedList -> ol { appendContent(content.children) }
-            is ContentUnorderedList -> ul { appendContent(content.children) }
-            is ContentListItem -> consumer.li {
-                (content.children.singleOrNull() as? ContentParagraph)
-                        ?.let { paragraph -> appendContent(paragraph.children) }
-                        ?: appendContent(content.children)
-            }
-
-
-            is ContentCode -> pre { code { appendContent(content.children) } }
-            is ContentBlockSampleCode -> pre { code {} }
-            is ContentBlockCode -> pre { code {} }
-
-
-            is ContentNonBreakingSpace -> +nbsp
-            is ContentSoftLineBreak, is ContentIndentedSoftLineBreak -> {
-            }
-
-            is ContentParagraph -> p { appendContent(content.children) }
-
-            is ContentNodeLink -> {
-                a(href = content.node?.let { uriProvider.linkTo(it, uri) }
-                        ?: "#unresolved") { appendContent(content.children) }
-            }
-            is ContentExternalLink -> {
-                a(href = content.href) { appendContent(content.children) }
-            }
-
-            is ContentBlock -> appendContent(content.children)
+            val functions = node.members(NodeKind.Function)
+            val properties = node.members(NodeKind.Property)
         }
     }
 }
 
 class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor(
-        val uriProvider: JavaLayoutHtmlUriProvider,
-        val languageService: LanguageService,
-        val templateService: JavaLayoutHtmlTemplateService,
-        val logger: DokkaLogger
+    val uriProvider: JavaLayoutHtmlUriProvider,
+    val languageService: LanguageService,
+    val templateService: JavaLayoutHtmlTemplateService,
+    val logger: DokkaLogger
 ) : JavaLayoutHtmlFormatOutputBuilderFactory {
     override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder {
         return createOutputBuilder(output, uriProvider.mainUri(node))
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
index 6f09a7b..270c912 100644
--- a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
@@ -3,6 +3,7 @@
 import com.google.inject.Inject
 import com.google.inject.name.Named
 import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatOutputBuilder.Page
 import org.jetbrains.dokka.NodeKind.Companion.classLike
 import org.jetbrains.kotlin.preprocessor.mkdirsOrFail
 import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
@@ -62,7 +63,7 @@
     fun buildClass(node: DocumentationNode, parentDir: File) {
         val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html")
         fileForClass.bufferedWriter().use {
-            createOutputBuilderForNode(node, it).appendClassLike(node)
+            createOutputBuilderForNode(node, it).generatePage(Page.ClassPage(node))
         }
         for (memberClass in node.members.filter { it.kind in NodeKind.classLike }) {
             buildClass(memberClass, parentDir)
@@ -76,7 +77,7 @@
         directoryForPackage.mkdirsOrFail()
 
         directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
-            createOutputBuilderForNode(node, it).appendPackage(node)
+            createOutputBuilderForNode(node, it).generatePage(Page.PackagePage(node))
         }
 
         members.filter { it.kind in NodeKind.classLike }.forEach {
@@ -87,15 +88,16 @@
     fun buildClassIndex(node: DocumentationNode, parentDir: File) {
         val file = parentDir.resolve("classes.html")
         file.bufferedWriter().use {
-            createOutputBuilderForNode(node, it).generateClassesIndex(node)
+            createOutputBuilderForNode(node, it).generatePage(Page.ClassIndex(node))
         }
     }
 
-    fun buildPackageIndex(nodes: List<DocumentationNode>, parentDir: File) {
+    fun buildPackageIndex(module: DocumentationNode, nodes: List<DocumentationNode>, parentDir: File) {
         val file = parentDir.resolve("packages.html")
         file.bufferedWriter().use {
-            outputBuilderFactoryService.createOutputBuilder(it, containerUri(nodes.first().owner!!).resolve("packages.html"))
-                    .generatePackageIndex(nodes)
+            val uri = containerUri(module).resolve("packages.html")
+            outputBuilderFactoryService.createOutputBuilder(it, uri)
+                .generatePage(Page.PackageIndex(nodes))
         }
     }
 
@@ -108,7 +110,7 @@
 
         if (options.generateIndexPages) {
             buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, moduleRoot)
-            buildPackageIndex(packages, moduleRoot)
+            buildPackageIndex(module, packages, moduleRoot)
         }
     }