export class ItemL5r5e extends Item {
    /**
     * A reference to the Collection of embedded Item instances in the document, indexed by _id.
     * @returns {Collection<BaseItem>}
     */
    get items() {
        return this.system.items || new Map();
    }

    /**
     * Return the linked Actor instance if any (current or embed)
     * @return {Actor|null}
     */
    get actor() {
        return super.actor || game.actors.get(this.system.parent_id?.actor_id) || null;
    }

    /**
     * A Universally Unique Identifier (uuid) for this Document instance patched for embedded items on items
     * @type {string}
     * @memberof ClientDocumentMixin#
     */
    get uuid() {
        const parents = this.system?.parent_id;
        if (!parents?.item_id) {
            return super.uuid;
        }

        // Embedded item
        const uuid = [];
        if (parents?.actor_id) {
            uuid.push(`Actor.${parents.actor_id}`);
        }
        uuid.push(`Item.${parents.item_id}`);
        uuid.push(`Item.${this._id}`);

        return uuid.join(".");
    }

    /**
     * Obtain a reference to the Array of source data within the data object for a certain embedded Document name
     *
     * TODO probably useless if we can add "items" in metadata.embedded, but no clue how to.
     *
     * @param {string} embeddedName   The name of the embedded Document type
     * @return {DocumentCollection}   The Collection instance of embedded Documents of the requested type
     */
    getEmbeddedCollection(embeddedName) {
        if (embeddedName === "Item") {
            return this.items;
        }
        return super.getEmbeddedCollection(embeddedName);
    }

    /**
     * Create a new entity using provided input data
     * @override
     */
    static async create(data, context = {}) {
        if (data.img === undefined) {
            data.img = `${CONFIG.l5r5e.paths.assets}icons/items/${data.type}.svg`;
        }
        return super.create(data, context);
    }

    /**
     * Update this Document using incremental data, saving it to the database.
     * @see {@link Document.updateDocuments}
     * @param {object} [data={}]                         Differential update data which modifies the existing values of this document data
     * @param {DocumentModificationContext} [context={}] Additional context which customizes the update workflow
     * @returns {Promise<Document>}                      The updated Document instance
     */
    async update(data = {}, context = {}) {
        // Regular
        if (!this.system.parent_id) {
            return super.update(data, context);
        }

        // **** Embed Items, need to get the parents ****
        const parentItem = this.getItemFromParentId();
        if (!parentItem) {
            console.warn(`L5R5E | Helpers | Embed parentItem not found`);
            return;
        }

        // Merge (DocumentData cannot be set)
        const result = foundry.utils.mergeObject(this, foundry.utils.expandObject(data));

        if (result.name) {
            this.name = result.name;
        }
        if (result.img) {
            this.img = result.img;
        }
        if (result.system) {
            this.system = result.system;
        }

        // Update
        await parentItem.updateEmbedItem(this.toObject(false));

        // Return new value for sheet
        return new Promise((resolve) => resolve(this));
    }

    /** @override */
    prepareData() {
        super.prepareData();

        // Prepare Embed items
        if (!(this.system.items instanceof Map)) {
            const itemsData = Array.isArray(this.system.items) ? this.system.items : [];
            this.system.items = new Map();

            itemsData.forEach((item) => {
                this.addEmbedItem(item, { save: false, newId: false, addBonusToActor: false });
            });
        }

        // Sanitize some values
        switch (this.type) {
            case "armor":
                this.system.armor.physical = this.system.armor.physical || 0;
                this.system.armor.supernatural = this.system.armor.supernatural || 0;
                break;

            case "weapon":
                this.system.range = this.system.range || 0;
                this.system.damage = this.system.damage || 0;
                this.system.deadliness = this.system.deadliness || 0;
                break;
        }
    }

    // ***** parent ids management *****
    /**
     * Return a string with idemId + actorId if any
     * @return {{item_id: (string|null), actor_id?: (string|null)}}
     */
    getParentsIds() {
        const parent = {
            item_id: this.id,
        };
        if (this.actor?._id) {
            parent.actor_id = this.actor._id;
        }
        return parent;
    }

    /**
     * Return the Item Object for the "parentId"
     * @return {ItemL5r5e|null}
     */
    getItemFromParentId() {
        const parentIds = this.system.parent_id;
        let parentItem;

        if (parentIds?.actor_id) {
            // Actor item object
            const parentActor = parentIds.actor_id ? game.actors.get(parentIds.actor_id) : null;
            parentItem = parentActor?.items.get(parentIds.item_id);
        } else if (parentIds?.item_id) {
            // World Object
            parentItem = game.items.get(parentIds.item_id);
        }

        return parentItem;
    }

