/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */
macro package std.deriving

import std.deriving.api.*
import std.deriving.impl.deriveImpl
import std.ast.*
import std.collection.*

const DeriveName = "Derive"
const DeriveIncludeName = "DeriveInclude"
const DeriveExcludeName = "DeriveExclude"
const DeriveOrderName = "DeriveOrder"

public macro Derive(rawOptions: Tokens, input: Tokens): Tokens {
    let logger = LoggerImpl({ at, message, error =>
        diagReport(if (error) { DiagReportLevel.ERROR } else { DiagReportLevel.WARNING }, at, message, "")
    })

    let includes = getChildMessages(DeriveIncludeName).iterator()
        .filterMap { it => DeriveIncludeData.fromMessage(it) }
        .map { it => Identifier(it.includedId) } |> collectArray
    let excludes = getChildMessages(DeriveExcludeName).iterator()
        .filterMap { it => DeriveExcludeData.fromMessage(it) }
        .map { it => Identifier(it.excludedId) } |>
        collectArray
    let ordering = getChildMessages(DeriveOrderName).iterator()
        .filterMap { it => DeriveOrderData.fromMessage(it) }
        .map { it => it.members } |>
        flatten |>
        map<Token, Identifier> { it => Identifier(it) } |>
        collectArray

    match (parseDeclUnwrapSafe(input)) {
        case Ok(decl) => deriveImpl(rawOptions, input, decl, includes, excludes, ordering, logger)
        case Err(cause) => reportNonDeclInput(input, logger, cause)
    }
}

private func reportNonDeclInput(input: Tokens, logger: Logger, cause: Exception): Tokens {
    let declKeyword = findDeclKeyword(input)

    if (let Some(declKeyword) <- declKeyword) {
        if (declKeyword.kind == TokenKind.CLASS || declKeyword.kind == TokenKind.STRUCT ||
            declKeyword.kind == TokenKind.ENUM) {
            logger.error(findReportTarget(input), "Unable to derive ${declKeyword.value}: ${cause}")
            return input
        }
    }

    // here we could try to make a lax parse and try to proceed but it requires
    // much more effort
    logger.error(declKeyword ?? findReportTarget(input), "Deriving is only supported for classes, enums and structs: ${cause}")

    return input
}

private let DECL_KEYWORD_KINDS = [
    TokenKind.FUNC, TokenKind.CLASS, TokenKind.STRUCT,
    TokenKind.ENUM, TokenKind.MAIN, TokenKind.MACRO,
    TokenKind.PROP, TokenKind.VAR, TokenKind.LET
].iterator().map { a => a.toString() }

private func findDeclKeyword(input: Tokens): ?Token {
    input.iterator().filter { it => DECL_KEYWORD_KINDS.contains(it.kind.toString()) }.first()
}

private func findIdentifier(input: Tokens): ?Token {
    input.iterator().filter { it => it.kind == TokenKind.IDENTIFIER }.first()
}

private func findReportTarget(input: Tokens): Token {
   findIdentifier(input) ?? input.iterator().first() ?? Token()
}

struct LoggerImpl <: Logger {
    LoggerImpl(private let sink: (Tokens, String, Bool) -> Unit) {
    }

    public func log(at: Tokens, message: String, error!: Bool): Unit {
        sink(at, message, error)
    }
}