import LineBuilder from "../../../core/codegen/LineBuilder.js";
import { Behavior } from "./Behavior.js";

/**
 *
 * @param {Behavior} behavior
 * @return {string}
 */
function compute_behavior_name(behavior) {

    // try to get name from the constructor field
    const typeName = behavior.constructor.typeName;
    if (typeof typeName === "string") {
        return typeName;
    }

    const constructor_name = behavior.constructor.name;
    if (typeof constructor_name === "string") {
        return constructor_name;
    }

    return '$Behavior';
}


/**
 *
 * @param {Behavior|SequenceBehavior|ParallelBehavior} behavior
 * @return {Object}
 */
function behavior_node_attributes(behavior) {
    const result = {};

    if (behavior.isCompositeBehavior === true) {

        result.fillcolor = "orange";

        if (behavior.isSequenceBehavior === true) {
            result.fillcolor = "paleturquoise";
        } else if (behavior.isParallelBehavior === true) {
            result.fillcolor = "plum";
        }
    } else {

    }

    return result;
}

/**
 *
 * @param {Object} attributes
 * @return {string}
 */
function attributes_to_dot(attributes) {
    const key_names = Object.keys(attributes);

    return key_names.map(key => {

        return `${key}=${attributes[key]}`;

    }).join(', ');
}

/**
 *
 * @param {string} prop
 * @param {Object} object
 */
function should_property_be_included(prop, object) {
    if (prop.startsWith('_')) {
        // private field
        return false;
    }

    const value = object[prop];

    if (object.constructor.prototype[prop] === value) {
        return false;
    }

    const typeof_value = typeof value;

    if (value === null || typeof_value === "undefined" || typeof_value === "symbol") {
        return false;
    }

    if (typeof_value === "object" && String(value) === '[object Object]') {
        return false;
    }

    return true;
}

function property_to_string(value, max_length = 200) {


    let string_value;

    if (typeof value !== "string") {
        string_value = String(value);
    } else {
        string_value = `\"${value}\"`;
    }

    const sanitized = string_value.replace(/\n/, "\n");

    if (sanitized.length > max_length) {
        return '[...]';
    }

    return sanitized;
}

/**
 *
 * @param {Behavior} behavior
 * @param {number} max_props
 */
function build_node_properties_string(behavior, max_props = 10) {

    const properties = {};

    let qualified_property_count = 0;

    for (const behaviorKey in behavior) {

        if (should_property_be_included(behaviorKey, behavior)) {
            if (qualified_property_count < max_props) {
                properties[behaviorKey] = property_to_string(behavior[behaviorKey]);
            }

            qualified_property_count++;
        }

    }

    // stringify and concatenate
    return Object.keys(properties).map(key => `${key} = ${properties[key]}`).join('<BR/>');

}

/**
 *
 * @param {Behavior} behavior
 * @return {boolean}
 */
function should_behavior_have_properties_string(behavior) {
    if (behavior.isCompositeBehavior === true) {
        return false;
    }

    return true;
}

function build_node_label(behavior) {
    const node_name = compute_behavior_name(behavior);

    const label_pieces = [];

    label_pieces.push(`<B>${node_name}</B>`);

    if (should_behavior_have_properties_string(behavior)) {
        const properties = build_node_properties_string(behavior);

        if (properties.trim().length > 0) {

            label_pieces.push(properties);

        }
    }

    return `<{ {${label_pieces.join('<BR/>')} } }>`;
}

/**
 *
 * @param {LineBuilder} out
 * @param {Behavior|CompositeBehavior|AbstractDecoratorBehavior} behavior
 * @param {{id_counter:number, node_ids:Map<Behavior,string>}} context
 * @returns {string}
 */
function parse_behavior(out, behavior, context) {
    const id = context.id_counter++;
    const node_id = `Node_${id}`;

    context.node_ids.set(behavior, node_id);

    const attributes = {
        label: build_node_label(behavior),
        style: "\"rounded,filled\""
    };

    Object.assign(attributes, behavior_node_attributes(behavior));

    out.add(`${node_id} [${attributes_to_dot(attributes)}]`);

    if (behavior.isCompositeBehavior === true) {
        // is a composite behavior, which means it includes other behaviors

        const children = behavior.children;

        const child_count = children.length;

        for (let i = 0; i < child_count; i++) {
            const child = children[i];

            const child_id = parse_behavior(out, child, context);

            // create edge
            out.add(`${node_id} -> ${child_id} [label="№${i + 1}"];`);
        }

    }

    if(behavior.isDecoratorBehavior === true){
        // is a decorator

        const source = behavior.getSource();

        const id = parse_behavior(out, source,context);

        out.add(`${node_id} -> ${id} [label="source"];`)
    }

    return node_id;
}

/**
 * Produces a diagram of behavior tree in DOT format, useful for debugging and exploration of complex trees
 *
 * @see https://en.wikipedia.org/wiki/DOT_(graph_description_language)
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 *
 * @param {Behavior} behavior
 * @returns {string}
 */
export function behavior_to_dot(behavior) {
    const out = new LineBuilder();

    const context = {
        id_counter: 0,
        node_ids: new Map()
    };

    const font = {
        name: "helvetica",
        size: 10
    };

    out.add("digraph BehaviorTree {");
    out.indent();
    out.add("graph [style=invis, rankdir=\"TB\" ordering=out, splines=spline]");
    out.add(`node [shape=record, fontname=\"${font.name}\", fontsize=${font.size}, margin=\"0.2,0.03\", fillcolor=ivory]`);
    out.add(`edge [labelfontname=\"${font.name}\", labelfontsize=${font.size}, fontname=\"${font.name}\", fontsize=${font.size}, fontcolor=grey]`);

    parse_behavior(out, behavior, context);

    out.dedent();
    out.add("}");

    return out.build();
}