    /**
     * Render the text template for this Item (tooltips and chat)
     * @return {Promise<string|null>}
     */
    async renderTextTemplate() {
        const sheetData = (await this.sheet?.getData()) || this;
        if (sheetData instanceof ItemL5r5e) {
            await game.l5r5e.HelpersL5r5e.refreshItemProperties(this);
        }
        const type = this.type.replace("_", "-"); // ex: item_pattern
        const tpl = await foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}items/${type}/${type}-text.html`, sheetData);
        if (!tpl) {
            return null;
        }
        return tpl;
    }

    // ***** Embedded items management *****
    /**
     * Shortcut for this.items.get
     * @param id
     * @return {ItemL5r5e|null}
     */
    getEmbedItem(id) {
        return this.items?.get(id) || null;
    }

    /**
     * Add an Embed Item
     * @param {ItemL5r5e} item Object to add
     * @param {boolean} save   if we save in db or not (used internally)
     * @param {boolean} newId  if we change the id
     * @param {boolean} addBonusToActor if we update the actor bonus for advancements
     * @return {Promise<string>}
     */
    async addEmbedItem(item, { save = true, newId = true, addBonusToActor = true } = {}) {
        if (!item) {
            return;
        }

        if (!(item instanceof Item) && item?.name && item?.type) {
            // Data -> Item
            item = new ItemL5r5e(item);
        }

        // New id
        if (newId || !item._id) {
            // Bypass the readonly for "_id"
            const tmpData = item.toJSON();
            tmpData._id = foundry.utils.randomID();
            item = new ItemL5r5e(tmpData);
        }

        // Copy the parent permission to the sub item
        // In v10 actor's items inherit the ownership from the actor, but theirs ownership do not reflect that.
        // So we must take actor's ownership for sub-item
        item.ownership = this.actor?.ownership ?? this.ownership;

        // Tag parent (flags won't work as we have no id in db)
        item.system.parent_id = this.getParentsIds();

        // Object
        this.system.items.set(item._id, item);

        // Add bonus to actor
        if (addBonusToActor) {
            const actor = this.actor;
            if (item instanceof Item && actor instanceof Actor) {
                actor.addBonus(item);
            }
        }

        if (save) {
            await this.saveEmbedItems();
        }
        return item._id;
    }

    /**
     * Update an Embed Item
     * @param {ItemL5r5e} item Object to add
     * @param {boolean} save   if we save in db or not (used internally)
     * @return {Promise<string>}
     */
    async updateEmbedItem(item, { save = true } = {}) {
        return await this.addEmbedItem(item, { save, newId: false, addBonusToActor: false });
    }

    /**
     * Delete the Embed Item and clear the actor bonus if any
     * @param id Item id
     * @param {boolean} save   if we save in db or not (used internally)
     * @param {boolean} removeBonusFromActor if we update the actor bonus for advancements
     * @return {Promise<void>}
     */
    async deleteEmbedItem(id, { save = true, removeBonusFromActor = true } = {}) {
        if (!this.system.items.has(id)) {
            return;
        }

        // Remove bonus from actor
        if (removeBonusFromActor) {
            const actor = this.actor;
            const item = this.system.items.get(id);
            if (item instanceof Item && actor instanceof Actor) {
                actor.removeBonus(item);
            }
        }

        // Remove the embed item
        this.system.items.delete(id);

        if (save) {
            await this.saveEmbedItems();
        }
    }

    /**
     * Generate new Ids for the embed items
     * @return {Promise<void>}
     */
    async generateNewIdsForAllEmbedItems() {
        // Clear olds ids
        const oldItems = Array.from(this.system.items);
        this.system.items = new Map();

        // Re-add with new ids
        oldItems.forEach(([id, item]) => {
            this.addEmbedItem(item, { save: false, newId: true, addBonusToActor: false });
        });

        return this.saveEmbedItems();
    }

    /**
     * Save all the Embed Items
     * @return {Promise<void>}
     */
    async saveEmbedItems() {
        await this.update({
            "system.items": Array.from(this.system.items).map(([id, item]) => item.toObject(false)),
        });
        this.sheet.render(false);
    }
}
