import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
import DhMeasuredTemplate from './measuredTemplate.mjs';

export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
    /** @inheritdoc */
    async _draw(options) {
        await super._draw(options);

        if (this.document.flags.daggerheart?.createPlacement)
            this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
    }

    /**@inheritdoc */
    _refreshTurnMarker() {
        // Should a Turn Marker be active?
        const { turnMarker } = this.document;
        const markersEnabled =
            CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED;
        const spotlighted = game.settings
            .get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker)
            .spotlightedTokens.has(this.document.uuid);

        const turnIsSet = typeof game.combat?.turn === 'number';
        const isTurn = game.combat?.combatant?.tokenId === this.id;
        const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted;

        // Activate a Turn Marker
        if (markerActive) {
            if (!this.turnMarker)
                this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0);
            canvas.tokens.turnMarkers.add(this);
            this.turnMarker.draw();
        }

        // Remove a Turn Marker
        else if (this.turnMarker) {
            canvas.tokens.turnMarkers.delete(this);
            this.turnMarker.destroy();
            this.turnMarker = null;
        }
    }

    /** @inheritDoc */
    async _drawEffects() {
        this.effects.renderable = false;

        // Clear Effects Container
        this.effects.removeChildren().forEach(c => c.destroy());
        this.effects.bg = this.effects.addChild(new PIXI.Graphics());
        this.effects.bg.zIndex = -1;
        this.effects.overlay = null;

        // Categorize effects
        const activeEffects = getIconVisibleActiveEffects(Array.from(this.actor?.allApplicableEffects() ?? []));
        const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));

        // Draw effects
        const promises = [];
        for (const [i, effect] of activeEffects.entries()) {
            if (!effect.img) continue;
            const promise =
                effect === overlayEffect
                    ? this._drawOverlay(effect.img, effect.tint, effect)
                    : this._drawEffect(effect.img, effect.tint, effect);
            promises.push(
                promise.then(e => {
                    if (e) e.zIndex = i;
                })
            );
        }
        await Promise.allSettled(promises);

        this.effects.sortChildren();
        this.effects.renderable = true;
        this.renderFlags.set({ refreshEffects: true });
    }

    /**@inheritdoc */
    async _drawEffect(src, tint, effect) {
        if (!src) return;
        const tex = await foundry.canvas.loadTexture(src, { fallback: 'icons/svg/hazard.svg' });
        const icon = new PIXI.Sprite(tex);
        icon.tint = tint ?? 0xffffff;

        if (effect.system.stacking?.value > 1) {
            const stackOverlay = new PIXI.Text(effect.system.stacking.value, {
                fill: '#f3c267',
                stroke: '#000000',
                fontSize: 96,
                strokeThickness: 4
            });
            const nrDigits = Math.floor(Math.log10(effect.system.stacking.value)) + 1;
            stackOverlay.y = -8;
            /* This does not account for 1:s being much less wide than other digits. I don't think it's desired however as it makes it look jumpy */
            stackOverlay.x = icon.width - 8 - nrDigits * 56;
            stackOverlay.anchor.set(0, 0);

            icon.addChild(stackOverlay);
        }

        return this.effects.addChild(icon);
    }

    async _drawOverlay(src, tint, effect) {
        const icon = await this._drawEffect(src, tint, effect);
        if (icon) icon.alpha = 0.8;
        this.effects.overlay = icon ?? null;
        return icon;
    }

    /**
     * Returns the distance from this token to another token object.
     * This value is corrected to handle alternate token sizes and other grid types
     * according to the diagonal rules.
     */
    distanceTo(target) {
        if (!canvas.ready) return NaN;
        if (this === target) return 0;

        const originPoint = this.center;
        const targetPoint = target.center;
        const thisBounds = this.bounds;
        const targetBounds = target.bounds;
        const adjacencyBuffer = canvas.grid.distance * 1.75; // handles diagonals with one square elevation difference

        // Figure out the elevation difference.
        // This intends to return "grid distance" for adjacent ones, so we add that number if not overlapping.
        const sizePerUnit = canvas.grid.size / canvas.grid.distance;
        const thisHeight = Math.max(thisBounds.width, thisBounds.height) / sizePerUnit;
        const targetHeight = Math.max(targetBounds.width, targetBounds.height) / sizePerUnit;
        const thisElevation = [this.document.elevation, this.document.elevation + thisHeight];
        const targetElevation = [target.document.elevation, target.document.elevation + targetHeight];
        const isSameAltitude =
            thisElevation[0] < targetElevation[1] && // bottom of this must be at or below the top of target
            thisElevation[1] > targetElevation[0]; // top of this must be at or above the bottom of target
        const [lower, higher] = [targetElevation, thisElevation].sort((a, b) => a[1] - b[1]);
        const elevation = isSameAltitude ? 0 : higher[0] - lower[1] + canvas.grid.distance;

        // Compute for gridless. This version returns circular edge to edge + grid distance,
        // so that tokens that are touching return 5.
        if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
            const boundsCorrection = canvas.grid.distance / canvas.grid.size;
            const originRadius = (thisBounds.width * boundsCorrection) / 2;
            const targetRadius = (targetBounds.width * boundsCorrection) / 2;
            const measuredDistance = canvas.grid.measurePath([
                { ...originPoint, elevation: 0 },
                { ...targetPoint, elevation }
            ]).distance;
            const distance = Math.floor(measuredDistance - originRadius - targetRadius + canvas.grid.distance);
            return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
        }

        // Compute what the closest grid space of each token is, then compute that distance
        const originEdge = this.#getEdgeBoundary(thisBounds, originPoint, targetPoint);
        const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint);
        const adjustedOriginPoint = originEdge
            ? canvas.grid.getTopLeftPoint({
                  x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
                  y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
              })
            : originPoint;
        const adjustDestinationPoint = targetEdge
            ? canvas.grid.getTopLeftPoint({
                  x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
                  y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
              })
            : targetPoint;
        const distance = canvas.grid.measurePath([
            { ...adjustedOriginPoint, elevation: 0 },
            { ...adjustDestinationPoint, elevation }
        ]).distance;
        return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
    }

    _onHoverIn(event, options) {
        super._onHoverIn(event, options);

        // Check if the setting is enabled
        const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).showTokenDistance;
        if (setting === 'never' || (setting === 'encounters' && !game.combat?.started)) return;

        // Check if this token isn't invisible and is actually being hovered
        const isTokenValid =
            this.visible &&
            this.hover &&
            !this.isPreview &&
            !this.document.isSecret &&
            !this.controlled &&
            !this.animation;
        if (!isTokenValid) return;

        // Ensure we have a single controlled token
        const originToken = canvas.tokens.controlled[0];
        if (!originToken || canvas.tokens.controlled.length > 1) return;

        // Determine the actual range
        const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
        const distanceResult = DhMeasuredTemplate.getRangeLabels(originToken.distanceTo(this), ranges);
        const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim();

        // Create the element
        const element = document.createElement('div');
        element.id = 'token-hover-distance';
        element.classList.add('waypoint-label', 'last');
        const ruler = document.createElement('i');
        ruler.classList.add('fa-solid', 'fa-ruler');
        element.appendChild(ruler);
        const labelEl = document.createElement('span');
        labelEl.classList.add('total-measurement');
        labelEl.textContent = distanceLabel;
        element.appendChild(labelEl);

        // Position the element and add to the DOM
        const center = this.getCenterPoint();
        element.style.setProperty('--transformY', 'calc(-100% - 10px)');
        element.style.setProperty('--position-y', `${this.y}px`);
        element.style.setProperty('--position-x', `${center.x}px`);
        element.style.setProperty('--ui-scale', String(canvas.dimensions.uiScale));
        document.querySelector('#token-hover-distance')?.remove();
        document.querySelector('#measurement').appendChild(element);
    }

    _onHoverOut(...args) {
        super._onHoverOut(...args);
        document.querySelector('#token-hover-distance')?.remove();
    }

    /** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
    #getEdgeBoundary(bounds, originPoint, destinationPoint) {
        const points = [
            { x: bounds.x, y: bounds.y },
            { x: bounds.x + bounds.width, y: bounds.y },
            { x: bounds.x + bounds.width, y: bounds.y + bounds.height },
            { x: bounds.x, y: bounds.y + bounds.height }
        ];
        const pairsToTest = [
            [points[0], points[1]],
            [points[1], points[2]],
            [points[2], points[3]],
            [points[3], points[0]]
        ];
        for (const pair of pairsToTest) {
            const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]);
            if (result) return result;
        }

        return null;
    }

    /** @inheritDoc */
    _drawBar(number, bar, data) {
        // Determine sizing
        const { width, height } = this.document.getSize();
        const s = canvas.dimensions.uiScale;
        const bw = width;
        const bh = 8 * (this.document.height >= 2 ? 1.5 : 1) * s;

        // Determine the color to use
        const Color = foundry.utils.Color;
        const fillColor = number === 0 ? Color.fromRGB([1, 0, 0]) : Color.fromString('#0032b1');
        const emptyColor = Color.fromRGB([0, 0, 0]);

        // Draw the bar (accounting floating point numbers from bar animations)
        const widthUnit = bw / Math.ceil(data.max);
        bar.clear().lineStyle(s, 0x000000, 1.0);
        const sections = [...Array(Math.ceil(data.max)).keys()];
        for (const mark of sections) {
            const x = mark * widthUnit;
            const marked = mark < Math.ceil(data.value);
            const remainder = mark === Math.ceil(data.value) - 1 ? data.value % 1 : 0;
            const color = !marked ? emptyColor : remainder ? emptyColor.mix(fillColor, remainder) : fillColor;
            if (mark === 0 || mark === sections.length - 1) {
                bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s); // Would like drawRoundedRect, but it's very troublsome with the corners. Leaving for now.
            } else {
                bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s);
            }
        }

        // Set position
        const posY = number === 0 ? height - bh : 0;
        bar.position.set(0, posY);
        return true;
    }

    /**
     * Draw a helptext for previews as a text object
     * @returns {PreciseText}    The Text object for the preview helper
     */
    #drawPreviewHelp() {
        const { uiScale } = canvas.dimensions;

        const textStyle = CONFIG.canvasTextStyle.clone();
        textStyle.fontSize = 18;
        textStyle.wordWrapWidth = this.w * 2.5;
        textStyle.fontStyle = 'italic';

        const helpText = new foundry.canvas.containers.PreciseText(
            `(${game.i18n.localize('DAGGERHEART.UI.Tooltip.previewTokenHelp')})`,
            textStyle
        );
        helpText.anchor.set(helpText.width / 900, 1);
        helpText.scale.set(uiScale, uiScale);
        return helpText;
    }
}
