import { assert } from "../../../core/assert.js";
import Name from "../../ecs/name/Name.js";
import { BoundQuaternionWriter } from "../curve/binding/BoundQuaternionWriter.js";
import { BoundValueWriter } from "../curve/binding/BoundValueWriter.js";
import { BoundVector3Writer } from "../curve/binding/BoundVector3Writer.js";

/**
 *
 * @param {EntityNode} node
 * @returns {string|undefined}
 */
function get_node_name(node) {
    /**
     * @type {Name}
     */
    const name = node.entity.getComponent(Name);

    if (name === null) {
        return undefined;
    }

    return name.getValue();
}

/**
 * Following THREE.js PropertyBinding.findNode implementation
 * @param {EntityNode} root
 * @param {string} node_name
 * @returns {EntityNode|undefined}
 */
function find_node(root, node_name) {
    assert.isString(node_name, 'node_name');

    const root_name = get_node_name(root);

    if (root_name === node_name) {
        return root;
    }

    // search into node subtree.
    const children = root.children;

    const child_count = children.length;

    for (let j = 0; j < child_count; j++) {
        /**
         * @type {EntityNode}
         */
        const child = children[j];

        const subtree_result = find_node(child, node_name);

        if (subtree_result !== undefined) {
            return subtree_result;
        }
    }
}

/**
 *
 * @param {EntityNode} root
 * @param {string[]} path_parts
 * @param {number} part_first
 * @param {number} part_last
 */
function entity_node_resolve_path(root, path_parts, part_first, part_last) {
    assert.isNonNegativeInteger(part_first, 'part_first');
    assert.isNonNegativeInteger(part_last, 'part_last');

    assert.lessThan(part_last, path_parts.length, 'overflow');

    let node = root;

    for (let i = part_first; i <= part_last; i++) {

        const part = path_parts[i];

        const sub_node = find_node(node, part);

        if (sub_node === undefined) {

            throw new Error(`Child '${part}' not found, at index ${i} of [${path_parts.join(', ')}]. Valid names at this level: [${node.children.map(get_node_name)}]`);

        }

        node = sub_node;
    }

    return node;
}

/**
 *
 * @param {EntityNode} node
 * @param {string[]} path_parts
 * @returns {BoundValueWriter}
 */
export function bind_property_writer(node, path_parts) {
    assert.defined(path_parts, 'path_parts');
    assert.isArray(path_parts, 'path_parts');

    const part_count = path_parts.length;

    const property_name = path_parts[part_count - 1];

    const parent = entity_node_resolve_path(node, path_parts, 0, part_count - 2);

    let writer;

    switch (property_name) {
        case "position":
            writer = new BoundVector3Writer(parent.transform.position);
            break;
        case "quaternion":
            writer = new BoundQuaternionWriter(parent.transform.rotation);
            break;
        case "scale":
            writer = new BoundVector3Writer(parent.transform.scale);
            break;
        default:
            console.log(`Unsupported property name: ${property_name}`);
            // NO-OP
            writer = new BoundValueWriter();
    }

    return writer;
}