import Foundation
import SaplingCore
import SaplingEditor

struct BenchmarkScenario {
    var name: String
    var url: URL
}

let arguments = Array(CommandLine.arguments.dropFirst())
let repositoryRoot = URL(fileURLWithPath: FileManager.default.currentDirectoryPath, isDirectory: true)

do {
    let scenarios = try benchmarkScenarios(arguments: arguments, repositoryRoot: repositoryRoot)
    print("# Sapling Editor Benchmark")
    print("")
    print("Generated: \(ISO8601DateFormatter().string(from: Date()))")
    print("")

    for scenario in scenarios {
        let result = try EditorBenchmarkProfiler.profileDocument(at: scenario.url)
        printScenario(name: scenario.name, result: result)
    }
} catch {
    fputs("SaplingEditorBenchmark failed: \(error)\n", stderr)
    exit(1)
}

func benchmarkScenarios(arguments: [String], repositoryRoot: URL) throws -> [BenchmarkScenario] {
    if !arguments.isEmpty {
        return arguments.map { path in
            let url = URL(fileURLWithPath: path)
            return BenchmarkScenario(name: url.deletingPathExtension().lastPathComponent, url: url)
        }
    }

    return try [
        sampleScenario(),
        BenchmarkScenario(
            name: "hybrid-large-2100",
            url: repositoryRoot.appendingPathComponent("Docs/EditorPrototypes/hybrid-large-2100.md")
        ),
        BenchmarkScenario(
            name: "5mb",
            url: repositoryRoot.appendingPathComponent("Docs/Benchmarks/5mb.md")
        )
    ]
}

func sampleScenario() throws -> BenchmarkScenario {
    let url = FileManager.default.temporaryDirectory
        .appendingPathComponent("SaplingEditorBenchmark-sample-document.md")
    try SaplingSampleData.document.content.write(to: url, atomically: true, encoding: .utf8)
    return BenchmarkScenario(name: "sample-document", url: url)
}

func printScenario(name: String, result: EditorBenchmarkResult) {
    print("## \(name)")
    print("")
    print("| Property | Value |")
    print("| --- | ---: |")
    print("| File | `\(result.document.fileName)` |")
    print("| Bytes | \(result.document.byteCount) |")
    print("| Characters | \(result.document.characterCount) |")
    print("| UTF-16 units | \(result.document.utf16Count) |")
    print("| Lines | \(result.document.lineCount) |")
    print("| Max line UTF-16 length | \(result.document.maxLineUTF16Length) |")
    print("| Average line UTF-16 length | \(format(result.document.averageLineUTF16Length)) |")
    print("")
    print("Markdown structure: \(result.document.markdownStructureSummary)")
    print("")
    print("Rendered mode trace: \(result.renderedModeTrace.renderedLineCount) rendered lines, "
        + "\(result.renderedModeTrace.sourceLineCount) source lines, "
        + "\(result.renderedModeTrace.headingPlanCount) heading plans, "
        + "\(result.renderedModeTrace.paragraphPlanCount) paragraph plans, "
        + "\(result.renderedModeTrace.inlineSpanCount) inline spans, "
        + "\(result.renderedModeTrace.unsupportedReferenceLinkLikeCount) reference-style markers.")
    print("")
    print("| Rank | Operation | Category | Time | Percent | Notes |")
    print("| ---: | --- | --- | ---: | ---: | --- |")
    for (index, measurement) in result.hottestMeasurements.prefix(20).enumerated() {
        let percentage = result.measuredTotalMilliseconds == 0
            ? 0
            : measurement.durationMilliseconds / result.measuredTotalMilliseconds * 100
        print("| \(index + 1) | `\(measurement.name)` | \(measurement.category.rawValue) | "
            + "\(format(measurement.durationMilliseconds)) ms | \(format(percentage))% | \(measurement.notes) |")
    }
    print("")
    print("Measured total: \(format(result.measuredTotalMilliseconds)) ms")
    print("")
}

func format(_ value: Double) -> String {
    String(format: "%.3f", value)
}
