// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.

#pragma once

#include <utility>

#include <hilti/ast/attribute.h>
#include <hilti/ast/ctor.h>
#include <hilti/ast/expression.h>
#include <hilti/ast/type.h>
#include <hilti/ast/types/auto.h>

#include <spicy/ast/declarations/hook.h>
#include <spicy/ast/types/sink.h>
#include <spicy/ast/types/unit-item.h>

namespace spicy::type::unit::item {

/**
 * AST node for a unit field with its type determined by a not yet resolved
 * ID. The ID may refer to either a type or an ctor.
 */
class UnresolvedField : public unit::Item {
public:
    const auto& fieldID() const { return id(); }
    const auto& unresolvedID() const { return _unresolved_id; }
    const auto& index() const { return _index; }

    // Only one of these will have return value.
    auto ctor() const { return childTryAs<Ctor>(1); }
    auto item() const { return childTryAs<Item>(1); }
    auto type() const { return childTryAs<QualifiedType>(1); }

    auto repeatCount() const { return child<Expression>(2); }
    auto attributes() const { return child<AttributeSet>(3); }
    auto condition() const { return child<Expression>(4); }
    auto arguments() const { return children<Expression>(args_start, _args_end); }
    auto sinks() const { return children<Expression>(_sinks_start, _sinks_end); }
    auto hooks() const { return children<declaration::Hook>(_hooks_start, _hooks_end); }
    auto isSkip() const { return _is_skip; }

    void setIndex(uint64_t index) { _index = index; }
    void setSkip(bool skip) { _is_skip = skip; }
    void setType(ASTContext* ctx, QualifiedType* t) { setChild(ctx, 1, t); }

    QualifiedType* itemType() const final { return child<QualifiedType>(0); /* return `auto` */ }

    bool isResolved(hilti::node::CycleDetector* cd) const final { return false; }

    std::string_view displayName() const final { return "unresolved unit field"; }

    node::Properties properties() const final {
        auto p = node::Properties{{"index", _index}};
        return unit::Item::properties() + std::move(p);
    }

    static auto create(ASTContext* ctx, ID id, QualifiedType* type, bool skip, Expressions args, Expression* repeat,
                       Expressions sinks, AttributeSet* attrs, Expression* cond, spicy::declaration::Hooks hooks,
                       Meta meta = {}) {
        return _create(ctx, std::move(id), type, skip, std::move(args), repeat, std::move(sinks), attrs, cond,
                       std::move(hooks), std::move(meta));
    }

    static auto create(ASTContext* ctx, ID id, Ctor* ctor, bool skip, Expressions args, Expression* repeat,
                       Expressions sinks, AttributeSet* attrs, Expression* cond, spicy::declaration::Hooks hooks,
                       Meta meta = {}) {
        return _create(ctx, std::move(id), ctor, skip, std::move(args), repeat, std::move(sinks), attrs, cond,
                       std::move(hooks), std::move(meta));
    }

    static auto create(ASTContext* ctx, ID id, type::unit::Item* item, bool skip, Expressions args, Expression* repeat,
                       Expressions sinks, AttributeSet* attrs, Expression* cond, spicy::declaration::Hooks hooks,
                       Meta meta = {}) {
        return _create(ctx, std::move(id), item, skip, std::move(args), repeat, std::move(sinks), attrs, cond,
                       std::move(hooks), std::move(meta));
    }

    static auto create(ASTContext* ctx, ID id, ID unresolved_id, bool skip, Expressions args, Expression* repeat,
                       Expressions sinks, AttributeSet* attrs, Expression* cond, spicy::declaration::Hooks hooks,
                       Meta meta = {}) {
        auto* f = _create(ctx, std::move(id), nullptr, skip, std::move(args), repeat, std::move(sinks), attrs, cond,
                          std::move(hooks), std::move(meta));
        f->_unresolved_id = std::move(unresolved_id);
        return f;
    }


protected:
    UnresolvedField(ASTContext* ctx, Nodes children, size_t args_start, size_t args_end, size_t sinks_start,
                    size_t sinks_end, size_t hooks_start, size_t hooks_end, ID id, bool skip, Meta meta)
        : unit::Item(ctx, NodeTags, std::move(children), std::move(id), std::move(meta)),
          _is_skip(skip),
          args_start(static_cast<int>(args_start)),
          _args_end(static_cast<int>(args_end)),
          _sinks_start(static_cast<int>(sinks_start)),
          _sinks_end(static_cast<int>(sinks_end)),
          _hooks_start(static_cast<int>(hooks_start)),
          _hooks_end(static_cast<int>(hooks_end)) {}

    SPICY_NODE_2(type::unit::item::UnresolvedField, type::unit::Item, Declaration, final);

private:
    static UnresolvedField* _create(ASTContext* ctx, ID id, Node* node, bool skip, Expressions args, Expression* repeat,
                                    Expressions sinks, AttributeSet* attrs, Expression* cond,
                                    spicy::declaration::Hooks hooks, Meta meta) {
        if ( ! attrs )
            attrs = AttributeSet::create(ctx);

        auto* auto_ = QualifiedType::create(ctx, hilti::type::Auto::create(ctx), hilti::Constness::Const, meta);
        auto num_args = args.size();
        auto num_sinks = sinks.size();
        auto num_hooks = hooks.size();

        return ctx->make<UnresolvedField>(ctx,
                                          node::flatten(auto_, node, repeat, attrs, cond, std::move(args),
                                                        std::move(sinks), std::move(hooks)),
                                          5U, 5U + num_args, 5U + num_args, 5U + num_args + num_sinks,
                                          5U + num_args + num_sinks, 5U + num_args + num_sinks + num_hooks,
                                          std::move(id), skip, std::move(meta));
    }

    ID _unresolved_id;
    bool _is_skip;
    std::optional<uint64_t> _index;
    const int args_start;
    const int _args_end;
    const int _sinks_start;
    const int _sinks_end;
    const int _hooks_start;
    const int _hooks_end;
};

} // namespace spicy::type::unit::item
