import { tileToWorld, type IsometricTileMap, type TileMapData, type Vec2 } from '@rpg/engine';
import type { AreaDef, TransitionDef } from '../data/locations';
import { resolveTransition, type TransitionCtx } from '../data/transitions';
import type { NpcDef } from '../data/npcs';
import type { InteractableDef } from '../data/interactables';
import type { Interactables } from './Interactables';
import type { SceneObjects } from './SceneObjects';
import type { PlayerController } from './PlayerController';
import type { Game } from '../Game';

/**
 * Маршрутизация взаимодействия: кнопка действия (ближайшая цель в радиусе)
 * и step-переходы с дисармом тайла входа. Чистый пикер (pickInteract)
 * тестируется в Vitest без Pixi; InteractionRouter — рантайм поверх него.
 * Сцена остаётся оркестратором вьюх. (Клик-роутинг удалён: движение —
 * WASD/стик/джойстик, взаимодействие — кнопкой действия.)
 */

/** Колбэки роутера в сцену: всё, что трогает вьюхи/катсцены/замену сцен. */
export interface RouterCallbacks {
    showToast(text: string): void;
    playUiClick(): void;
    talkTo(def: NpcDef): void;
    collectFlower(x: number, y: number): void;
    useTransition(def: TransitionDef): void;
}

/** Зависимости рантайма роутера (всё уже собрано сценой). */
export interface RouterDeps {
    game: Game;
    map: IsometricTileMap;
    area: AreaDef;
    player: PlayerController;
    interactables: Interactables;
    /** Реестр объектов сцены: «кто на тайле» (NPC/интерактивы, grid путей). */
    objects: SceneObjects;
    /** Тайл входа в область (step-переходы на нём дисармованы). */
    disarmTile: { x: number; y: number } | null;
    callbacks: RouterCallbacks;
}

/** Радиус взаимодействия (юнитов): от ног героя до центра тайла цели. */
const INTERACT_RANGE = 1.5;

/** Дистанция до центра тайла цели: истинная евклидова линейка (юнит = 1 тайл). */
function tileDist(playerPos: Vec2, tx: number, ty: number): number {
    const c = tileToWorld(tx, ty);
    return Math.hypot(c.x - playerPos.x, c.y - playerPos.y);
}

/**
 * Радиус взаимодействия: истинная евклидова линейка (юнит = 1 тайл), не
 * проекционная. Проекция анизотропна (2:1) — «юниты» в ней вытянуты вдоль
 * мировой диагонали, и радиус в inCircleW давал зону в 2-3 тайла: объект
 * срабатывал издалека, до подхода героя. Орто-сосед 1.0 ✓, диагональный
 * сосед 1.414 ✓ (findPathToNeighbor приводит в него), 2 тайла ✗.
 */
export function inInteractRangePos(playerPos: Vec2, tx: number, ty: number): boolean {
    return tileDist(playerPos, tx, ty) <= INTERACT_RANGE;
}

/** Кандидат кнопки действия: то, с чем можно взаимодействовать в радиусе. */
export interface InteractCandidate {
    kind: 'npc' | 'transition' | 'locked' | 'interact' | 'flower';
    tile: { x: number; y: number };
    /** Дистанция от героя до центра тайла (юниты) — решает выбор. */
    dist: number;
    /** Человекочитаемое имя для подписи «[E] …». */
    label: string;
    npc?: NpcDef;
    inter?: InteractableDef;
    trans?: TransitionDef;
    /** Текст «заперто» для kind 'locked'. */
    text?: string;
    /** Тайл цветка (kind 'flower'). */
    x?: number;
    y?: number;
}

/** Приоритет при ничьей по дистанции: NPC важнее перехода, тот — объекта, объект — цветка. */
const PICK_PRIORITY: Record<InteractCandidate['kind'], number> = {
    npc: 0,
    transition: 1,
    locked: 1,
    interact: 2,
    flower: 3
};

/** Эпсилон ничьей по дистанции (юниты): ближе — выигрывает дистанция, иначе приоритет. */
const PICK_EPS = 0.05;

/**
 * Выбор цели кнопки действия: ближайший кандидат в радиусе; при ничьей
 * (~эпсилон) — по приоритету kind. Чистая функция — тестируется в Vitest.
 */
export function pickInteract(candidates: InteractCandidate[]): InteractCandidate | null {
    let best: InteractCandidate | null = null;
    for (const c of candidates) {
        if (c.dist > INTERACT_RANGE) continue;
        if (
            best === null ||
            c.dist < best.dist - PICK_EPS ||
            (c.dist <= best.dist + PICK_EPS && PICK_PRIORITY[c.kind] < PICK_PRIORITY[best.kind])
        ) {
            best = c;
        }
    }
    return best;
}

/**
 * Рантайм маршрутизации: кнопка действия (ближайшая цель в радиусе) и
 * step-переходы с дисармом тайла входа.
 */
export class InteractionRouter {
    private disarmTile: { x: number; y: number } | null;
    private prevStepTile: { x: number; y: number } | null = null;

    constructor(private deps: RouterDeps) {
        this.disarmTile = deps.disarmTile;
    }

