import { CONFIG } from "./_config.js";
import { loadTutorialData } from "./loadJson.js";
import { loadSprite } from "./loadSprite.js";

const PANEL_SIZE = 256;
const PANEL_MARGIN = 10;
const TEXT_AREA = {
    x: 19,
    y: 21,
    width: 218,
    height: 122
};
const SPRITE_AREA = {
    x: 19,
    y: 143,
    width: 146,
    height: 82
};
const RESERVED_AREA = {
    x: 165,
    y: 143,
    width: 72,
    height: 82
};
const TEXT_FONT = "bold 12px monospace";
const TEXT_COLOR = "#2B2118";
const TEXT_LINE_HEIGHT = 14;
const PENDING_TRIGGER_LIMIT = 32;

function normalizeStep(step) {
    if (!step || typeof step !== "object") {
        return null;
    }

    const triggers = Array.isArray(step.triggers)
        ? step.triggers
            .filter(trigger => typeof trigger === "string")
            .map(trigger => trigger.trim())
            .filter(trigger => trigger)
        : [];
    const trigger = typeof step.trigger === "string" ? step.trigger.trim() : "";
    const text = typeof step.text === "string" ? step.text.trim() : "";
    const sprite = typeof step.sprite === "string" ? step.sprite.trim() : "";
    const scale = Number.isFinite(step.scale) && step.scale > 0 ? step.scale : 1;
    const normalizedTriggers = triggers.length > 0 ? triggers : (trigger ? [trigger] : []);

    if (normalizedTriggers.length === 0 || !text) {
        return null;
    }

    return {
        trigger: normalizedTriggers[0],
        triggers: normalizedTriggers,
        text,
        sprite,
        scale
    };
}

function stepMatchesTrigger(step, triggerId) {
    if (!step || !triggerId) {
        return false;
    }

    if (Array.isArray(step.triggers) && step.triggers.length > 0) {
        return step.triggers.includes(triggerId);
    }

    return step.trigger === triggerId;
}

function splitLongToken(ctx, token, maxWidth) {
    if (!token) {
        return [];
    }

    if (ctx.measureText(token).width <= maxWidth) {
        return [token];
    }

    const parts = [];
    let currentPart = "";

    for (const character of token) {
        const nextPart = currentPart + character;
        if (currentPart && ctx.measureText(nextPart).width > maxWidth) {
            parts.push(currentPart);
            currentPart = character;
        } else {
            currentPart = nextPart;
        }
    }

    if (currentPart) {
        parts.push(currentPart);
    }

    return parts;
}

export class Tutorial {
    constructor() {
        this.layout = {
            width: PANEL_SIZE,
            height: PANEL_SIZE,
            margin: PANEL_MARGIN,
            textArea: TEXT_AREA,
            spriteArea: SPRITE_AREA,
            reservedArea: RESERVED_AREA,
            textFont: TEXT_FONT,
            textColor: TEXT_COLOR,
            lineHeight: TEXT_LINE_HEIGHT
        };
        this.panelSprite = loadSprite(CONFIG.TUTORIAL.PANEL_SPRITE);
        this.steps = [];
        this.currentStepIndex = 0;
        this.isLoaded = false;
        this.isDismissed = false;
        this.pendingTriggers = [];
        this.x = 0;
        this.y = 0;

        loadTutorialData()
            .then(data => {
                this.steps = Array.isArray(data) ? data.map(normalizeStep).filter(step => step) : [];
                this.isLoaded = true;
                this.flushPendingTriggers();
                if (!this.getCurrentStep()) {
                    this.isDismissed = true;
                }
            })
            .catch(() => {
                this.steps = [];
                this.isLoaded = true;
                this.isDismissed = true;
            });
    }

    reposition(options = {}) {
        const avoidRect = options.avoidRect || null;
        const defaultX = Math.max(this.layout.margin, window.innerWidth - this.layout.margin - this.layout.width);
        const defaultY = this.layout.margin;
        const maxY = Math.max(this.layout.margin, window.innerHeight - this.layout.margin - this.layout.height);

        this.x = defaultX;
        this.y = defaultY;

        if (!avoidRect) {
            return;
        }

        const overlapsHorizontally = this.x < avoidRect.x + avoidRect.width && this.x + this.layout.width > avoidRect.x;
        const overlapsVertically = this.y < avoidRect.y + avoidRect.height && this.y + this.layout.height > avoidRect.y;

        if (overlapsHorizontally && overlapsVertically) {
            this.y = Math.min(maxY, avoidRect.y + avoidRect.height + this.layout.margin);
        }
    }

    getPanelRect() {
        return {
            x: this.x,
            y: this.y,
            width: this.layout.width,
            height: this.layout.height
        };
    }

    isLastStep() {
        return this.steps.length > 0 && this.currentStepIndex === this.steps.length - 1;
    }

    isPointInside(x, y) {
        const panelRect = this.getPanelRect();
        return x >= panelRect.x
            && x <= panelRect.x + panelRect.width
            && y >= panelRect.y
            && y <= panelRect.y + panelRect.height;
    }

    dismiss() {
        this.isDismissed = true;
    }