    /** Радиус взаимодействия (для подписей объектов в сцене). */
    inInteractRange(tx: number, ty: number): boolean {
        return inInteractRangePos(this.deps.player.position, tx, ty);
    }

    /**
     * Единый механизм переходов (step-триггер): герой наступил на тайл.
     * На тайле входа step-переходы дисармованы, пока герой с него не ушёл.
     */
    checkTransitions(tile: { x: number; y: number }): void {
        const fresh =
            this.prevStepTile === null ||
            this.prevStepTile.x !== tile.x ||
            this.prevStepTile.y !== tile.y;
        this.prevStepTile = tile;
        // Ушёл с тайла входа — дисарм снят, переходы снова работают.
        if (this.disarmTile && (tile.x !== this.disarmTile.x || tile.y !== this.disarmTile.y)) {
            this.disarmTile = null;
        }
        const pick = resolveTransition(
            this.deps.area.transitions,
            tile,
            'step',
            this.transitionCtx(),
            this.disarmTile
        );
        if (!pick) return;
        if (!pick.ok) {
            // «Заперто» — не спамим: только при приходе на тайл.
            if (fresh) {
                this.deps.callbacks.showToast(pick.lockedText);
                this.deps.callbacks.playUiClick();
            }
            return;
        }
        this.deps.callbacks.useTransition(pick.def);
    }

    // ---------- кнопка действия: ближайшая цель в радиусе ----------

    /** Кандидаты кнопки действия вокруг героя: NPC, click-переходы, интерактивы, цветы. */
    interactCandidates(): InteractCandidate[] {
        const player = this.deps.player.position;
        const heroTile = this.deps.player.currentTile();
        const out: InteractCandidate[] = [];
        for (const def of this.deps.objects.npcs()) {
            out.push({
                kind: 'npc',
                npc: def,
                tile: def.tile,
                dist: tileDist(player, def.tile.x, def.tile.y),
                label: def.name
            });
        }
        for (const t of this.deps.area.transitions) {
            if ((t.trigger ?? 'step') !== 'click') continue;
            const pick = resolveTransition(this.deps.area.transitions, t.tile, 'click', this.transitionCtx(), null);
            if (!pick) continue;
            out.push({
                kind: pick.ok ? 'transition' : 'locked',
                trans: pick.ok ? pick.def : undefined,
                text: pick.ok ? undefined : pick.lockedText,
                tile: t.tile,
                dist: tileDist(player, t.tile.x, t.tile.y),
                label: t.label ?? 'проход'
            });
        }
        for (const def of this.deps.area.interactables ?? []) {
            if (def.once && this.deps.interactables.isUsed(def.id)) continue;
            out.push({
                kind: 'interact',
                inter: def,
                tile: def.tile,
                dist: tileDist(player, def.tile.x, def.tile.y),
                label: def.label ?? def.id
            });
        }
        // Цветы: скан окна ±2 тайла вокруг героя по сборному тайлу области.
        if (this.deps.area.harvestTile !== undefined) {
            for (let dy = -2; dy <= 2; dy++) {
                for (let dx = -2; dx <= 2; dx++) {
                    const x = heroTile.x + dx;
                    const y = heroTile.y + dy;
                    if (!this.isFlower(x, y)) continue;
                    out.push({ kind: 'flower', x, y, tile: { x, y }, dist: tileDist(player, x, y), label: 'лунный колокольчик' });
                }
            }
        }
        return out;
    }

    /** Ближайшая цель кнопки действия — для подписи «[E] имя» и снапшота моста. */
    interactTarget(): { kind: InteractCandidate['kind']; label: string; tile: { x: number; y: number } } | null {
        const c = pickInteract(this.interactCandidates());
        return c && { kind: c.kind, label: c.label, tile: c.tile };
    }

    /** Кнопка действия: сработать ближайшей целью в радиусе (вне радиуса — ничего). */
    resolveInteract(): void {
        const c = pickInteract(this.interactCandidates());
        if (!c) return;
        switch (c.kind) {
            case 'npc':
                this.deps.callbacks.talkTo(c.npc!);
                return;
            case 'transition':
                this.deps.callbacks.useTransition(c.trans!);
                return;
            case 'locked':
                this.deps.callbacks.showToast(c.text ?? 'Не пускает.');
                this.deps.callbacks.playUiClick();
                return;
            case 'interact':
                this.deps.interactables.tryInteract(c.inter!);
                return;
            case 'flower':
                this.deps.callbacks.collectFlower(c.x!, c.y!);
        }
    }

    /** Сборный тайл области (лунный колокольчик); нет harvestTile — не собирается. */
    private isFlower(x: number, y: number): boolean {
        const harvest = this.deps.area.harvestTile;
        if (harvest === undefined) return false;
        const d: TileMapData = this.deps.map.data;
        return d.tiles[y * d.width + x] === harvest;
    }

    /** Контекст условий перехода: флаги GameState, сумка героя. */
    private transitionCtx(): TransitionCtx {
        return {
            hasFlag: (f) => this.deps.game.state.hasFlag(f),
            hasItem: (id) => this.deps.game.inventory.has(id)
        };
    }
}