    handleLeftClick(x, y, options = {}) {
        if (!this.getCurrentStep() || !this.isLastStep()) {
            return false;
        }

        this.reposition(options);

        if (!this.isPointInside(x, y)) {
            return false;
        }

        this.dismiss();
        return true;
    }

    getCurrentStep() {
        if (this.isDismissed) {
            return null;
        }

        return this.steps[this.currentStepIndex] || null;
    }

    trigger(triggerId) {
        if (typeof triggerId !== "string") {
            return false;
        }

        const normalizedTrigger = triggerId.trim();
        if (!normalizedTrigger || this.isDismissed) {
            return false;
        }

        if (!this.isLoaded) {
            if (this.pendingTriggers.length >= PENDING_TRIGGER_LIMIT) {
                this.pendingTriggers.shift();
            }
            this.pendingTriggers.push(normalizedTrigger);
            return false;
        }

        const currentStep = this.getCurrentStep();
        if (!stepMatchesTrigger(currentStep, normalizedTrigger)) {
            return false;
        }

        this.currentStepIndex += 1;

        if (!this.getCurrentStep()) {
            this.isDismissed = true;
        }

        return true;
    }

    flushPendingTriggers() {
        if (!this.isLoaded || this.isDismissed || this.pendingTriggers.length === 0) {
            this.pendingTriggers = [];
            return;
        }

        const triggers = [...this.pendingTriggers];
        this.pendingTriggers = [];

        for (const triggerId of triggers) {
            if (this.isDismissed) {
                break;
            }

            const currentStep = this.getCurrentStep();
            if (!stepMatchesTrigger(currentStep, triggerId)) {
                continue;
            }

            this.trigger(triggerId);
        }
    }

    getWrappedLines(ctx, text, maxWidth, maxHeight) {
        const maxLines = Math.max(0, Math.floor(maxHeight / this.layout.lineHeight));
        if (maxLines === 0) {
            return [];
        }

        const paragraphs = String(text || "").split(/\n+/);
        const lines = [];

        for (const paragraph of paragraphs) {
            const rawTokens = paragraph.trim().length > 0 ? paragraph.trim().split(/\s+/) : [""];
            const tokens = [];

            for (const token of rawTokens) {
                const splitTokens = splitLongToken(ctx, token, maxWidth);
                if (splitTokens.length === 0) {
                    continue;
                }
                tokens.push(...splitTokens);
            }

            if (tokens.length === 0) {
                if (lines.length < maxLines) {
                    lines.push("");
                }
                continue;
            }

            let currentLine = "";
            for (const token of tokens) {
                const nextLine = currentLine ? `${currentLine} ${token}` : token;
                if (!currentLine || ctx.measureText(nextLine).width <= maxWidth) {
                    currentLine = nextLine;
                    continue;
                }

                lines.push(currentLine);
                if (lines.length >= maxLines) {
                    return lines.slice(0, maxLines);
                }

                currentLine = token;
            }

            if (currentLine) {
                lines.push(currentLine);
                if (lines.length >= maxLines) {
                    return lines.slice(0, maxLines);
                }
            }
        }

        return lines.slice(0, maxLines);
    }

    drawText(ctx, step) {
        const area = this.layout.textArea;
        const x = this.x + area.x;
        const y = this.y + area.y;

        ctx.save();
        ctx.beginPath();
        ctx.rect(x, y, area.width, area.height);
        ctx.clip();
        ctx.fillStyle = this.layout.textColor;
        ctx.font = this.layout.textFont;
        ctx.textAlign = "left";
        ctx.textBaseline = "top";

        const lines = this.getWrappedLines(ctx, step.text, area.width, area.height);

        for (let index = 0; index < lines.length; index++) {
            ctx.fillText(lines[index], x, y + index * this.layout.lineHeight);
        }

        ctx.restore();
    }

    drawStepSprite(ctx, step) {
        if (!step.sprite) {
            return;
        }

        const sprite = loadSprite(step.sprite);
        if (!sprite || !sprite.complete || !sprite.naturalWidth || !sprite.naturalHeight) {
            return;
        }

        const area = this.layout.spriteArea;
        const fittedScale = Math.min(
            step.scale,
            area.width / sprite.naturalWidth,
            area.height / sprite.naturalHeight
        );
        const drawWidth = sprite.naturalWidth * fittedScale;
        const drawHeight = sprite.naturalHeight * fittedScale;
        const drawX = Math.round(this.x + area.x + (area.width - drawWidth) / 2);
        const drawY = Math.round(this.y + area.y + (area.height - drawHeight) / 2);

        ctx.drawImage(sprite, drawX, drawY, Math.round(drawWidth), Math.round(drawHeight));
    }

    draw(ctx, options = {}) {
        const currentStep = this.getCurrentStep();
        if (!currentStep) {
            return;
        }

        this.reposition(options);

        if (!this.panelSprite || !this.panelSprite.complete) {
            return;
        }

        ctx.drawImage(this.panelSprite, this.x, this.y, this.layout.width, this.layout.height);
        this.drawText(ctx, currentStep);
        this.drawStepSprite(ctx, currentStep);
    }
}

export const tutorial = new Tutorial();